原文: http://www.cnblogs.com/haimingwey/archive/2012/04/25/2470190.html

守护进程在Linux/Unix系统中有着广泛的应用。有时,开发人员也想把自己的程序变成守护进程。在创建一个守护进程的时候,要接触到子进程、进程组、会晤期、信号机制、文件、目录和控制终端等多个概念。因此守护进程还是比较复杂的,在这里详细地讨论Linux/Unix的守护进程的编写,总结出八条经验,并给出应用范例。 
编程要点 
1.屏蔽一些有关控制终端操作的信号。防止在守护进程没有正常运转起来时,控制终端受到干扰退出或挂起。示例如下:

signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP ,SIG_IGN);

所有的信号都有自己的名字。这些名字都以“SIG”开头,只是后面有所不同。开发人员可以通过这些名字了解到系统中发生了什么事。当信号出现时,开发人员可以要求系统进行以下三种操作: 
◆ 忽略信号。大多数信号都是采取这种方式进行处理的,这里就采用了这种用法。但值得注意的是对SIGKILL和SIGSTOP信号不能做忽略处理。 
◆ 捕捉信号。最常见的情况就是,如果捕捉到SIGCHID信号,则表示子进程已经终止。然后可在此信号的捕捉函数中调用waitpid()函数取得该子进程的进程ID和它的终止状态。另外,如果进程创建了临时文件,那么就要为进程终止信号SIGTERM编写一个信号捕捉函数来清除这些临时文件。 
◆ 执行系统的默认动作。对绝大多数信号而言,系统的默认动作都是终止该进程。 
对这些有关终端的信号,一般采用忽略处理,从而保障了终端免受干扰。 
这类信号分别是,SIGTTOU(表示后台进程写控制终端)、SIGTTIN(表示后台进程读控制终端)、SIGTSTP(表示终端挂起)和SIGHUP(进程组长退出时向所有会议成员发出的)。 
2.将程序进入后台执行。由于守护进程最终脱离控制终端,到后台去运行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。这就是常说的“脱壳”。子进程继续函数fork()的定义如下:

#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

该函数是Linux/Unix编程中非常重要的函数。它被调用一次,但返回两次。这两次返回的区别是子进程的返回值为“0”,而父进程的返回值为子进程的ID。如果出错则返回“-1”。 
3.脱离控制终端、登录会话和进程组。开发人员如果要摆脱它们,不受它们的影响,一般使用 setsid() 设置新会话的领头进程,并与原来的登录会话和进程组脱离。这只是其中的一种方法,也有如下处理的办法:

if  ((fd = open("/dev/tty",O_RDWR)) >= 0) {
ioctl(fd,TIOCNOTTY,NULL);
close(fd);
}

其中/dev/tty是一个流设备,也是终端映射,调用close()函数将终端关闭。 
4.禁止进程重新打开控制终端。进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。开发人员可以通过不再让进程成为会话组长的方式来禁止进程重新打开控制终端,需要再次调用fork函数。 
上面的程序代码表示结束第一子进程,第二子进程继续(第二子进程不再是会话组长)。 
5. 关闭打开的文件描述符,并重定向标准输入、标准输出和标准错误输出的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如果不关闭,将会浪费系统资源,引起无法预料的错误。关闭三者的代码如下:

for (fd = 0, fdtablesize = getdtablesize();
fd < fdtablesize; fd++)
close(fd);

但标准输入、标准输出和标准错误输出的重定向是可选的。也许有的程序想保留标准输入(0)、标准输出(1)和标准错误输出(2),那么循环应绕过这三者。代码如下:

for (fd =3, fdtablesize = getdtablesize();
fd < fdtablesize; fd++)
close(fd);

有的程序有些特殊的需求,还需要将这三者重新定向。示例如下:

