守护进程也称为精灵进程是一种生存期较长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。unix系统有很多守护进程,大多数服务器都是用守护进程实现的,例如inetd守护进程。

1、守护进程的特征

用ps命令察看一些常用的系统守护进程,看一下他们和几个概念:进程组、控制终端和会话有什么联系。执行: ps –axj ,结果如下所示:

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

 守护进程与后台进程的区别:(1) 后台运行程序,即加&启动的程序,(2)后台运行的程序拥有控制终端,守护进程没有。

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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/resource.h>void daemonize(const char *cmd)
{int                 i,fd0,fd1,fd2;pid_t               pid;struct rlimit       r1;struct sigaction    sa;umask(0);//获取文件描述符最大值getrlimit(RLIMIT_NOFILE,&r1);//创建子进程if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0)  //使父进程退出exit(0);setsid();  //创建会话//创建子进程避免获取终端sa.sa_handler = SIG_IGN;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(SIGHUP,&sa,NULL);if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0)exit(0);//修改目录chdir("/");//关闭不需要的文件描述符if(r1.rlim_max == RLIM_INFINITY)r1.rlim_max = 1024;for(i=0;i<r1.rlim_max;++i)close(i);//打开文件描述符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()
{daemonize("ls");sleep(30);  //主进程休眠,以便查看守护进程状态exit(0);
}

 第一次调用fork的目的是保证调用setsid的调用进程不是进程组长。(而setsid函数是实现与控制终端脱离的唯一方法);setsid函数使进程成为新会话的会话头和进程组长,并与控制终端断开连接;第二次调用fork的目的是:即使守护进程将来打开一个终端设备,也不会自动获得控制终端。(因为在SVR4中,当没有控制终端的会话头进程打开终端设备时,如果这个终端不是其他会话的控制终端,该终端将自动成为这个会话的控制终端),这样可以保证这次生成的进程不再是一个会话头。忽略SIGHUP信号的原因是,当第一次生成的子进程(会话头)终止时,该会话中的所有进程(第二次生成的子进程)都会收到该信号。

程序执行结果,输入ps -axj命令查看守护进程的信息:

3、出错记录

  守护进程没有控制终端,不能将错误写到标准输错上。大多数进程使用集中的守护进程出错syslog设施,该设施的接口是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实现将使消息多时间处于队列中,如果在此时间中到达了重复消息,那么syslog守护进程将不把它写到日志记录中,而是打印输出重复消息。

4、单实例守护进程

  为了正常运作,某些守护进程实现为单实例,即在任一时刻只运行该守护进程的一个副本。采用文件锁和记录锁机制可以实现单实例守护进程,如果每一个守护进程创建一个文件,并且在整个文件上加上一把锁,那就只允许创建一把这样的写锁,之后试图再创建这样的一把写锁将会失败。这样就保证守护进程只有一个副本在运行。使用文件和记录锁保证只运行某守护进程的一个副本,守护进程的每个副本都试图创建一个文件,并将其进程ID写到该文件中。程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/stat.h>#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH )
extern int lockfile(int);int already_running(void)
{int     fd;char    buf[16];//打开文件,不存在则创建fd = open(LOCKFILE,O-RDWR|O_CREAT,LOCKMODE);if(fd < 0){syslog(LOG_ERR,"can't open %s : %s",LOCKFILE,strerror(errno));exit(1);}//对文件加锁if(lockfile(fd)<0){if(errno == EACCES | errno == EAGAIN){close(fd);return 1;}syslog(LOG_ERR,"can,t lock %s : %s",LOCKFILE,strerror(errno));exit(1);}ftruncate(fd,0); //将文件长度截短为0sprintf(buf,"%ld",(long)getpid());write(fd,buf,strlen(buf)+1);return 0;
}

5、守护进程的惯例

(1)若守护进程使用锁文件,那么该文件通常存放在/var/run目录中。

(2)若守护进程支持配置选项,那么配置文件通常存放在/etc中目录中。

(3)守护进程可以用命令行启动,通常是系统初始化脚本。

(4)若一守护进程有一配置文件,那么当该守护进程启动时,读取该文件,此后一把不会在查看它。

使用sigwait及多线程实现守护进程重读配置文件程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <signal.h>
#include <fcntl.h>
#include <syslog.h>
#include <sys/stat.h>
#include <sys/resource.h>#define LOCKFILE "/var/run/daemon.pid"
#define LOCKMODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH )sigset_t mask;
int lockfile(int fd)
{struct flock f1;f1.l_type = F_WRLCK;f1.l_start = 0;f1.l_whence = SEEK_SET;f1.l_len = 0;return fcntl(fd,F_SETLK,&f1);
}
int already_running(void)
{int     fd;char    buf[16];fd = open(LOCKFILE,O_RDWR|O_CREAT,LOCKMODE);if(fd < 0){syslog(LOG_ERR,"can't open %s : %s",LOCKFILE,strerror(errno));exit(1);}if(lockfile(fd)<0){if(errno == EACCES | errno == EAGAIN){close(fd);return 1;}syslog(LOG_ERR,"can,t lock %s : %s",LOCKFILE,strerror(errno));exit(1);}ftruncate(fd,0);sprintf(buf,"%ld",(long)getpid());write(fd,buf,strlen(buf)+1);return 0;
}void daemonize(const char *cmd)
{int                 i,fd0,fd1,fd2;pid_t               pid;struct rlimit       r1;struct sigaction    sa;umask(0);getrlimit(RLIMIT_NOFILE,&r1);if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0)exit(0);setsid();sa.sa_handler = SIG_IGN;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(SIGHUP,&sa,NULL);if((pid = fork()) < 0){perror("fork() error");exit(0);}else if(pid > 0)exit(0);chdir("/");if(r1.rlim_max == RLIM_INFINITY)r1.rlim_max = 1024;for(i=0;i<r1.rlim_max;++i)close(i);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);}
}void reread()
{printf("read daemon config file again.\n");
}
void * thread_func(void *arg)
{int err,signo;while(1){sigwait(&mask,&signo);switch(signo){case SIGHUP:syslog(LOG_INFO,"Re-reading configuration file.\n");reread();break;case SIGTERM:syslog(LOG_INFO,"got SIGTERM;exiting.\n");exit(0);default:syslog(LOG_INFO,"unexpected signal %d.\n",signo);}}return NULL;
}
int main(int argc,char *argv[])
{pthread_t           tid;char                *cmd;struct sigaction    sa;if((cmd = strrchr(argv[0],'/')) == NULL)cmd = argv[0];elsecmd++;daemonize(cmd);if(already_running()){syslog(LOG_ERR,"daemon already running.\n");exit(1);}sa.sa_handler =SIG_DFL;sigemptyset(&sa.sa_mask);sa.sa_flags = 0;sigaction(SIGHUP,&sa,NULL);sigfillset(&mask);pthread_sigmask(SIG_BLOCK,&mask,NULL);pthread_create(&tid,NULL,thread_func,0);sleep(90);exit(0);
}

