守护进程(daemon)是生存期较长的一种进程,在系统开启时启动,关闭时才终止,没有控制终端在后台运行。
守护进程结构:
依赖操作系统实现,以超级用户特权运行,无控制终端,无命令行,大多数守护进程的父进程是init进程。
常见的守护进程:
init:负责启动各运行层次特定的系统任务。
kswapd:页面调出守护进程,通过脏页面以低速写到磁盘上从而使这些页面在需要时可回收使用。
bdflush和kupdated:将高速缓存页面冲洗到磁盘上。
portmap:将远程过程调用映射为网络端口号的服务。
syslog:可由帮助操作人员把系统消息记入日志的任何程序使用。
inited:侦听系统网络接口,以便取得来自网络的对各种网络服务进程的请求。
编写守护进程:步骤:
1. 创建子进程,父进程退出:所有工作在子进程中进行,形式上脱离控制终端。
2. 在进程中创建新会话:setsid()函数,使子进程完全独立,脱离控制。
3. 修改当前目录为根目录:chdir()函数,防止占用可卸载的文件系统,也可换成其他路径。
4. 重设文件权限掩码:unmask函数,防止继承的文件创建屏蔽字拒绝某些权限,增加守护进程灵活性
5. 关闭不再需要的文件描述符:继承的打开文件不会用到,浪费系统资源无法卸载。
for (i = 0 ; i 关闭打开的文件描述符close ( i ))
范例程序如下:
void daemonize(const char *cmd)
{
int i, fd0, fd1, fd2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
/* 将文件模式屏蔽位设置为0 */
umask(0);
/* 获取最大文件描述符值,用于关闭不再需要的文件描述符 */
if (getrlimit(RLIMIT_NOFILE, &rl) < 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) /* parent */
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");
if ((pid = fork()) < 0)
err_quit("%s: can't fork", cmd);
else if (pid != 0) /* parent */
exit(0);
/* 将当前工作目录更改为根目录,使装配文件可拆卸 */
if (chdir("/") < 0)
err_quit("%s: can't change directory to /");
/* 关闭不再需要的文件描述符 */
if (rl.rlim_max == RLIM_INFINITY)
rl.rlim_max = 1024;
for (i = 0; i < rl.rlim_max; i++)
close(i);
/* 使文件描述符0、1和2与/dev/null关联 */
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(1);
}
}
//创建一个守护进程监视系统所有运行的进程
#include<unsitd.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
#include<sys/types.h>
#include<sys/resource.h>
#include<sys/stat.h>
//创建一个守护进程
void init ()
{
pid_t pid ;
int i ;
struct rlimit rl ; //获取进城资源的限制
struct sigaction sa ;
if(getrlimit(RLIMIT_NOFILE , &rl) < 0)
{
printf("con't get file limit") ;
}
if((pid = fork()) < 0)
err_quit("%s: can't fork" , cmd);
else if (pid != 0)
exit(0); //父进程退出
//子进程继续执行
setsid();
//防止子进程(组长)获取控制终端
if((pid = fork()) < 0)
err_quit("%s:can't fork", cmd);
else if (pid != 0)
exit(0);
//第二子进程继续运行,不再是会话组组长
//关闭打开的文件描述符
if(rl.rlim_max == RLM_INFINITY)
rl.rlim_max = 1024;
for(i = 0; i < rl.rlim_max; i++)
close(i);
chdir("/tmp"); //切换到工作目录
unmask(0); //重设文件创建掩码
return;
}
int main ()
{
FILE *fp ;
FILE *fstream;
signal(SIGCHLD, SIG_IGN); //忽略子进程信号,防止出现僵尸进程
init() ; //创建守护进程
while(1)
{
//进程id,user:父进程,comm:进程名,lstart:进程开始时间,etime:进程持续时间
fstream = open("ps -eo pid,user,comm,lstart,etime>time.txt","r");
if(fstream==NULL)
{
//在打开或者创建error.log成功的情况下,写入错误(使用errno时失败)
if((fp = fopen("error.log", "a+")) != NULL)
{
fprintf(fp, "%s\n", "执行命令失败");
fclose(fp);
}
else
exit(1); //写入错误失败,则终止程序推出并关闭所有进程
}
else
pclose(fstream); //关闭popen打开的I/O流
sleep(120); //设置成5分钟获取一次系统进程情况
}
return 0;
}
守护进程报告出错情况:
守护进程没有控制终端,所以不是简单的写到标准输出上,而是使用syslog设施,有三种方法产生日至消息:1)内核例程调用log函数;2)进程调用syslog函数产生日志消息;3)此主机上的一个用户进程可将日志消息发向UDP端口514,然后,syslogd守护进程读取三种格式的日志信息,根据配置文件(一般是/etc/syslog.conf)决定不同种类的信息应送往何处,守护进程通常使用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 maskpri);