error=open("/tmp/error",O_WRONLY|O_CREAT,
0600);
dup2(error,2);
close(error);
in=open("/tmp/in",O_RDONLY|O_CREAT,0600);
if(dup2(in,0)==-1) perror("in");
close(in);
out=open("/tmp/out",O_WRONLY|O_CREAT,0600);
if(dup2(out,1)==-1) perror("out");
close(out);

6.改变工作目录到根目录或特定目录进程活动时,其工作目录所在的文件系统不能卸下。 
一般需要将工作目录改变到根目录或特定目录,注意用户对此目录需要有读写权。防止超级用户卸载设备时系统报告设备忙。 
7.处理SIGCHLD信号。SIGCHLD信号是子进程结束时,向内核发送的信号。 
如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。因此需要对SIGCHLD信号做出处理,回收僵尸进程的资源,避免造成不必要的资源浪费。可以用如下语句: 
signal(SIGCHLD,(void *)reap_status); 
捕捉信号SIGCHLD,用下面的函数进行处理:

void reap_status()
{ int pid;
union wait status;
while ((pid = wait3(&status,WNOHANG,NULL)) > 0)
…… }

8.在Linux/Unix下有个syslogd的守护进程,向用户提供了syslog()系统调用。任何程序都可以通过syslog记录事件。 
由于syslog非常好用和易配置,所以很多程序都使用syslog来发送它们的记录信息。一般守护进程也使用syslog向系统输出信息。syslog有三个函数,一般只需要用syslog(...)函数,openlog()/closelog()可有可无。syslog()在shslog.h定义如下:

#include <syslog.h>
void syslog(int priority,char *format,...);

其中参数priority指明了进程要写入信息的等级和用途。第二个参数是一个格式串,指定了记录输出的格式。在这个串的最后需要指定一个%m,对应errno错误码。 
应用范例 
下面给出Linux下编程的守护进程的应用范例,在UNIX中,不同版本实现的细节可能不一致,但其实现的原则是与Linux一致的。

#include <stdio.h>
#include <signal.h>
#include <sys/file.h>
main(int argc,char **argv)
{
time_t now;
int childpid,fd,fdtablesize;
int error,in,out;
/* 忽略终端 I/O信号,STOP信号 */
signal(SIGTTOU,SIG_IGN);
signal(SIGTTIN,SIG_IGN);
signal(SIGTSTP,SIG_IGN);
signal(SIGHUP ,SIG_IGN);
/* 父进程退出,程序进入后台运行 */
if(fork()!=0) exit(1);
if(setsid()<0)exit(1);/* 创建一个新的会议组 */
/* 子进程退出,孙进程没有控制终端了 */
if(fork()!=0) exit(1);
if(chdir("/tmp")==-1)exit(1);
/* 关闭打开的文件描述符,包括标准输入、标准输出和标准错误输出 */
for (fd = 0, fdtablesize = getdtablesize(); fd < fdtablesize; fd++)
close(fd);
umask(0);/*重设文件创建掩模 */
signal(SIGCHLD,SIG_IGN);/* 忽略SIGCHLD信号 */
/*打开log系统*/
syslog(LOG_USER|LOG_INFO,"守护进程测试!n");
while(1)
{
time(&now);
syslog(LOG_USER|LOG_INFO,"当前时间:t%sttn",ctime(&now));
sleep(6);
}
}

此程序在Turbo Linux 4.0下编译通过。这个程序比较简单,但基本体现了守护进程的编程要点。读者针对实际应用中不同的需要,还可以做相应的调整。

http://liubin.itpub.net/post/325/24427   出处

另外可以参考:http://topic.csdn.net/u/20080626/13/85947b12-b3a5-40eb-8e3b-2e1b9c3f0087.html

