守护进程-----也就是常说的Daemon进程,是linux后台的服务进程,是一个生存期较长的进程,独立于终端并且周期性的执行某种任务或等待处理某些任务。守护进程在系统引 导时载入时启动,在系统关闭时终止。linux下的许多系统服务都是守护进程完成的。守护进程还能完成许多系统任务,如作业规划进程crond,打印进程lqd(d就是 daemon的意思)
编写守护进程主要有五个步骤:
1.创建子进程,父进程退出
守护进程需要脱离shell终端,造成一种程序关闭的假象,所有的工作都在子进程中完成,创建子进程,父进程退出,会造成子进程成为一个孤儿进程,在linux中当系统发现一个孤儿进程,就会1号进程(init进程)收养它,这样子进程就变成init子进程了。代码如下:
pid = fork() ;
if (pid > 0)
exit(0) ;//父进程退出
2.在子进程中创建新会话
这一步实现简单,但很重要,先了解两个概念
进程组-------进程组就是多个进程,进程组由进程组ID标识,进程组ID 也是一个进程的必备属性,每个进程组都有一个组长进程,组长进程的ID等于进程组的ID,进程ID不会因为 组长进程的退出而受到影响
会话期-------会话组是多个进程组组成的,一个会话开始于用户登录,终止于用户退出,在此期间用户运行的所有进程都属于这个会话期,关系如下:
setsid()-------创建一个新的会话,并担任会话组的组长,具体有三个作用
(1)让进程摆脱原会话的控制
(2)让进程原进程组的控制
(3)让进程摆脱原控制终端的控制
ps:虽然创建子进程父进程退出了,但子进程复制了父进程的会话期,进程组和控制终端,所以要让子进程完全的独立出来就需要调用setsid()函数,摆脱所有其他进程的控制
setsid()语法如下:
3.改变当前目录为根目录
由于子进程继承了父进程的当前工作目录,由于在进程运行过程中当前工作目录的文件系统(如”/mnt/usb“)是不能卸载的,这回给以后使用造成麻烦(比如系统需要进入单用户模式),所以通常的做法是让"/"作为当前目录,当然也可以把当前目录换成其它目录,如/tmp,使用函数chdir()即可。
4.重设文件权限掩码
文件权限掩码是指屏蔽掉文件权限中的对应位,比如一个文件权限掩码是050它就屏蔽了文件拥有者得可读与可写权限,fork()创建的子进程会继承父进程的文件权限掩码,所以我们把文件权限掩码设为0,可以增加子进程的灵活性,使用umask(0) 即可。
5.关闭文件描述符,同文件权限掩码一样,子进程会进程父进程的一些已经打开的文件,这些打开的文件可能永远都不会被守护进程读与写,但它会占用系统资源,而且会导致所在文件系统无法卸载,在上面第二步之后子进程就会与shell终端脱离,所以文件描述符为0, 1, 2的三个文件就失去了价值,所以也应当关闭通常使用以下方式:
for(i = 0; i < MAXFILE; i++)
close(i);
这样一个守护进程就建立起来了,具体过程如下
这里有个例子,这个守护进程每隔10s向/tmp/daemon.log写入一句话
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
int main()
{
pid_t pid ;
int i, fd ;
char *buf = "This is a daemon\n" ;
pid = fork() ;//第一步,创建子进程
if(pid < 0)
{
printf("fork error\n") ;
exit(1) ;
}
else if (pid > 0)
exit(0) ;
setsid() ;//第二步,创建新会话
chdir("/") ;//改变当前工作目录为跟目录
umask(0) ;//设置文件权限掩码
for(i = 0; i < getdtablesize(); ++i)
{
close(i) ;//关闭文件权限描述符
}
//守护进程每隔10s打日志
while(1)
{
if (fd = open("/tmp/daemon.log", O_CREAT|O_WRONLY|O_APPEND, 0600) < 0)
{
printf("open file error\n") ;
exit(1) ;
}
write(fd, buf, strlen(buf)+1) ;
close(fd) ;
sleep(10) ;
}
exit(0) ;
}
我们可以使用tail -f /tmp/daemon.log或者直接到目录下查看,运行如下
*********************************************
守护进程由于完全脱离终端,即使使用gdb也是无法调试的,一种通用方法是使用syslog服务,将程序的出错信息输入到系统的日志文件中(如:/var/log/messages)从而可以直观的看到程序的问题所在
syslog是linux系统日志管理服务,通过守护进程syslogd维护,进程在启动时会读取配置文件"/etc/syslog.conf"该文件决定了不同种类的消息送向何处,如紧急消息会送向系统管理员并在控制台上显示,而警告信息会记录在一个文件中
这个机制会提供三个syslog相关的函数openlog(),syslog(),closelog()------openlog()函数用于打开系统日志服务的一个链接,syslog()用于向日志文件中写入信息其中会规定消息的等级,消息输出格式等,closelog()用于关闭日志服务
以下是openlog()的语法:
以下是syslog()的语法:
closelog()函数语法:
下面是使用syslog系统调试的例子:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <syslog.h>
int main()
{
pid_t pid ;
int i, fd ;
char *buf = "This is a daemon\n" ;
pid = fork() ;
if(pid < 0)
{
printf("fork error\n") ;
exit(1) ;
}
else if (pid > 0)
exit(0) ;
openlog("daemon_syslog", LOG_PID, LOG_DAEMON) ;
if(setsid() < 0 )
{
syslog(LOG_ERR, "%s", "setsid") ;
exit(1) ;
}
if (chdir("/") < 0)
{
syslog(LOG_ERR, "%s", "chdir") ;
exit(1) ;
}
umask(0) ;
for(i = 0; i < getdtablesize(); ++i)
{
close(i) ;
}
while(1)
{
if (fd = open("/tmp/daemon.log", O_CREAT|O_WRONLY|O_APPEND, 0600) < 0)
{
printf("open file error\n") ;
exit(1) ;
}
syslog(LOG_ERR, "%s", "open\n") ;
write(fd, buf, strlen(buf) + 1) ;
close(fd) ;
sleep(10) ;
}
closelog() ;
exit(0) ;
}
程序会将错误信息写入系统日志里面,open函数必须具有root权限,系统才会将错误信息写入系统日志/var/log/messages,这个日志必须要在root下查看,普通用户查看不到的