当前位置:   article > 正文

Linux进程创建fork、进程退出exit()、进程等待waitpid()_子进程退出代码

子进程退出代码

        虽然通过标题,我们就轻易的知道了这三个函数的作用,可是,你真的了解这几个函数码?下面让我们来看看这三个函数到底有什么!

一、进程创建fork()

        首先,我们来看一看fork()的函数声明:

        #include <unistd.h>
        pid_t fork(void);
        返回值:自进程中返回0,父进程返回子进程id,出错返回-1

        可以看到十分简单,既然如此,我们便来理解一下fork()之后的过程:

    1.内核分配新的内存块和内核数据结构给子进程
    2.将父进程部分数据结构内容拷贝至子进程
    3.添加子进程到系统进程列表当中
    4.fork返回,开始调度器调度

       

         看完了过程,脑海中是否有了一些大致的过程?可有了过程,是否又有了一些新的疑惑?既然子进程继承了父进程的代码,犹如又运行了一份代码,那子进程到底是从头开始,还是从fork()处开始呢?如果从fork()开始,那一分被“截断”了的代码又是如何保证正常运行的呢?

        1.第一问        

先回答第一问答案:fork()处开始有运行,下面看代码结果

  1. #include<stdio.h>
  2. #include<unistd.h>
  3. #include<stdlib.h>
  4. int main()
  5. {
  6. pid_t pid;
  7. printf("马上开始创建另一个进程\n");
  8. pid=fork();
  9. if(pid==-1)
  10. {
  11. perror("fork()");
  12. exit(1);
  13. }
  14. printf("我是进程,我的pid是:%d,我的ppid是:%d,我的fork返回值是:%d\n",
  15. getpid(),getppid(),pid);
  16. return 0;
  17. }

        结果:

        可以看到,只有三行,说明了子进程是从fork()开始执行的,不过需要注意的是, fork()之后,谁先执行,完全是由调度器决定的!

        2.第二问

        回答第二问答案:父子代码共享,父子再不写入时,数据也是共享的,当任意一方试图写入,便以写时拷贝的方式各自一份副本。如图:

        这幅图便很好的解释了第二问。既然数据共享,那么就算代码发生了“截断”,子进程也能访问到“原来”的数据了。

            

二、进程退出exit()

        这个函数一看,是不是有些熟悉,没错,这个函数出现在了上面当fork()返回值为-1的情况下,至于用法,我们先看声明:

        #include <stdlib.h>
        void exit(int status);

        看完了声明,各位再等一等,我还想介绍一个函数, 名叫_exit(),这个函数的功能也是退出进程,不过真的是仅仅退出进程,缓冲区的什么的,全都不管了。各位可能会好奇我介绍这个函数干嘛呢。原因一,各位不要觉得这个函数和exit()差不多长得一样,就当相同功能的函数用了,最重要的原因二在于,_exit()为exit()的基础函数,exit()是_exit()的封装函数,一个是爹,一个是儿子。

        那么既然exit()是_exit()的封装函数,具体是如何封装的呢?

        1. 执行用户通过 atexit或on_exit定义的清理函数。
        2. 关闭所有打开的流,所有的缓存数据均被写入
        3. 调用_exit

        至此,一个exit()函数才真正的展开了,再回到开头的使用方法,其实用法很简单,就是在参数列表,就是括号里输入你想输入的错误码即可退出进程并返回错误码了。

        不过这听起来这使用方法和return好像没啥区别,但其实执行return n等同于执行exit(n),因为调用main的运行时函数会将main的返回值当做 exit的参数。 

     

三、进程等待waitpid()

        对于进程等待,各位可能有些不了解应用场景,觉得必要性不大,但放在压轴的函数,怎么可能必要性不大!下面便是应用的场景:

        1.当创建的子进程退出,父进程如果不管不顾,就可能造成‘僵尸进程’的问题,进而造成内存泄漏。

        2.进程一旦变成僵尸状态,那就刀枪不入,“杀人不眨眼”的kill -9 也无能为力,因为谁也没有办法杀死一个已经退出的进程。
        3.最后,父进程派给子进程的任务完成的如何,我们需要知道。如,子进程运行完成,结果对还是不对,或者是否正常退出。

        4.父进程通过进程等待的方式,回收子进程资源,获取子进程退出信息

         看到这么多条,就算不仔细看,怎么说也知道必要性了吧!等待进程就像老板等待员工提交工作情况一样,老板要是不管不顾的话,严重性不必我来说了吧!

        而对于等待进程,其实有着两个函数,一个是wait(),一个是waitpid()。我都来介绍一下,不过重点放在waitpid()。注意,两个是同级函数,是”兄弟“

      (1)定义

wait() 函数

          

        #include<sys/types.h>
        #include<sys/wait.h>
        pid_t wait(int*status);
        返回值:
                成功等待到结束进程返回被等待进程pid,失败返回-1。
        参数:
                输出型参数,获取子进程退出状态,不关心则可以设置成为NULL 

