一、概念及其特征

守护进程是系统中生存期较长的一种进程,常常在系统引导装入时启动,在系统关闭时终止,没有控制终端,在后台运行。守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断。

在这里,我们在Linux2.6内核的centos中,ps -ef |awk '{print $1"\t "$2"\t "$3"\t  "$8}'看到:PPID=0的进程有两个,分别是PID=1的/sbin/init进程和PID=2的[kthreadd]进程。

其中,[kthreadd]为内核进程,由它fork出来的子进程都是内核进程,并且内核守护进程的名字出现在方括号中,对于需要在进程上下文执行工作但却不被用户层进程(init)上下文调用的每一个内核组件,通常有它自己的内核守护进程。

而对于init进程,它是一个由内核在引导装入时启动的用户层次的命令,属于用户级守护进程,主要负责启动各运行层次特定系统服务。这些服务通常是在它们自己拥有的守护进程的帮助下实现的。用户层守护进程缺少控制终端可能是守护进程调用了setsid的结果。大多数用户层守护进程都是进程组的组长进程以及会话的首进程,而且是这些进程组和会话中的唯一进程。

守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。此外,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建屏蔽字等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。

二、编程规则(细节可参考Unix环境高级编程)

1、调用umask将文件模式创建屏蔽字设置为一个已知值(通常是0)。如前所述,由继承得来的文件模式创建屏蔽字可能会被设置为拒绝权限。我们可以根据我们的具体需求设定特定的权限。

2、调用fork,然后使父进程exit。这样做,使得当我们以./的shell命令启动守护进程时,父进程终止会让shell认为此命令已经执行完毕,而且,这也使子进程获得了一个新的进程ID。此外,让父进程先于子进程exit,会使子进程变为孤儿进程,这样子进程成功被init这个用户级守护进程收养。

3、调用setsid创建一个新会话。这在setsid函数中有介绍,调用setsid,会使这个子进程成为(a)新会话的首进程,(b)成为一个新进程组的组长进程,(c)切断其与控制终端的联系,或者就是没有控制终端。至此,这个子进程作为新的进程组的组长,完全脱离了其他进程的控制,并且没有控制终端。

4、将当前工作目录更改为根目录(或某一特定目录位置)。这是为了保证守护进程的当前工作目录在一个挂载的文件系统中,该文件系统不能被卸载。

5、关闭不再需要的文件描述符。根据具体情况来定。

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

7、忽略SIGCHLD信号

这一步并非必须的,只对需要创建子进程的守护进程才有必要,很多服务器守护进程设计成通过派生子进程来处理客户端的请求,如果父进程不对SIGCHLD信号进行处理的话,子进程在终止后变成僵尸进程,通过将信号SIGCHLD的处理方式设置为SIG_IGN可以避免这种情况发生。

8、用日志系统记录出错信息

因为守护进程没有控制终端,当进程出现错误时无法写入到标准输出上,可以通过调用syslog将出错信息写入到指定的文件中。该接口函数包括openlog、syslog、closelog、setlogmask,具体可参考13.4节出错记录。

9、守护进程退出处理

当用户需要外部停止守护进程运行时,往往会使用 kill命令停止该守护进程。所以,守护进程中需要编码来实现kill发出的signal信号处理,达到进程的正常退出。可用如下信号函数处理:

signal(SIGTERM, sigterm_handler);

voidsigterm_handler(int arg)

{

//进行相应处理的函数

}


三、一个简单的守护进程实例

按照上面的编程规则,结合书中示例,写了一个简单的守护进程,可以测试通过。其中为了方便查看出错记录的过程,将syslog函数注释掉,改在/tmp文件下记录日志。此外,想了一下,由于在子进程中已经将守护进程的所有标准IO加入到/dev/null中,因此我们无法通过printf函数来交互了,所以程序中还是有很多瑕疵的,我们只能将所有信息写入日志中,后续还需多考虑改进,信号处理函数有问题,还需要加一段信号屏蔽字的处理函数,防止收到的信号进行默认动作处理,也就是这个原因,使我下面这段程序无法触发往/tmp/daemon.txt文件中记录退出信息。

