守护进程也称精灵进程( Daemon),是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。
守护进程是一种很有用的进程。 Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd 等。同时,守护进程完成许多系统任务。比如,作业规划进程crond等 Linux系统启动时会启动很多系统服务进程,这些系统服 务进程没有控制终端,不能直接和用 户交互。其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统 服务进程不受用户登录注销的影响,它们一直在运行着。这种进程有一个名称叫守护进程 (Daemon)。
如何查看守护进程 在终端敲:ps axj a 表示不仅列当前用户的进程,也列出所有其他用户的进程 x 表示不仅列有控制终端的进程,也列出所有无控制终端的进程 j 表示列出与作业控制相关的信息
更具体的是使用ps axj | grep -E ‘d$’这个指令
凡是TPGID一栏写着-1的都是没有控制终端的进程,也就是守护进程。 在COMMAND一列用 []括起来的名字表示内核线程,这些线程在内核里创建,没有用户空间代码,因此没有程序文件 名和命令行, 通常采用以k开头的名字,表示Kernel。 init进程我们已经很熟悉了,udevd负责维护/dev目录下的 设备文件,acpid负责电源管理,syslogd负责维护/var/log下的日志⽂文件,可以看 出,守护进程通 常采用以d结尾的名字,表示Daemon。
一般情况下,守护进程可以通过以下方式启动: (1)在系统启动时由启动脚本启动,这些启动脚本通常放在 /etc/rc.d 目录下; (2)利用 inetd超级服务器启动,如 telnet 等; (3)由 cron 定时启动以及在终端用 nohup 启动的进程也是守护进程
下面是编写守护进程的基本过程:
1) 调用umask将文件模式创建屏蔽字设置为0. 进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取权限。为防止这一点,将文件创建掩模清除:
2) 调用fork,父进程退出(exit)。 原因:1)如果该守护进程是作为一条简单的shell命令 启动的,那么父进程终止使得shell认为该命令已经执行完毕。 2)保证子进程不是一个进程组的组长进程。
if( pid = fork() ) { // 父进程 exit(0); //结束父进程,子进程继续 }3)脱离控制终端、登录会话和进程组
有必要先介绍一下 Linux 中的进程与控制终端,登录会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的 shell 登录终端。 控制终端、登录会话和进程组通常是从父进程继承下来的。我们的目的就是要摆脱它们 ,使之不受它们的影响。因此需要调用 setsid() 使子进程成为新的会话组长,示例代码如下:
setsid();该函数调用成功时返回新创建的Session的id(其实也就是当前进程的id),出错返回-1。 注意,调用这个函数之前,当前进程不允许是进程组的Leader,否则该函数返回-1。 要保证当前进程不是进程组的Leader也很容易,只要先fork再调用setsid就行了。fork创建的子进程和父进程在同 一个进程组中,进程组的Leader必然是该组的第一个进程,所以子进程不可能是该组的第⼀一个进程,在子进程中调用setsid就不会有问题了。
成功调用该函数的结 1.创建一个新的Session,当前进程成为Session Leader,当前进程的id就是Session的id。 2. 创建一个新的进程组,当前进程成为进程组的Leader,当前进程的id就是进程组的id。 3.如果当前进程原本有⼀一个控制终端,则它失去这个控制终端,成为⼀一个没有控制终端的进 程。所谓失去控制终端是指,原来的控制终端仍然是打开的,仍然可以读写,但只是⼀一个普 通的打开⽂文件⽽而不是控制终端了。
4)工作目录更改为根目录。
进程活动时,其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心,写运行日志的进程将工作目录改变到特定目录如 /tmp。
chdir("/");头文件:#include
close(0); close(1); close(2);6)处理 SIGCHLD 信号
但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。
如果父进程等待子进程结束,将增加父进程的负担,影响服务器进程的并发性能。在 Linux 下可以简单地将 SIGCHLD 信号的操作设为 SIG_IGN 。这样,内核在子进程结束时不会产生僵尸进程。
signal(SIGCHLD, SIG_IGN);完整代码:
//mydemon.c代码: #include<stdio.h> #include <unistd.h> int main() { daemon(1, 1); while(1); return 0; } //Makefile代码: mydaemon:mydaemon.c gcc -o $@ $^ .PHONY:clean clean: rm -f mydaemon //mydaemon.c代码: #include <stdio.h> #include <unistd.h> #include <signal.h> #include <sys/types.h> #include <sys/stat.h> #include <stdlib.h> void mydaemon() { umask(0); pid_t id1 = fork(); pid_t id2 = fork(); if(id1 > 0) { exit(0); } else if(id1 < 0) { printf("fork\n"); exit(1); } if(id1 > 0) { exit(0); } else if(id2 < 0) { printf("fork\n"); exit(1); } setsid(); chdir("/"); close(0); close(1); close(2); signal(SIGCHLD, SIG_IGN); } int main() { mydaemon(); while(1); return 0; }运行结果:
要知道在创建守护进程的时候fork一次和fork两次两者有什么区别,就要先知道第一次fork和第二次fork都起到了什么作用:
(1)调用一次fork的作用: 第一次fork的作用是让shell认为这条命令已经终止,不用挂在终端输入上,还有就是为了后面的setsid服务,因为调用setsid函数的进程不能是进程组组长,如果不fork出子进程,则此时的父进程是进程组组长,就无法调用setsid。当子进程调用完setsid函数之后,子进程是会话组长也是进程组组长,并且脱离了控制终端,此时,不管控制终端如何操作,新的进程都不会收到一些信号使得进程退出。
(2)第二次fork的作用: 虽然当前关闭了和终端的联系,但是后期可能会误操作打开了终端。 只有会话首进程能打开终端设备,也就是再fork一次,再把父进程退出,再次fork的子进程作为守护进程继续运行,保证了该精灵进程不是对话期的首进程, 第二次不是必须的,是可选的,市面上有些开源项目也是fork一次