关于本章介绍的守护进程,很多地方不是很懂,具体怎么应用还不清楚,先了解个大概,知道什么是守护进程及其作用。具体怎么用日后再来补充,有待加强。

转载于:https://www.cnblogs.com/Anker/archive/2012/12/20/2825929.html

《APUE》读书笔记—第十三章守护进程相关推荐

  1. 《UNIX环境高级编程》笔记 第十三章-守护进程

    1. 概念 守护进程(daemon)是生存期长的一种进程.它们常常在系统引导装入时启动,仅在系统关闭时才终止.因为它们没有控制终端,所以说它们是在后台运行的. Linux的大多数服务就是用守护进程实现 ...

  2. 《Java编程思想》读书笔记 第十三章 字符串

    <Java编程思想>读书笔记 第十三章 字符串 不可变String String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创建一个全新的String对象,以及包含修 ...

  3. APUE读书笔记-第14章-高级I/O

    14.1 引言 *高级I/O包括非阻塞I/O.记录锁.系统V流机制.I/O多路转换(select和poll函数).readv和writev函数以及存储映射I/O(mmap) 14.2 非阻塞I/O * ...

  4. APUE读书笔记-第15章-进程间通信

    15.1 引言 *进程之间交换信息的方法可以经由fork或exec传送打开文件,或者通过文件系统 *进程之间相互通信的其他技术--IPC(InterProcess Communication)包括半双 ...

  5. 《C++ Primer》读书笔记——第十三章_拷贝控制

    一个类有5种特殊的成员函数:拷贝构造函数.拷贝赋值运算符.移动构造函数.移动赋值运算符.析构函数.如果没有定义这些拷贝控制成员,编译器会自动为它定义缺失的操作. A a; A b = a;//报错 1 ...

  6. APUE读书笔记-第十一章-线程

    新创建的线程可以访问进程的地址空间,并且继承调用线程的浮点环境和信号屏蔽字,但是该线程的挂起信号集会被清除 新线程时调用pthread_self函数获取自己的线程ID,而不是从共享内存中读出,或者从线 ...

  7. C++ Primer 读书笔记 - 第十三章

    1. Initialization和Assignment不一样.其中Initialization包括direct-initialization (如A a(...))和copy-initializat ...

  8. 《深入浅出DPDK》读书笔记(十三):DPDK虚拟化技术篇(加速包处理的vhost优化方案)

    Table of Contents 加速包处理的vhost优化方案 142.vhost的演进和原理 143.Qemu与virtio-net 144.Linux内核态vhost-net 145.用户态v ...

  9. 《Linux内核设计与实现》 第八周读书笔记 第四章 进程调度

    20135307 张嘉琪 第八周读书笔记 第四章 进程调度 调度程序负责决定将哪个进程投入运行,何时运行以及运行多长时间,进程调度程序可看做在可运行态进程之间分配有限的处理器时间资源的内核子系统.只有 ...