<span style="font-size:18px;">#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h> void daemonize(const char *cmd)
{
pid_t pid;
int i, fd0, fd1, fd2;
struct rlimit r1; umask(0); //第一步:将文件模式创建屏蔽字设置为0 /*
* 第二步:fork子进程,并且使子进程成为会话首进程,脱离终端控制
*/
if ((pid = fork()) < 0)
printf("ERROR:can't fork a son process");
else if (pid != 0) //父进程
exit(0);
setsid(); /*
* 第三步:改变当前工作目前为根目录,以防卸载当前文件系统所在的目录
*/
if (chdir("/") < 0)
printf("ERROR:can't change the directory to root(/)"); /*
* 先获取当前最大的文件描述符,并且关闭所有不需要的文件描述符
*/
if (getrlimit(RLIMIT_NOFILE, &r1) < 0)
printf("ERROR:can't get the maximum fd");
if (r1.rlim_max == RLIM_INFINITY)
r1.rlim_max = 1024;
for (i = 0; i < r1.rlim_max; i++)
close(i); /*
* 把fd为0、1、2三个fd加入到/dev/null中
*/
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0); /*
* 初始化日志文件,调用syslog函数的openlog
* openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2){
syslog(LOG_ERR,"unexpected fd %d %d %d", fd0,fd1,fd2);
exit(1);
}
*/ //以下代码段为了演示记录日志的效果
char *buf="this is a dameon \n";
int len, fd;
len = strlen(buf);
while(1)
{
if((fd=open("/tmp/dameon.txt",O_CREAT|O_WRONLY|O_APPEND,0600))<0)
{
printf("open file err \n");
exit(0);
}
write(fd,buf,len+1);
close(fd);
sleep(60); //每60秒向日志中记录一次,daemon还存在
} }
void sigterm_handler(int arg)
{
//进行相应处理的函数
char *buf="I have received a signal,I will exit 3 seconds later \n";
int len, fd;
len = strlen(buf);
if((fd=open("/tmp/dameon.txt",O_WRONLY|O_APPEND,0600))<0)
{
printf("open file err \n");
exit(0);
}
write(fd,buf,len+1);
close(fd);
sleep(3);
exit(0);
} int main()
{
char *cmd;
cmd = "cron";
daemonize(cmd);
pause();
signal(SIGUSR1, sigterm_handler);
}</span>

下面是加了信号屏蔽字处理代码段的程序,这回可以将SIGUSR1信号抓取,并且写入日志了。

#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <string.h> void siguser1(int signo)
{
char *buf="I have received a signal,I will exit 3 seconds later \n";
int len, fd;
len = strlen(buf);
if((fd=open("/tmp/dameon_exit.txt",O_CREAT|O_WRONLY|O_APPEND,0600))<0)
{
printf("open file err \n");
exit(0);
}
write(fd,buf,len+1);
close(fd);
sleep(3);
exit(0);
} void daemonize(const char *cmd)
{
pid_t pid;
int i, fd0, fd1, fd2;
struct rlimit r1;
struct sigaction sa; umask(0); //第一步:将文件模式创建屏蔽字设置为0 /*
* 第二步:fork子进程,并且使子进程成为会话首进程,脱离终端控制
*/
if ((pid = fork()) < 0)
printf("ERROR:can't fork a son process");
else if (pid != 0) //父进程
exit(0);
setsid(); /*
*对SIGUSR1信号进行处理 (注意,这个代码段必须写在第三步之前)
*/
sa.sa_handler = siguser1;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGUSR1, &sa, NULL) < 0){
syslog(LOG_ERR,"can't catch SIGUSR1");
exit(1);
} /*
* 第三步:改变当前工作目前为根目录,以防卸载当前文件系统所在的目录
*/
if (chdir("/") < 0)
printf("ERROR:can't change the directory to root(/)"); /*
* 先获取当前最大的文件描述符,并且关闭所有不需要的文件描述符
*/
if (getrlimit(RLIMIT_NOFILE, &r1) < 0)
printf("ERROR:can't get the maximum fd");
if (r1.rlim_max == RLIM_INFINITY)
r1.rlim_max = 1024;
for (i = 0; i < r1.rlim_max; i++)
close(i); /*
* 把fd为0、1、2三个fd加入到/dev/null中
*/
fd0 = open("/dev/null", O_RDWR);
fd1 = dup(0);
fd2 = dup(0); /*
* 初始化日志文件,调用syslog函数的openlog
* openlog(cmd, LOG_CONS, LOG_DAEMON);
if (fd0 != 0 || fd1 != 1 || fd2 != 2){
syslog(LOG_ERR,"unexpected fd %d %d %d", fd0,fd1,fd2);
exit(1);
}
*/ //以下代码段为了演示记录日志的效果
char *buf="this is a dameon \n";
int len, fd;
len = strlen(buf);
while(1)
{
if((fd=open("/tmp/dameon.txt",O_CREAT|O_WRONLY|O_APPEND,0600))<0)
{
printf("open file err \n");
exit(0);
}
write(fd,buf,len+1);
close(fd);
sleep(60); //每60秒向日志中记录一次,daemon还存在
} } int main()
{
char *cmd;
cmd = "cron";
daemonize(cmd);
pause();
}

