Linux守护进程编程

Linux守护进程

1. 守护进程简介

守护进程(Daemon)是Linux三种进程类型之一,是后台服务进程,始终在后台运行。通常在系统启动时运行,系统关闭时结束。守护进程独立于任何终端,周期性的执行某种任务或等待处理特定事件。
Linux以会话(session)、进程组的方式管理进程,每个进程属于一个进程组。会话数一个或多个进程组的集合,通常用户打开一个终端时,系统会创建一个会话。所有通过该终端运行的进程都属于这个会话。终端关闭时,所有相关进程会被结束。但是守护进程却能突破这种限制,不受终端关闭的影响。

2. 编写守护进程

守护进程的创建分为五个步骤

2.1 创建子进程,父进程退出

父进程创建子进程后退出,子进程变成孤儿进程后被1号进程(init进程)收养,子进进入后台运行

pid = fork();
if(pid > 0){
    
    
	exit(0);	//父进程退出
}
2.2 子进程创建新会话
  • 进程组:是一个或过个进程的集合。由进程组ID来唯一表示。除了进程号(PID)之外,进程组也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID,且进程组ID不会因组长进程的退出而受到影响
  • 会话期:是一个或多个进程组的集合。一个会话开始于终端打开,结束于终端关闭。会话期的第一个进程成为会话组长。在此期间该用户运行的所有进程都属于这个会话期,它们之间的关系如下图所示

进程组和会话期之间的关系
这个步骤使用的函数是setsid(),该函数用于创建一个新的会话,并担任该会话组长。有三个作用:让进程摆脱原会话的控制;让进程摆脱原进程组的控制;让进程摆脱原控制终端的控制;

/*****setsid()函数*****/
函数原型: pid_t setsid(void)
函数返回值:成功返回该进程组ID;失败返回-1

子进程创建新会话,子进程成为新的会话组长,摆脱原先的终端

if(setsid() < 0){
    
    
	exit(-1);
}
2.3 改变当前目录

子进程继承了父进程的当前工作目录,由于守护进程一直在后台运行,其工作目录不能被卸载,因此需要改变工作目录。通常让根目录作为守护进程的当前工作目录。
使用 chdir() 函数,例如 chdir("/tmp")

2.4 重设文件权限掩码

子进程继承了父进程的文件权限掩码,给该子进程使用文件带来一定的影响,因此把文件权限掩码设置为0,可以增强该守护进程的灵活性。
使用 umask() 函数,例如 umask(0)

2.5 关闭文件描述符

子进程继承了父进程的一些已经打开了的文件,这些被打开的文件可能永远不会被守护进程访问,但它们一样占用系统资源,而且还可能导致所在的文件系统无法被卸载。
使用 close() 函数,如下示例

int num;
num = getdtablesize();	//获取当前进程文件描述符表大小
for(i = 0;i < num;i++){
    
    
	close(i);
}

创建守护进程实例

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/stat.h>

int main()
{
    
    
	pid_t pid;
	int i, fd;
	char *buf = "This is a daemon\n";

	pid = fork();	//第一步
	if(pid < 0){
    
    
		printf("Error fork\n");
		exit(1);
	}
	else if(pid > 0){
    
    
		exit(0);	//父进程退出
	}

	setsid();	//第二步
	chdir("/tmp");	//第三步
	umask(0);	//第四步
	for(i = 0;i < getdtablesize();i++){
    
    	  //第五步
		close(i);
	}
	/*这时创建完守护进程,以下开始正式进入守护进程工作*/
	while(1){
    
    
		if((fd = open("daemon.log",O_CREAT|O_WRONLY|O_TRUNC,0600)) < 0){
    
    
			printf("OPen file error\n");
			exit(1);
		}
		write(fd,buf,strlen(buf));
		close(fd);
		sleep(2);
	}
	exit(0);
}

编译并执行,使用tail -f /tmp/daemon.log可以看到每隔2s就会在对应的文件中写入字符串。使用ps命令可以看到该进程在后台运行

linux@linux-virtual-machine:~/andy/proc$ tail -f /tmp/daemon.log
This is a daemon
...
linux@linux-virtual-machine:~/andy/proc$ ps -ef|grep daemon
linux     3184  2001  0 13:01 ?        00:00:00 ./daemon
linux     3247  2001  0 13:04 ?        00:00:00 ./daemon
linux     3250  2667  0 13:05 pts/0    00:00:00 grep --color=auto daemon

3. 守护进程的出错处理

守护进程由于完全脱离了控制终端,因此不能像普通进程一样将错误信息输出到控制终端。一般情况下,守护进程通过使用 syslog 服务来调试,将程序中的出错信息输入到系统日志文件中(如"/var/log/messages"),从而可以直观地看到程序问题所在。
syslog 是Linux中的系统日志管理服务,通过守护进程 syslogd 来维护。该守护进程在启动时会读一个配置文件"/etc/syslog.conf",该文件觉得了不同种类的消息会发送向何处。例如紧急消息可被送向系统管理员并在控制台上显示,而警告消息则可被记录到一个文件中。
该机制提供了3个 syslog 相关函数,分别为:
openlog():打开系统日志服务的一个连接
syslog():向日志文件中写入消息,可规定消息的优先级、消息输出格式等
closelog():关闭系统日志服务的连接

/************openlog()函数************/
函数原型:void openlog(char *ident, int option, int facility)
传入值:ident 要向每个消息加入的字符串,通常为程序的名称
	   option 
	   -->LOG_CONS 如果消息无法送到系统日志服务,则直接输出到系统控制终端
	   -->LOG_NDELAY 立即打开系统日志服务的连接,一般直接发送第一条消息时才打开连接
	   -->LOG_PERROR 将消息也同时送到stderr-->LOG_PID 在每条消息中包含进程的PID
	   facility 指定程序发送的消息类型
	   -->LOG_AUTHPRIV 安全/授权信息
	   -->LOG_CRON 时间守护进程(cron及at)
	   -->LOG_DAEMON 其他守护进程
	   -->LOG_KERN 内核信息
	   -->LOG_LOCAL[0~7] 保留
	   -->LOG_LPR 行打印机子系统
	   -->LOG_MAIL 邮件子系统
	   -->LOG_NEWS 新闻子系统
	   -->LOG_SYSLOG syslogd内部所产生的信息
	   -->LOG_USER 一般使用者等级信息
	   -->LOG_UUCP UUCP子系统
/************syslog()函数************/
函数原型:void syslog(int priority, char *format,...)
传入值:priority 
	   -->LOG_EMERG 系统无法使用
	   -->LOG_ALERT 需要立即采取措施
	   -->LOG_CRIT 有重要情况发生
	   -->LOG_ERR 有错误发生
	   -->LOG_WARNING 有警告发生
	   -->LOG_NOTICE 正常情况,但也是重要情况
	   -->LOG_INFO 信息消息
	   -->LOG_DEBUG 调试信息
	   format 以字符串指针的形式表示输出的格式,类似于printf中的格式
/************closelog()函数************/
函数原型:void closelog(void)

猜你喜欢

转载自blog.csdn.net/Chuangke_Andy/article/details/108303020