Unix环境高级编程(十三)守护进程
守护进程也称为精灵进程是一种生存期较长的一种进程。它们独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件。他们常常在系统引导装入时启动,在系统关闭时终止。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信号的原因是,当第一次生成的子进程(会话头)终止时,该会话中的所有进程(第二次生成的子进程)都会收到该信号。

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环境高级编程(十三)守护进程的更多相关文章
- Unix环境高级编程:守护进程
参考 Unix环境高级编程,第9,13章 介绍 守护进程就是Linux中使用ps aux那些一般以d结尾的程序,比如rsyslogd,sshd等,为daemon简称.他们是长期在后台执行的随终端关闭而 ...
- (七) 一起学 Unix 环境高级编程(APUE) 之 进程关系 和 守护进程
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- (五) 一起学 Unix 环境高级编程 (APUE) 之 进程环境
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- (六) 一起学 Unix 环境高级编程 (APUE) 之 进程控制
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
- UNIX环境高级编程笔记之进程控制
本章重点介绍了进程控制的几个函数:fork.exec族._exit.wait和waitpid等,主要需要掌握的是父进程和子进程之间的运行机制,怎么处理进程的正常和异常终止.以及怎么让进程执行不同的程序 ...
- UNIX环境高级编程笔记之进程环境
本章讲的都是一些非常基础的知识,目的是为了下一章讲进程控制做铺垫,所以,本章就不做过多的总结了,直接看图吧.
- UNIX环境高级编程——创建孤儿进程
/* 创建孤儿进程 父进程终止后,向子进程发送挂断信号,又接着发送继续信号. */ #include <stdio.h> #include <stdlib.h> #includ ...
- UNIX环境高级编程——线程与进程区别
进程和线程都是由操作系统所体会的程序运行的基本单元,系统利用该基本单元实现系统对应用的并发性.进程和线程的区别在于: (1)一个程序至少有一个进程,一个进程至少有一个线程. (2)线程的划分尺度小于进 ...
- (十三) [终篇] 一起学 Unix 环境高级编程 (APUE) 之 网络 IPC:套接字
. . . . . 目录 (一) 一起学 Unix 环境高级编程 (APUE) 之 标准IO (二) 一起学 Unix 环境高级编程 (APUE) 之 文件 IO (三) 一起学 Unix 环境高级编 ...
随机推荐
- activity 保存数据
activity 保存数据对android的商业项目十分的重要,譬如你在发微博的时候,突然来了一个电话,你洋洋洒洒写了100个字,你不能保存的话,你岂不要卖要骂娘. 那activity究竟是保存数据的 ...
- (转)<Unity3D>Unity3D在android下调试
转自:http://blog.csdn.net/zuoyamin/article/details/11827309 一.工具准备 1.JDK——由于android是基于Java平台开发的,jdk是必须 ...
- QML 与 C++ 交互之工厂方法
QML 与 C++ 交互之工厂方法 先看例如以下的类声明,声明了一个产品类和工厂类. #include <QObject> class Productor : public QObject ...
- Web前端开发资源集锦
前端开发已经成为当前炙手可热的技术之一.本周我们除了给大家带技术相关资讯,还有一些技术人员常用的网站.希望大家不要错过我们本周的内容.原文来自:极客标签 为神马说写程序是很艰难的 程序员 做一名优秀程 ...
- 算法(第四版)学习笔记之java实现希尔排序
希尔排序思想:使数组中随意间隔为h的元素都是有序的. 希尔排序是插入排序的优化.先对数组局部进行排序,最后再使用插入排序将部分有序的数组排序. 代码例如以下: /** * * @author seab ...
- leetcode Wildcard Matching greedy algrithm
The recursive program will result in TLE like this: class Solution { public: bool isMatch(const char ...
- ubuntu Server 设置主机静态 ip地址
ubuntu Server 设置主机静态 ip地址 1:先输入 ifconfig 查看当前网络配置 2:然后关闭 eth0 网卡 sudo ifdown eth0 3:配置静态ip sudo vim ...
- EL运算符(web基础学习笔记十七)
一.EL语法 1.1.语法结构 ${expression} 1.2.[]与.运算符 EL 提供.和[]两种运算符来存取数据. 当要存取的属性名称中包含一些特殊字符,如.或?等并非字母或数字的符号,就一 ...
- Linux软件安装方法
常用的两种软件安装方法: 1.RPM软件安装 rpm -qi 软件名 查询 rpm -ivh *.rpm 安装 rpm -e 软件名 卸载 系统自带的一些rpm包在系统镜像的Server文件夹里,需要 ...
- LogUtils.java
package com.xdsjs.save.utils; /** * 日志相关管理类 * Created by xdsjs on 2015/10/13. */ import android.util ...