守护进程也称为精灵进程是一种生存期较长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。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); //将文件长度截短为0
sprintf(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];
else
cmd++;
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);
}

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

Unix环境高级编程(十三)守护进程的更多相关文章

  1. Unix环境高级编程:守护进程

    参考 Unix环境高级编程,第9,13章 介绍 守护进程就是Linux中使用ps aux那些一般以d结尾的程序,比如rsyslogd,sshd等,为daemon简称.他们是长期在后台执行的随终端关闭而 ...

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

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

  3. (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境

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

  4. (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制

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

  5. UNIX环境高级编程笔记之进程控制

    本章重点介绍了进程控制的几个函数:fork.exec族._exit.wait和waitpid等,主要需要掌握的是父进程和子进程之间的运行机制,怎么处理进程的正常和异常终止.以及怎么让进程执行不同的程序 ...

  6. UNIX环境高级编程笔记之进程环境

    本章讲的都是一些非常基础的知识,目的是为了下一章讲进程控制做铺垫,所以,本章就不做过多的总结了,直接看图吧.

  7. UNIX环境高级编程——创建孤儿进程

    /* 创建孤儿进程 父进程终止后,向子进程发送挂断信号,又接着发送继续信号. */ #include <stdio.h> #include <stdlib.h> #includ ...

  8. UNIX环境高级编程——线程与进程区别

    进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性.进程和线程的区别在于: (1)一个程序至少有一个进程,一个进程至少有一个线程. (2)线程的划分尺度小于进 ...

  9. (十三) [终篇] 一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字

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

随机推荐

  1. Android动画-补间(Tween)动画

    Android动画的两种方式,其中帧动画上篇文章已经讲了,这次主要讲解的就是补间动画,补间动画就是动画业务场景中常用的旋转,平移,缩放,和渐变效果,帧动画是通过轮播动画实现动画效果,补间动画通过在两个 ...

  2. IE9对HTML5中一部分属性不提供支持的原因

    为什么在IE9中对于HTML5标准中的离线应用程序以及CSS3中的一部分不提供支持?笔者间接了解到了这个原因. 微软日前已经发布了Internet Explorer 9(以下简称IE9)正式版.在该版 ...

  3. python3 操作sqlSever

    相关代码如下: #coding =utf-8 import os import pyodbc import time class SqlDb: def __init__(self, server='D ...

  4. Oracle Agile PLM Web Services 的实现

    Oracle 的产品Agile PLM内置了许多Web Services,其他系统可以通过Web Servcies实现对Agile PLM系统资源的访问.快速学会使用的方法,是去Oracle的官网下载 ...

  5. Android Studio安装&&安装bug

    1.安装SDK:Android SDK安装 2.安装Android Studio 3.配置HTTP Proxy: 转自:Android Studio设置HTTP代理(可用) 因为大陆的内网的防火墙很厉 ...

  6. [Node.js]27. Level 5: URL Building & Doing the Request

    Let's create a page which calls the twitter search API and displays the last few results for Code Sc ...

  7. select设置高度的兼容问题

    在IE678下,我们给select设置高度的话,里面的option无法居中,折中的兼容方式就是,我们给select的border:0:外面套一层div,这个div给他设置padding,让select ...

  8. Android动态设置字体颜色

    步骤: 1.在values目录下的strings.xml文件中加入颜色:比方 <color name="ccc">#ccc</color> 2.假设你直接这 ...

  9. GP开发示例:数据库去重

    这个例子专业讲解基于ArcEngine使用GP开发的过程及遇到的问题.更多GP使用方法:GP使用心得 功能需求:现在外业第一次数据(简称调绘.mdb)和第二次数据(简称检查.mdb)有重复.第二次是在 ...

  10. web开发学习之旅

    过段时间要去实习,提前问了下老师我要准备哪些知识. 2015年3月19日,老师告诉我的,ionic Framework,Yii Framework,AngularJS,还有一些前端开发知识. 我除了听 ...