Linux系统编程-进程回收
目录一. 进程回收二. 进程回收相关函数2.1 wait()2.2 waitpid()三. exec函数族3.1 exec函数族实现的原理命令whereis3.2 exec函数族的区别环境变量3.3 shell命令的种类3.4 返回值3.5 system函数一. 进程回收一个进程在终止时会关闭所有文件描述符释放在用户空间分配的内存但它的PCB还保留着内核在其中保存了一些信息如果是正常终止则保存着退出状态如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息然后彻底清除掉这个进程。二. 进程回收相关函数2.1 wait()作用阻塞等待子进程结束并且将其回收返回值成功返回回收的进程的pid 失败返回-1且设置errno参数wstatus的作用当进程终止时操作系统的隐式回收机制1.关闭所有文件描述符 2. 释放用户空间分配的内存。内核的PCB仍存在。其中保存该进程的退出状态。(正常终止→退出值异常终止→终止信号)不关心子进程的退出状态时可用传入NULL可使用wait函数传出参数wstatus来保存进程的退出状态。借助宏函数来进一步判断进程终止的具体原因。宏函数可分为如下三组1.WIFEXITED(wstatus)为真 → 进程正常结束WEXITSTATUS(wstatus)如上宏为真使用此宏 → 获取进程退出状态 (exit的参数)注意wstatus是一个int类型的32位数而进程的退出状态在wstatus中只占8位故需要用宏函数来提取注意退出状态是8位0~255在子进程中的退出值最好不要超过此范围2.WIFSIGNALED(wstatus)为真 → 进程异常终止WTERMSIG(wstatus)如上宏为真使用此宏 → 取得使进程终止的那个信号的编号。*3.WIFSTOPPED(wstatus)为真 → 进程处于暂停状态WSTOPSIG(wstatus)如上宏为真使用此宏 → 取得使进程暂停的那个信号的编号。WIFCONTINUED(wstatus)为真 → 进程暂停后已经继续运行程序int main() { pid_t pid; pid fork(); if(pid 0){ perror(fork()); exit(1); } else if(pid 0){ int wstatus; //pid_t wpid wait(NULL);//不保存子进程的退出状态 //printf(parent:wait finish,pid is %d\n,wpid); pid_t wpid wait(wstatus); if(WIFEXITED(wstatus)){ printf(parent:child terminated normally,pid is %d,status is %d\n, wpid,WEXITSTATUS(wstatus)); //注意这里退出状态是8位范围0255在子进程中注意退出值不可超过此范围 } else if(WIFSIGNALED(wstatus)){ printf(parent:child kill by sig,pid is %d,sig is %d\n, wpid,WTERMSIG(wstatus)); } while(1){ printf(parent:pid is %d\n,getpid()); sleep(1); } } else{ int num 5; while(num--){ printf(child:pid is %d\n,getpid()); sleep(1); } exit(66); } return 0; }结果子进程正常退出结果子进程被信号结束结果2.2 waitpid()作用可用指定回收子进程也可用设置非阻塞模式参数pid 0回收指定pid的子进程 0回收与调用该函数同组的任意进程 -1回收任意子进程 -1回收取绝对值对应的进程组内的任意进程options可以设置非阻塞回收WNOHANGwstatus作用同wait函数返回值成功返回回收进程的pid号 失败返回-1并且设置errno注意当设置了非阻塞回收并且无子进程结束时将返回0注意使用waitpid回收子进程时应该使用轮询的方法代码int main() { pid_t pid; pid fork(); if(pid 0){ perror(fork()); exit(1); } else if(pid 0){ int wstatus; //使用waitpid非阻塞回收时要轮询 pid_t wpid; while(1){ wpid waitpid(pid, NULL, WNOHANG); if(wpid 0){ printf(wait finish, wpid is %d\n,wpid); } else if(wpid 0){ perror(waitpid()); } printf(parent:pid is %d\n,getpid()); sleep(1); } } else{ int num 5; while(num--){ printf(child:pid is %d\n,getpid()); sleep(1); } exit(66); } return 0; }结果三. exec函数族3.1 exec函数族实现的原理1、fork创建子进程后执行的是和父进程相同的程序但有可能执行不同的代码分支子进程往往要调用一种exec函数以执行另一个程序。当进程调用一种exec函数时该进程的用户空间代码和数据完全被新程序替换(代码区和数据区)从新程序的启动例程开始执行。调用exec并不创建新进程所以调用exec前后该进程的pid并未改变。2、将当前进程的.text、.data替换为所要加载的程序的.text、.data然后让进程从新的.text第一条指令开始执行但进程ID不变。3、实现原理系统实际上是完成文本区的二进制代码替换并不创建新进程3、作用执行一个新的文件在一个进程中执行另外一个文件4、参数路径执行程序时需要传递的参数NULL哨兵位命令whereis查看指定命令的可执行程序文件在哪里3.2 exec函数族的区别1、execl-l代表list列表要求以列表的方式传参执行自己的可执行程序要求传入路径path要执行的文件的路径与名称arg执行该文件所需要的参数2、execv-v代表vector容器(数组)执行可执行程序或者系统命令要求是传入一个数组数组中也要带NULLpath要执行的文件的路径和名称argv执行该文件需要的参数存放的数组3、execlp-p代表path路径执行系统命令借助环境变量PATH中的路径file需要执行的文件名称arg执行该文件需要的参数环境变量使用命令env可查看系统中的环境变量(操作系统自己为其定义的全局变量)其中PATH保存当前系统下可执行程序的路径PATH/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin: /bin:/usr/games:/usr/local/games:/snap/bin: /home/linux/opt/arm-2009q3/bin/:/home/linux/opt/arm-2009q3/bin/3.3 shell命令的种类shell命令分为三种外部命令cprmmv ---- 可以通过execl来执行内置命令cd别名命令ll使用whereis 二进制/库 来查看对应的位置3.4 返回值只有在出现错误时exec() 函数才会返回。其返回值为 -1并且会设置errno。当执行成功是代码段会被替换不会执行下面的代码故执行错误时直接使用perror打印错误信息即可不用if判断能执行到perror这句一定出错3.5 system函数作用系统库函数 system() 利用 fork() 函数创建一个子进程该子进程会使用 execl() 函数来执行指定的 shell 命令。在程序中执行一条系统指令参数command要执行的命令字符串返回值创建进程失败返回-1命令执行完成成功 / 失败都会返回一个整数返回值 32位整数结构如下高 8 位 低 8 位 退出状态 终止信号要想知道命令是否正常结束可以使用宏WIFEXITED(status) // 是否正常退出 WEXITSTATUS(status) // 退出码现在根据system函数的原理来实现my_system/根据system的原理去实现system void my_system(char *cmd) { char *argv[10] { NULL }; char buff[512]; int i 0; strcpy(buff, cmd);/传参过来的是一个常量字符串不可以直接使用 /先将字符串分割进行命令解析 for(argv[i] strtok(buff, ); argv[i] ! NULL; argv[i] strtok(NULL, )){ i; } /* argv[i] strtok(buff, ); while(argv[i]){ i; argv[i] strtok(NULL, ); }*/ /创建子进程去执行 pid_t pid; pid fork(); if(pid 0){ perror(fork()); exit(1); } else if(pid 0){ wait(NULL); } else{ execvp(argv[0], argv); } } int main(int argc, const char *argv[]) { my_system(ls -l -h); printf(after system\n); return 0; }