范德萨发而为
全部博文(392)
分类:
2010-09-27 10:37:25
Daemon编程与启动曹新宇 |
背景知识 当我们登录进入UNIX操作系统时,系统为我们产生一个登录shell, 登录shell的标准输 入、输出和错误输出指向一个终端文件。在登录shell中,假如我们键入如下命令: $proc1 | proc2 &$proc3 | proc4 | proc5这两个命令产生的进程组和登录shell这一 进程组的集合称作一个会话(session),登录shell被称作会话的会话头(session leader),与 登录shell相连的终端文件被称作控制终端(control terminal)。一个会话最多有一个控制 终端,也可能没有控制终端。有控制终端的会话的会话头被称作控制进程(control proces) 。一个会话中的进程组可分为前台进程组和后台进程组,当会话有控制终端时,则它有一个前 台进程组,会话中的其他进程组都是后台进程组。只有前台进程组可以接收终端输入,当我们 键入终端的控制键时,如中断键(一般为control-c)或者退出键(一般为control-\),这会引起 中断信号SIGINT或者退出信号SIGQUIT发送给前台进程组中的所有进程。当终端挂起时,IGH UP信号发送到会话中的每一个进程,不管是前台进程组,还是后台进程组(这一关系如下图所 示)。 @@36213000.GIF;图1@@ 一个进程可以调用setsid函数建立一个新的会话,如果调用进程不是进程组头,这个函数 产生一个新的会话,同时发生下面三件事: ·这个进程变为新会话的会话头,并且是会话中的唯一进程。 ·这个进程变为新进程组的进程组头,新进程ID是调用进程的ID。 ·进程没有控制终端,如果调用setsid之前进程拥有控制终端,调用后不再拥有。 如果调用进程是进程组头,则该函数调用失败。 什么是Daemon daemon的意思是精灵或恶魔,在UNIX中一般翻译为守护或驻留进程。除非意外情况或人 为干预,daemon一般在操作系统启动时运行,关机时结束。daemon通常作为客户 /服务器模 式中的服务器进程。UNIX操作系统中有大量的daemon日复一日地为客户服务。其主要特点是 :daemon是一种后台进程,没有控制终端,因此不受终端控制键的影响,也不会由于中断挂起而 退出。而且它是进程组头和会话头,是进程组和会话中唯一的进程。 daemon编码基本原则如下: (1)调用fork并且结束父进程。这样做可以达到两个目的。首先,如果daemon是在shel命 令中启动的,结束父进程会使shell认为命令已经结束。第二,子进程继承了父进程的进程组 ID同时得到一个新的进程ID,所以我们可以保证子进程不是进程组头,这是调用setsid函数的 先决条件。 (2)调用setsid产生一个新的会话。进程变为新会话的会话头和新进程组的进程组头,同 时进程没有控制终端。以上这些满足了daemon的特点。也就是说,进程已经基本成为daemo进 程。但是还有其他一些工作要做。 (3)关闭所有打开的文件描述字。 (4)改变当前的工作目录。 (5)重设文件存取建立的屏蔽码。 (6)忽略SIGCLD信号。作为server,当daemon接收到client的请求时,一般fork一个子进 程为client服务。当子进程退出时,如果daemon未调用wait等待子进程结束,则子进程成为o mbie进程;对每一个zombie进程,内核会保留一些有关的信息,如进程ID,以便父进程调用wit 。如果有大量client请求服务,必将产生很多zombie进程占用系统内存,忽略SIGCLD信号则可 避免zombie进程的积累。 下面是使用前面的编码原则编写的简单的daemon进程。 #include #include #include int daemon_init(void){ pid_t pid; if ( (pid = fork()) < 0) return(-1); else if (pid != 0) exit(0); /* 父进程退出 */ /* 子进程继续 */ setsid(); /* 变为会话头 */ chdir("/"); /* 改变工作目录 */ umask(0); /* 重设文件存取建立的屏蔽码 */ signal(SIGCLD,SIG_IGN); /* 忽略SIGCLD信号 */ return(0); } Daemon日志 守护进程没有控制终端,所以当daemon希望处理错误信息时,我们不能使用printf函数向 终端写错误信息。当然,daemon可以打开一个文件,在此文件中记录错误信息。这样的文件应 该以添加方式打开,以便前边的信息不被后边的信息所覆盖。但使用这种方法存在一些问题 :随着日志信息的增多,系统管理员必须不断地对该文件进行检查,使之保持在一定长度范围 内,这对系统管理员是一个比较麻烦的事情。此外,如果我们想使用其他文件记录日志时,必 须重新修改daemon源文件。因此,在UNIX系统中提供了syslogd daemon专门用于记录日志信 息,使用syslogd daemon可以使我们灵活安排日志使用的文件。 syslogd daemon在启动时读配置文件/etc/syslog.conf决定不同的信息分别存放在哪 个文件中,例如,可以指定紧急信息直接送往控制台,而一般的告警事件记录在某一文件中。 使用syslogd的编程接口是: #include void openlog(char *ident,int option,int facility); void syslog(int priority,char *format,……); void closelog(void); 在这些系统调用中,我们可以指定信息的来源、信息的重要程度,可以在信息上附加其 他信息。这些接口的具体使用方法和配置文件syslog.conf的格式,可参考系统提供的手册。 Daemon的重配置 daemon在启动时首先读配置文件对daemon中使用的参数进行配置。当我们想改变某些参 数时,我们可以编辑配置文件。当然,编辑好配置文件后并不能立刻改变了aemon中的参数,d aemon必须重新读配置文件。一种方法是重新启动系统,当然这种方法是一种最笨的方法。好 一点的方法是使用ps命令找出daemon进程ID,结束该进程然后重新运行demon。这种办法的缺 点是:如果某一进程正在与daemon通信时,会使该进程感觉到。最好的办法是:daemon收到某 个事先约定的信号时,重新读配置文件,这样就不必中断daemon正常运行。 这样的daemon程序如下所示: void read_config_file() { int fd; fd=open("config_file",O_RDONLY); /*读配置文件并配置参数*/ close(fd); signal(SIGHUP,read_config_file); } main() { read_config_file(); /*进入服务循环*/ while(1) { } } 需要注意的是:在read_config_file中,最好不要使用fopen,fread等函数,因为它们是不 可重入函数(nonreentarnt function),否则可能产生无法预测的结果。 假如我们想重新配置syslogd daemon参数,编辑好syslog.conf文件后,可利用如下的命 令,对syslogd进行配置: $ps -ae | grep syslogd /* 查出syslogd daemon的ID */$kill -HUP processID* 强 迫daemon重新读配置文件并配置 */ Daemon的运行 一般情况下,daemon在系统启动时自动启动,要使daemon随系统的启动而启动,必须在相 应的启动运行脚本中加入一些内容。当UNX系统启动时,内核产生一个init进程,它的进程ID 为1,由init进程执行daemon启动脚本。 在BSD中,init进程执行/etc/rc.boot、/etc/rc和/etc/rc.local脚本。rc.local脚本是 存放运行本地daemon命令的地方。这些命令是简单的B shell命令。因此启动daemon只需在 rc.local中加入运行该daemon的命令即可。 在SVR4中情况比较复杂。init进程支持多个运行级别,从0到6和s(单用户)。所谓运行级 别可以看作是系统的软件配置。每种运行级别选择运行不同的进程。运行级别不同时,ini所 采取的动作也不同,这由配置文件和多个shell脚本文件控制。其中配置文件是/etc/initab 文件。下面是该文件的一个例子: init:2:initdefault: stty::sysinit:stty 9600 clocal icanon echo opost onlcr ienqak ixon icrnl in par link::wait:/bin/sh -c "rm -f /dev/syscon; ln /dev/systty /dev/syscon" >/dev /console 2>&1 rc ::wait:/etc/rc /dev/console 2>&1 # system initializatio halt:6:wait:/usr/lib/X11/ignition/shutdown.ksh # NOTE: runlevel 6 is reserved for system shutdown. vue :234:respawn:/etc/vuerc #VUE validation and invocation inittab文件的格式是: 标记::运行级别:动作:进程每行的结尾由回车键结束。#后是对该行的注释。每行中的 各段由冒号隔开,各段的意义如下: ·标记:用于标记这一行。 ·运行级别:每行所处的运行级别。例如,如果init的运行级别是1,则它将处理标有1的 一行内容。当该段空时,则任何级别都需运行该行命令。 ·动作:告诉init管理该daemon时应该采取的策略。最普通的策略是wait,即该daemon只 在系统启动时被运行,不管以后是否中断。respawn的意思是:系统监视daemon的运行状况,当 它中断时自动重新启动。还有其他的策略值这里就不一一介绍了。 ·进程:被执行的B shell脚本或shell命令。 我们可以直接在inittab文件中写入daemon启动命令,或者写在B shell脚本中。 |