1,完善进程管理,结束上节课的内容
2,引出进程同步
3,提到了部分进程间的管道通信。
当父进程通过fork()创建了一个子进程,由于子进程是讲父进程的程序完完全全的复制过来的,当我们需要在子进程中执行一些指令那么父进程必须先设置,所以为了解决这个问题引入一个函数exec家族的函数。
exec()函数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 execve(const char *path, char * const argv[], char * const envp[]);
- execl类:函数将以列举的形式传入参数,由于参数列表的长度不定,所以要用哨兵NULL表示列举结束;
- execv类:函数将以参数向量表传递参数,char * argv[]的形式传递文件执行时使用的参数,数组中最后一个参数为NULL;
当参数是path时,传入的为命令的路径名;当参数是file,传入的可执行文件名。
案列1#includeexit()函数#include #include int main(){ pid_t tempPid; tempPid=fork(); if(tempPid == -1){ perror("fork error"); exit(1); } else if(tempPid > 0) { printf("parent process:pid=%dn", getpid()); } else { printf("child process:pid=%dn", getpid()); //execl("/bin/ls","-a","-l","./",NULL); //① //execlp("ls","-a","-l","./",NULL); //② char *arg[]={"-a","-l","./", NULL}; //③ execvp("ls", arg); perror("error execn"); printf("child process:pid=%dn", getpid()); } //of if return 0; } //of main
void exit(int status);
参数status即状态,为0时正常退出,其它值为异常退出,在exit封装内部有一个核心函数,_exit()
- _exit:系统会无条件停止操作,终止进程并清除进程所用内存空间及进程在内核中的各种数据结构
- exit:对_exit进行了包装,在调用_exit()之前先检查文件的打开情况,将缓冲区中的内容写回文件。相对来说exit比_exit更为安全
简单来说就是)_exit()更接近底层,但exit()更安全
-
孤儿进程:父进程负责回收子进程,如果父进程在子进程退出之前退出,子进程就会变成孤儿进程,此时init进程将代替父进程完成子进程的回收工作;
-
僵尸进程:调用exit函数后,该进程不会马上消失,而是留下一个称为僵尸进程的数据结构。它几乎放弃进程退出前占用的所有内存,既没有可执行代码也不能被调度,只是在进程列表中保留一个位置,记载进程的退出状态等信息供父进程回收。若父进程没有回收子进程的代码,子进程将会一直处于僵尸态。
总结:孤儿进程等待init回收,程序结束退出但没有被回收则为僵尸进程,我的理解为当孤儿进程结束了但没有被init回收,那么它也算是一个僵尸进程,至于僵尸进程的危害,上篇也讲了。
进程同步进程同步:在多道程序环境下,进程是并发执行的,不同进程之间存在着不同的相互制约关系。
重点:进程之间存在着相互制约关系,父进程和子进程之间得关系可能导致孤儿进程和僵尸进程。
这时我们就要用到wati()函数
pid_t wait(int *status);
功能:挂起进程,进程进入阻塞状态,直到子进程变为僵尸态,如果捕获到子进程的退出信息就会转为运行态,然后回收子进程资源并返回;若没有变为僵尸态的子进程,wait函数就会让进程一直阻塞。若当前进程有多个子进程,只要捕获到一个变为僵尸态的子进程,wait函数就会恢复执行态。
返回值:
- 成功:返回子进程的进程id
- 失败:返回-1,errno被设置为ECHILD。
#include#include #include #include int main(){ pid_t tempPid, tempW; tempPid = fork(); if(tempPid == -1){ perror("fork error"); exit(1); }else if(tempPid == 0){//child sleep(3); printf("Child process, pid = %d, ppid = %dn", getpid(), getppid()); }else{//parent tempW = wait(NULL); printf("Catched a child process, tempW = %d, ppid = %dn", tempW, getppid()); }//of if printf(">>>>>>>>finish<<<<<<< 把父进程挂起就可以避免子进程变成孤儿进程,因为必须得子进程结束父进程的挂起才会结束。
若wait函数的参数不为空,可以获取子进程的退出状态,退出状态存放在参数status的低八位中。Linux定义了一组判断进程退出状态的宏函数,其中最基础的两个如下:
#include案列3int WIFEXITED(int status);//判断子进程是否正常退出,若是,返回非0值,否则返回0 int WEXITSTATUS(int status);//和WIFEXITED配合使用,WIFEXITED返回非0值,则使用该宏提取子进程的返回值。 #include#include #include #include int main(){ int tempStatus; pid_t tempPid, tempW; tempPid = fork(); if(tempPid == -1){ perror("fork error"); exit(1); } else if(tempPid == 0){//child sleep(3); printf("Child process: pid=%dn",getpid()); exit(5); } else{//parent tempW = wait(&tempStatus); if(WIFEXITED(tempStatus)){ printf("Child process pid=%d exit normally.n", tempW ); printf("Return Code:%dn",WEXITSTATUS(tempStatus)); } else { printf("Child process pid=%d exit abnormally.n", tempW); }//of if }//of if return 0; }//of main waitpid()函数
但wait()存在明显缺陷:如果存在多个子进程,那么只要其中一个子进程结束,那么父进程就解开了挂起状态,则后面的子进程都会成为孤儿进程。
为了解决这个问题我们引用函数waitpid()。#includepid_t waitpid(pid_t pid, int *status, int options); 参数说明:
案列4
pid:一般是进程的pid,也有可能是其他取值。进一步说明如下:
– pid > 0:等待子进程(编号为pid)退出,若退出,函数返回;若未结束,则一直等待;
– pid = 0:等待同一进程组的所有子进程退出,若某子进程加入了其他进程组,则waitpid不再关心它的状态;
– pid = -1:waitpid函数退化为wait函数,阻塞等待并回收一个子进程;
– pid < -1:等待指定进程组中的任何子进程,进程组的id等于pid的绝对值。
options: 提供控制选项,可以是一个常量,也可以是|连接的两个常量,选项如下:
– WNOHANG:如果子进程没有终止,waitpid不会阻塞父进程,会立即返回;
– WUNTRACED:如果子进程暂停执行,waitpid立即返回;
– 0:不使用选项。
返回值说明:
成功:返回捕捉到的子进程id;
0:options = WNOHANG, waitpid发现没有已退出的子进程可回收;
-1:出错,errno被设置。父进程等待进程组中指定子进程,该进程不退出,则父进程一直阻塞
#include案列5#include #include #include int main(){ pid_t tempPid, tempP, tempW; tempPid= fork(); //创建第一个子进程 if (tempPid == -1){ perror("fork1 error"); exit(1); } else if (tempPid == 0){ //子进程沉睡 sleep(5); printf("First child process:pid=%dn", getpid()); } else { //父进程继续创建进程 int i; tempP = tempPid; for (i = 0; i < 3; i++){ //由父进程创建3个子进程 if ((tempPid = fork()) == 0){ break; }//of if }//of for i if (tempPid == -1){ //出错 perror("fork error"); exit(2); } else if (tempPid == 0){ //子进程 printf("Child process:pid=%dn", getpid()); exit(0); } else { //父进程 tempW = waitpid(tempP, NULL, 0); //等待第一个子进程执行 if (tempW == tempP){ printf("Catch a child Process: tempW=%dn", tempW); }else{ printf("waitpid errorn"); }//of if }//of if }//of if return 0; }//of main waitpid函数不断获取子进程的状态。
#include进程间通信(部分)#include #include #include int main() { pid_t tempPid, tempW; tempPid = fork(); if (tempPid == -1){ perror("fork error"); exit(1); } else if (tempPid == 0){ sleep(3); printf("Child process:pid=%dn", getpid()); exit(0); } else { do{ tempW = waitpid(tempPid, NULL, WNOHANG); if (tempW == 0){ printf("No child exitedn"); sleep(1); }//of if } while (tempW == 0); if (tempW == tempPid){ printf("Catch a Child process:tempW=%dn", tempW); }else{ printf("waitpid errorn"); }//of if }//of if return 0; }//of main 这节课我们主要讲的进程间通信就是管道,因此就只写了管道
管道只能读或者写,写和读不能同时进行,如果两个进程同时对管道进行操作,则两个进程中必须有一个停止下来让另一个进行操作,否则就会错误。
管道分为:匿名管道
- 匿名管道:只能用于有亲缘关系的进程间通信,进程退出后管道会被销毁。
- 命名管道:命名管道与进程的联系较弱,相当于一个读写内存的接口,进程退出后,命名管道依然存在。
匿名管道匿名管道的使用流程如下:
①在进程中创建匿名管道,pipe函数;
②关闭进程中不使用的管道端口,close函数;
③在待通信的进程中分别对管道的读、写端口进行操作,read/write函数;
④关闭管道,close函数。0x01 pipe函数
#includeint pipe(int pipefd[2]); 功能:创建匿名管道
参数:
pipefd:传入参数,一个文件描述符数组;Linux将管道抽象为一个特殊文件
案列6
返回值:
- 成功:返回0.
- 不成功:返回-1。使用pipe()实现父子进程间通信,父进程作为读端,子进程作为写端。
#include#include #include #include #include #include int main(){ int tempFd[2];//定义文件描述符数组 int tempRet=pipe(tempFd);//创建管道 if(tempRet == -1){ perror("pipe"); exit(1); } pid_t tempPid=fork(); if(tempPid > 0){//父进程—读 close(tempFd[1]);//关闭写端 char tempBuf[64]={0}; tempRet = read(tempFd[0], tempBuf, sizeof(tempBuf));//读数据 close(tempFd[0]); write(STDOUT_FILENO, tempBuf, tempRet);//将读到的数据写到标准输出 wait(NULL); } else if(tempPid == 0){//子进程—写 close(tempFd[0]);//关闭读端 char *tempStr="hello,pipen"; write(tempFd[1], tempStr, strlen(tempStr)+1);//写数据 close(tempFd[1]); }//of if return 0; }//of main
把tempFd[]设为读和写的管道,在子进程中关闭读端,在父进程中关闭写端,由于在管道中不能同时读写,所以我们执行完父进程的读要close再挂起等待子进程写入数据。



