`

守护进程编写规则与出错记录

阅读更多
    在编写守护进程程序时需要遵循以下一些基本规则,以防产生不必要的交互作用:
    (1)应先调用 umask 将文件模式创建屏蔽字设置为一个已知值(通常是 0),因为由继承得来的文件模式屏蔽字可能会被设置为拒绝某些权限。而另一方面,如果守护进程调用的库函数创建了文件,那么将文件模式创建屏蔽字设置为一个限制性更强的值(如 007)可能更明智,因为库函数可能不允许调用者通过一个显示的函数参数来设置权限。
    (2)调用 fork,然后使父进程 exit。这就保证了子进程不是一个进程组的组长进程,这是下一步调用 setsid 的先决条件。
    (3)调用 setsid 创建一个新会话,这会使调用进程:a、成为新会话的首进程,b、成为一个新进程组的组长进程,c、没有控制终端(在基于 System V 的系统中,有人建议在此时再次调用 fork,终止父进程,这就保证了该守护进程不是会话首进程,于是按照 System V 规则可以防止它取得控制终端。另外,打开一个终端设备时指定 O_NOCTTY 标志也可以避免取得控制终端)。
    (4)将当前工作目录更改为根目录。从父进程继承的当前工作目录可能在一个挂载的文件系统中。因为守护进程通常在系统引导之前是一直存在的,所以如果守护进程的当前工作目录在一个挂载文件系统中,那么该文件系统就不能被卸载(不过也有些守护进程可能会把当前工作目录更改到某个指定位置来进行全部工作,例如行式打印机假脱机守护进程就可能将其工作目录更改到它们的 spool 目录上)。
    (5)关闭不需要的文件描述符。这使守护进程不再持有从父进程继承来的文件描述符。可以使用 open_max 函数或 getrlimit 函数来判定最高文件描述符,并关闭直到该值的所有描述符。
    (6)某些守护进程打开 /dev/null 使其具有文件描述符 0、1 和 2,这样任何一个试图读标准输入、写标准输出或标准错误的库例程都不会产生任何效果。
    守护进程存在的一个问题是如何处理出错消息。因为它本就不应该有控制终端,所以不能只是简单地写到标准错误上。而把每个守护进程的错误消息写到一个单独的文件中管理起来又比较麻烦,因此需要有一个集中的守护进程出错记录设施,这也是 syslog 设施得以广泛应用的原因。下图显示了 syslog 设施的详细组织结构。

    从图中可见,有以下 3 中产生日志消息的方法。
    (1)内核例程调用 log 函数。任何一个用户进程都可以通过打开 /dev/klog 设备来读取这些消息。
    (2)大多数用户进程(守护进程)调用 syslog 函数来产生日志消息,这使消息被发送至 UNIX 域数据报套接字 /dev/log。
    (3)无论一个用户进程是在此主机上,还是在通过 TCP/IP 网络连接到此主机的其他主机上,都可将日志消息发向 UDP 的 514 端口(syslog 函数从不产生这些 UDP 数据报,它们要求产生此日志消息的进程进行显示的网络编程)。
    通常 syslogd 守护进程读取所有这 3 种格式的日志消息,它在启动时读一个一般叫 /etc/syslog.conf 的配置文件,该文件决定了不同种类的消息应发送向何处。
    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 maskprl);      /* 返回值:当前日志记录优先级屏蔽字值 */

    其中,调用 openlog 是可选的,如果没调用,则在第一次调用 syslog 时会自动调用。closelog 调用也是类似,因为它只是关闭曾被用于与 syslogd 守护进程进行通信的描述符。
    调用 openlog 时可以指定一个 ident(一般是程序的名称,如 cron 等),它会被加到每则日志消息中。option 参数用于指定各种选项的位屏蔽,下表介绍了可用的 option。

    设置 openlog 的 facility 参数可以让配置文件说明,来自不同设施的消息将以不同的方式进行处理。如果不调用 openlog,或者设置 facility 为 0,那么在调用 syslog 时,可将 facility 作为 priority 参数的一部分来进行说明。下表是 facility 的可选值。

    调用 syslog 产生一个日志消息,其 priority 参数是 facility 和 level 的组合。其中 facility 的取值见上图,而 level 值按优先级从高到低排列如下表。

    syslog 的 format 参数以及其他所有参数都会被传至 vsprintf 函数以便格式化。在该参数中出现的每个 %m 字符都会代换成与 errno 值对应的出错消息字符串(strerror)。
    setlogmask 函数用于设置进程的记录优先级屏蔽字,它返回调用它之前的屏蔽字。当设置了记录优先级屏蔽字时,各条消息除非已在记录优先级屏蔽字中进行了设置,否则将不被记录(注意,试图将记录优先级屏蔽字设置为 0 并不会有什么作用)。
    大多数 syslog 实现将使消息短时间处于队列中,如果在此段时间中有重复消息到达,syslog 守护进程不会把它写到日志记录中,而是会打印输出一条类似于“上一条消息重复了 N 次”的消息。
    此外,很多系统也将 logger 程序作为向 syslog 设施发送日志消息的方法,logger 命令是专门为以非交互方式运行的需要产生日志消息的 shell 脚本设计的。
    除了 syslog,很多平台还提供它的一种变体 vsyslog 来处理可变参数列表,不过如果要使它的声明对应用程序可见,可能需要定义一个额外的符号,例如,FreeBSD 中要定义 __BSD_VISIBLE,Linux 中要定义 __USE_BSD。
#include <syslog>
#include <stdarg.h>
void vsyslog(int priority, const char *format, va_list arg);

    根据以上所述,下面提供了一个可由一个想要初始化为守护进程的程序调用的实例。
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <syslog.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/resource.h>

void daemonize(const char *cmd){
	/* Clear file creation mask. */
	umask(0);

	/* Become a session leader to lose controlling TTY. */
	pid_t	pid;
	if((pid=fork()) < 0){
		printf("%s: can't fork\n", cmd);
		exit(1);
	}else if(pid != 0)		// parent
		exit(0);
	setsid();
	
	/* Ensure future opens won't allocate controlling TTYs. */
	struct sigaction	sa;
	sa.sa_handler = SIG_IGN;
	sigemptyset(&sa.sa_mask);
	sa.sa_flags = 0;
	sigaction(SIGHUP, &sa, NULL);
	if((pid=fork()) < 0){
		printf("%s: can't fork\n", cmd);
		exit(1);
	}else if(pid != 0)		// parent
		exit(0);

	/* Change the current working directory to the root. */
	chdir("/");

	/* Close all open file descriptors. */
	struct rlimit	rl;
	getrlimit(RLIMIT_NOFILE, &rl);	// maximum number of file descriptors
	if(rl.rlim_max == RLIM_INFINITY)
		rl.rlim_max = 1024;
	int i;
	for(i=0; i<rl.rlim_max; i++)
		close(i);

	/* Attach file descriptors 0, 1 and 2 to /dev/null. */
	int fd0 = open("/dev/null", O_RDWR);
	int fd1 = dup(0);
	int fd2 = dup(0);

	/* Initialize the log file. */
	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(void){
	daemonize("myDeamon");
	sleep(60);
	exit(0);
}

    运行结果如下:
$ ./daemonize.out
$ ps -efj | grep daemonize
UID         PID   PPID   PGID    SID  TTY          CMD
lei       94709      1  94708  94708    ?       ./daemonize.out

    可见,该守护进程被正确地初始化了,它已没有控制终端,第二个 fork 也使其不再是会话首进程。
  • 大小: 8.2 KB
  • 大小: 23.7 KB
  • 大小: 27.6 KB
  • 大小: 9.1 KB
分享到:
评论

相关推荐

    Qt编写守护进程

    本篇文章提供一个软件实现守护进程的办法,原理就是udp通信,单独写个守护进程程序,专门负责检测主程序是否存在,不存在则启动。主程序只需要启动live类监听端口,收到hello就回复ok就行。 为了使得兼容任意程序,...

    调用系统VBS对进程进行保护

    此外,可以使用.NET Framework的内置函数进行字符串编码转换,以保证与VBS之间的数据传递不会出错。 综上所述,这个场景涉及到的知识点包括: 1. Visual Basic Script (VBS)及其在系统管理中的应用。 2. C#编程...

    进程监护程序(实现进程的管理)

    进程监护程序,也被称为进程监控器或守护进程,是计算机系统中用于管理和控制其他进程的重要工具。在操作系统中,进程是程序的执行实例,而进程监护程序的主要职责就是确保特定的进程按照预定的规则运行,如定时开启...

    delphi 实现进程自动监控、重启,非控件实现托盘程序

    如果找不到,可能意味着进程已关闭或者出错。 3. **事件处理**:当检测到目标进程不在运行时,你可以设置一个事件处理程序来执行重启操作。这通常涉及使用`CreateProcess`或`ShellExecute`函数来启动新的进程实例。...

    更改IP及守护程序

    这个程序的主要功能可以分为两部分:IP地址的修改和守护进程(守护程序)的管理。 首先,让我们深入理解IP地址更改这一功能。IP地址是互联网协议地址的缩写,是每个设备在网络上的唯一标识。在某些情况下,例如测试...

    Delphi禁止系统或指定程序自动关闭/退出.rar

    标签"Delphi源码-系统相关"表明这个程序与系统操作紧密相关,可能涉及到系统进程管理、线程控制、系统权限等复杂领域。Delphi的源码可以帮助学习者理解如何使用Pascal语言和Delphi的组件来实现这些系统级功能,对于...

    FreeBSD_rc.d脚本程序_中文手册

    - **注意事项**:在设计脚本时需要注意处理好守护进程的状态检查、日志记录等细节,确保服务的可靠性和稳定性。 #### 6. 启动并停止高级守护进程 - **高级功能**:针对更加复杂的守护进程,可能需要处理多个进程的...

    学习《高级Linux环境编程》读书笔记(APUE读书笔记)

    这里会介绍守护进程的设计规则、系统日志机制、daemon函数以及守护进程设计中的一些常见惯例。 14. 高级I/O:包括记录锁、SysVSTREAMS机制、非阻塞I/O、I/O多路转接、异步I/O、readv和writev函数、存储映射I/O等。 ...

    嵌入式Linux应用程序开发标准教程(第2版全)

    7.3.3 守护进程的出错处理 7.4 实验内容 7.4.1 编写多进程程序 7.4.2 编写守护进程 7.5 本章小结 7.6 思考与练习 第8章 进程间通信 8.1 Linux下进程间通信概述 8.2 管道 8.2.1 管道概述 8.2.2 管道系统调用 8.2.3 ...

    APUE读书笔记(Unix高级环境编程)

    - **设计良好的守护进程的规则**:包括脱离终端、成为会话领导等步骤。 - **系统日志机制**:使用`syslog`函数记录日志信息。 - **daemon(3)函数**:帮助编写守护进程的实用函数。 - **守护进程设计的惯例**:例如...

    嵌入式Linux应用程序开发详解

    7.3.3 守护进程的出错处理 229 7.4 实验内容 232 7.4.1 编写多进程程序 232 7.4.2 编写守护进程 235 本章小结 238 思考与练习 239 第8章 进程间通信 240 8.1 Linux下进程间通信概述 240 8.2...

    linux大作业报告

    现要求用户编写一个守护进程应用程序,定时向日志文件写入字符串。 4、编写程序,在不同的进程间实现信号发送和接收,同时在传达过程中附加其他信息。 5、编写程序,使用alarm函数在系统中设置一个定时器,期间对...

    Linux系统性能的远程监控.pdf

    在Linux系统中,可以通过编写守护进程(daemon)程序,该程序在后台持续运行,收集性能数据并通过网络发送到远程客户端。客户端可以解析这些数据,展示实时性能指标,并进行趋势分析。 3. 守护进程设计 守护进程...

    APUE读书笔记《UNIX环境高级编程第二版》

    设计一个良好的守护进程的一般编程规则** - 守护进程应该脱离终端,重新打开文件描述符。 **2. 系统日志机制** - syslog()函数用于向系统日志记录消息。 **3. daemon(3)函数** - `daemon()`简化守护进程的创建...

    vagga:Vagga是一个没有守护程序的容器化工具

    Vagga是一个创新的容器化工具,主要设计用于Linux系统,其特点是不依赖任何守护进程,这与传统的Docker等容器技术有所不同。Vagga由Rust编程语言编写,提供了安全、轻量级且可重复的开发环境构建方式。下面我们将...

    Linux信号列表详解.docx

    此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。 2. SIGINT(编号2) SIGINT信号是程序终止(interrupt)信号,在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止...

    oracle 日常操作维护

    如果`/etc/inittab`中的配置出错或者缺失,守护进程可能无法正确启动。可以通过以下命令检查这些守护进程是否在运行: ```shell ps -ef | grep evmd ps -ef | grep cssd ps -ef | grep crsd ``` 其中,`evmd...

Global site tag (gtag.js) - Google Analytics