进程控制
fork函数
我们创建多个进程以后,哪个进程先运行是不确定的,是由进程调度器决定。
整个的调度过程我们不可知,父子兄弟进程谁先运行我们不确定。
进程终止
exit()
为什么main函数总是返回0,这个东西给谁了,不能返回1,2,3等等其他的吗。
main函数的返回值表示的是进程运行完成时是否是正确的结果,如果不是,可以用不同的数字,表示不同的出错原因。
进程的退出场景:
代码运行完毕,结果正确
代码运行完毕,结果不正确
代码异常终止
0表示进程的退出码,进程的结果是正确的。
谁会关心我们进程运行完的结果呢?
那就是父进程,父进程要获取子进程不正确的原因。
结果不正确:是进程的退出码不为0。
main函数的返回值表示:运行完成是是否是正确的结果,如果不是,可以用不同的数字,表示不同的出错原因。
echo &? 打印最近一次运行结果的退出码。
strerror()把错误码转换为退出信息,描述错误码信息。
errno:c语言给我们提供的全局变量,保存的是最近一次的退出码。库函数调用出错以后,errno将会保存,errno保存的是最近一次的。
进程异常终止, 本质就可能是代码没有跑完,进程的退出码没有意义,我们就不关心退出码了。根本就不会执行最后一跳return语句。
进程出现了异常本质是我们的进程收到了对应的信号。
exit就是进程的退出码,return也可以达到向对应的效果。
exit函数是c语言的库函数。
exit在任意地方被调用都表示调用该进程直接退出
return只表示当前函数返回 ,在main函数中表示进程退出。
_exit系统调用
也能够提前终止,直接终止掉,缓冲区的数据就没有了。
exit是库函数是对_exit函数进行了包装。
我们printf函数一定是先把数据写入缓冲区,合适的时候,在进行刷新。
进程等待
是什么:
通过系统调用wait/waitpid,来进行对子进程状态检测与回收功能。
为什么:
进程等待的必要性,子进程如果退出了,
僵尸进程是无法被杀死的,需要通过进程等待来杀掉它,进而解决内存泄露的问题。(必须解决的)
我们要通过进程等待,获取子进程退出的情况,知道我布置给子进程的任务,它完成的怎么样了(可选择的)
怎么办:
代码:
如果有多个子进程,wait等待哪一个呢?
wait等待任意一个子进程退出。
如果子进程一直不退出,那么父进程将一直阻塞,等待子进程的退出。
wait是阻塞等待,需要等到子进程退出。
如果子进程不退出,父进程默认在wait的时候,调用这个系统调用的时候,也就不返回,默认叫做阻塞状态。
子进程的退出结果我们如何得知呢?
pid_t waitpid(pid_t pid, int * status , int options )
第一个参数可以传递不同的值,pid = -1 ,等待任一子进程与wait等效。
pid > 0,等待进程ID与pid相等的子进程。
pid = 0 等待的条件还没有就绪
waitpid的返回值是等待的那个进程的pid。
*第二个参数int status:
这是一个输出型参数,期望通过指针将函数内部的资源带出来 。
这个int是被当作几部分使用的。
低的8个比特位表示进程的代码是否出了异常。进程异常其实就是收到了信号。形如kill- l 的这些信号
这里的次八个比特位,表示退出状态。就是我们的退出码。
WIFEXITED(status) 检查进程退出时是否出异常了
WEXITSTATUS(status) 若WIFEXITED(status)非零,提取子进程的退出码。
这两个是宏。
第三个参数:option
option设置当前等待的方式,默认是0,也就是阻塞等待的方式。
纯阻塞式调用就是父进程一直等待子进程的结束,父进程什么也不做。
第二种等待方式:非阻塞轮询。WNOHANG
非阻塞轮询是一直在问子进程是否结束。
非阻塞轮询+自己的事情。让我们父进程可以做其他的事情。
父进程投递到等待队列,就是子进程的等待队列,就是子进程PCB内维护的。
非阻塞轮询 + 自己的事情 WNOHANG
waitpid实时监测子进程状态,第三个参数为0表示父进程正在阻塞,在等待子进程退出。
任务不要太长,子进程退出是也不是立马回收,等待其他子进程一起回收。
子进程等待过程当中,父进程的任务应该简单。
父进程一定是最后一个退出的进程
等待过程中,可以将曾经创建的子进程进行释放。
父进程一定是最开始运行的
进程等待的原理:
任意进程都有自己的退出信息。
父进程通过waitpid函数来检查子进程的退出信息。
wait一次只能等待一个进程结束,如果有多个子进程,需要用for循环进行调用wait,将每个进程都回收
核心工作就是读取子进程的task_struct,内核数据结构对象。
操作系统也会检测你等待的是否是你的子进程
程序替换
进程替换的原理:
程序替换不创建新进程,只进行进程的程序代码和数据的替换工作。
execl
函数的原理如下:
- 加载新程序: 当调用
execl
时,它会加载一个新的程序映像到当前进程的地址空间中。这个新程序会完全取代当前进程的内容,包括代码段、数据段等。 - 清理当前进程: 调用
execl
后,操作系统会清理当前进程的内存空间,释放旧程序所占用的资源。这包括清除当前进程的代码、数据和堆栈等内容。 - 加载新程序内容:
execl
接受要执行的新程序的路径和参数作为参数,并用它们替换当前进程的内容。新程序的路径指定了要执行的可执行文件,而后续的参数指定了新程序的命令行参数。 - 执行新程序: 一旦
execl
成功执行,控制权就会移交给新程序。新程序会从其入口点开始执行,而当前进程则被完全替换,不再存在于系统中。
这种 exec
系列函数的特性使得进程可以在运行时动态地切换到其他程序,从而实现了程序的连续执行和功能的扩展。通常,execl
函数会在子进程中调用,因为在调用成功后,当前进程的内容将被完全替换,包括代码和数据,不再是原始的父进程。****
可变参数列表
!
第一个参数是文件路径,第二个是执行的指令。
程序替换把别人的程序替换一下后面的程序不跑
exec系列的函数,只有失败返回值,没有成功返回值
Linux中形成的可执行程序,是有格式的,ELF,可执行程序的表头,可执行程序的入口地址。
execl第一个参数找到该程序。
第二个第三个参数是找到程序之后,接下来的操作
execlp:
不用带路径,会在环境变量中查找路径。自己会在默认的PATH环境变量中查找。第一个参数直接写文件名字即可。
execv v代表的是vector
v改变的是第二个参数,不再是一个可变参数,而是一个字符数组的形式传入。
在linux系统中,所有的进程启动都是由execv系列函数来启动的。
exec函数承载的是加载器的功能。
承担着把数据从磁盘加载到内存的功能。
execvp不用加PATH,直接告诉文件名就行。
execle 第一个参数是程序路径,第二个是指令,第三个是环境变量
e是环境变量,代表可以传输自己的环境变量。
环境变量是什么时候给进程的?
环境变量也是数据
他们也在地址空间当中
创建子进程的时候,环境变量就已经被子进程继承下去了,所以程序替换过程中,环境变量不会被替换。
所以我如果想给子进程传递环境变量,该怎么传递?
1 新增环境变量
在父进程的空间直接putenv
enevle 传递环境变量,直接采取的是覆盖。
2 彻底替换环境变量
execve 系统调用接口