1. 守护进程和普通进程的区别
守护进程在后台运行, 与父进程的环境隔离, 这些环境包括
1.1 控制终端、会话组和进程组
1.2 工作目录、文件创建掩码
1.3 未关闭的文件描述符
察看常用的系统守护进程,看一下它们和进程组、控制终端和对话期有什么联系。
ps –axj
父进程ID 进程ID 进程组ID 会话期ID 终端ID 终端进程组ID 状态 用户 运行时间 指令
PPID
PID
PGID
SID
TTY
TPGID
STAT UID TIME
COMMAND
0
1
0
0
?
-1
S
0
0:04
init
1
2
1
1
?
-1
SW
0
0:00
[keventd]
1
3
1
1
?
-1
SW
0
0:00
[kapm-idled]
0
4
1
1
?
-1
SWN
0
0:00
[ksoftirqd_CPU0]
0
5
1
1
?
-1
SW
0
0:00
[kswapd]
0
6
1
1
?
-1
SW
0
0:00
[kreclaimd]
0
7
1
1
?
-1
SW
0
0:00
[bdflush]
0
8
1
1
?
-1
SW
0
0:00
[kupdated]
1
9
1
1
?
-1
SW<
0
0:00
[mdrecoveryd]
1
17
1
1
?
-1
SW
0
0:02
[kjournald]
1
92
1
1
?
-1
SW
0
0:00
[khubd]
1
573
573
573
?
-1
S
0
0:03
syslogd -r -x
1
578
578
578
?
-1
S
0
0:00
klogd -2
1
598
598
598
?
-1
S
32
0:00
portmap
进程号为1、2的进程特殊,存在于系统的整个生命期中, 没有父进程ID,没有组进程ID,也没有对话期ID。
syslogd
被任何记录系统消息的程序使用, 可以在实际的控制台上打印这些消息,也可将它们写入文件。
sendmail
是标准邮递守护进程。
updated
定期将内核缓存中的内容写入硬盘(一般为每隔30秒), 实现手段是: 每隔30秒updated调用sync(2)函数。
cron
在指定的日期和时间执行指定的命令, 它定期地执行相关程序以实现许多系统管理任务。
inetd
监听系统的网络接口,以输入对各种网络服务器的请求。
lpd
处理对系统提出的打印请求。
注意,所有守护进程都以超级用户(用户ID为0)的优先权运行。没有一个守护进程具有控制终端,终端名称设置为问号(?)、终端前台进程组ID设置为-1。 缺少控制终端是守护进程调用了setsid的结果。除update以外的所有守护进程都是进程组的和对话期的首进程,而且是这些进程组和对话期中的唯一进程。最后注意,所有这些守护进程的父进程都是init进程。
2. 在接触实际编程前,我们来看看编写守护进程要碰到的概念:进程组和会话期
2.1 进程组
每个进程有一个唯一的进程组ID。进程组ID类似于进程ID--它是一个正整数,并可存放在pid_t数据类型中。
每个进程组有一个组长进程。组长进程的标识是,其进程组ID等于其进程ID,进程组组长可以创建一个进程组,创建该组中的进程,然后终止,只要在某个进程组中有一个进程存在,则该进程组就存在,这与其组长进程是否终止无关。从进程组创建开始到其中最后一个进程离开为止的时间区间称为进程组的生命期。某个进程组中的最后一个进程可以终止,也可以参加另一进程组。 进程调用setgid可以参加一个现存的组或者创建一个新进程组(setsid也可以创建一个新的进程组,后面将用到)。
2.2 会话期
会话期(session)是一个或多个进程组的集合。其中,在一个会话期中有3个进程组,通常是由shell的管道线将几个进程编成一组的。
2.3 会话期和进程组的一些特性:
一个会话期可以有一个单独的控制终端(controlling terminal),这一般是我们在其上登录的终端设备(终端登录)或伪终端设备(网络登录),但这个控制终端并不是必需的。
建立与控制终端连接的会话期首进程,被称之为控制进程(contronlling process)。一个会话期中的几个进程组可被分为一个前台进程组(foreground process group)以及一个或几个后台进程组(background process group)。
如果一个会话期有一个控制终端,则它有一个前台进程组,其他进程组为后台进程组。无论何时键入中断键(通常是delete或ctrl-c)或退出键(通常是ctrl-),就会造成将中断信号或退出信号送至前台进程组的所有进程。
3. 守护进程的编程规则
3.1 脱离控制终端,登录会话和进程组
登录会话可以包含多个进程组,这些进程组共享一个控制终端,这个控制终端通常是创建进程的登录终端。控制终端,登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们,使之不受它们的影响。
其方法是在fork()的基础上,调用setsid()使进程成为会话组长。调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登录会话和进程组脱离,由于会话过程对控制终端的独占性,进程同时与控制终端脱离。
setsid()实现了以下效果:
(a) 成为新对话期的首进程
(b) 成为一个新进程组的首进程
(c) 没有控制终端。
3.2 禁止进程重新打开控制终端
现在,进程已经成为无终端的会话组长,但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端,再fork()一次。
3.3 关闭打开的文件描述符
进程从创建它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,造成进程所在地文件系统无法卸下以及无法预料的错误。一般来说, 必要的是关闭0、1、2三个文件描述符,即标准输入、标准输出、标准错误。因为我们一般希望守护进程自己有一套信息输出、输入的体系,而不是把所有的东西都发送到终端屏幕上。
方法: close(文件描述符)。
3.4 改变当前工作目录
将当前工作目录更改为根目录。从父进程继承过来的当前工作目录可能在一个装配的文件系统中。因为守护进程通常在系统重启之前是一直存在的,所以如果守护进程的当前工作目录在一个装配文件系统中,那么该文件系统就不能被拆卸。
另外,某些守护进程可能会把当前工作目录更改到某个指定位置,在此位置做它们的工作。例如,行式打印机假脱机守护进程常常将其工作目录更改到它们的spool目录上。
方法: chdir("目录");
3.5 重设文件创建掩码
将文件方式创建屏蔽字设置为0:umask(0)。 由继承得来的文件方式创建的屏蔽字可能会拒绝设置某些许可权。例如,若守护进程要创建一个组可读、写的文件,而继承的文件方式创建屏蔽字,屏蔽了这两种许可权,则所要求的组可读、写就不能起作用。
3.6 处理SIGCHLD信号
处理SIGCHLD信号并不是必须的。但对于某些进程, 特别是服务器进程往往在请求到来时fork子进程出来处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)而仍占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在系统V下可以简单地将SIGCHLD信号的操作设为SIG_IGN:
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程,这一点与BSD4不同,在BSD4下必须显示等待子进程结束才能释放僵尸进程。
4. 守护进程实例
守护进程实例包括两部分:主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp目录中的日志test.log报告运行状态。初始化程序中的init_daemon 函数负责生成守护进程
void init_daemon(void)
{
if(fork()!=0)
exit(0);
setsid();
if(fork()!=0)
exit(0);
for(int i=0; i<getdtablesize(); i++)
close(i);
chdir("/");
umask(0);
signal(SIGCHLD, SIG_IGN);
}
5. 守护进程的错误输出
守护进程不属于任何终端,所以当需要输出某些信息时,它无法像一般程序那样将信息直接输出到标准输出和标准错误输出中。我们很大时候也不希望每个守护进程将它自己的出错消息写到一个单独的文件中。因为对于系统管理人员而言,要记住哪一个守护进程写到哪一个记录文件中,并定期的检查这些文件,他一定会为此感到头疼的。所以,我们需要有一个集中的守护进程出错记录机制。目前很多系统都引入了syslog记录进程来实现这一目的。
自伯克利开发了BSD syslog并广泛应用以来,BSD syslog机制被大多数守护进程所使用。我们下面介绍BSD syslog的用法。有三种方法产生记录消息:
1 内核例程可以调用log函数。任何一个用户进程通过打开和读/dev/klog设备就可以读取这些消息。因为我们无意编写内核中的例程,所以不再进一步说明此函数。
2 大多数用户进程(守护进程)调用syslog函数以产生记录消息。我们将在下面说明其调用序列。这使消息发送至Unix域数据报套接口/dev/log。
3 在此主机上,或通过TCP/IP网络连接到此主机的某一其他主机上的一个用户进程可将记录消息发向UDP端口514。
注意: syslog函数并不产生这些UDP数据报——它们要求产生此记录消息的进程具有显式的网络编程。
通常,syslog守护进程读取三种格式的记录消息。此守护进程在启动时读一个配置文件。一般来说,其文件名为/etc/syslog.conf,该文件决定了不同种类的消息应送向何处。例如,紧急消息可被送向系统管理员(若已登录),并在控制台上显示,而警告消息则可记录到一个文件中。该机制提供了syslog函数,其调用格式如下:
#include <syslog.h>
void openlog(char* ident,int option ,int facility);
void syslog(int priority,char*format,……)
void closelog();
调用openlog是可选择的。如果不调用openlog,则在第一次调用syslog时,自动调用openlog。调用closelog也是可选择的,它只是关闭被用于与syslog守护进程通信的描述符。 调用openlog使我们可以指定一个ident,以后,此ident将被加至每则记录消息中。ident一般是程序的名称(例如,cron,inetd等)。option有4种可能:
LOG_CONS
若日志消息不能通过Unix域数据报发送至syslog,则将该消息写至控制台。
LOG_NDELAY1 立即打开Unix域数据报套接口至syslog守护进程,而不要等到记录第一消息。通常,在记录第一条消息之前,该套接口不打开。
LOG_PERROR 除将日志消息发送给syslog外,还将它发送至标准出错。此选项仅由4.3BSD Reno及以后版本支持。
LOG_PID
每条消息都包含进程ID。此选项可供对每个请求都fork一个子进程的守护进程使用。
在openlog中设置facility参数的目的是让配置文件可以说明, 来自不同设施的消息以不同的方式进行处理。如果不调用openlog,或者以facility为0来调用它,那么在调用syslog时,可将facility作为priority参数的一个部分进行说明。调用syslog产生一个记录消息。其priority参数是facility和level的组合,它们可选取的值分别列于下面。level值按优先级从高级到最低按序排列。