最新文章

  1. 今日 Paper | 模态平衡模型;组合语义分析;高表达性SQL查询;多人姿态估计模型等
  2. 自己动手写C语言编译器(2)
  3. 关于new String(new byte[]{0})
  4. 引入jQuery后$冲突的解决办法
  5. mysql global temporary table_【转】MySQL Temporary Table相关问题的探究
  6. MYSQL安装和配置
  7. shell中判断远程主机的某个tcp端口是否存活
  8. 【BZOJ2839】集合计数【BZOJ3622】已经没有什么好害怕的了
  9. socket网络字节序以及大端序小端序
  10. CAD看图软件中如何将CAD图纸由天正T20版本转换为T3版本?
  11. Python接口自动化
  12. H5获取用户code,换openID
  13. Go语言十一大主流微服务框架
  14. Jquery 回到顶部
  15. 微信开发者工具如何打开企业微信小程序
  16. Towards Open World Detection
  17. Magix 促销:让你的音视频制作更加专业
  18. mysql中on用法_详解mysql中的Using与On的用法
  19. Summernote实现图片上传功能
  20. There is no matching specific subroutine for this generic subroutine call.

热门文章

  1. 树,森林与二叉树之间的转换
  2. js时间搓化为今天明天_打乒乓球的搓球技巧!你掌握了吗?
  3. 计算机mac地址里面0,如何查计算机MAC地址
  4. ArcEngine的拓扑分析之ITopologicalOperator
  5. 题解报告:hdu 5695 Gym Class(拓扑排序)
  6. cglib invoke 和 invokeSuper 可用的组合
  7. 剑指Offer_编程题_22
  8. 【oracle】oracle经典sql,exception,database link纠错
  9. Unity使用陀螺仪控制Camera
  10. 围观窗体与组件02 - 零基础入门学习Delphi24