精华内容
下载资源
问答
  • 两次fork
    2017-05-23 15:14:54

    我觉得这里还有些重要的东西没讲,比如setsid()(参见apne 8-11.).


    两次fork()的作用

    首先,要了解什么叫僵尸进程,什么叫孤儿进程,以及服务器进程运行所需要的一些条件。两次fork()就是为了解决这些相关的问题而出现的一种编程方法。

    孤儿进程

        孤儿进程是指父进程在子进程结束之前死亡(return 或exit)。如下图1所示:
        在一定时间内,当系统发现孤儿进程时,init进程就收养孤儿进程,成为它的父亲,child进程exit后的资源回收就都由init进程来完成。

    僵尸进程
        僵尸进程是指子进程在父进程之前结束了,但是父进程没有用wait或waitpid回收子进程。

        父进程没有用wait回收子进程并不说明它不会回收子进程。子进程在结束的时候会给其父进程发送一个SIGCHILD信号,父进程默认是忽略SIGCHILD信号的,如果父进程通过signal()函数设置了SIGCHILD的信号处理函数,则在信号处理函数中可以回收子进程的资源。

          事实上,即便是父进程没有设置SIGCHILD的信号处理函数,也没有关系,因为在父进程结束之前,子进程可以一直保持僵尸状态,当父进程结束后,init进程就会负责回收僵尸子进程。

          但是,如果父进程是一个服务器进程,一直循环着不退出,那子进程就会一直保持着僵尸状态。虽然僵尸进程不会占用任何内存资源,但是过多的僵尸进程总还是会影响系统性能的。黔驴技穷的情况下,该怎么办呢?

             这个时候就需要一个英雄来拯救整个世界,它就是两次fork()技法。

    两次fork()技法
           如上图3所示,为了避免子进程child成为僵尸进程,我们可以人为地创建一个子进程child1,再让child1成为工作子进程child2的父进程,child2出生后child1退出,这个时候child2相当于是child1产生的孤儿进程,这个孤儿进程由系统进程init回收。这样,当child2退出的时候,init就会回收child2的资源,child2就不会成为孤魂野鬼祸国殃民了。

    <unix环境高级编程>这本书里提供了两次fork的一个例子,代码如下:

     

    [cpp] view plaincopy
    
        int main(void)  
        {  
            pid_t        pid;  
          
            if ( (pid = fork()) < 0)  
                  err_sys("fork error");  
            else if (pid == 0)   
                {                /* first child */  
                   if ( (pid = fork()) < 0)  
                                err_sys("fork error");  
                   else if (pid > 0)  
                         exit(0);        /* parent from second fork == first child */  
          
                        /* We're the second child; our parent becomes init as soon 
                           as our real parent calls exit() in the statement above. 
                           Here's where we'd continue executing, knowing that when 
                           we're done, init will reap our status. */  
          
                    sleep(2);  
                    printf("second child, parent pid = %d\n", getppid());  
                    exit(0);  
                }  
          
            if (waitpid(pid, NULL, 0) != pid)        /* wait for first child */  
                    err_sys("waitpid error");  
          
                /* We're the parent (the original process); we continue executing, 
                   knowing that we're not the parent of the second child. */  
          
            exit(0);  
        }  

     

     

     

     

     


        理所当然,第二个子进程的父进程是进程号为1的init进程。  

        一言以蔽之,两次fork()是人为地创建一个工作子进程的父进程,然后让这个人为父进程退出,之后工作子进程就由init回收,避免了工作子进程成为僵尸进程。

     

     

    更多相关内容
  • 一次fork与两次fork的区别

    千次阅读 2018-03-24 21:42:41
    在讲一次fork和两次fork之前,有必要先来简单讲解一下wait的作用1、阻塞当前进程2、获得子进程退出的相关信息(回收僵尸进程)子进程不返回,父进程后边的内容就没法执行。注:wait函数只能在有子进程的父进程中调用...
    在讲一次fork和两次fork之前,有必要先来简单讲解一下wait的作用
    1、阻塞当前进程
    2、获得子进程退出的相关信息(回收僵尸进程)
    子进程不返回,父进程后边的内容就没法执行。
    注:wait函数只能在有子进程的父进程中调用。


    我们使用fork()函数创建一个子进程出来往往是为了父子进程能够同时执行两段代码。如果在父进程中不写wait()会导致子进程在退出后变成僵尸;但是使用wait()又会导致父进程阻塞,只有等子进程退出后才能继续执行,相当于依然是单进程。对于使用wait带来的问题,可以使用两次fork来解决。

    所以区别就是:使用wait时一次fork实现的其实也就是单进程,两次fork的话可以实现多个进程同时执行。

    怎么通过两次fork实现多进程的过程
    第一次fork 创建出一个子进程,然后父进程wait(),
    第二次fork 子进程再创建一个子进程,也就相当于有了一个 孙子进程
    然后将第一次fork出来的子进程结束掉,它的父进程wait()不再阻塞,可以继续执行。
    此时的“孙子进程”就变成了孤儿进程,被托管到“孤儿院”(1号进程)。

    现在“爷爷进程”和“孙子进程”的执行互不影响,实现了多个进程的同时执行。

    展开全文
  • 为何要使用两次fork?

    千次阅读 2017-10-16 15:13:17
    两次fork法: 流程如图所示: int main( int argc, char *argv[]) { //这是一个服务器进程 pid_t pid; if ((pid=fork())< 0 ) //服务器创建一个子进程 { cout "error" ; } else if (pid==...

    我们来看看什么事孤儿进程和僵尸进程:

    孤儿进程:当子进程还没有结束的时候,其父进程退出,此时此进程就变成了孤儿进程,如图所示:
    这里写图片描述
    但是孤儿进程并不会存在很久,当系统发现孤儿进程时,init进程就收养孤儿进程,负责这个孤儿进程exit后的清理工作。

    僵尸进程:当子进程在父进程退出前退出的情况下,子进程在从自己退出到父进程退出前的这一段时间是僵尸进程,父进程退出时候,会对这个僵尸进程做清理工作。
    这里写图片描述

    当父进程没有用wait回收子进程并不说明它不会回收子进程。子进程结束的时候会给其父进程发送一个SIGCHILD信号,父进程默认是忽略这个信号的,如果父进程通过signal函数设置了SIGCHILD的信号处理函数,则在信号处理函数中可以回收子进程的资源。事实上,即使父进程没有设置处理函数,子进程可以一直保持僵尸状态,当父进程结束后,init进程就会负责回收僵尸子进程。
    但是,父进程是一个服务器进程,一直循环不退出,那么子进程一直保持这僵尸状态。虽然僵尸进程不会占用任何内存资源,但是过多的僵尸进程总是会影响系统性能的,这个时候怎么办呢?

    两次fork法:
    流程如图所示:
    这里写图片描述

    int main(int argc,char *argv[])
    {
        //这是一个服务器进程
        pid_t pid;
        if((pid=fork())<0)  //服务器创建一个子进程
        {
            cout<<"error"<<endl;
        }else if(pid==0)
        {
            if((pid=fork())<0)//服务器的子进程中再创建一个子进程
            {
                cout<<"error"<<endl;
            }else if(pid>0)
            {
                cout<<"this is a child process of server"<<endl;
                exit(0);//服务器的子进程退出
            }else
            {
                cout<<"this is a child process of server's child"<<endl;
                sleep(10);//为了保证exit退出的时间晚于其父进程
                exit(0);//退出后,服务器的子进程已经退出,所以init进程将会接管这个僵尸进程
            }
        }else //pid>0,原始服务器进程
        {
            cout<<"this is server process"<<endl;
        }
    
    
    }

    如何避免僵尸进程?

    1.通过signal(SIGCHLD, SIG_IGN)通知内核对子进程的结束不关心,由内核回收。如果不想让父进程挂起,可以在父进程中加入一条语句:signal(SIGCHLD,SIG_IGN);表示父进程忽略SIGCHLD信号,该信号是子进程退出的时候向父进程发送的。
    2.父进程调用wait/waitpid等函数等待子进程结束,如果尚无子进程退出wait会导致父进程阻塞。waitpid可以通过传递WNOHANG使父进程不阻塞立即返回。
    3.如果父进程很忙可以用signal注册信号处理函数,在信号处理函数调用wait/waitpid等待子进程退出。
    通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。
    第一种方法忽略SIGCHLD信号,这常用于并发服务器的性能的一个技巧因为并发服务器常常fork很多子进程,子进程终结之后需要服务器进程去wait清理资源。如果将此信号的处理方式设为忽略,可让内核把僵尸子进程转交给init进程去处理,省去了大量僵尸进程占用系统资源。

    展开全文
  • 1.daemonize干嘛要两次fork啊?我的守护进程打死也不会去获得一个终端; 2.daemonize干嘛要关闭文件描述符?我的nginx子进程需要pipe和master进程通信啊。 答案: 1.deamonize函数不是守护进程,是上帝stevens...
    一、守护进程和daemonize()函数
    这一小节是自己的理解,无干货,下一节有干货。
    守护进程查wikipedia的定义:In multitasking computer operating systems, a daemon is a computer program that runs as a background process, rather than being under the direct control of an interactive user.关键词就是后台执行,而非直接用户交互。还有一句“However, more commonly, a daemon may be any background process, whether a child of the init process or not.”,是说啥后台程序都可以叫守护进程。
    学究式的讲定义是为了引出困扰我以前的很长一段时间的问题,《unix高级环境编程》里面的建立守护进程的那些步骤到底目的是啥?改工作目录,改建立文件权限mask,fork两次,setsid,关闭或重定向文件描述符,重定义信号处理函数。这个定义说明这些都是不是必须的。
    书中定义的实际上是一个deamonize函数,是作者觉得实际守护进程可能会需要的性质和功能。该函数可以从诸多场景调用,比如:
    1.父进程fork+exec调用子进程执行新程序,假设子进程完全不需要父进程的任何打开文件,这时这一种守护进程就需要关闭所有的fd(除非每个fd打开时都用closeonexec)可以调用daemonize。这个以前我接触到一个事例,父进程仅仅只是读配置,之后按照配置隔一段时间启动某个程序,并且监控超时任务超时kill;
    2.nginx master进程的依赖pid文件,所以pid文件所在的文件系统不能被卸载,所以nginx master调用daemonize修改工作路径到/就显得多余;
    3.同上,master创建的pid文件需要nginx程序有读权限(比如nginx -s reload会读取),这时daeminze函数中的umask(0)这一步就没用了。
    可见,daemonize的某些步骤对有的场景有用,有的没用。作者应该是想通过daemonize建立一个统一的守护进程初始化状态,之后调用者可以在此基础上统一处理。
    总之,下面的问题貌似有答案了:
    1.daemonize干嘛要两次fork啊?我的守护进程打死也不会去获得一个终端;
    2.daemonize干嘛要关闭文件描述符?我的nginx子进程需要pipe和master进程通信啊。
    答案:
    1.deamonize函数不是守护进程,是上帝stevens对守护进程可能的需求做的总结,不是调用deamonize用户守护进程会不会获得终端的问题,而是不准;
    2.可以个性化自己的守护进程,守护进程的定义可没有让子进程关闭父进程的所有文件描述符。


    二、fork两次和终端
    守护进程初始化过程其他都还是比较好理解,fork两次这儿很不直观。这一节用实际代码示例说明,终端对进程的影响以及fork两次+setsid的确获取不了终端了。

    1.网络终端连接终端导致登录bash发送sighup给该终端对应所有会话的所有进程;

    void action(int num,siginfo_t *psi,void *p){
            //收到之后信息写入文件
        int fd = open("log",O_RDWR | O_APPEND | O_CREAT,S_IRWXU);
        string out("received SIGUP from ");
            //打印发信号进程id和本进程id
        out += to_string(psi->si_pid) + ",pid:" + to_string(getpid()) + "\n";
        write(fd,out.c_str(),out.length());
        exit(0);
    }
    
    int main(){
        struct sigaction act;
        memset(&act,0,sizeof(struct sigaction));
        act.sa_sigaction = action;
        act.sa_flags = SA_SIGINFO;
            //安装SIGHUP处理函数,fork之前安装,父子进程都生效
        sigaction(SIGHUP,&act,NULL);
        if(fork() != 0){
            printf("parent session id:%u,pid:%u,pgid:%u\n",getsid(0),getpid(),getpgid(0));
            sleep(SLEEPTIME);
            exit(0);
        }
        printf("child session id before:%u,pid:%u,pgid:%u\n",getsid(0),getpid(),getpgid(0));
        //printf("setting a new session:%d\n",setsid());
        //printf("child session id after:%u,pid:%u,pgid:%u\n",getsid(0),getpid(),getpgid(0));
            sleep(SLEEPTIME);
    }
    


            编译后运行,父进程和子进程都会sleep等待,这时关闭终端(securecrt右上角小红叉)观察日志。
            received SIGHUP from 29247,pid:2516 //父子进程收到来自29247的SIGHUP
            received SIGHUP from 29247,pid:2517


            在此之前从另一个终端ps aux |grep out:
            ubuntu    2516  0.0  0.0  12540   820 pts/4    S+   15:34   0:00 ./a.out
            ubuntu    2517  0.0  0.0  12536   160 pts/4    S+   15:34   0:00 ./a.out
            ps axj |grep 29247:
            29246 29247 29247 29247 pts/10   29247 Ss+    500   0:00 -bash //29247是本终端的bash
            上述事实说明关闭远程终端,终端对应bash会发送SIGHUP信号给本终端关联的进程,无论是直属子进程还是子进程的子进程。至于底层点的initd\rlogin\tty\ptm\pts\bash的恩怨情仇这儿不管。


    2.setsid从本终端脱离后不会收到SIGHUP
            1中注释的两行取消注释后:
            child session id before:22481,pid:3469,pgid:3468
            setting a new session:3469
            child session id after:3469,pid:3469,pgid:3469
            22481  3468  3468 22481 pts/2     3468 S+     500   0:00 ./a.out //父进程终端还是pts/2
            3468  3469  3469  3469 ?           -1 Ss     500   0:00 ./a.out //子进程终端没了,显示?
            关闭终端之后子进程不退出。


    3.setsid之后子进程作死还是能获得新的终端
            把上面代码最后一个sleep替换成下面代码。
        int mfd = posix_openpt(O_RDWR);
        if(mfd < 0)
            perror("open pts error:");
        printf("open pts done\n");
        if(grantpt(mfd) == -1)
            perror("grantpt pts error:");
        printf("grantpt pts done\n");
        if(unlockpt(mfd) == -1)
            perror("unlockpt pts error:");
    
        char *name = ptsname(mfd);
        printf("device name is:%s\n",name);
    
        int sfd = open(name,O_RDWR);
        dup2(sfd,STDIN_FILENO);
        dup2(sfd,STDOUT_FILENO);
        dup2(sfd,STDERR_FILENO);
        if(ioctl(sfd,TIOCSCTTY,NULL) == -1)
            perror("ioctl error:");
        sleep(SLEEPTIME);
        close(sfd);
        close(mfd);



            ps aux|grep out之后得到:
            13235 18358 18358 13235 pts/2    18358 S+     500   0:00 ./a.out
            18358 18359 18359 18359 pts/6    18359 Ss+    500   0:00 ./a.out
            可见子进程已经获得了新终端。


    4.setsid之后再次fork获得新终端失败
            再次fork可以使得子进程不是当前会话的首进程,从而无法获得新终端:
            ubuntu    4478  0.0  0.0  12540   820 pts/2    S+   14:55   0:00 ./a.out
            ubuntu    4479  0.0  0.0      0     0 ?        Zs   14:55   0:00 [a.out] <defunct>
            ubuntu    4480  0.0  0.0  21100   816 ?        S    14:55   0:00 ./a.out
            可见,孙进程设置终端失败(显示?)。

    展开全文
  • 为什么有时候有些代码会调用两次fork呢? (1)调用一次fork的作用: 第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组...
  • 在一些程序中经常看见使用两次fork创建子进程,原因如下: 以下摘自《UNIX环境高级编程》 如果一个进程fork一个子进程,但不要它等待子进程终止,也不希望子进程处于僵尸状态直到父进程终止,实现这一要求的技巧...
  • 先给出一个会产生僵尸进程的程序,运行后观察结果. #include<stdio.h> #include<stdlib.h> #include<sys/types.h> #include<sys/wait.h>... pid = fork(); if(pid > 0)...
  • fork函数的几个基本知识点 ①父子进程执行顺序:先后顺序由程序本身来决定,至于哪个先,在用户态看来都是随机的。 ② fork的作用是复制一个与当前进程一样的进程。新进程的所有数据(变量、环境变量、程序计数器等...
  • 最近在看APUE,在进程控制章节中有个关于两次fork防止进程僵死的列子很是不理解,经过一番研究发现原来原理很简单,以下是实例: 1 #include 2 #include 3 #include 4 5 int main(void) 6 { 7 pid_t pid;...
  • 可以每个接触到多进程编程的人在遇到fork()函数的时候都会由一些疑惑,它怎么能返回两次?而且返回值不同。对于以前的认知大家都知道一个函数只能返回依次啊。  呵呵,这是fork的神奇所在,它为什么这么神奇?它...
  • 可以看到上面的代码中调用了fork,也有人在setsid函数调用之后再调用一次fork,是对程序的优化,但第二次fork并不是必须。 首先第一次fork:让shell认为这条命令已经终止,不用挂在终端输入上。也是为了后面的...
  • 两次fork产生进程ID分析

    千次阅读 2018-05-12 10:23:24
    进行两次fork,会产生几个进程,他们的ID是多少,本文通过实例进行分析。基本概念:pid_t:pid_t是typedef定义的类型,表示进程的id。在sys/types.h中定义:typedef short pid_t;所以说pid_t就是一个short类型的...
  • fork()/execve()过程中,假设子进程结束时父进程仍存在,而父进程fork()之前既没安装SIGCHLD 信号处理函数调用waitpid()等待子进程结束,又没有显式忽略该信号,则子进程成为僵尸进程,无法正常结束,此时即使是...
  • 两次fork防止僵尸进程

    2014-03-02 23:28:34
    3、解决办法:调用两次fork避免僵尸进程 #include "apue.h" #include int main(void) { pid_t pid; if ((pid = fork()) ) { err_sys("fork error"); } else if (pid == 0) // 第一个子进程 { if ((pid =...
  • :这里第一次fork的作用就是让shell认为这条命令已经终止,不用挂在终端输入上;另一个原因就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork子进程,那么此时的父进程是进程组组长...
  • 创建守护进程以及为什么fork两次

    千次阅读 2017-05-25 13:30:40
    守护进程守护进程也叫精灵进程(Daemon),它在后台运行,独立于控制终端并周期性地执行某种任务或等待某些事情发生,一般用于C/S 服务中,在服务端监听端口,等待客户端的连接,如果有客户端发出连接,则fork出一...
  • linux下我们可以调用fork函数创建子进程,创建的子进程将会得到父进程的数据...2.调用fork两次(第一次调用产生一个子进程,第二次调用fork是在第一个子进程中调用,同时将父进程退出(第一个子进程退出),此时的第
  • 要知道在创建守护进程的时候fork一次和fork两次两者有什么区别,就要先知道第一次fork和第二次fork都起到了什么作用: (1)调用一次fork的作用: 第一次fork的作用是让shell认为这条命令已经终止,不用挂在...
  • 上一篇博客讲了,fork的作用,那么为什么有时候有些代码会调用两次fork呢? (1)调用一次fork的作用:  第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面的setsid服务...
  • fork android 防清理(ndk 源码)

    热门讨论 2014-09-23 12:04:44
    android 后台服务防止被清理类软件清理。完整的.so文件,可直接嵌入在项目中调用。
  •  为避免产生僵尸进程,可以通过两次fork()操作,第一次fork产生第一个子进程,第二次fork由第一个子进程产生第二个子进程,目前的状态变成:爷爷->父亲->孙子。爷爷回收父亲后退出,此时孙子的父进程自动托管给init...
  • 为什么守护进程要fork()两次

    千次阅读 2016-11-08 10:58:28
    进程也就是task,看看内核里维护进程的数据结构task_struct,这里有个成员: struct task_struct  {  volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */  ... } 看看include/...
  • APUE 上的定义如下: ...为何两次Fork 这是创建Daemon进程的大致步骤及作用: 第一次fork(产生父子进程,父进程退出,子进程与父进程控制终端脱离) 子进程setsid(子进程成为会话组长,与...
  • fork两次解决僵尸进程

    千次阅读 2014-06-03 17:29:50
     这个时候就需要一个英雄来拯救整个世界,它就是两次fork()技法。 两次fork()技法  两次fork()的流程如下所示: 图3 两次fork的控制流  如上图3所示,为了避免子进程child成为僵尸...
  • 在刚学习多进程时,很容易产生这个疑惑:为什么fork函数会返回两次? //进程号和进程组相关函数: pid_t getpid(void); //获取当前进程ID pid_t getppid(void); //获取当前进程的父进程的ID pid_t ...
  • 下面总结下两次fork()的作用。 首先,要了解什么叫僵尸进程,什么叫孤儿进程,以及服务器进程运行所需要的一些条件。两次fork()就是为了解决这些相关的问题而出现的一种编程方法。 孤儿进程  孤儿进程是指父...
  • linux下连续三次fork() --深度理解进程创建函数

    万次阅读 多人点赞 2018-04-26 21:41:24
    初识linux操作系统,fork作为系统调用理解起来却并不是很容易。 整理一下学习笔记,希望能对后来的初学者有所帮助。 代码能说明问题 #include&lt;stdio.h&gt; #include&lt;unistd.h&gt; int ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 115,153
精华内容 46,061
关键字:

两次fork

友情链接: vbjknet.rar