Linux----进程控制
进程终止
函数的返回值是做什么用的。函数返回值可以是return,或者exit():
可以用echo打印最近一个进程的退出码,当然也可以手动其他的值。

其实是子进程返回给父进程的退出码,用于处理子进程。
一个函数,执行完毕,结果正确,返回0;
执行完毕,结果错误,返回其他值,让程序员知道哪里出错了,可以手动设置错误信息;
函数出现异常,没有执行完,给父进程传信号。
C语言中有strerror可以查看错误信息:

这是C语言数字对应错误的信息。

ll指令其实也是程序,当找不存在文件时就会报错,与2对应。ll就是用C语言写的。
return、exit()、_exit()的区别
return与exit()都可以为函数返回值:



但其实,exit()会引起当前进程的终止,如果不在main函数中写,而是在其中的任意一个函数写就会终止,不再执行下面的程序:


接下来说一说exit()与_exit()的区别,最大的区别就是_exit()是系统调用,执行进程直接中止,不会刷新缓冲区的内容,而exit()作为库中的函数,会刷新缓冲区的内容后,再终止进程:


而_exit():


所以缓冲区肯定不能在虚拟内存中的内核区部分,否则操作系统在终止一个进程肯定不会浪费这么一块资源。
都可以为函数返回值,但是exit()会直接终止进程,_exit()是系统调用,不会刷新缓冲区中的数据。
出现异常时
当进程出现异常时,会将发信号给父进程。如下都是异常的信号,可以使用kill指令让指定进程出现对应的异常

比如:


浮点数异常
进程等待
进程等待是通过系统调用wait/waitpid,来进行对子进程状态检测与回收的的功能。
解决僵尸进程
之前在进程状态时说过,僵尸进程不能使用kill或者ctrl+c杀死,只能通过进程等待来杀掉他,进而解决内存泄露问题。先看看僵尸进程是如何产生的:


可以通过wait()回收子进程资源:



创建多个子进程,并且回收资源:


如果子进程一直不结束,那么wait会使父进程阻塞,如果子进程结束,那么可以回收子进程资源。
退出信息
通过进程等待,获得子进程的退出情况,给子进程布置的任务他完成的怎么样了。
waitpid
这里要用到waitpid:
说明:如果pid参数为-1,效果与wait一致,可以等待任何进程;
如果pid>0,那么会等待对应id的进程;
status为一个输出型参数,怎么理解呢?前面说了进程退出的三种情况:1.正常退出,结果正确;2.正常退出,结果错误;3.出现异常。
那么父进程在接收子进程信息时,就希望知道子进程是否出现异常,出现什么异常?子进程正常退出的话,结果是否正确?如不正确,出现什么错误了?所以在这种情况下,需要将这些信息写在status中。
status格式

可以看一个例子:

子进程退出1,返回的是256,其实就是2的8次方,就是status的第9个位置为1,子进程的退出码给到了status对应的位置。退出码、异常信号对应的值是多少,就填到上面图中对应的位置。
再看一个例子:浮点数错误,返回异常信号



宏信息
还可以使用宏函数来获取退出信息:
WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出),其实就是status&0x7F,如果为0,就返回真。
WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)其实就是(status>>8)&0xFF
![]()
返回状态为0,也就是假,有异常。
waitpid返回值
waitpid正常时,返回等待进程的pid,不正常时,会返回-1,当且仅当它接收的进程不是参数中的id的进程。

![]()
waitpid中第三个参数为WNOHANG时,父进程执行到waitpid时,如果子进程还没有结束,直接退出返回0,不会再阻塞等待子进程;而当其为0时,会阻塞等待子进程。
给子进程睡5秒,让父进程快些执行:


父进程没有阻塞等待子进程,先退出了,而子进程称为孤儿进程,父亲变成了init,执行完成后后被处理。
非阻塞轮询访问
如果我们希望用非阻塞的方式等待子进程呢?可以用到非阻塞轮询访问:不等待结果、主动反复检查状态 的一种工作方式。可以用循环+WNOHANG来实现:


进程程序替换
可以使用以下库函数进行程序替换:接下来会给出用法以及原理

单进程程序替换
举一个例子,可以看到在该进程中,并没有打印code end,而是打印code begin后执行了ls程序。这就是程序替换

原理
当进程调用一种exec函数时,该进程的用户空间代码和数据完全被新程序替换(原进程的代码、堆、栈、数据段都被覆盖),从新程序的入口函数开始执行,不会从原 main() 继续执行。调用exec并不创建新进程,所以调用exec前后该进程的id并未改变(如进程号、父进程号、文件描述符表(可选保留))。
多进程程序替换
可以看到在子进程内进行程序替换,并不会影响父进程的运行。因为进程间的独立性,子进程发生覆盖时,进行写时拷贝。

exec函数介绍

其中,第一个参数为path的,都是要给出命令(程序)所在的具体路径的,而file直接给出程序名就可以,他自己会去PATH中找对应的程序名;第二个参数一般都是如何执行命令的参数,比如*arg,....,这个是可变参数,可以带很多命令的选项,如果是argv,那么要将命令都写在这个数组中,注意,最后一个参数必须是NULL;有第三个参数的函数,可以所替换的程序传入对应的环境变量。
execl和execlp
可以使用其替换成自己写的程序

与之前ls的path不同,这里给出ls指令的名字就可以成功执行

execv和execvp
注意,参数数组要以nullptr为结尾。两个的区别与上面类似,第一个传入参数。

execle
该函数可以为替换程序传递,在讲这个函数之前,我们首先要清楚,即使我们不使用这个函数为替换程序传递环境变量,它自己也会继承父进程的环境变量:
隐式传环境变量

可以看到,project成功打印了父进程的环境变量,在这里我们使用了putenv函数为替换进程添加了一个环境变量BROTHER,这个环境变量在父进程中是没有的。为什么替换的程序会有父进程的环境变量呢?因为之前说过,进程的命令行参数与环境变量是存在用户虚拟空间中的:

而进程程序替换只替换栈堆数据段和代码段的内容,不会替换别的内容,所以该程序依然存储着父进程的环境变量!
显式传环境变量
如果非要为替换进程传递环境变量呢?可以使用execle函数和environ变量。environ是一个全局变量指针,在所有使用 C/C++ 的进程中都存在。它保存了当前进程的 环境变量表。

还可以自己定义环境变量,覆盖父进程环境变量:
environ
environ 是一个全局变量,保存当前进程的环境变量表(即 char **environ,指向一个以 NULL 结尾的字符串数组,每个元素形如 "KEY=VALUE")。我们可以通过它访问或修改环境变量,比如查看 PATH 或设置自定义变量。
在 C 程序中使用 environ 时,通常要加上extern char **environ;这是因为 environ 不是在当前源文件里定义的变量,而是在系统的 C 运行库(如 glibc)中定义的。extern 告诉编译器:“这个变量在别的地方定义,我这里只是引用它”。否则编译器会以为你要重新定义一个 environ,导致链接错误或与系统变量冲突。
总结
l(list) 表示参数采用列表 v(vector) 参数用数组 p(path) 有p自动搜索环境变量PATH e(env) 表示自己维护环境变量 只有execve是真正的系统调用,其它五个函数最终都调用 execve。
调用其他语言
可以使用exec调用其他语言进程,如脚本:

还有python:
之所以可以使用
exec调用由其他语言编写的程序,是因为exec本质上是在操纵进程:它让当前进程的程序内容被新的可执行文件替换,而不是创建新进程。
只要目标程序能够被编译或解释为可执行文件,在操作系统中能以进程形式运行,就可以被exec加载执行。


