一、进程的创建与相关函数
1.进程创建
系统允许一个进程创建新的进程,新进程为原进程的子进程,子进程还可以创建新的子进程,形成进程树结构模型,其相关函数为pid_t fork(void);具体使用方式如下:
/* #include <sys/types.h> #include <unistd.h> pid_t fork(void); 作用:用于复制创建子进程 返回值: fork()返回值会返回两次,一次在父进程一次在子进程 在父进程中:返回子进程id 在子进程中:返回0 通过返回值区分父子进程 失败:返回-1(在父进程中),并设置errno,可能是进程数上线,或者是系统内存不足 父子进程之间的关系: 区别: 1.fork()函数返回值不同 父进程>0,为子进程id 子进程==0 2.pcb中的数据: 当前的id, pid 当前的进程的父进程的id,和ppid 共同点: 某些状态下,子进程刚刚被创建,还没有进行数据的写入,此时,父子进程共享 -用户区数据 -文件描述符表 一开始变量共享,若修改了数据,则不共享变量 */ #include <sys/types.h> #include <unistd.h> #include<stdio.h> int main(){ int num =10; //创建子进程 pid_t pid = fork(); //判断父进程还是子进程 if (pid>0) { printf("pid:%d\n",pid); //如果>0,返回子进程进程号,当前是父进程 printf("i am parent process, pid:%d, ppid:%d\n", getpid(), getppid()); printf("parent_num:%d\n", num); num+=10; printf("parent_num+=10:%d\n", num); } else if (pid==0) { //当前是子进程 printf("i am child process, pid:%d, ppid:%d\n", getpid(), getppid()); printf("child_num:%d\n", num); num+=100;//子进程num与父进程num无关 printf("child_num+=100:%d\n", num); } else{ perror("fork\n"); return -1; } //for循环 for (int i = 0; i < 3; ++i) { printf("i: %d, pid: %d\n",i , getpid()); sleep(1); } return 0; }
父子进程共享内核区,但是内核区中pid不同。用户区互不相关。
2.exec函数族
exec函数族的作用是根据指定的文件名找到可执行文件,并用它来取代调用进程的内容,换句话说,就是在调用进程内部执行一个可执行文件。exec函数族的函数执行成功后不会返回,因为调用进程的实体,包括代码段,数据段和堆栈等都已经被新的内容取代,只留下进程ID 等一些表面上的信息仍保持原样。看上去还是旧的躯壳,却已经注入了新的灵魂。只有调用失败了,它们才会返回 -1,从原程序的调用点接着往下执行。常用函数有int execl(const char *pathname, const char *arg); int execlp(const char *file, const char *arg);
/* #include <unistd.h> int execl(const char *pathname, const char *arg); 参数: -pathname:需要指定可执行的文件路径的名称(建议绝对路径) -arg:字符串,即可执行文件需要的参数,被main的arg[]数组获取 第一个参数一般写可执行程序的名称,没有实际作用 第二个及以后的参数为可执行程序需要的参数列表 参数最后需要以NULL结束(称为哨兵) 返回值: 只有出错的时候才有返回值,为-1,并设置errno,成功了没有返回值(已经不执行原文件了) */ /* #include <unistd.h> int execlp(const char *file, const char *arg); 作用:会到环境变量中去寻找可执行文件,找到了则执行 参数: -file:环境变量中文件的名称 -arg:字符串,即可执行文件需要的参数,被main的arg[]数组获取 第一个参数一般写可执行程序的名称,没有实际作用 第二个及以后的参数为可执行程序需要的参数列表 参数最后需要以NULL结束(称为哨兵) 返回值: 只有出错的时候才有返回值,为-1,并设置errno,成功了没有返回值(已经不执行原文件了) */ #include<stdio.h> #include<unistd.h> int main(){ //创建一个子进程,在子进程中执行exec中函数 pid_t pid = fork(); if(pid>0){ printf("i am parent process, pid: %d\n", getpid()); sleep(1); }else if(pid==0){ //子进程替换 execl("/usr/bin/ps","ps","aux",NULL); printf("i am child process, pid: %d\n",getpid()); } for (int i = 0; i < 3; i++) { printf("i :%d, pid = %d\n", i, getpid()); } return 0; }
二、进程的消亡
1. 进程正常退出
/* #include <stdlib.h> void exit(int status); #include <unistd.h> void _exit(int status); status参数:是进程退出时的一个状态信息,父进程回收子进程资源时可以获取到。 */ #include <unistd.h> #include <stdlib.h> #include<stdio.h> int main(){ printf("hello\n"); printf("world"); //_exit(0); Linux系统函数不刷新io缓冲 exit(0);//标准c库会刷新io缓冲,使用更多 return 0; }
2.孤儿进程
父进程死亡,但是子进程还未结束运行。此时孤儿进程会被系统init进程领养。孤儿进程并没有什么危害。
3.僵尸进程
每个进程结束后,都会释放自己地址空间中的用户区数据,但是内核区PCB必须要由父进程去释放,当进程终止时,父进程未回收,子进程PCB残留在内核中,变成僵尸进程。以下代码会创建出僵尸进程,即父进程未结束,并且未回收子进程所占用的信息。僵尸进程如其名,不能被kill -9 命令杀死,只能通过强制结束父进程来完成回收,如果产生大量僵尸进程,会导致有限的系统进程号被大量占用,导致不能产生新的进程,会造成不可估计的损失,应当避免。
#include <sys/types.h> #include <unistd.h> #include<stdio.h> int main(){ int num =10; //创建子进程 pid_t pid = fork(); //判断父进程还是子进程 if (pid>0) { //父进程一直循环 while(1){ printf("i am parent process, pid:%d, ppid:%d\n", getpid(), getppid()); sleep(1); } } else if (pid==0) { //当前是子进程,直接打印并结束 printf("i am child process, pid:%d, ppid:%d\n", getpid(), getppid()); } else{ perror("fork\n"); return -1; } //for循环 for (int i = 0; i < 3; ++i) { printf("i: %d, pid: %d\n",i , getpid()); sleep(1); } return 0; }
4.避免僵尸进程的方法——进程回收命令
在每个进程退出的时候,内核释放该进程所有的资源、包括打开的文件、占用的内存等。但是仍然为其保留一定的信息,这些信息主要主要指进程控制块PCB的信息(包括进程号、退出状态、运行时间等)。父进程可以通过调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。wait ()和waitpid()函数的功能一样,区别在于, wait()函数会阻塞,waitpid ()可以设置不阻塞, waitpid()还可以指定等待哪个子进程结束。注意:一次wait或waitpid调用只能清理一个子进程,清理多个子进程应使用循环。
/* #include <sys/types.h> #include <sys/wait.h> pid_t wait(int *wstatus); -功能:等待任意一个子进程结束,回收状态变化信息,释放相关资源 -参数: int *wstatus:进程退出时的状态信息,传入的是一个Int类型的地址,传出参数。 -返回值: -成功返回被回收子进程id -失败返回-1(所有子进程结束) ----调用wait函数时,此进程会被挂起,直到子进程退出或者受到一个不能被忽略的信号,则被唤醒 若没有子进程或者子进程都结束了,返回-1 */ #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> #include<stdio.h> int main(){ //有一个父进程,要创建5个子进程 pid_t pid; for (int i = 0; i < 5; i++){ pid = fork(); if(pid==0)break;//避免子进程嵌套 } if(pid>0){ //父进程 while(1){ printf("parent , pid = %d\n", getpid()); int st; int ret = wait(&st); if(ret == -1){ break; } if(WIFEXITED(st)){ //是不是正常退出 printf("退出的状态码:%d\n",WEXITSTATUS(st)); } if(WIFSIGNALED(st)){ //是不是异常终止 printf("被什么信号终止:%d\n",WTERMSIG(st)); } printf("child die, pid = %d\n", ret); sleep(1); } }else if (pid==0) { /* 子进程 */ //while(1){ printf("child, pid = %d, ppid = %d\n", getpid(), getppid()); sleep(1); //} exit(1); } return 0; }
/* #include <sys/types.h> #include <sys/wait.h> pid_t waitpid(pid_t pid, int *wstatus, int options); 功能:回收指定进程号的子进程,但是可以设置非阻塞,即不等待子进程终止 参数: -pid_t pid: pid>0:某个子进程pid pid==0:回收当前进程组所有的子进程 pid==-1:表示回收所有的子进程,相当于调用wait pid<-1:回收某个进程组子进程(组号为此参数的绝对值) -int *wstatus:回收信息 -int options:设置阻塞或者非阻塞 0:阻塞 WNOHANG:非阻塞 返回值: >0:返回子进程的id ==0:options=WNOHANG,表示还有子进程活着 ==-1:错误,或者没有子进程了 */ #include <sys/types.h> #include <unistd.h> #include <sys/wait.h> #include <stdlib.h> #include<stdio.h> int main(){ //有一个父进程,要创建5个子进程 pid_t pid; for (int i = 0; i < 5; i++){ pid = fork(); if(pid==0)break;//避免子进程嵌套 } if(pid>0){ //父进程 while(1){ sleep(1); printf("parent , pid = %d\n", getpid()); int st; int ret = waitpid(-1, &st, WNOHANG); if(ret == -1){ break; } if(ret==0){ continue; } else if (ret > 0){ if(WIFEXITED(st)){ //是不是正常退出 printf("退出的状态码:%d\n",WEXITSTATUS(st)); } if(WIFSIGNALED(st)){ //是不是异常终止 printf("被什么信号终止:%d\n",WTERMSIG(st)); } printf("child die, pid = %d\n", ret); } } }else if (pid==0) { /* 子进程 */ while(1){ printf("child, pid = %d\n", getpid()); sleep(1); } exit(0); } return 0; }
退出信息宏如下(代码中st):