《unix环境高级编程》-13、守护进程-读书笔记

一、基本概念

1、守护进程也成为精灵进程,是生存周期较长的一种进程。它们常常在系统自举时启动,在系统关闭时才终止。因为没有控制终端,所以说它们是在后台运行的。

2、父进程ID为0的各进程通常是内核进程,它们作为系统自举过程都得一部分而启动。

3、大多数守护进程都以超级用户(用户ID为0)特权运行。没有一个守护进程具有控制终端,其终端名设置为问号(?),终端前台进程组ID设置为-1。内核守护进程以无控制终端方式启动。用户层守护进程缺少控制终端可能是守护进程调用了setsid的结果。所有用户层守护进程都是进程组的组长进程以及会话的首进程,而且是这些进程组和会话中的唯一进程。最后,注意,大多数守护进程的父进程是init进程。


注:

没有终端限制
  让某个进程不因为用户、终端或者其他的变化而受到影响,那么就必须把这个进程变成一个守护进程


二、编程规则:

(1)首先要做的是调用umask将文件模式创建屏蔽字设置为0。由继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限

例如,若守护进程要创建一组可读、可写的文件,而继承的文件模式创建屏蔽字可能屏蔽了这两种权限,于是所要求的组可读、写就不能起作用。

(2)调用fork,然后使父进程退出(exit)。这样做实现了以下几点:第一:如果守护进程是作为一条简单shell命令启动的,但具有一个新的ID,这就保证了子进程不是一个进程组的组长进程。这对于下面就要做的setsid调用是必要的前提条件。

(3)调用setsid以创建一个新会话。于是执行三个操作:

使得调用进程完成以下动作:

--称为新会话的首进程

--称为一个新进程组的组长进程

--没有控制终端

(4)将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配文件 系统中。因为守护进程通常在系统再引导之前是一直存在的,所以如果守护进程的当前工作目录在一个装配文件系统中,那么文件系统就不能被拆卸。这与装配文件系统的原意不符。

    另外,某些守护进程可能会把当前工作目录更改到某个指定位置,在那里做它们的工作。

(5)关闭不再需要的文件描述符。这使得守护进程不再持有从其父进程继承来的某些文件描述符(父进程可能是shell进程或者其他某个进程)。

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

因为守护进程并不与终端设备相关联,所以不能再终端设备上显示其输出,也无处从交互式用户那里接受输入。

即使守护进程是从交互式会话启动的,但因为守护进程是后台运行的,所以登录会话的终止并不影响守护进程。如果其他用户在同一终端设备上登录,我们也不会见到守护进程的输出,用户也不希望他们在终端上的输入会由守护进程读取。



注:某些时候可能需要处理SIGCHLD信号:

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

在Linux下可以简单的将SIGCHLD信号的操作设为SIG_IGN。

signal(SIGCHLD, SIG_IGN);

这样,内核在子进程结束时不会产生僵尸进程。


转:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <signal.h>

void my_daemon() {
    int pid, fd;

    // 1.转变为后台进程
    if ((pid = fork()) == -1) exit(1);
    if (pid != 0) exit(0); // 父进程(前台进程)退出

    // 2.离开原先的进程组,会话
    if (setsid() == -1) exit(1); // 开启一个新会话

    // 3.禁止再次打开控制终端
    if ((pid = fork()) == -1) exit(1);
    if (pid != 0) exit(0); // 父进程(会话领头进程)退出

    // 4.关闭打开的文件描述符,避免浪费系统资源
    for (int i = 0; i < NOFILE; i++)
        close(i);

    // 5.改变当前的工作目录,避免卸载不了文件系统
    if (chdir("/") == -1) exit(1);

    // 6.重设文件掩码,防止某些属性被父进程屏蔽
    if (umask(0) == -1) exit(1);

    // 7.重定向标准输入,输出,错误流,因为守护进程没有控制终端
    if ((fd = open("/dev/null", O_RDWR)) == -1) exit(1); // 打开一个指向/dev/null的文件描述符
    dup2(fd, STDIN_FILENO);
    dup2(fd, STDOUT_FILENO);
    dup2(fd, STDERR_FILENO);
    close(fd);

    // 8.本守护进程的子进程若不需要返回信息,那么交给init进程回收,避免产生僵尸进程
    if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) exit(1);
}

#define INTERVAL 2

int main(int argc, char *argv[]) {
    my_daemon(); // 首先使之成为守护进程

    int t = 0;
    FILE *fp = fopen("/root/tmp.txt", "a");
    fprintf(fp, "ppid = %d, pid = %d, sid = %d, pgrp = %d\n", getppid(), getpid(), getsid(0), getpgrp());
    fflush(fp);

    do { // 测试此后台进程,每INTERVAL秒打印当前时间t,30秒后退出此后台进程
        fprintf(fp, "%d\n", t);
        fflush(fp); // 输出缓冲区内容到文件中
        sleep(INTERVAL);
        t += INTERVAL;
    } while(t < 30);

    fclose(fp);

    return 0;
}

保存为daemon.c
编译命令 gcc daemon.c
运行 ./a.out
查看tmp.txt文件内容 cat /root/tmp.txt




三、出错记录

    由于守护进程没有控制终端,所以需要有一个集中的守护进程出错记录设施。



四、守护进程惯例

在UNIX系统中,守护进程遵循以下公共惯例:

1、若守护进程使用锁文件,那么该文件通常存放在/var/run目录中,注意,守护进程可能需要具有超级用户权限才能在此目录下创建文件。锁文件的名字通常是name.pid,其中,name是该守护进程或服务的名字。例如cron守护进程锁文件的的名字是/var/run/cron.pid


2、若守护进程支持配置选项,那么配置文件通常存放在/etc目录中。配置文件的名字通常是name.conf,其中,name是该守护进程或服务的名字。syslogd守护进程的配置文件是/etc/syslog.conf


3、守护进程可用命令行启动,但通常它们是由系统初始化脚本之一(/etc/rc* 或 /etc/init.d/* )启动的。如果在守护进程终止时,应当自动重启它,则我们可以再/etc/inittab中为该守护进程包括_respawn记录项,这样,init就将重启该守护进程。


4、若一个守护进程有一个配置文件,那么当该守护进程启动时,它读该文件,但在此之后一般不会再查看它。





















你可能感兴趣的:(Unix环境高级编程)