linux下根文件系得统制作工具。内带shell命令
源代码在线查看: unix系统开发-守护进程.txt
UNIX系统开发-守护进程
守护进程(Demons)是在后台运行而有无终端或者登录shell和它结合在一起的进程。有许多标准的守护进程,其中的一些周期地运行来完成特定的任务(像atrun,典型地由cron每五分钟执行一次),而其余的则连续地运行,等待处理某些特定的事件(像inetd和lpd)。
1.原理
有几种启动守护进程的方法。最常用的是:
在引导系统时启动。在这时运行的守护进程通常在系统启动script的执行周期间被启动。这些script典型地被存放在目录/etc/rc.d中。
手工地,从shell提示符启动.对任何具有相应的执行权限的用户可以用这种方法启动守护进程
由crond守护进程启动。这个程序查询通常存放在/var/spool/crontabs目录中的一组文件,它们规定了要执行的周期性任务
由执行at命令启动。这将使一个程序在规定的日期和时间执行一次。
为了做到完全稳定可靠,一个守护进程应该能够在所有的这些方式下被正确地执行。唯一问题是其中的一些启动方法使守护进程处于一种脆弱状态。它可能受到在它运行之前为它设置的环境的影响。你将要写的将普通程序转换成守护程序的大部分代码将涉及到把你的程序与这些环境影响隔离开。除此之外,准备作为守护进程使用的程序通常必须比普通的用户程序更可靠。你常常需要这样去编写程序,使得当它作为守护程序运行时,即使出现各种类型的系统错误,也不至于引起程序的崩溃。
2.实践
在启动守护进程的过程中,包含了比你能想象的更多的步骤,虽然它们全部可能只用极少的代码去完成。本节的其余部分将逐步介绍这一过程的更重要的方面。
2.1 关闭文件描述符
第一项任务是关闭所有不必要的文件描述符。如果你的守护进程留下一个普通文件处于打开状态,这将阻止该文件被任何其他进程从文件系统中删除。它也阻止包含该打开文件的已装配的文件系统被卸下。在终端文件(通常是stdin,stdout和stderr)的情况下,关闭不必要的连接更加重要。因为当在该终端上的用户退出系统后,将执行vhangup()系统调用,守护进程访问该终端的权利将被撤消。这表示守护进程虽然由它认为处于打开状态的文件描述符,事实上它已不再能通过这些文件描述符访问该终端。
最简单的做法是关闭所有的文件描述符,它将使守护进程和这些问题隔离开。将close()系统调用用在没有打开的文件描述符上不存在任何问题,所以下列的代码段可以被使用:
#include
for (id=1;i close(i);
符号常量NOFILE给出一个进程一次可以打开的文件的最大个数。
2.2 甩开控制终端
如果一个进程是从登录对话过程中被启动,而它将从对话过程中继承与它结合的控制终端。对于守护进程来说,其结果是它可以接收由该控制终端产生的信号(诸如SIGINT和SIGQUIT),如果这些信号不被捕获,将结束该进程。这个问题可以由守护进程忽略所有它可能忽略的信号而加以克服,但是这将阻止守护进程利用信号作为简单的进程间通信手段使用。一个较好的解决方式是使用守护进程本身和控制终端分开,使得这些信号首先不传播到守护进程。
在linux下这样做的一种方法是打开文件/dev/tty,并且使用ioctl()在该文件上执行TIOCNOTTY命令。它使得每一个具有控制终端的进程通过文件/dev/tty访问那个终端。
简短的代码如下:
if((fd=open("/dev/tty",D_RDWR))>=0)
{
ioctr(fd,TIOCNOTTY,0);
close(fd);
}
在LIUNX下,这不是使进程本身和它的控制终端分离的唯一的方法。事实上,在下一节中我们将看到做这件事的更简单的方法。
2.3 脱离对话过程和进程组
进程从它的双亲进程获得它的对话过程和进程组识别号。由于它属于一个对话过程和一个进程组,一个进程将接收到任何作为整体发送给该对话过程或进程组的信号。这类似于从控制终端接收信号的问题,并且实际的解决方法也是同样的,即将进程和这一环境影响分开。
在POSIX中存在一个单一的系统调用,它将进程和它的当前的对话过程和进程组分离开,并且把它设置为一个新的对话过程的领头进程(leader)。这个系统调用设置对话过程识别号:
setsid()
由于一个控制终端仅可以和单一的对话过程相联,作为setsid()调用的副作用,它也把进程和它的控制终端(如果它有控制终端的话)分离开。
setsid()的唯一问题是:只有执行它的守护进程不是对话过程的领头进程(leader)时,它才能发挥作用。在我们的情况下,容易假定该守护进程不是对话过程的领头进程,但是这不能保证,除非采取特殊步骤使它成为这样。你可以毫无疑问地这样说:如果一个特定的对话过程的领头进程执行了fork()系统调用,则默认地这一子女进程应该不是对话过程的领头进程。这提供了确保执行setsid()的守护进程不是对话过程的领头进程的机制。守护进程需要做的只是按如下所示的执行fork()调用,然后在双亲进程中执行exit(),并且在子女进程中执行sesid():
/*(在这以后,子女进程不再是对话过程的领头进程)*/
if (fork())
/*不管它的状态如何结束双亲进程*/
exit(0);
/*(把子女进程和对话过程,进程组及tty分离)*/
setsid();
即使采取了这些措施,还没有彻底解决将守护进程和它的控制终端分离的全部问题。这是由于当一个没有控制终端的对话过程的领头进程(就像我们限现在的情况)打开其本身还不是另一个对话过程的控制终端的终端设备时,该终端将自动变成新的对话过程的控制终端。
但是注意,以这种方式获得控制终端仅可以由对话过程的领头进程完成。因此,明显的解决办法是要确保我们的进程不再是对话过程的领头进程。这可以由第二次执行fork()以及再次结束双亲进程来完成。这留下一个不属于其原始对话过程或进程组的子女进程,它没有控制终端,而且现在也不能再重新获得一个控制终端。所要求的代码如下:
/*(启动新的对话过程,脱离旧的的对话过程和进程组)*/
if (fork())
exit(0);
/*(把进程组和控制终端分离)*/
setsid();
/*(变成一个非对话过程的领头进程)*/
/*(使它不可能重新获得控制终端)*/
if (fork())
exit(0);
2.4 改变工作目录
在进程存在期间,内核(kernel)保存系统中的任何进程打开的当前工作目录。在正常的情况下,这不成为一个问题。但是如果该进程在已经装配的文件系统上有一个当前工作目录,则该文件系统被标记为"在使用的(in use)'状态,而且它不可以被卸下。为了允许系统超级用户(superuser)卸下文件系统,守护进程可以执行:
chdir("/");
为了正确的进行操作,一个守护进程也可能必须用chdir()改变到一个特定的目录。 其中服务程序可以提供的所有文件都放在守护进程的当前的工作目录中。在这种情况下,你不得不接收限制,或者由系统管理员在根(root)文件系统中提供守护进程可以运行的一个位置。将当前工作目录改变到根目录可能出现的一个问题。如果出现守护进程结束运行和卸出一个核心(core)文件的情况下,内核将试图打开当前目录中的文件。如果这是根目录,则操作将失败,除非守护进程正在用超级用户的权限运行。
为了克服这个问题,下面将使用/tmp作为当前目录。通常它只是根文件系统的一个子目录,并且所有进程都有写的权限;
chdir("/tmp");
2.5 重新设置文件的创建掩码
进程一开始,它继承它的双亲进程的文件创建掩码。典型地,它具有022值,表示由守护进程创建的任何文件将不把写的权限给组和其他用户,不管守护进程本身规定什么权限位。依赖于守护进程的性质,这个操作可能或不可能成为问题。但是,将下列行包括在你的代码中,取消由双亲进程设置的文件创建掩码值产生的效果将是一件简单的事情,无论它当前是什么值:
umask(0);
即使到现在,仍然存在从双亲进程继承的环境中的一些特性。它们可以使守护进程产生某些问题。例如,nice()进程设置的优先权,或者用alarm()设置的闹钟信号留下的时间。但是,虽然这些事情是可能的,它应该是相当恶意的双亲进程给粗心大意的守护进程设置的陷阱。
2.6 处理SIGCHLD信号
有时守护进程被写成创建子女进程。在此情况下,双亲进程保持循环接收更多的客户连接,而子女进程服务客户的请求。注意,双亲进程不执行wait()系统调用等待它的子女进程的结束。默认地,在这个情况下的子女进程将变成zombie,直到将来某个进程(很可能是init)等待它们为止。在这些情况下zombie的创建浪费系统资源。在LINUX下有几种方法可以绕过这个问题。最简单的是按照如下的方式将SIGGHLD信号的操作设置为SIG_IGN;
signal(SIGGHLD,SIG_IGN);
这是SIGCHLD信号的特殊的特征,它告诉内核(kernel)不从调用进程的子女进程中产生zombie。