进程终止
三种场景,进程结束结果正确,进程结束不正确,异常终止。
进程的退出码,也就是return,表征着进程运行结果是否正常,return 返回值被父进程接收。return 0表示进程运行结束并且结果正确。他是给用户的。
而进程异常终止,这种场景是父进程需要关心的。进程运行结果是正确的,这是一个唯一确定的状态,只需要return0就可以。但是运行结果不正确有很多种情况,这就可以用return 返回非0的值来表征错误原因。
echo $? 指令输出最近一次的进程退出的退出码。非0的退出码对应具体的错误原因:
该函数将退出码转换为字符串类型的错误信息。错误码对应的错误信息当然也可以自己定义。C语言中errno中保存最近一次使用的错误码。
当程序因为异常而退出,此时的退出码没有意义。即先判断进程是否异常退出,再关心返回结果。
进程异常退出本质上是接收到了信号:
exit()与_exit():
前者是库函数,后者是系统调用。exit()在退出时会对缓冲区做刷新,_exit()直接在OS将进程退出,不对用户层的缓冲区刷新。
进程等待:
子进程退出后需要父进程对其进行资源回收并接收退出信息。子进程执行结束向父进程发信号,父进程如果因意外终止或者其他原因未对已经终止的子进程进行资源回收,该子进程编程僵尸进程,其创建的资源如PCB等在OS中继续存在,造成资源的浪费。
所以父进程需要通过进程等待的方式回收子进程:
僵尸进程演示代码:
进程等待的两个系统调用:
pid_t wait(int *wstatus):等待一个任意的进程,等待成功返回该进程的pid,等待失败返回-1。wstatus,一个输出型参数,OS向该参数写入,用于返回进程的退出码和异常状况。
当父进程运行带wait,子进程没有返回,父进程会阻塞等待。
pid_t waitpid(pid_t pid, int *wstatus, int options):
suatus参数:
被当作位图,若进程异常,低七位被OS填充相应的异常信号,第八位是Core dump标志,次八位被OS用来填充进程的退出结果。
即当等待进程退出后,先检查status的低七位,若为0,说明进程没有发生异常。其次再取次八位的返回结果。
status由OS从相应进程的task_struct中获取:
非阻塞轮询:
waitpid() 第三个参数设置为WNOHANG为非阻塞轮询。设为0为阻塞等待。
用返回值来判断该调用的执行情况。ret<0 等待失败;ret=0子进程未就绪,继续等待;ret>0为成功等待的子进程的pid。
父进程在轮询是在ret=0的代码块中做自己的任务。
waitpid()可以保证父进程是最后退出的。
进程替换:
3号手册程序替换的系统调用有六个,2号手册有一个,一共七个相关接口。
示例代码:
原理:
进程执行到execl()系统调用,将要替换的程序和数据在内存中直接覆盖原进程的代码和数据,然后在PCB中调整下相关结构,就完成一次进程替换
多进程时,代码数据覆盖时发生写时拷贝,不影响父进程,父子进程仍保持独立性。并且替换时只修改了相应进程task_struct和mm_struct结构内的一些成员。
execl系列函数调用失败时才会执行原程序后续代码。它不需要返回值。
由于可执行程序ELF文件是有自己的格式的。分段:
程序的入口就被保存在文件头中,所以,程序替换后,liunx可以定位到表头中可执行程序的入口 。
execl():可变参数。第一个参数是要替换的程序所在位置,然后将后续参数以一个一个分割的字符串形式传递。最后一个参数为NULL。
execlp():无需传递路径,他会在系统的环境变量路径中查找要替换的可执行程序文件。只需要传递文件名和参数。
execv():路径加一个指针数组
execvp():默认去系统路径寻找文件。传递指令参数 。
execvpe():第三个参数允许传递自己的设计的环境变量。
将文件指定为相应的解释器,如python,bash,java等解释器,然后传入相应的调用指令,就可以实现c语言调用其他语言的程序文件。execvpe()允许向调用的程序中传入环境变量,通过它就可以实现将一个程序执行后形成的环境变量导入到另一个程序。
一个进程的地址空间中本身就有环境变量,进程替换后系统维护的task_struct中的环境变量不被替换。fork()出的子进程会继承该环境变量。
由于在shell中执行一个程序是以shell的子进程形式执行,当向shell export 一个环境变量时,在该shell下执行的进程也能继承到该环境变量,并且也会被该子进程的子进程一路继承下去。
获取环境变量
程序获取环境变量有三种方式:
命令行参数,;
声明外部变量 extern char **environ;
getenv();接口
向该进程的上下文中添加环境变量:putenv(); 该接口只向当前进程的上下文导入环境变量,不影响父进程,这就允许在一路下沉中的一系列子进程中,可以通过在父进程中调用putenv(),使子进程继承到该变量。
putenv();修改后的环境变量通过char **environ获取,并可以通过它调用execle()传递给子进程。这样就可是将一些数据传递给子进程。
如果不想传递系统的环境变量,可以自定义一些环境变量:
makefile一次编译两个文件: