在Linux中创建进程的话通常也就两种方式,一个方法是创建可执行程序,然后通过我们的./的操作让进程创建。还有一种方法就是在程序代码过程中写fork函数,再父进程的状态下,创建子进程。 fork返回值返回两次,对于父子进程返回的值不相同,如果需要,我们可以通过if,else来分流,让父子进程在拥有相同代码的基础上实现不同的任务。子进程fork返回0,父进程返回值是子进程的PID。 进程:内核的相关管理数据结构(task_struct+mm_struct+页表)+代码和数据。其中的代码是共享,数据是写时拷贝的。 所以进程调用到fork的时候内核就需要这样做, 1、分配新的内存块和内核数据结构给子进程 2、将父进程的部分数据结构内容拷贝至子进程 3、添加子进程到系统进程列表中 4、fork返回,开始调度器调度 因此,由于代码不会相互影响,数据会在改变的时候发生写时拷贝并且父子进程都拥有属于自己的相关内核的数据结构。所以这样的进程拥有独立性的特点。 也是一个进程崩溃不影响另一个的原因。 所以为什么父进程返回的是子进程的PID,子进程的返回时0? 因为父进程得到子进程的PID的话,就能够尽可能方便管理,控制子进程,包括后续的杀掉进程,回收进程。 fork创建失败也是很常见的,失败的原因: 1、系统中有过多的进程 2、实际用户的进程数超过了限制 其中对于进程来说是两个部分,那么这两个部分是哪一个部分先创建的呢?先有内核的相关管理数据,然后才有的代码和数据。 这种状况在现实中也有,就比如说,你去找工作,找到工作之后收到offer之后,对于那个公司来说,在你人还没有到公司报到的时候,就已经算是创建好你的档案了,只有在你真正的抱到结束之后,才算是真正的员工,这里的报道,相当于就是你人去了,相当于上面的数据和代码本体,然后收到offer的时候,算是已经创建了PCB等一系列结构体。
1、释放曾经的代码和数据所占据的空间 2、释放内核数据结构 其中的内核数据结构中的task_struct结构体会延迟删除,但是别的会删除了。此时的状态已经是Z状态了,所以代码和数据也不会再调用了,所以才只会留下task_struct。
先问一个问题: 为什么之前我们写程序的时候main函数的返回都是0呢?不返回0会有什么结果吗? 当我们的两个程序,一个程序是return 0,另一个程序是return 100。我们通过echo的内建命令,能够观察到两个程序的?的数值不一样
在上一篇文章中我们知道echo命令不是bash的子进程能够读取本地变量,这个之前讲过。 那这个代表的是什么呢?表示的是父进程bash获取到的最近一个子进程退出的退出码(子进程的return值)。 有需要注意的地方
echo也当作是一个进程来看做,所以第二次的时候echo的值就是0,因为上一个的echo成功的运行了。 退出码0表示成功,非0表示失败。 退出码的作用就是告诉关系方(一般指的就是父进程),任务完成的怎么样。 当为0的时候表示的成功,程序结束不需要知道什么别的消息,但是非0失败的时候,就应该知道为什么程序会执行不到结果,为什么程序出现错误。 所以对于返回值1,2,3,4,5…这些数来说,一方面表示的是失败,另一方面表示的就是每一个数字在编译器中都有相对应的错误描述。 我们怎么知道错误信息表示的都是什么? 所以直接用程序打印出来看看。
所以父进程bash为什么要得到子进程的退出码呢?那就是为了是否成功,如果失败的情况之下,能够得到具体失败的原因。所以即使是得到失败的原因又能怎么样呢?不能怎么样,但是得到错误码能够为用户负责。 我们来写一个实例来帮助我们,退出码其实是很好用的。 如果说我们写一个简单的除法的运算器的话,肯定要保证除数不能是0,所以我们需要判断如果是0的话,应该返回-1,但是如果直接返回,然后用echo看的话,确实是-1,但是没什么用啊,因为不知道是为什么错误啊,用户不知道啊,所以我们需要稍微的修改一下原本的代码版本。
这样的话,就能够明白是什么错误了。
所以进程终止的三个情况是: 1、代码跑完,结果正确。 2、代码跑完,结果不正确 3、代码执行时,出现了异常,提前退出了 这下面的代表的就是第三种的情况,在运行的过程中出现了除零的操作,直接进行报错。
前两个能够通过进程的退出码来决定是否代码正确。如果错误的话能够看退出码来判断是什么样的错误。 如果是异常退出的话,退出码就失去了效果,不能够起到相应的作用。 为什么出现了异常?那是因为进程出现了异常,本质是因为进程收到了OS发给进程的信号。
这是段错误的异常进程。
所以根本也就是操作系统给进程发送了11信号。 当是异常的时候,我们可以看退出信号是多少我们就能够判断我们的进程为什么异常了。所以这个信号也就像类似于之前的退出码,有异曲同工的妙处。
综上所述,衡量一个进程,我们需要两个数字,一个是退出码,还有一个是退出信号! 当退出信号为非0的时候,退出码无论是什么这个进程都已经是错误的了。 当退出码为非0的时候,退出信号为0的时候说明程序没有问题,但是答案不对。
所以代码判断是否错误的先后是: 1、首先判断是否异常 2、若不是异常,就一定是代码跑完了,看退出码
对于一个进程的task_struct来说,其中会有两个变量,一个叫做exit_code,还有一个是exit_signal。为什么子进程会在结束之后变为Z状态还保留了本身的数据结构,就是为了保住其中的进程退出的两个数,让父进程读取之后判断是否异常。
1、main函数return,表示进程终止(非main函数,return,函数结束) 2、代码调用exit函数(注意:在代码的任意位置调用exit,都表示进程终止) 3、_exit—system call。其中和exit不同的地方就是exit会在进程退出的时候冲刷缓冲区 ,_exit不会。 exit是C语言的可函数,而_exit是系统调用。有因为_exit没有刷新缓冲区,也就是说明此时我们说的缓冲区表示的不是内核缓冲区!因为如果是的话_exit也就能够刷新缓冲区了。exit的本质还是调用了_exit。
任何子进程,在退出的情况下,一般都必须要被父进程等待。如果在退出的时候父进程不管不顾,子进程就会一直保持状态Z,可能还会造成内存泄漏的问题。 原因: 1、父进程通过等待,解决子进程退出的僵尸问题,回收系统资源。(一定要考虑的) 2、获取子进程的退出信息,知道子进程退出的原因(可选)。
wait:等待成功时,返回子进程的pid。作用是等待任意一个子进程的退出
如果子进程没有退出的话,父进程就会在阻塞等待。 此时子进程本身就是软件,父进程本质是在等待某种软件条件就绪,那么此时该如何理解阻塞等待子进程呢? 本质就是子进程让父进程放在自己的等待队列中,让状态设置为S状态,当子进程全部调度结束的时候,再把父进程放出,在开始执行父进程。
waitpid:返回值的含义和上面的wait相同
pid_t rid=waitpid(-1,NULL,0)
和pid_t rid=wait(NULL)效果相同
pid_t waitpid(pid_t pid, int *status,int options)
返回值: 当正常返回时waitpid收集到的是子进程的进程ID 如果调用中出错,则返回-1,这是errno会被设置成相对于的值以指示错误所在 (上面的两种是阻塞等待) 如果设置了选项WNOHANG,而调度中waitpid发现没有已退出的子进程可收集,则返回0,不过还需要后续的重复等待(此时是非阻塞等待) 参数: pid: Pid=-1,等待任何一个子进程,与wait等效。 Pid>0,等待其进程ID与pid相等的子进程。 status: WIFEXITED(status):若正常终止子进程返回状态,则为真。(用来查看进程是否正常退出) WEXITSTATUS(status):若WIFEXITED非零则提取子进程退出码。(用来查看进程的退出码) options: WNOHANG:若pid指定的子进程没有结束,则waitpid()函数返回0,不予等待。若正常结束,则返回该子进程的ID 其中status表示的是输出型参数,此处表示的是退出信息。退出信息只需要掌握**退出码+退出信号。**可是这可是两个信息啊,返回一个值怎么能够表示的是两个数呢? status设置的是int的32位,其中低地址的16到第9位置表示的是退出状态,第7到第1位置表示的是终止信号,其中的第8位置,表示的就是core dump标志。 所以在上面一个小节中,我用了从0到255的循环来查看退出状态。 代码直接体现status的两个数据含义
改变其中的exit的值,返回的的code就会改变。 可是如果我不知道status表示的是两个数字啊?那我们怎么该如何得到呢,换句话说就是如何简单的判断两个退出信号是否是正确的?
WIFEXITED(status):如果没有异常的话为真,如果有则为假
WEXITSTATUS(status):展示退出信号
其中waitpid的第三个参数能够设置为非阻塞等待,参数设置为WNOHANG 所以举一个例子,就好比说是,快要到期末考试了,但是张三几乎这学期就没有多认真的学习,但是它有一个好朋友,李四,每天都认真学习,所以张三就想着最后几天的时候找李四帮帮忙,划重点,可是李四也是需要自己干自己的事情,所以张三来找的时候就会先让张三等一会,但是张三又怕李四忘记自己在等他,所以就让李四一直打着电话,张三听到李四快好的声音就会立马让李四出来。这样的状态就像是张三是一个父进程,一直在等待着李四的子进程运行结束,结束了张三才能继续他自己原本的事情。 (如果设置参数的情况的话) 当下一次考试快开始的时候,张三再一次找李四,这次张三知道了,李四不会忘记自己找他帮忙的,所以在等待李四复习结束之前,张三都可以干一些别的事情,时不时的再问问李四好没好就行了,当李四好了的话,就立刻张三开始复习。 所以上面来看的话,还是非阻塞等待更适合程序的进行,非阻塞等待+循环判断=非阻塞轮询,允许父进程做一些其他的事情。 这样的代码就是能够父进程处于非阻塞状态,继续进行父进程的任务。
如果想看实现在子进程进行的时候,父进程也能够运行的话,请看我的Gitte上的代码,希望能够帮你们能够更深层次的理解非阻塞等待的程序