waitpid()函数 

        #include<sys/types.h>
        #include<sys/wait.h>

        pid_ t waitpid(pid_t pid, int *status, int options);
        返回值
              当正常返回的时候waitpid返回收集到的子进程的进程ID;
              如果设置了选项WNOHANG,而调用中waitpid发现没有已退出的子进程可收集,则                    返回0;
              如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;
        参数:
          pid:
                Pid=-1,等待任一个子进程。与wait等效
                Pid>0.等待其进程ID与pid相等的子进程。
          status:
                WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)
                WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)
          options:
               WNOHANG: 若pid指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的ID。

         看到这么多的文字,是不是觉得很复杂,我们来提炼一下功能:

        1.如果子进程已经退出,调用wait/waitpid时,wait/waitpid会立即返回,并且释放资源,             获得子进程退出信息。
        2.如果在任意时刻调用wait/waitpid,子进程存在且正常运行,则进程可能阻塞。
        3.如果不存在该子进程,则立即出错返回。

        总体功能如上,如果你想实现更多样化的的功能,则可以详细看看定义。下面我们来看一下代码使用。

     (2)代码使用

        1.wait()

  1. #include <sys/wait.h>
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <string.h>
  5. #include <errno.h>
  6. int main( void )
  7. {
  8. pid_t pid;
  9. if ( (pid=fork()) == -1 )
  10. perror("fork"),exit(1);
  11. if ( pid == 0 ){
  12. sleep(20);
  13. exit(10);
  14. }
  15. else {
  16. int st;
  17. int ret = wait(&st);
  18. if ( ret > 0 && ( st & 0X7F ) == 0 ){ // 正常退出
  19. printf("child exit code:%d\n", (st>>8)&0XFF);//*注意点,稍后讲
  20. }
  21. else if( ret > 0 ) { // 异常退出
  22. printf("sig code : %d\n", st&0X7F );//*注意点
  23. }
  24. }
  25. }

         2.waitpid()

  1. int main()
  2. {
  3. pid_t pid;
  4. pid = fork();
  5. if(pid < 0){
  6. printf("%s fork error\n",__FUNCTION__);
  7. return 1;
  8. }
  9. else if( pid == 0 ){ //child
  10. printf("child is run, pid is : %d\n",getpid());
  11. sleep(5);
  12. exit(257);
  13. }
  14. else{
  15. int status = 0;
  16. pid_t ret = waitpid(-1, &status, 0);//*注意点,阻塞式等待,等待5S
  17. printf("this is test for wait\n");
  18. if( WIFEXITED(status) && ret == pid ){
  19. printf("wait child 5s success, child return codeis:%d.\n",
  20. WEXITSTATUS(status));//宏函数的使用方法
  21. }
  22. else{
  23. printf("wait child failed, return.\n");
  24. return 1;
  25. }
  26. }
  27. return 0;
  28. }

         仔细看代码,会发现我注释了两个注意点,一个是关于参数status,一个是waipid的第三个参数。

    (3)注意点说明

        1.参数status

        先来讲一讲参数status的使用方式”(st>>8)&0XFF、st&0X7F“,为什么这么奇怪?这是因为stasus作为一个输出型参数,为了携带更多的信息,使用了位图结构,如下图:

        

        位图结构便是使用类型里特定的比特位存储信息, 在参数status中我们只用关心低16位。所以可以看到代码中使用了这种奇怪的方式获取进程退出信息。如果觉得不好用,可以将等待函数换为waitpid(),使用WIFEXITED、WEXITSTATUS这两个宏函数获取退出信息。当然,要是不关心退出信息,定义中也告诉我们了,将status置为NULL指针即可。

        2.waitpid()第三个参数

        代码中可以看见,第三个参数为0,特意注释了阻塞式等待,即为父进程到了waitpid()这儿就等着子进程运行完了获取结果了,再往下运行。请问,你觉得这合适吗?哪里还有老板一直等员工的!所以waitpid()第三个参数就顺应非阻塞式等待而生,这是wait()所不能及的,使用如下:

         ret = waitpid(-1, &status, WNOHANG);//非阻塞式等待

         至于设置 WNOHANG之后发生的事,可以在上文的”定义“中看到,我想要说的是,waitpid()返回0之后,继续运行到子进程死亡时,有两种情况,1.父未死、2.父已经死了,子进程变成孤儿进程

        那么子进程的状态就接收不到了吗?答案是当然不会,第2种情况发生后,init进程(一号进程)会来收养子进程,确保子进程一直有个“爹”。

        所以既然一直有爸爸,当子进程死后,就会发送SIGCHLD信号给父进程,父进程收到此信号后,执行waitpid()函数为子进程收尸。

        那么至此,waitpid()函数等待进程的过程已经讲解完毕。至于使用时wait()和waitpid的选择,我倾向于waitpid()。

         

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/210707
推荐阅读
相关标签
  

闽ICP备14008679号