一、守护进程创建
Daemon(守护进程)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。它不需要用户输入就能运行而且提供某种服务,不是对整个系统就是对某个用户程序提供服务。Linux系统的大多数服务器就是通过守护进程实现的。常见的守护进程包括系统日志进程syslogd、 web服务器httpd、邮件服务器sendmail和数据库服务器mysqld等。
守护进程一般在系统启动时开始运行,除非强行终止,否则直到系统关机都保持运行。守护进程经常以超级用户(root)权限运行,因为它们要使用特殊的端口(1-1024)或访问某些特殊的资源
一个守护进程的父进程是init进程,它是一个孤儿进程,没有控制终端,所以任何输出,无论是向标准输出设备stdout还是标准出错设备stderr的输出都被丢到了/dev/null中。守护进程一般用作服务器进程,如httpd,syslogd等。在编写守护进程时,需要遵守一定的规则:
(1)首先将该进程的屏蔽字通过umask()函数重置为已知值,这样确保在守护进程中不会因为继承而来的屏蔽字,导致进程的一些文件操作被拒绝。
(2)调用fork(),然后使父进程退出。这样做的目标有以下几个原因:a、如果父进程是一个shell命令,那么这样做就可以让系统以为程序已经执行完毕,此时剩下的子进程将会在后台运行。b、这样做也保证了守护进程不是一个进程组的组长,这是下面要进行的setsid()的前提条件。
(3)调用setsid()创建一个新会话。进程调用 setsid()函数会:
首先请注意:只有当该进程不是一个进程组长时,才会成功创建一个新的会话期。
(a)摆脱原会话的控制。该进程变成新会话期的首进程
(b)摆脱原进程组。成为一个新进程组的组长
(c)摆脱终端控制。如果在调用 setsid() 前,该进程有控制终端,那么与该终端的联系被解除。 如果该进程是一个进程组的组长,此函数返回错误。
(4)将当前工作目录更改为根目录
(5)、关闭不再需要的文件描述符。
守护进程退出处理
当用户需要外部停止守护进程运行时,往往会使用 kill 命令停止该守护进程。所以,守护进程中需要编码来实现 kill 发出的signal信号处理,达到进程的正常退出。
代码示例:
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
static int flag = 1;
void create_daemon()
{
pid_t pid;
//设置屏蔽字
umask(0);
//创建子进程
pid = fork();
if(pid == -1)
{
printf("fork error\n");
exit(1);
}
else if(pid)//父进程退出
{
exit(0);
}
//建立新会话,摆脱原会话的控制
if(-1 == setsid())
{
printf("setsid error\n");
exit(1);
}
//此处再次fork,确保子进程被init进程回收
pid = fork();
if(pid == -1)
{
printf("fork error\n");
exit(1);
}
else if(pid)//退出父进程
{
exit(0);
}
//修改进程工作目录为根目录
chdir("/");
//关闭所有打开的文件,这里只有标准出错、标准输入、标准输出
for(int i = 0; i < 3; ++i)
{
close(i);
}
return;
}
//信号处理函数,设置标志位,结束进程
void handler(int sig)
{
printf("I got a signal %d\nI'm quitting.\n", sig);
flag = 0;
}
int main()
{
time_t t;
int fd;
//守护进程创建
create_daemon();
//设置信号SIGQUIT的处理函数
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGQUIT, &act, NULL))
{
printf("sigaction error.\n");
exit(0);
}
while(flag)
{
//守护进程工作内容
fd = open("/home/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(fd == -1)
{
printf("open error\n");
}
t = time(0);
char *buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
close(fd);
sleep(60);
}
return 0;
}
注意守护进程一般需要在 root 权限下运行。
通过ps -ef | grep ‘daemon’ ,可以看到:
root 26454 2025 0 14:20 ? 00:00:00 ./daemon
并且产生了 daemon.log,里面是这样的时间标签
Thu Dec 8 14:35:11 2016
Thu Dec 8 14:36:11 2016
Thu Dec 8 14:37:11 2016
最后我们想退出守护进程,只需给守护进程发送 SIGQUIT 信号即可
sudo kill -3 26454
再次使用 ps 会发现进程已经退出。
拓展:利用库函数daemon()创建守护进程
#include <unistd.h>
int daemon(int nochdir, int noclose);
参数:nochdir:=0将当前目录更改至“/”
noclose:=0将标准输入、标准输出、标准错误重定向至“/dev/null”
返回值:成功:0失败:-1
作用:创建一个守护进程
代码示例:
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <time.h>
#include <stdio.h>
static bool flag = true;
void handler(int sig)
{
printf("I got a signal %d\nI'm quitting.\n", sig);
flag = false;
}
int main()
{
time_t t;
int fd;
//创建守护进程
if(-1 == daemon(0, 0))
{
printf("daemon error\n");
exit(1);
}
//设置信号处理函数
struct sigaction act;
act.sa_handler = handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if(sigaction(SIGQUIT, &act, NULL))
{
printf("sigaction error.\n");
exit(0);
}
//进程工作内容
while(flag)
{
fd = open("/home/mick/daemon.log", O_WRONLY | O_CREAT | O_APPEND, 0644);
if(fd == -1)
{
printf("open error\n");
}
t = time(0);
char *buf = asctime(localtime(&t));
write(fd, buf, strlen(buf));
close(fd);
sleep(60);
}
return 0;
}
二、守护进程出错处理
syslog服务:专门用来提供记录日志功能的。可以把每一个程序理解为子系统。
syslog有两个进程:
syslogd(sys:系统):专门负责记录非内核的其它设施产生的日志;
klogd(k:内核):专门负责记录内核产生的日志;
syslog日志系统的配置文件:/etc/syslog.conf(每一行用来指定每一个子系统产生的哪个级别的日志记录到什么位置上去)格式如下:
配置文件定义格式为: facility.priority action
说明:
facility:可以理解为日志的来源或设备:
priority:(log level)日志的级别:
action:日志记录的位置,action前若加-表示异步写入
Linux C中提供一套系统日记写入接口,包括三个函数:openlog,syslog和closelog。
调用openlog是可选择的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。调用closelog也是可选择的,它只是关闭被用于与syslog守护进程通信的描述符。调用openlog是可选择的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。调用closelog也是可选择的,它只是关闭被用于与syslog守护进程通信的描述符。
#include //头文件
void openlog (char*ident, int option, int facility); //ident将被加到每条日志信息中去,option指定各种选项的位屏蔽
void syslog(int priority, char*format,……);
void vsyslog(int priority, const char*format,va_list arg);//支持可变参数
void closelog();
例如:syslog(LOG_ERR|LOG_USER,"test - %d",5);