Linux下的守护进程

来源:互联网 发布:淘宝数码店铺简介 编辑:程序博客网 时间:2024/06/05 02:27

什么是守护进程

守护进程也称精灵进程( Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。Linux的大多数服务器就是用守护进程实现的。比如, Internet服务器inetd, Web服务器httpd等。同时,守护进程完成许多系统任务。⽐如,作业规划进程crond等(若是对crond不理解请点击我上一篇博客)。

Linux系统启动时会启动很多系统服务进程,这些系统服务进程没有控制终端,不能直接和用户交互。其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户登录注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程(Daemon)。

下面呢我们通过ps axj这个命令来查看一下系统中的进程:参数a表示不仅列当前用户的进程,也列出所有其他用户的进程,参数x表示不仅列有控制终端的进程,也列出所有无控制终端的进程,参数j表示列出与作业控制相关的信息。


我们可以看到,凡是TGPID一栏写着-1的都是没有控制终端的进程,也就是守护进程。在COMMAND一列用[]括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件名和命令行, 通常采用以k开头的名字,表示Kernel。 init进程我们已经很熟悉了,udevd负责维护/dev目录下的设备文件,acpid负责电源管理,syslogd负责维护/var/log下的日志文件,可以看出,守护进程通常采用以d结尾的名字,表示Daemon。

守护进程的特点

①所有的守护进程都没有控制终端,其终端名(TTY)设置为问号(?)。

②自成会话,自成进程组。不与其他会话或进程组相互关联,干扰。所以一般一个守护进程的进程ID,组ID,会话ID都相同。(自成进程组这点说的也不太严谨,若父进程是守护进程,父进程fork的子进程也是守护进程。这时父子进程属于同一进程组)

③命令以‘d’结尾。

④守护进程不受用户登录注销的影响,当你注销或者重登后,守护进程一直在运行。

⑤生存期长,在系统引导装入时启动,仅在系统关闭时终止。

⑥在后台运行(原因可归结于没有控制终端)。

⑦大多数的守护进程都以root特权运行。

创建守护进程

创建守护进程最关键的一步是调用setsid函数创建一个新的会话(Session),并让当前这个进程成文话首进程,即Session  Leader。

这里我介绍一下setsid函数:


该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。

注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第一个进程,在子进程中调用setsid就不会有问题了。

这个函数的执行结果为:

①创建一个新会话,使该进程变成新会话的会话首进程(会话首进程也可理解为创建会话的进程),此时,该进程是当前会话的唯一进程

②使该进程称为一个新进程组的组长进程,进程组ID就是该进程的进程ID

③如果当前进程原本有一个控制终端,则它失去这个控制终端,成为一个没有控制终端的进程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是一个普通的打开文件而不是控制终端了。

④当前进程的进程ID,进程组ID,会话ID都相等。

这里我列出具体的实现步骤:

1、调用umask将文件模式创建屏蔽字设置为0。在上面提到过,我们要操作的进程是一个子进程。而子进程从父进程的PCB继承过来的文件模式创建屏蔽字可能会屏蔽某些权限。而加入我们要创建的守护进程正好需要这些权限的话就会造成很麻烦的问题。另一方面,如果守护进程调用了库函数创建了文件,那么文件模式创建屏蔽字应该设置为更强的(如007)。因为库函数不允许调用者通过一个显式的函数参数来设置权限。

2、调用fork,子进程退出(exit) 。原因: 1)如果该守护进程是作为一条简单的shell命令启动的,那么父进程终止使得shell认为该命令已经执行完毕。 2)保证子进程不是一个进程组的组长进程。

3、调用setsid创建一个新会话。 setsid会导致: 1)调用进程成为新会话的首进程。 2)调用进程成为一个进程组的组长进程 。 3)调用进程没有控制终端。

4、调用函数chdir将当前进程的工作目录更改为根目录或者某个指定位置。因为子进程从父进程继承来的工作目录可能是在一个挂载的文件系统中,而守护进程在系统再次引导前是一直存在的,如果不更改,那么挂载的文件系统就一直卸载不了。

5、调用fclose函数关闭不在需要的文件描述符(0,1,2等)。使守护进程不再持有从父进程继承来的任何文件描述符。或者还可以将文件描述符重定向到文件(/dev/null),这个文件就相当于一个垃圾桶,这样等于将当前进程的标准输入,标准输出,标准错误都失效。关闭文件描述符的原因是守护进程与控制终端没有任何联系的,并且它是在后台运行。后台并没有接受它输入输出也无处显示,我们不希望在终端上简单守护进程的输出,用户也不想在终端上的输入被守护进程读取。

6、其他:忽略SIGCHLD信号等。

实例:


运行之后我们可以通过ps axj去查看


其中父进程PPID为1,表示是init进程;然后他们的进程ID(PID)、进程组ID(PGID)、会话ID(SID)都相同;TTY为?表示没有终端;TPGID为-1。

上面的其实是我们自己创建的一种模拟实现守护进程的方法,但是其实Linux专门为我们提供了一个函数接口来实现创建守护进程。


两个参数,第一个参数nochdir如果设置为0的话表示将工作目录改为根目录。第二个参数noclose如果设置为0的话就将文件描述符重定向到/dev/null文件。与上面原理相似。
所以,简单的执行下面代码就可以创建一个守护进程。


然后运行一下程序得到:


我们可以看到和上面的运行结果基本是一致的。


然后我们可以在进程文件中再去看一下,其标准输入输出都指向了/dev/null这个文件。

补充:fork一次和fork两次

如果我们需要再次fork一次是要做什么呢?


我们与之前的代码进行比较可以看到,图中黄色部分就是再次fork的部分。这样做的原因呢,我认为是:

避免打开新的终端。我们知道,守护进程是没有控制终端的。先说明一下,打开终端的一个前提条件是该进程必须是会话组长。而我们fork一次并且setsid的儿子进程正好是会话组长。fork第二次,终止子进程,保持孙子进程不是话首进程,避免后续再与其他终端相关联。即孙子进程ID != 儿子进程ID(会话组长),防止了守护进程再次打开新的控制终端。