之前在晋城地址空间中有提到fork函数,可以创建一个子进程,但是它却有两个返回值附进程,返回子进程的进程号,PID子进程则返回零,那如何理解这一过程?一个函数返回两个返回值呢?
可以看到帮助文档中显示有两个返回值.
Fork函数是一个系统调用,其中包括如下几个步骤,有创建子进程的PCB进程控制块,初始化进程PCB控制块,创建子进程新的地址空间,在初始化子进程的地址空间,创建页表的映射再将pcb进程控制块加入CPU的运行列表中,再当fork返回值进行写回时,他们虽然是同一个变量,但是会发生写时拷贝,因此同一个变量会有两个不同的值.
在C语言当一个程序是从main函数开始运行的,main函数也有一个return返回值,这个返回值在操作系统中看来是一个进程的退出码,表示用来得到一个进程运行完成后的处理结果如何当有个c语言程序执行完成后如果这个c语言的return值返回的是零则使用问号变量来查看上一个程序执行的返回值就会发现问号变量的返回值也是零所以在linux命令行,中问号变量中保存了上一个可执行程序的最终返回值信息.
如下当输入未知指令时错误码为127,当ls指令成功执行时错误码为0,表示没有错误.
可以用C语言来打印linux中所有的错误码:
进程退出:当一个进程退出时,有三种情况,第一种情况表示正确退出,第二种情况表示错误退出,第三种情况表示进程异常终止,而进程的退出码则只有在错误退出时才会有可控的参考作用,C语言中一个进程,从main函数中返回的返回值表示,进程结束还有一种是用exit函数来退出,并且设置退出码需要特别注意。当调用了exit退出时,进程会直接退出,无论这个exit写在哪里,此外,还有一种,_exit,函数也用于退出,只不过_exit系统调用,而exit则是C语言封装的_exit,C语言封装的exit退出函数,包含了在进程退出时同时刷新缓冲区的作用.
如上图_exit中处于man帮助第二页的系统调用,
如上图使c库函数的封装.
进城等待:之前有提到过一个僵尸进程,在没有被回收是会占用系统资源的,因为他的PCB进程块一直存在于操作系统中,等待父进程的回收,一般来说,父进程如果没有特定的操作,他会默认的回收僵尸状态的子进程,但并不会返回子进程的退出状态,而系统调用waitpid可以让父进程等待回收子进程,并且返回值为子进程的PID,具体需要传入三个参数,第一个参数为需要回收的子进程的PID,第二个参数为一个输出型参数,他会将错误码设置,第三个参数为等待方式,可以设置为阻塞等待或非阻塞等待.
特殊的是第二个参数,它是一个输出型参数,而这个参数是一个位图结构,它共有32位。当进程退出状态被置位后,从低到高8到15位是进程正常退出后的退出码,而0到7位表示信号终止的退出码,第八位则为core dump标志位,我们首先可以使用按位与的运算来得到第二个参数的进程退出状态,也可以使用系统中提供的WIFEXITED函数来直接判断进程的退出状态,waitpid系统调用中的第三个参数,如果设置为零,则是阻塞等待的方式来回收紫子进程。如果是系统中封装的wonhange表示非阻塞等待的方式来回收子进程的错误信息,非阻塞的等待方式的优点是可以让父进程有时间去干别的任务,定期轮巡的来检测被等待的子进程.
进程替换:父进程可以使用fork创建一个子进程来执行他们后面共同的代码,但也其实可以创建一个新的子进程来让这个子进程执行全新的程序,这个叫linux中的进程替换,这也是使用到了系统调用来达到进程替换的目的,使用exec系统调用可以执行磁盘中的一个文件,第一个参数表示程序的存放路径,第二个表示执行命令,然后可变参数可以选择程序执行的选项,最后,需要用到null来结尾,进程替换的本质是将进程地址空间中的物理内存地址替换为新的可执行程序的物理内存数据,进程pid不变也没有新建新的子进程.
进程替换只有一个系统调用,但是C语言库中对这个系统调用进行了多个封装,可以按需求的去调用不同的c接口来实现进程替换的目的,例如有execlp ,execv, execvp等这些c库函数,因此,我们可以理解为一个解释器的shell的一个简单的原理,就是在一个死循环中不断的使用子进程调用进程替换来不断的执行新的进程,在linux中,每个进程都有自己的工作目录,而我们使用CD命令则是切换解释器进程的工作目录使解释器进程本身在指定目录下工作,因此CD命令并没有创建新子进程来,而是使用了chdir来切换解释器的工作目录,这样的命令叫做解释器内键命令,echo也是同样的原理.
如下是c库函数封装的进程替换和接口,