Linux C语言 23-守护进程

Linux C语言 23-守护进程

本节关键字:守护进程、daemon
相关C库函数:sigset、signal、fork、setsid、chdir、umask、open、write、close、sleep

什么是守护进程?

  • 守护进程(Daemon)也被翻译为精灵进程、后台进程,守护进程的运行不受终端影响,可以长期稳定地运行服务,直到整个系统关闭才退出;
  • 守护进程是后台运行的、系统启动是就存在的、不予任何终端关联的,用于处理一些系统级别任务的特殊进程;
  • 若是想让某个进程不受用户、中断或其它变化的影响,那么必须把这个进程变成一个守护进程;

Unix/Linux系统中守护进程的查看

  • ps -ef | grep ‘.*d$’ 可以看到许多以-d结尾的进程,这些进程大多数都是守护进程;
  • ps -axj a表示列出所有用户的进程 x表示列出有终端和无终端的进程 j表示列出与作业控制相关的信息,这些信息中凡是TPGID一栏为-1的基本上都是守护进程。

守护进程的编写

守护进程的惯例

在unix系统中,守护进程的设计通常遵循以下原则:

  • 如果守护进程使用锁文件,则该锁文件通常位于/var/run目录中。然而需要注意的是,在该目录创建文件通常需要超级用户的权限,锁文件的名字通常是name.pid,例如cron守护进程锁文件的名字是/var/run/crond.pid;
  • 如果守护进程支持配置选项,那么该配置文件一般是放在/etc目录下,文件名字一般是name.conf,name是该守护进程或服务的名字,例如mysqld配置文件的名字是/etc/my.cnf;
  • 守护进程可以使用命令启动,但是该启动命令一般在系统初始化的脚本中进行。如果守护进程终止,应当自动进行重启;如果一个守护进程更新了配置文件,则应该重新启动;
  • 为了避免每次更改配置文件都需要重启进程的问题,某些进程将捕捉SIGHUP信号,接收到此信号时自动重新读取配置文件。
守护进程的编写步骤

(1)创建子进程,父进程退出(使子进程称为孤儿进程,此时init进程会将孤儿进程收录);
这是编写守护进程的第一步,因为守护进程是脱离终端的,所以完成第一步后就会在shell终端里形成一个程序已经运行完毕的假象。以后的全部工做在子进程中完成,而用户在shell终端里则能够执行其余命令,从而在形式上作到了与控制终端脱离。
(2)在子进程中建立新的会话(脱离控制终端);
在子进程中建立新的会话(脱离控制终端):这步是建立守护进程中最重要的一步,虽然实现起来很简单,可是它的意义很是重要,在这里使用的是系统函数setsid()来建立一个新的会话,而且担任该会话组的组长。
(3)更改当前工作目录;
使用fork()建立的子进程是继承了父进程的当前工做目录,因为在进程运行中,当前目录所在的文件系统是不能卸载的,这对之后使用会形成不少的麻烦。所以一般的作法是让“/”做为守护进程的当前目录,固然也能够指定其余的别的目录来做为守护进程的工做目录。
(4)重设文件权限掩码;
文件权限掩码是屏蔽掉文件权限中的对应位。因为使用fork()函数新建立的子进程继承了父进程的文件权限掩码,这就给该子进程使用文件带了不少的麻烦(好比父进程中的文件没有执行文件的权限,然而在子进程中但愿执行相应的文件这个时候就会出问题)。所以在子进程中要把文件的权限掩码设置成为0,即此时有最大的权限,这样能够大大加强该守护进程的灵活性。设置的方法是:umask(0)。
(5)关闭文件描述符;
同文件权限码同样,用fork()函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些文件被打开的文件可能永远不会被守护进程读写,若是不进行关闭的话将会浪费系统的资源,形成进程所在的文件系统没法卸下以及引发预料的错误。
(6)守护进程的退出,如果程序内部不做任何处理,则需要使用kill命令来终止守护进程,因此建议在守护进程中使用signal信号处理来实现进程的正常退出。

补充说明:

  • 进程组(process group):一个或多个进程的集合,每个进程组有惟一一个进程组ID,即进程组长进程的ID。
  • 会话期(session):一个或多个进程组的集合,有惟一一个会话期首进程(session leader)。会话期ID为首进程的ID。会话期能够有一个单独的控制终端(controlling terminal)。与控制终端链接的会话期首进程叫作控制进程(controlling process)。当前与终端交互的进程称为前台进程组。其他进程组称为后台进程组。
  • 挂断信号(SIGHUP):默认的动做是终止程序。当终端接口检测到网络链接断开,将挂断信号发送给控制进程(会话期首进程)。若是会话期首进程终止,则该信号发送到该会话期前台进程组。一个进程退出致使一个孤儿进程组中产生时,若是任意一个孤儿进程组进程处于STOP状态,发送SIGHUP和SIGCONT信号到该进程组中全部进程。所以当网络断开或终端窗口关闭后,控制进程收到SIGHUP信号退出,会致使该会话期内其余进程退出。
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define MAXFILE 65535

int main(void)
{
    pid_t pid;
    int i, fd, len;
    char *buf = "this is my dameon\n";
    len = strlen(buf);
    
#ifdef SIGTTOU // (可选)
    sigset(SIGTTOU, SIG_IGN); // 忽略写终端信号
    sigset(SIGTTIN, SIG_IGN); // 忽略读终端信号
    sigset(SIGTSTP, SIG_IGN); // 忽略Ctrl+Z信号
#endif

    // 1、创建子进程 并结束父进程
    pid = fork(); 
    if (pid < 0)
    {
        printf("error fork \n");
        exit(1);
    }
    else if (pid > 0)
    {
        exit(0);    // 结束父进程
    }
    
    // 2、在子进程中建立新的会话 
    // 调用setsid函数的进程成为新会话的领头进程,并脱离其父进程的会话组和进程组
    // 由于会话对控制终端的独占性,子进程同时与控制终端脱离
    setsid();
    
    if ((fd=open("/dev/tty", O_RDWR)) >= 0)
    {
        // 解除进程与当前控制台的联系
        ioctl(fd, TIOCNOTTY, (char*)NULL);
        close(fd);
    }
    if (fork() > 0)
    {
        // fork第二次主要目的是。防止进程再次打开一个控制终端
        // 可以省略此步,因为打开一个控制终端的前台条件是该进程必须是会话组长。再fork一次,子进程ID != sid(sid是进程父进程的sid)。因此也没法打开新的控制终端
        exit(0);
    }

    // 3、更改当前的工作目录 摆脱父进程的影响
    chdir("/");
    
    // 4、重新文件权限掩码
    umask(0);
    
    // 5、关闭文件描述符(有些操作系统可不关闭)
    for (i=0; i<MAXFILE; i++)
    {
        // 关闭文件描述符(常说的输入,输出,报错3个文件),
        // 由于守护进程要失去了对所属的控制终端的联系,这三个文件要关闭
        close(i);
    }
    
    while (1)
    {
        // 测试程序,每10秒向指定文件中写入一次内容
        if ((fd=open("/tmp/mydameontest.txt", O_CREAT|O_WRONLY|O_APPEND, 0600)) < 0)
        {
            printf("open file err \n");
            exit(0);
        }
        write(fd, buf, len+1);
        close(fd);
        sleep(10);
    }
}
/** 运行结果:
运行程序后,执行命令 tail -f /tmp/mydaemontest.txt
# tail -f mydaemontest.txt 
this is my daemon
this is my daemon
this is my daemon
this is my daemon

*/

你可能感兴趣的:(Linux_C语言,linux,c语言,运维)