Unix环境高级编程——守护进程记录总结(从基础到实现)的更多相关文章

  1. UNIX环境高级编程——守护进程

    一.守护进程简介 守护进程,也就是通常说的Daemon进程,是Linux中的后台服务进程.它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.守护进程常常在系 ...

  2. UNIX环境高级编程——守护进程列表

    amd:自动安装NFS(网络文件系统)守侯进程apmd:高级电源治理Arpwatch:记录日志并构建一个在LAN接口上看到的以太网地址和ip地址对数据库Autofs:自动安装治理进程automount ...

  3. UNIX环境高级编程--8. 进程控制

    进程控制进程标识:    每一个进程都有一个非负整型表示的唯一进程ID.虽然唯一,但是ID可以复用.当一个进程结束后,其进程ID会被延迟复用.    ID=0的进程通常是调度进程,常被称作交换进程(s ...

  4. Unix环境高级编程(八)进程关系

    本章看后给人似懂非懂的感觉,主要是不知道实际当中如何去使用.通过前面几章的学习,每个进程都有一个父进程,当子进程终止时,父进程得到通知并取得子进程的退出状态.先将本章基本的知识点总结如下,日后再看时候 ...

  5. Unix环境高级编程(六)进程控制

    本章介绍Unix的进程控制,包括进程创建,执行程序和进程终止,进程的属性,exec函数系列,system函数,进程会计机制. 1.进程标识符 每一个进程都有一个非负整数标识的唯一进程ID.ID为0表示 ...

  6. Unix环境高级编程(五)进程环境

    本章主要介绍了Unix进程环境,包含main函数是如何被调用的,命令行参数如何传递,存储方式布局,分配存储空间,环境变量,进程终止方法,全局跳转longjmp和setjmp函数及进程的资源限制. ma ...

  7. UNIX环境高级编程--9. 进程控制

    进程关系    当子进程终止时,父进程得到通知并能取得子进程的退出状态. 终端登录:    早起UNIX系统通过哑终端登录,本地的终端 or 远程的终端 .主机上链接的终端设备是固定的,所以同时登录数 ...

  8. UNIX环境高级编程——Linux进程地址空间和虚拟内存

    一.虚拟内存 分段机制:即分成代码段,数据段,堆栈段.每个内存段都与一个特权级相关联,即0~3,0具有最高特权级(内核),3则是最低特权级(用户),每当程序试图访问(权限又分为可读.可写和可执行)一个 ...

  9. (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程

    . . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...

随机推荐

  1. Codeforces 161D Distance in Tree(树型DP)

    题目链接 Distance in Tree $k <= 500$ 这个条件十分重要. 设$f[i][j]$为以$i$为子树,所有后代中相对深度为$j$的结点个数. 状态转移的时候,一个结点的信息 ...

  2. codevs——3111 CYD啃骨头(背包)

    裸的01背包  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题解       题目描述 Description CYD吃饭时有N个骨头可以啃,但CYD要午睡了 ...

  3. MariaDB主从复制、主主复制

    1.部署 10.0.0.21  MariaDB-21 10.0.0.22  MariaDB-22 cat /etc/yum.repos.d/mariadb.repo [mariadb] name=Ma ...

  4. luogu P1489 猫狗大战

    题目描述 新一年度的猫狗大战通过SC(星际争霸)这款经典的游戏来较量,野猫和飞狗这对冤家为此已经准备好久了,为了使战争更有难度和戏剧性,双方约定只能选择Terran(人族)并且只能造机枪兵. 比赛开始 ...

  5. gtest 自动化测试 部署

    1.部署 a)编译框架 1.1下载gtest库1.6.0 并解压到文件夹 "/user/{user}/gtest.1.6.0" 下载地址:https://code.google.c ...

  6. ListView设置某一项item的文本居中

    使用ListView和volley写了一个使用网络获取天气的demo ListView中Item的文本模式都是左侧对齐 我这边需要一些标题文本居中对齐 网上也找不到示例,不过找到了getView这个函 ...

  7. JVM中的内存分区简介

    1.JVM的内存区域划分: 大多数 JVM 将内存区域划分为 Method Area(Non-Heap)(方法区) ,Heap(堆) , Program Counter Register(程序计数器) ...

  8. Linux ClientDataSet libmidas.so.2

    Linux ClientDataSet libmidas.so.2 DELPHI LINUX程序使用CLIENTDATASET控件,部署的时候需要libmidas.so,相当于WINDOWS程序的MI ...

  9. Unity -- 使用easyAR的基础教程

    “三人行,必有我师焉”,抱着共同学习进步的态度,和大家一起交流下EasyAR的用法.有不足的地方,欢迎指出!大家都知道,今年的QQ,支付宝,都用到了AR的技术,扫描一张图片,就会出现虚拟模型,及其想要 ...

  10. Linux下Utuntu使用

    以前一直用Centos,在下面安装了Vmware Tools和Eclipse C++基本能使用,也遇到过一些问题.突然心血来潮,试试Utuntu,所以在实验室电脑虚拟机上装一下,安装过程很熟练了,参考 ...