整理了下守护进程的一些东西,在简单的代码背后,其实有很多值得去探索的东西。用注释的形式,请多指教。
void
daemonize(char *cmd)
{
int i, f0, f1, f2;
pid_t pid;
struct rlimit rl;
struct sigaction sa;
umask(0);
/*清除文件屏蔽字,从进程中继承得来的文件可能被屏蔽了某些权限,所以在一开始就要清除掉这些屏蔽。那么这句话是如何实现的呢?首先我们要理解umask这个函数的作用,当文件
*被创建时,并不会马上拥有执行权限,而是由umask赋予执行权限,这个umask的默认值是022,在/etc/profile中定义。umask译过来就是掩码,如果也就是权限的补码,你可以理解为umask&7777,
从7777中减去umask,剩下的就是权限。所以我们可以在创建文件时,再加上执行chmod 7777,就可以重新定位权限。以上可以看出,umask(0)就是可读可写的权限。
*/
if ((getrlimit(RLIMIT_NOFILE, &rl) < 0))
{
syslog(LOG_ERR, "can't get the resource maxinum");
exit(0);
}
/*获取进程的最多资源描述符,RLIMIT_NOFIL表示如果超出这个系统设定的最大资源现
*限制,系统会再重新打开一个新的文件描述符,在该函数中,如果获取的文件描述符超过资源设置,则会报错。在/etc/security/limis.conf中可设置软限制和硬限制。
*所谓硬限制就是系统对资源节点和数据块大小设置了绝对的限制,不允许用户超过这个限制。软限制就是系统规定的限制,同时规定允许用户超过使用的期限,并在用户每次使用时进行警告和提醒。
*如果要实现限制用户的硬盘使用大小,可以文件系统根目录下创建quotas空文件.chmod 644 quotas,只有root能修改。
*/
if ((pid = fork()) < 0)
{
syslog(LOG_ERR, "fork failed");
exit(1);
}
else if (pid != 0)
exit(0);
setsid();
/*setsid()是创建守护进程最关键的地方,看《unix高级环境编程》第一版,作者将是
*守护进程写到这里就结束了,说明守护进程的真谛在这里。setsid()创建一个新会话,并且使当前进程成为新会话的首进程,也使该进程脱离终端,这就实现了我们所希望的脱离终端,在后台执行,让通过终端产生的中断信号无法执行。
*那为什么要先fork一个子进程呢?因为让这个函数产生效果的前提的是调用这个函数的不是进程组的进程组长,如果是父进程调用,则可能会失败。这也是为什么要退出父进程的原因。
*/
sa.sa_handler = SIG_IGN;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGHUP, &sa, NULL) < 0)
{
syslog(LOG_ERR, "can't ingore SIGHUP");
exit(1);
}
/*要明白这几句代码,首先要知道,unix中信号处理是如何实现的.
*sigaction(int sigo, const struct sigaction *restrict act, const struct sigaction *restrict oact)//sigo是待处理的信号,act非空时,处理该信号的动作,oact非空时,系统经由oact指针返回该信号的上一个动作。
*struct sigaction {
void (*sa_handler)()int // 函数指针,用来处理当sigo的动作
sigset_t sa_mask//需要额外阻塞的信号集,置空不能简单使用=0,而是sigemptyset(&sa_mask)
int sa_flags//处理该信号的选项
void (*sa_sigaction)(int, siginfo_t *, void *)//可替代的信号处理程序,当在sigaction结构中使用SA_SIGINFO标志时,使用该信号处理程序。sa_sigaction和sa_handler放在同一存储区,所以在实际使用中,只能使用其中一个。
}
所以这几句代码的作用就是忽略中断信号。
restrict 用来修饰指针,他的作用是指明只有唯一这个方式可以访问对象以及初始化,volatile用来修饰变量,强制要求从内存中访问变量,而不是从寄存器中.
*/
if ((pid = fork()) < 0)
{
syslog(LOG_ERR, "fork failed");
exit(1);
}
else if (pid != 0)
exit(0);
/*这里fork 是为了保证这个守护进程不是会话的首进程,将其变成孤儿进程,父进程
* id号为1,为init进程,这是因为由setsid()创建的会话首进程在该终端中仍有可能
*可能获取控制终端。
*/
if (chdir("/") < 0)
{
syslog(LOG_ERR, "can not cahnge the current directory to the root directory");
exit(1);
}
/*将当前工作目录更改为根目录,因为原进程有可能是在一个装配文件系统中,什么是
* 装配文件呢,因为在linux中,并不是用过设备标志来访问不同的文件系统,而是
*将他们链接到一个独立的树形层次结构,变成一个独立的实体,无论什么文件系统,就会被装载到某个目录,覆盖原有的目录。当对其进行访问时,一直在跑的是设备驱动程序,将要访问的文件系统放入缓存区中快速读写。
* 为什么可以实现呢?因为linux在很久以前将真实的文件系统独立出来,而是提供了
* 一个软件接口访问真是的文件系统,这就是VFS,将文件系统的访问软件化,而不同
*不同的文件系统对VFS的接口却是一样的,所以VFS并不管不同文件系统下层的物理结构,而是提供可以访问该设备的术语,比如所在的磁道,扇面。硬盘初始化时,将物理硬盘划分成逻辑分区。每个分区加载ext2/ext3独立文件系统,通过目录将
* 这些文件系统按逻辑层次结构组织起来,目录则是物理设备上的软连接.*/
if (rl.rlim_max == RLIM_INFINITY)
{
rl.rlim_max = 1024;
}
for (i = 0; i < rl.rlim_max; i++)
{
close(i);
}
f0 = open("/dev/null",O_RDWR);
f1 = dup(0);
f2 = dup(2);
openlog(cmd, LOG_CONS, LOG_DAEMON);
if (f0 !=0 || f1 !=1 || f2 !=2)
{
syslog(LOG_ERR, "attach file0,file1,file2 fsiled");
exit(1);
}
}
void
main()
{
daemonize("test");
}