linux系统编程之进程(全)

    科技2025-01-22  18

    文章目录

    一、进程的启动1.1 getpid获取当前进程标识符1.1.1代码演示将在子进程处 演示 1.2启动新进程例1 system()使用1.2.1 system的局限性 1.3 exec系列函数例2 execl()使用例3 execlp()使用例4 excvp()使用例5 execle()使用 二、创建子进程2.1 fork创建子进程例6 getpid()和fork()使用 2.2等待一个进程例7 wait()使用 三、总结

    一、进程的启动

    1.1 getpid获取当前进程标识符

    进程标识符(PID),通常的范围为2~32768,0~1的取值有系统固定的意义,可参考 上篇文章对其的简述

    由上图我门,可以看出获取进程标识符(以下都称为PID)的函数有两个 pid_t getpid(void); pid_t getppid(void); 所需头文件: #include<sys/types.h> #include<unistd.h> pid_t 是返回值类型,类型为进程标识符类型 通过程序测试, 我们知道了 getpid()返回的是当前进程的PID,而getppid()返回的是父进程PID 注意: 此函数不会失败,也就是说只要调用这个两个函数中的一个, 都会成功且返回一个PID

    1.1.1代码演示将在子进程处 演示

    1.2启动新进程

           在一个程序内部启动另一个程序,从而创建一个新进程。这个工作一个通过库函数system完成

    int system(const char *string); 所需头文件: #include<stdlib.h> 返回值: 成功:返回值为该命令的退出码 失败: 若comand(即string形参) 为空 返回0 若子进程不能被创建或者它的状态不能被检测 返回-1 若无法启动shell启动命令 返回127 其他错误 都返回-1

    例1 system()使用

    1.2.1 system的局限性

           system很有用,它也有很大的局限性,因为程序必须等待由system函数启动的进程结束后才能继续,也就是说,不能立刻执行其他任务。        一般来说,使用system函数并非启动其他进程的理想手段,因为它必须使用一个shell来启动需要的程序,所以这对shell的安装情况和使用环境也有很大的以来,所以system函数的使用效率并不高,接下来我们介绍一中更好的调用程序的方法。

    1.3 exec系列函数

    功能:   在调用进程内部执行一个可执行文件。可执行文件既可以是二进制文件,也可以是任何Linux下可执行的脚本文件。    函数族:   exec函数族分别是:execl, execlp, execle, execv, execvp, execvpe

    函数原型:

    #include <unistd.h> int execl(const char *path, const char *arg, ...); int execlp(const char *file, const char *arg, ...); int execle(const char *path, const char *arg,..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execvpe(const char *file, char *const argv[],char *const envp[]);

    返回值:        exec函数族的函数执行成功后不会返回,调用失败时, 会设置errno并返回-1,然后从原程序的调用点接着往下执行。       参数说明:        path:可执行文件的路径名字        arg:可执行程序所带的参数,第一个参数为可执行文件名字,没有带路径且arg必须以NULL结束 ,可以为系统自带的命令        file:如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。

    exec族函数有很多并且相似,可以根据函数中某些字符区分:        l : 使用参数列表        p:使用文件名,并从PATH环境进行寻找可执行文件        v:应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。        e:多了envp[]数组,使用新的环境变量代替调用进程的环境变量

    下面将exac函数归为带l、带p、带v、带e 四类来说明参数特点。

           一、带l的一类exac函数(l表示list),如execl、execlp、execle,要求将新程序的每个命令行参数都说明为 一个单独的参数。这种参数表以空指针结尾。 以execl函数为例子来说明:

    例2 execl()使用

    //文件exec1.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> // int excel(const char *path,const char *arg,...); int main() { printf("before excel\n"); if(execl("./file1","file1",NULL)==-1)//file1为可运行程序(xx.exe) { printf("execle failed\n"); perror("why:"); } printf("after execl\n"); return 0; } //文件file1.c #include<stdio.h> int main() { printf("Hello world\n"); return 0; } 运行结果: gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec1.c gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out before excel Hello world

    实验说明:        在exec1.c文件中使用execl()函数,file1.c作为被调用文件,在执行exec1可执行文件后,程序在此函数调用处的后面内容将被覆盖,所以”after execl” 没有在终端被打印出来。

           二、带p的一类exac函数,如execlp、execvp、execvpe,如果参数file中包含/,则就将其视为路径名,否则就按 PATH环境变量,在它所指定的各目录中搜寻可执行文件。举个例子,PATH=/bin:/usr/bin

    例3 execlp()使用

    //文件execl_no_path.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> //函数原型:int execl(const char *path, const char *arg, ...); int main(void) { printf("before execl****\n"); if(execl("ps","ps","-l",NULL) == -1) { printf("execl failed!\n"); } printf("after execl*****\n"); return 0; } 运行结果: gec@ubuntu:/mnt/hgfs/vrshare$ gcc execl_no_path.c gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out before execl**** execl failed! after execl***** //文件exec3.c #include<unistd.h> #include<stdio.h> #include<stdlib.h> // int execlp(char *file,const char *arg,...,NULL); int main() { printf("before execlp\n"); if(execlp("date","date",NULL)==-1)//调用的是Linux系统自带的命令 { printf("execle failed\n"); perror("why:"); } printf("after execlp\n"); return 0; } 运行结果: gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec3.c gec@ubuntu:/mnt/hgfs/vrshare$./a.out before execlp Fri Oct 9 23:34:25 PDT 2020 从上面的实验结果可以看出,上面的exaclp函数带p, 所以能通过环境变量PATH查找到可执行文件ps,这样就看清了两者的不同

           三、带v不带l的一类exac函数,包括execv、execvp、execve,应先构造一个指向各参数的指针数组,然后将该数组的地址作为这些函数的参数。

    例4 excvp()使用

    #include<unistd.h> #include<stdio.h> #include<stdlib.h> // int execvp(const char *file,const char *argv[]); int main() { printf("before execvp\n"); char *argv[]={"ps",NULL}; if(execvp("ps",argv)==-1) { printf("execvp failed\n"); perror("why:"); } printf("after execvp\n"); return 0; } 运行结果: gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec4.c gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out before execvp PID TTY TIME CMD 2343 pts/6 00:00:00 bash 70519 pts/6 00:00:00 ps

           四、带e的一类exac函数,包括execle、execvpe,可以传递一个指向环境字符串指针数组的指针。 参数例如char *env_init[] = {“AA=aa”,”BB=bb”,NULL}; 带e表示该函数取envp[]数组,而不使用当前环境。

    例5 execle()使用

    //文件exec5.c #include <stdio.h> #include <stdlib.h> #include <unistd.h> //函数原型:int execle(const char *path, const char *arg,..., char * const envp[]); char *buf[] = {"AA=aa","BB=bb",NULL}; int main(void) { printf("before execle****\n"); if(execle("./bin/exec_test03","exec_test03",NULL,buf) == -1) { printf("execle failed!\n"); } printf("after execle*****\n"); return 0; } //文件exec_test03.c #include <stdio.h> #include <unistd.h> extern char** buf; int main(int argc , char *argv[]) { int i; char **ptr; for(ptr = buf;*ptr != 0; ptr++) printf("%s\n",*ptr); return 0; } 运行结果: gec@ubuntu:/mnt/hgfs/vrshare$ gcc exec5.c gec@ubuntu:/mnt/hgfs/vrshare$ ./a.out before execle**** AA=aa BB=bb

    二、创建子进程

    2.1 fork创建子进程

    pid_t fork(void); 所需头文件: #include<unistd.h> 返回值: 成功:返回值为子进程PID 失败:返回值为-1 作用: 创建一个和原进程内容一样的进程,从此处开始执行新进程。 注意: 两个进程是同时进行的!!

    例6 getpid()和fork()使用

           通过加之限定条件,我们清楚了父进程和子进程!知道了在同时有父进程和子进程时,父进程PID为非负数,子进程PID为0,fork()调用成功返回两次!

    2.2等待一个进程

           当fork创建一个子进程时,子进程有了它自己的生命周期并且独立于父进程运行,有时我们希望知道什么时候一个子进程结束。例如,有时我们想让输出不乱,那么我们需要父进程等待子进程结束,再结束父进程。通过查库我们得到函数:

    pid_t wait(int *status); pid_t waitpid(pid_t pid,int *status,int options); status: 用来保存被收集进程退出时的一些状态,它是一个指向int类型的指针。通常 设为NULL,原因自查。 pid:进程标识符,进程号 1.pid>0 只等待进程ID等于pid的子进程,只要指定的子进程未结束waitpid就会一直等下去。 2.pid=-1 等待任何一个子进程退出,没有任何限制,此时waitpid和wait的作用一模一样。 3.pid=0 等待同一个进程组中的任何子进程,如果子进程已经加入了别的进程组,waitpid不会对它做任何理睬。 4.pid<-1 等待一个指定进程组中的任何子进程,这个进程组的ID等于pid的绝对值。 options: options提供了一些额外的选项来控制waitpid,具体请查看手册 所需有文件: #include<sys/types.h> #include<sys/wati.h> 返回值: 因为篇幅原因,(这里仅说wait(NULL)),对于wait其他状态宏和waitpid的返回值 具体请参考: https://blog.csdn.net/wyhh_0101/article/details/83933308 成功: wait(NULL),返回最终结束的子进程的PID 失败: wait(NULL),返回 -1 函数说明: 1.wait() 父进程阻塞直到子进程结束或者终止。如果该父进程没有子进程 或者它的子进程已经结束,则wait()函数就会立即返回。 2.waitpid() 它并不一定要等待第一个终止的子进程(可以指定需要等待终止的子进程), 它还有若干选项(options),如可提供一个非阻塞版本的 wait()功能, 也能支持作业控制。可以说wait()函数只是 waitpid()函数的一个特例, 在Linux 内部实现 wait()函数时直接调用的就是waitpid()函数。

    例7 wait()使用

    #include<stdio.h> #include<unistd.h> #include<sys/types.h> #include<stdlib.h> #include<sys/wait.h> int main() { pid_t pid; int cnt=0; int status=10; pid=fork(); if(pid>0) { wait(&status); printf("child quit,child status = %d\n",WEXITSTATUS(status)); while(1) { printf("cnt=%d\n",cnt); printf("This is father process %d\n",getpid()); sleep(1); } } else if(pid==0) { while(1) { printf("This is child process %d\n",getpid()); sleep(1); cnt++; if(cnt==3) { exit(3); } } } return 0; } 运行结果: gec@ubuntu:/mnt/hgfs/vrshare/eng/linux_Sys/day4$ ./a.out This is child process 70951 This is child process 70951 This is child process 70951 child quit,child status = 3 cnt=0 This is father process 70950 cnt=0 This is father process 70950 cnt=0 This is father process 70950 cnt=0 This is father process 70950 cnt=0 This is father process 70950 //程序设置子进程执行三次后,退出,父进程接到终止信号后运行

    三、总结

           1.由于篇幅原因,将在下一篇文章补充僵尸进程和孤儿进程相关概念        2.增强编程能力,光看不行,动手最关键,最后总结出一片文章记录自己的学习过程。

    Processed: 0.010, SQL: 8