编写Linux/Unix守护进程的更多相关文章

  1. linux 创建守护进程的相关知识

    linux 创建守护进程的相关知识 http://www.114390.com/article/46410.htm linux 创建守护进程的相关知识,这篇文章主要介绍了linux 创建守护进程的相关 ...

  2. linux C守护进程编写

    linux编程-守护进程编写 守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待 处理某些发生的事件.守护进程是一种很有用的进程. Linux的大多数服 ...

  3. Unix守护进程

    问题描述:         Unix守护进程 问题解决:     Unix守护进程没有控制终端,终端名设置为问号(?),终端前台进程组ID设置(TPGID)为-1 守护进程编写规则:      (1) ...

  4. asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二)

    原文:asp.net core2.0 部署centos7/linux系统 --守护进程supervisor(二) 续上一篇文章:asp.net core2.0 部署centos7/linux系统 -- ...

  5. python实现Linux启动守护进程

    python实现Linux启动守护进程 DaemonClass.py代码: #/usr/bin/env python # -*- coding: utf-8 -*- import sys import ...

  6. Linux/Unix分配进程ID的方法以及源代码实现

    在Linux/Unix系统中.每一个进程都有一个非负整型表示的唯一进程ID.尽管是唯一的.可是进程的ID能够重用.当一个进程终止后,其进程ID就能够再次使用了. 大多数Linux/Unix系统採用延迟 ...

  7. 【Linux】- 守护进程的启动方法

    转自:Linux 守护进程的启动方法 Linux中"守护进程"(daemon)就是一直在后台运行的进程(daemon). 本文介绍如何将一个 Web 应用,启动为守护进程. 一.问 ...

  8. 拾遗:Unix 守护进程编写规范

    //标准库自带函数,通常以 daemon(0, 0) 方式调用 int daemon(int nochdir, int noclose) Linux: #include <unistd.h> ...

  9. linux下编写简单的守护进程

    搭建linux服务器的时候,需要写一个简单的守护进程来监控服务的运行情况,shell脚本如下: #!/bin/sh function daemon() { while true do server=` ...

随机推荐

  1. chrome、safari中的input或textarea

    1.去掉chrome.safari input或textarea在得到焦点时出现黄色边框的方法 input{ outline:0;} 2.去掉chrome.safari textarea右下角可拖动鼠 ...

  2. Node.js:回调函数

    概要:本篇博客主要通过对比node.js异步与同步方式读取文件的方式来解释node中回调函数的作用. 1.异步方式读取文件: 新建一个文本文档用于测试,如下图: 代码如下: // node异步方式读取 ...

  3. 关于JavaScript的小笔记

    1.当 JavaScript 中的变量被声明的时候,程序内部会给它一个初始值 undefined.当你对一个值为 undefined 的变量进行运算操作的时候,算出来的结果将会是 NaN,NaN 的意 ...

  4. yum安装memcache,mongo扩展以及python的mysql模块安装

    //启动memcached/usr/local/memcached/bin/memcached -d -c 10240 -m 1024 -p 11211 -u root/usr/local/memca ...

  5. Leetcode015 3Sum

    public class S015 { public List<List<Integer>> threeSum(int[] nums) { Arrays.sort(nums); ...

  6. javascript点击焦点图

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  7. Python笔记3-20151027

    函数的参数 Python的函数定义非常简单,但是灵活度却非常大.除了正常定义的必选参数外,还可以使用默认参数.可变参数和关键字参数,使得函数定义出来的接口,不但能处理复杂的参数,还可以简化调用者的代码 ...

  8. spring AOP 代理机制、执行过程、四种实现方式及示例详解

    1.加载过程 spring首先检测配置文件中的代理配置,然后去加载bean; 如果配置文件中没有配置代理,自然代理不会生效,如果配置了代理,但是代理还没有生效,那么有可能是加载顺序的问题,即在检测到代 ...

  9. mac中eclipse安装openExplore插件

    插件地址:https://github.com/samsonw/OpenExplorer/downloads 将下载的jar包放入下面地址:应用程序->eclipse.app,右击->打开 ...

  10. HDU 3338 Kakuro Extension

    网络最大流 TLE了两天的题目.80次Submit才AC,发现是刘汝佳白书的Dinic代码还可以优化.....瞬间无语..... #include<cstdio> #include< ...