守护进程(Daemon),一说精灵进程,是指在后台运行的,没有控制终端与之相连的程序.它独立于控制终端周期性地执行某种任务或等待处理某些发生的事件。它是一个生存期较长的进程,守护进程常常在系统引导装入时启动,在系统关闭时终止。Linux系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程crond、打印进程lqd等(这里的结尾字母d就是Daemon的意思)。
由于在Linux中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程。守护进程在后台运行,类似于windows中的系统服务
守护进程的启动方式有多种,它可以在linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由终端(通常是shell)执行.
编写创建守护进程的程序,要尽量避免产生不必要的交互,编写
创建守护进程的程序有以下要点:
1)创建子进程,父进程退出
这是编写守护进程的第一步。由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在Shell终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。
在Linux中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时,就会自动由1号进程(init)收养它,这样,原先的子进程就会变成init进程的子进程。
2)在子进程中创建新会话
这个步骤是创建守护进程中最重要的一步,虽然它的实现非常简单,但它的意义却非常重大。在这里使用的是系统函数setsid,创建一个新对话,控制终端,登陆会话和进程组通常是从父进程继承下来的,守护进程要摆脱他们,不受他们影响,其方法是调用setsid使进程成为一个会话组长
进程组:是一个或多个进程的集合。进程组有进程组ID来唯一标识。除了进程号(PID)之外,进程组ID也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组ID。且该进程组ID不会因组长进程的退出而受到影响。
一个进程创建了子进程,父子进程可以构成一个进程组,父进程的PID就是这个组的组id
会话有多个进程组构成,每次登录一次系统,就是一个会话
进程组是一个或多个进程的集合,每个进程组有一个组长( 其进程ID=进程组ID ),组长进程终止时,进程组依然存在。
会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始于用户登录,终止于用户退出,在此期间该用户运行的所有进程都属于这个会话期。
会话和进程组有一些特性:
1). 一个会话可以有一个控制终端。
2). 建立与控制终端连接的会话首进程被称为控制进程。
3). 一个会话中的几个进程组可被分成一个前台进程组和几个后台进程组。
4). 如果一个会话有一个控制终端,则它有一个前台进程组。
5). 无论何时键入终端的中断键(DELETE或Ctrl+C),就会将中断信号发送给前台进程组的所有进程。
6). 无论何时键入终端的退出键(Ctrl+\),就会将退出信号发送给前台进程组的所有进程。
7). 如果终端检测到调制解调器(或网络)已经断开连接,则将挂断信号发送给控制进程(会话首进程)。
setsid():当进程是会话的领头进程时setsid()调用失败并返回(-1)。setsid()调用成功后,返回新的会话的ID,调用setsid函数的进程成为新的会话的领头进程,并与其父进程的会话组和进程组脱离。由于会话对控制终端的独占性,进程同时与控制终端脱离。
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <sys/types.h> 4 #include <sys/param.h> 5 #include <unistd.h> 6 #include <signal.h> 7 #include <sys/stat.h> 8 #include <time.h> 9 #include <syslog.h> 10 11 12 int init_daemon() 13 { 14 int pid ; 15 int i; 16 17 /*忽略终端I/O信号,STOP信号*/ 18 signal(SIGTTOU ,SIG_IGN); 19 signal(SIGTTIN ,SIG_IGN); 20 signal(SIGTSTP ,SIG_IGN); 21 signal(SIGHUP ,SIG_IGN); 22 23 pid = fork(); 24 if(pid >0 ){ 25 exit (0);//结束父进程,是子进程成为后台程序 26 } 27 else if(pid < 0){ 28 return -1; 29 } 30 //建立一个新进程组,在这个新进程里,子程序成为这个进程组的首进程,以使该进程脱离所有终端 31 setsid(); 32 33 //再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法在打开一个新的终端 34 pid =fork(); 35 if( pid > 0) { 36 exit(0); 37 } 38 else if ( pid <0 ) { 39 return -1; 40 } 41 //关闭所有从父进程继承的不再需要的文件描述符 42 for (i =0 ; i<NOFILE ;close(i++ )); 43 //改变工作目录,使进程不与任何文件系统联系 44 chdir("/"); 45 //将文件屏蔽字设置为0 46 umask(0); 47 48 //忽略SIGHLD信号 49 signal(SIGCHLD,SIG_IGN); 50 51 return 0; 52 } 53 54 55 int main() 56 { 57 time_t now; 58 init_daemon(); 59 syslog(LOG_USER | LOG_INFO ,"测试守护进程!\n"); 60 while (1){ 61 sleep(8); 62 time(&now ); 63 syslog(LOG_USER | LOG_INFO ,"系统时间:\t%s\t\t\n",ctime(&now)); 64 } 65 }
setsid()之后又加了一步fork()创建子进程exit()使父进程退出,原因是,此时的子进程已经成为会话组的组长,有权利再调出一个终端,如果出现此情况,则未达到完全脱离终端的目的,此时再调用fork并退出父进程,使得此时的子进程成为完全的后台进程,独立于任何的终端。