《APUE》笔记-第十三章-守护进程

重点:守护进程的编写

1.守护进程

守护进程在系统引导装入时启动,仅在系统关闭时才终止,无控制终端,在后台运行。

通过ps -efj 命令查看守护进程,如下图所示


从结果可以看出守护进程没有控制终端,其终端名设置为?,init进程ID为1。系统进程依赖于操作系统实现,父进程ID为0的各进程通常是内核进程,它们作为系统自举的一部分而启动。内核进程以超级用户特权运行,无控制终端,无命令行。

大多数守护进程都以超级用户特权运行,所有的守护进程都没有控制终端,其终端名设置为问号。守护进程又分内核守护进程和用户守护进程,用户层守护进程的父进程是init进程。内核守护进程的名字出现在方括号中,用户守护进程的名字没有方括号

2.守护进程的编程规则

(1)调用umask将文件模式创建屏蔽字设置为0。因为进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点,将文件创建掩模清除:调用umask(0)。

(2)调用fork,然后使父进程退出。这样可避免挂起控制终端将Daemon放入后台执行。

(3)调用setsid以创建一个新会话。这样可以使得调用进程成为新会话的首进程,成为一个新进程组的组长进程,没有控制终端。

(4)将当前工作目录更改为根目录。进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如/tmpchdir("/") 。

(5)关闭不再需要的文件描述符。进程从父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在的文件系统无法卸下以及引起无法预料的错误。

(6)某些守护进程打开/dev/null使其具有文件描述符0、1和2。使得任何一个试图读标准输入、写标准输出或者标准出错的历程都不会产生任何效果。

(7)处理SIGCHLD信号 。处理SIGCHLD信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN。

程序练习:初始化一个守护进程

#include <signal.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/resource.h>

void daemonsize(const char *cmd)
{
        int i, fd0, fd1, fd2;
        pid_t pid;
        struct rlimit rl;
        struct sigaction sa;

        //1.设置文件模式创建屏蔽字
        umask(0);

        //2.创建子进程以创建会话
        pid = fork();
        if (pid < 0)
                exit(0);
        else if (pid > 0)
                exit(0);
        setsid();//创建新会话

        //3.创建子进程以避免获取终端
        sa.sa_handler = SIG_IGN;
        sigemptyset(&sa.sa_mask);
        sa.sa_flags = 0;
        if (sigaction(SIGHUP, &sa, NULL) < 0)
                exit(0);
        if ((pid = fork()) < 0)
                exit(0);
        else if (pid > 0)
                exit(0);

        //4.更改当前工作目录
        chdir("/");

        //5.关闭文件描述符
        if (getrlimit(RLIMIT_NOFILE, &rl) < 0)//获取最大文件描述符
                exit(0);
        if (rl.rlim_max = RLIM_INFINITY)
                rl.rlim_max = 1024;
        for (i = 0; i < rl.rlim_max; i++)
                close(i);

        //6.将文件描述符定向到/dev/null上
        fd0 = open("/dev/null", O_RDWR);
        fd1 = dup(0);
        fd2 = dup(0);

        //初始化日志文件
        openlog(cmd, LOG_CONS, LOG_DAEMON);
        if (fd0 != 0 || fd1 != 1 || fd2 != 2)
        {
                syslog(LOG_ERR, "unexpected file descriptors %d %d %d", fd0, fd1, fd2);
                exit(1);
        }
}

int main()
{
        daemonsize("ls");
        sleep(30);
        exit(0);
}
结果:


图中第二行为守护进程

分析:

参考文章:守护进程

1.第一次调用fork的目的是保证调用setsid的调用进程不是进程组长。(而setsid函数是实现与控制终端脱离的唯一方法);setsid函数使进程成为新会话的会话头和进程组长,并与控制终端断开连接;

2.第二次调用fork的目的是:即使守护进程将来打开一个终端设备,也不会自动获得控制终端。(因为在SVR4中,当没有控制终端的会话头进程打开终端设备时,如果这个终端不是其他会话的控制终端,该终端将自动成为这个会话的控制终端),这样可以保证这次生成的进程不再是一个会话头。

3.忽略SIGHUP信号的原因是,当第一次生成的子进程(会话头)终止时,该会话中的所有进程(第二次生成的子进程)都会收到该信号。

3.出错记录

守护进程存在的一个问题是如何处理出错记录,因为它无控制终端,而且若将每个守护进程出错记录写到一个单独文件中,则管理起来很麻烦。所以需要有一个几种的守护进程出错记录设施。即syslog设施,接口函数如下:

#include <syslog.h> 
void openlog(const char *ident, int option, int facility);
void syslog(int priority, const char *format, ...);
void closelog(void); 
int setlogmask(int mask);
#include <stdarg.h> 
void vsyslog(int priority, const char *format, va_list ap);

syslog()函数产生一个日志。上述函数的参数见书P378 P379

大多数syslog实现将使消息多时间处于队列中,如果在此时间中到达了重复消息,那么syslog守护进程将不把它写到日志记录中,而是打印输出重复消息。



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值