aemon 进程为什么要fork两次,与后台运行程序区别?

daemon进程是后台守护进程.linux 下server都是daemon进程。相信大部分开发人员都知道如何去写一个daemon进程。但是另一方面,大部分人不知道为什么要这么做,不少人是从某个地方copy一个函数。但是具体为什么这么实现,却不是很透彻。daemon函数存在的原因是因为控制终端由于某些原因(如断开终端链接)会发送一些信号的原因。而接收进城处理这些信号缺省动作会让进程退出。这些信号会由于终端上敲一些特殊按键而产生。

贴一个daemon函数常见的实现:

int daemon(void)
{
    pid_t pid = fork();
    if( pid != 0 ) exit(0);//parent
    //first children
    if(setsid() == -1)
    {
       printf("setsid failed\n");
       assert(0);
       exit(-1);
    }
    umask(0);
    pid = fork();
    if( pid != 0) exit(0);
    //second children 
    chdir ("/");
    for (int i = 0; i < 3; i++)
    {
        close (i);
    }
    int stdfd = open ("/dev/null", O_RDWR);
    dup2(stdfd, STDOUT_FILENO);
    dup2(stdfd, STDERR_FILENO);
    return 0;
}

1 、第一次fork的作用是让shell 认为本条命令 已经终止,不用挂在终端输入上。还有一个作用是为后面setsid服务。setsid的调用者不能是进程组组长(group leader). 此时父进程是进程组组长。

2 、setsid() 是本函数最重要的一个调用。它完成了daemon函数想要做的大部分事情。调用完整个函数。子进程是会话组长(sid==pid),也是进程组组长(pgid == pid),并且脱离了原来控制终端。到了这一步,基本上不管控制终端如何怎么样。新的进程都不会收到那些信号。

3 、经过前面2个步骤,基本想要做的都做了。第2次fork不是必须的。也看到很多开源服务没有fork第二次。fork第二次主要目的是。防止进程再次打开一个控制终端。因为打开一个控制终端的前提条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。所以也无法打开新的控制终端。

daemon目的就是防止终端产生的一些信号让进程退出。上面函数并没有直接调用signal函数去处理它。而是间接通过fork和setsid函数使用更少代码优雅处理。而被有些人误以为是僵死进程的原因需要这样处理。

当然,也有很多程序不是像上面函数那样去实现。而是直接通过忽略信号方式处理。这样其实也不错,因为这些信号很少会有用到的价值。直接忽略基本上不存在误杀的情况。反正达到最终目的就可以。条条大路通罗马。

下面罗列一下控制终端会产生哪些信号。程序中只要处理好这些信号,同样能达到上面函数实现的目的。

//后台进程读取/写入终端输入产生下面两个信号,或者控制终端不存在情况读取和写入会产生

signal(SIGTTOU, SIG_IGN);
   signal(SIGTTIN, SIG_IGN);

   //按CTRL-C ,CTRL-\ CTRL-Z会向前台进程组发送下面这些信号
   signal(SIGINT,  SIG_IGN );
   signal(SIGQUIT, SIG_IGN );
   signal(SIGTSTP, SIG_IGN );

   //终端断开,会给会话组长或孤儿进程组所有成员发送下面信号
   signal(SIGHUP,  SIG_IGN );

   还有有些信号也可以由终端shell产生,需要关注
   signal(SIGCONT, SIG_IGN );
   signal(SIGSTOP, SIG_IGN );

上面这些信号,应该有些程序缺省处理(SIG_DFL)本身动作就是忽略(SIG_IGN),不是退出进程。不过按照上面写也不会造成什么问题。

dup2(stdfd, STDOUT_FILENO) 是将该程序的标准输出重定向到了黑洞,dup和dup2都可用来复制一个现存的文件描述符,使两个文件描述符指向同一个file结构体

#include <unistd.h>
int dup(int newfd);
int dup2(int newfd, int oldfd);

下面用一个简单的例子解释两个函数的用法和区别:

#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

 int main(void)
 {
     int fd, save_fd;
     char msg[] = "This is a test of dup() & dup2()\n";
     int test;
     fd = open("somefile", O_RDWR|O_CREAT, S_IRUSR|S_IWUSR);
     if(fd<0) {
         perror("open");
         exit(1);
     }
     save_fd = dup(STDOUT_FILENO);                        //运行后save_fd指向STDOUT——FILENO,即save_fd指向标准输出
     printf("save_fd=%d\n",save_fd);                             //测试用
     test=dup2(fd, STDOUT_FILENO);                         //运行后STDOUT_FILENO指向fd所指向的文件,即STDOUT_FILENO指向somefile
     printf("dup2_1=%d\n",test);                                   //测试用 此时的标准输出不再指向显示器,因此该段测试将写入somefile文件中
     close(fd);
     write(STDOUT_FILENO, msg, strlen(msg));           //此时STDOUT_FILENO所指向的是somefile文件不再是标准输出流,因此该段将
                                                                              //写入somefile文件中
     test=dup2(save_fd, STDOUT_FILENO);                 //运行后STDOUT_FILENO指向save_fd所指向的文件,即标准输出流
     printf("dup2_2=%d\n",test);                                  //测试用 此时标准输出流重新指回显示器,因此该段测试将写入显示器
     write(STDOUT_FILENO, msg, strlen(msg));           //此时STDOUT_FILENO所指向的便回标准输出流该段将写入显示器
     close(save_fd);
     return 0;
 }

守护进程与用&结尾的后台运行程序有什么区别呢?
最大的区别有几点:
1)守护进程已经完全脱离终端控制台了,而后台程序并未完全脱离终端,在终端未关闭前还是会往终端输出结果
2)守护进程在关闭终端控制台时不会受影响,而后台程序会随用户退出而停止,需要在以nohug xxx & 格式运行才能避免影响
3)守护进程的会话组和当前目录,文件描述符都是独立的。后台运行只是终端进行了一次fork,让程序在后台执行,这些都没改变。

error execution phase preflight: [preflight] Some fatal errors occurred: [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-apiserver:v1.22.17: output: Error response from daemon: Ge t "https://k8s.gcr.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers), error: exit status 1 [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-controller-manager:v1.22.17: output: Error response from d aemon: Get "https://k8s.gcr.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers), error: exit status 1 [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-scheduler:v1.22.17: output: Error response from daemon: Ge t "https://k8s.gcr.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers), error: exit status 1 [ERROR ImagePull]: failed to pull image k8s.gcr.io/kube-proxy:v1.22.17: output: Error response from daemon: Get "h ttps://k8s.gcr.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers), error: exit status 1 [ERROR ImagePull]: failed to pull image k8s.gcr.io/pause:3.5: output: Error response from daemon: Get "https://k8s .gcr.io/v2/": context deadline exceeded, error: exit status 1 [ERROR ImagePull]: failed to pull image k8s.gcr.io/etcd:3.5.0-0: output: Error response from daemon: Get "https:// k8s.gcr.io/v2/": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers), error: exit status 1 [ERROR ImagePull]: failed to pull image k8s.gcr.io/coredns/coredns:v1.8.4: output: Error response from daemon: Get "https://k8s.gcr.io/v2/": context deadline exceeded, error: exit status 1 [preflight] If you know what you are doing, you can make a check non-fatal with `--ignore-preflight-errors=...` To see the stack trace of this error execute with --v=5 or higher
07-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值