守护进程学习————两次fork细节、deamon()函数

一、守护进程和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
        可见,孙进程设置终端失败(显示?)。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值