守护进程
1什么叫做守护进程
守护进程一般在系统引导装入的时候启动,在系统关闭的时候关闭,因为他们没有控制终端,所以说他们是后台运行的,用来执行一些日长的任务
利用ps -axj可以查看,其中a是查看其他的用户所拥有的进程,x显示没有控制终端的进程状态,-j显示与作业有关的信息,主要是第一行的信息
这个ps命令要正确 的打印,系统必须支持会话,否则不能正确的显示,父进程是0的通常是内核进程,内核进程的生命周期存在于系统的整个生命周期中,他们以超级用户权限运行,没有控制终端,不能使用命令行
如何判断是否是守护进程,当TPGID=-1的时候就是守护进程,comman命令加了[]就是系统级的守护进程,没有加[]就是用户级的守护进程,要注意,init并不是一个内核级的守护进程,他是用户级的守护进程的父进程
我们如何判定是否使用内核守护进程:我们使用一个内核组件,却不被用户进程的上下文所调用,并且是常驻内存,我们就应该使用内核守护进程
使用守护进程的大体原则:
如果一个进程永远都是以后台方式启动,并且不能受到Shell退出影响而退出,一个正统的做法是将其创建为守护进程(daemon)。守护进程值得是系统长期运行的后台进程,类似Windows服务。守护进程信息通过ps –a无法查看到,需要用到–x参数,当使
用这条命令的时候,往往还附上-j参数以查看作业控制信息,其中TPGID一栏为-1就是守护进程。
2对于守护进程的编程规则
#include "apue.h"
#include <syslog.h>
#include <fcntl.h>
#include <sys/resource.h>
void daemonize(const char *cmd)
{
int i,fd0,fd1,fd2;
pid_t pid;
struct rlimit r1;
struct sigaction sa;
umask(0);
if(getrlimit(RLIMIT_NOFILE,&r1) < 0)
err_quit("%s:can't get file limit",cmd);
if((pid = fork())<0)
err_quit("%s:can't fork",cmd);
else if(pid != 0)
exit(0);
setsid();
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGHUP,&sa,NULL) < 0)
err_quit("%s:can't ignore SIGHUP",cmd);
if((pid = fork()) < 0)
err_quit("%s:can't fork",cmd);
else if(pid != 0)
{
exit(0);
}
if(chdir("/") < 0)
err_quit("%s:can't change directory to /",cmd);
if(r1.rlim_max == RLIM_INFINITY)
r1.rlim_max = 1024;
for(i = 0; i < r1.rlim_max;i++)
close(i);
fd0 = open("/dev/null",O_RDWR);
fd1 = dup(0);
fd2 = dup(0);
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(0);
}
}
int main()
{
daemonize("my ");
sleep(10);
return 0;
}
将一个进程变成一个守护进程的方法
(1)首先应该改变的是文件模式屏蔽字,因为在原本的进程上下文中,文件屏蔽字是不一定符合规范的,而且守护进程一般需要创建一个具有特定要求的屏蔽字,所以应该自己设置一个合理的屏蔽字,如果有更加严格的要求一般将文件模式设置为007
(2)调用fork,进行创建一个新的子进程,为什么要这么做第一,如果在shell中这样启动一个程序,当父进程exit的时候,会让shell认为此程序已经执行完毕第二,这样保证了这个进程不是一个子进程,这是使用setsid的先决条件
(3)调用setsid来进行设置一个新的会话,在创建会话的时候为了保证不分配一个控制终端,通常会在fork一次,这样就一定这个进程不是控制终端进程(其实在第一次fork的时候,并且使用了setsid的时候就确定了一定不是控制终端)这样做其实是为了更加确定(可以这样理解)
(4)更改当前的工作目录成为根目录,因为如果不更改为根目录,本来工作的目录其实就要一直被挂载,这在某些系统中,显然是这样做到,因为有可能这个操作系统会被删除
(5)进行所有的文件描述符的关闭
(6)某些守护进程打开/dev/null等,使其映射到0,1,2
关于程序的运行的结果显示
3守护进程出错记录
首先我们进行syslog的介绍
syslog是一种工业标准的协议,可用来记录设备的日志。在UNIX系统,路由器、交换机等网络设备中,系统日志(System Log)记录系统中任何时间发生的大小事件。管理者可以通过查看系统记录,随时掌握系统状况。UNIX的系统日志是通过syslogd这个进程记录系统有关事件记录,也可以记录应用程序运作事件。通过适当的配置,我们还可以实现运行syslog协议的机器间通信,通过分析这些网络行为日志,藉以追踪掌握与设备和网络有关的状况。
因为守护进程是没有控制终端的,所以一般不能要求守护进程将出错记录写到控制台上,当然也不能写到一个文件中,如果每个守护进程都写到一个文件中,则无疑再用的时候是很麻烦的
这是日志系统的主要设施,这个设施被称为syslog设施,与用户进程调用syslog不是一个东西,三种可以产生日志的方法
(1)用户守护进程调用syslog函数产生日志消息,这些消息被送往UNIX域数据报套接字(这是一个设备,目前先了解为就是一个设施)然后接下来syslog是不产生UDP数据报的,要注意
(2)通过tcp/ip网络在或者不在此系统上的进程发送消息到UDP端口54
(3)内核通过log发送到klog
其中syslogd这个守护进程读取这三种格式的日志消息,并且根据/etc/syslog.conf发送,该文件决定了不同种类的消息应该送向何处,列如紧急消息可送往系统管理员,而警告信息可以送往一个文件
该syslog整个设备的接口(这里的接口指的是操作整个syslog的接口,可以往/dev/log或者UDP或者klog里面写,总之就是整个syslog的写入接口,但是可以根据选项选择怎样写,写什么样的小设备)
主要有四个接口
#include<syslog.h>
void openlog(const char* ident,int option,int facility);
ident:一般是程序的名字,自动追加到每则日志消息中
option:
打开日志系统,其中这个是可选的,如果没有调用openlog,则在第一次调用syslog的时候,自动调用openlog。
facility:
facility可以让配置文件说明,来自不同设施的消息将以不同的方式进行处理
注意:如果不调用openlog,或者以facility为0来调用,那么在调用syslog,可将facility作为priority参数的一个不分进行说明
void syslog(int priority,const char *format,....);
priority:是facility和level的组合,其中level可以是
format:以及其他的所有参数传至vsprintf函数进行格式话,在format中,每个出现的%m字符都被替换成和errno对应的字符串
void closelog(void );
用来关闭被用于与syslogd函数通信的描述符
int setlogmask(int makdpri);
设置屏蔽字,如果被设置,则不进行屏蔽,优先级屏蔽字
列如,我们提供一个范列
4单实例守护进程
什么叫做单实例守护进程
因为出于需要,系统在某个时间只需要一个守护进程运行,列如有的守护进程需要排他性的访问一个设备;比如说,cron进程在具体 的时间内运行某一个进程,此进程是守护进程,如果运行多个这样子的守护进程,则会在相同的时间内运行多个一样的程序,造成混轮,所以这个cron进程需要字任意一个时间内运行一个守护进程!!
守护进程采用文件-和记录锁实现了单实例守护进程,此方法假设,守护进程都需要创建一个文件,并且在第一个守护进程创建文件之后就对整个文件进行加锁,使其他的相同的守护进程不能自进行此文件的加锁,这样就实现了守护进程的单实例运行
#include "unistd.h"
#include "stdlib.h"
#include "fcntl.h"
#include "syslog.h"
#include "string.h"
#include "errno.h"
#include "stdio.h"
#include <sys/stat.h>
#define LOCKFILE "var/run/daemon.pid"
#define LOCKMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)
extern int lockfile(int);
int already_running(void)
{
int fd;
char buf[16];
fd = open(LOCKFILE,O_RDWR|O_CREAT,LOCKMODE);
if(fd < 0)
{
syslog(LOG_ERR,"can't open :%s %s",LOCKFILE,strerror(errno));
exit(1);
}
if(lockfile(fd) < 0)
{
if(errno == EACCES || errno ==EAGAIN)
{
close(fd);
return(1);
}
syslog(LOG_ERR,"can't lock %s:%s",LOCKFILE,strerror(errno));
exit(1);
}
ftruncate(fd,0);
sprintf(buf,"%ld",(long)getpid());
write(fd,buf,strlen(buf)+1);
return 0;
}
int main()
{
already_running();
return 0;
}
如以上的程序,先创建一个文件夹,并且在运用此函数时候锁住该文件,如果再有守护进程调用的时候,则进行锁住,并且将其写入文件,strerror是将错误标志转换成字符串
5守护进程的惯例
在unix系统中,通常的守护进程设计通常是如此设计
(1)如果守护进程使用锁文件,则该锁文件通常位于/var/run目录中。然而需要主义的是,在该目录创建文件通常需要超级用户的权限,锁文件的名字通常是name.pid,例如cron守护进程锁文件的名字是/var/run/crond.pid
(2)如果守护进程支持配置选项,那么该配置文件一般是放在/etc上的,文件名字一般是name.cond,name是该守护进程或服务的名字,
(3)守护进程可以使用命令启动,但是该启动命令一班是在系统初始化 的脚本中进行的,如果守护进程终止,应当自动进行重启,并且如果一个守护进程更新了配置文件,则应该重新启动
(4)为了避免当更改配置文件,需要停止在启动进程的时候,某些进程将捕捉SIGHUP信号,当接受到此信号的时候,重新读取配置文件。因为守护进程不与终端结合,他们或者是没有控制终端的会话首进程,或者是孤儿进程组的成员,所以守护进程没有里有期望接受SIGHUP,因此,守护进程可以重复使用SIGHUP