linux编程-守护进程编写html
守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端而且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种颇有用的进程。 linux
Linux的大多数服务器就是用守护进程实现的。好比,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。好比,做业规划进程crond,打印进程lpd等。shell
守护进程的编程自己并不复杂,复杂的是各类版本的Unix的实现机制不尽相同,形成不一样 Unix环境下守护进程的编程规则并不一致。编程
须要注意,照搬某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。ubuntu
守护进程及其特性
守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之类似。其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工做目录以及文件建立掩模等。这些环境一般是守护进程从执行它的父进程(特别是shell)中继承下来的。最后,守护进程的启动方式有其特殊之处。它能够在Linux系统启动时从启动脚本/etc/rc.d中启动,能够由做业规划进程crond启动,还能够由用户终端(一般是shell)执行。
总之,除开这些特殊性之外,守护进程与普通进程基本上没有什么区别。所以,编写守护进程其实是把一个普通进程按照上述的守护进程的特性改形成为守护进程。若是读者对进程有比较深刻的认识就更容易理解和编程了。服务器
基本概念及特性网络
进程:session
系统进行资源分配和CPU调度的单位.函数getpid能够获得进程的进程ID:pid_t getpid(void);函数getppid能够获得进程的父进程ID:pid_t getppid(void);并发
① 每一个进程都有一个父进程函数
② 当子进程终止时,父进程会获得通知并能取得子进程的退出状态.
进程组:
进程组是一个或多个进程的集合。它们与同一做业相关联,能够接受来自同一终端的各类信号。每一个进程组都有惟一的进程组ID。函数getpgrp能够获得进程的进程组ID。
pid_t getpgrp(void);
每一个进程组均可以有一个组长进程。组长进程的标识是,其进程组ID等于进程ID。
① 每一个进程也属于一个进程组。
② 每一个进程主都有一个进程组号,该号等于该进程组组长的PID号
③ 一个进程只能为它本身或子进程设置进程组ID号
会话期:
对话期(session)是一个或多个进程组的集合。函数getsid返回会话首进程的进程组ID。此函数是Single UNIX Specification的XSI扩展。pid_t getsid(pid_t pid);
若是pid是0,返回调用进程的会话首进程的进程组ID。若是pid并不属于调用者所在的会话,那么调用者就不能获得该会话首进程的进程组ID。
① setsid()函数能够创建一个对话期:
② 若是,调用setsid的进程不是一个进程组的组长,此函数建立一个新的会话期。
(1)此进程变成该新的对话期的首进程
(2)此进程变成一个新进程组的组长进程。
(3)此进程没有控制终端,若是在调用setsid前,该进程有控制终端,那么与该终端的联系被解除。若是该进程是一个进程组的组长,此函数返回错误。
(4)为了保证这一点,咱们先调用fork()而后exit(),此时只有子进程在运行,子进程继承了父进程的进程组ID,可是进程PID倒是新分配的,因此不多是新会话的进程组的PID。
控制终端:
linux是一个多用户多任务的分时操做系统,必需要支持多个用户同时登录同一个操做系统,当一个用户登录一次终端时就会产生一个会话,
每一个会话有一个会话首进程,即建立会话的进程,创建与终端链接的就是这个会话首进程,也被称为控制进程。
pid_t tcgetpgrp(int filedes);
函数tcgetpgrp返回前台进程组的进程组ID,该前台进程组与在filedes上打开的终端相关联;若是进程有一个控制终端,则该进程能够调用tcsetpgrp将前台进程组ID设置为pgrpid,pgrpid的值应该是在同一会话中的一个进程组的ID,filedes必须引用该会话的控制终端。
下图能够表示以上四者的基本关系:
会话和进程组有一些特性:
1). 一个会话能够有一个控制终端(controlling terminal)。
2). 创建与控制终端链接的会话首进程被称为控制进程(controlling process)。
3). 一个会话中的几个进程组可被分红一个前台进程组(forkground process group)和几个后台进程组(background process group)。
4). 若是一个会话有一个控制终端,则它有一个前台进程组。
5). 不管什么时候键入终端的中断键(DELETE或Ctrl+C),就会将中断信号发送给前台进程组的全部进程。
6). 不管什么时候键入终端的退出键(Ctrl+\),就会将退出信号发送给前台进程组的全部进程。
7). 若是终端检测到调制解调器(或网络)已经断开链接,则将挂断信号发送给控制进程(会话首进程)。
下边就以守护进程的实际代码运行,辅助理解。
守护进程的编程要点
前面讲过,不一样Unix环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则其实都同样,区别在于具体的实现细节不一样。这个原则就是要知足守护进程的特性。同时,Linux是基于Syetem V的SVR4并遵循Posix标准,实现起来与BSD4相比更方便。编程要点以下;
1. 在后台运行。
为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止,让Daemon在子进程中后台执行。
if(pid=fork())
exit(0);//是父进程,结束父进程,子进程继续
2. 脱离控制终端,登陆会话和进程组
有必要先介绍一下Linux中的进程与控制终端,登陆会话和进程组之间的关系:进程属于一个进程组,进程组号(GID)就是进程组长的进程号(PID)。登陆会话能够包含多个进程组。这些进程组共享一个控制终端。这个控制终端一般是建立进程的登陆终端。
控制终端,登陆会话和进程组一般是从父进程继承下来的。咱们的目的就是要摆脱它们,使之不受它们的影响。方法是在第1点的基础上,调用setsid()使进程成为会话组长:
setsid();
说明:当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后,进程成为新的会话组长和新的进程组长,并与原来的登陆会话和进程组脱离。因为会话过程对控制终端的独占性,进程同时与控制终端脱离。
3. 禁止进程从新打开控制终端
如今,进程已经成为无终端的会话组长。但它能够从新申请打开一个控制终端。能够经过使进程再也不成为会话组长来禁止进程从新打开控制终端:
if(pid=fork())
exit(0);//结束第一子进程,第二子进程继续(第二子进程再也不是会话组长)
4. 关闭打开的文件描述符
进程从建立它的父进程那里继承了打开的文件描述符。如不关闭,将会浪费系统资源,形成进程所在的文件系统没法卸下以及引发没法预料的错误。按以下方法关闭它们:
for(i=0;i 关闭打开的文件描述符close(i);>
5. 改变当前工做目录
进程活动时,其工做目录所在的文件系统不能卸下。通常须要将工做目录改变到根目录。对于须要转储核心,写运行日志的进程将工做目录改变到特定目录如/tmpchdir("/")
6. 重设文件建立掩模
进程从建立它的父进程那里继承了文件建立掩模。它可能修改守护进程所建立的文件的存取位。为防止这一点,将文件建立掩模清除:umask(0);
7. 处理SIGCHLD信号
处理SIGCHLD信号并非必须的。但对于某些进程,特别是服务器进程每每在请求到来时生成子进程处理请求。若是父进程不等待子进程结束,子进程将成为僵尸进程(zombie)从而占用系统资源。若是父进程等待子进程结束,将增长父进程的负担,影响服务器进程的并发性能。在Linux下能够简单地将SIGCHLD信号的操做设为SIG_IGN。
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不一样,BSD4下必须显式等待子进程结束才能释放僵尸进程。
关于信号的处理此处作一些补充:参考此博客:http://www.cnblogs.com/hoys/archive/2012/08/19/2646377.html
void setupSignal(void) { signal( SIGTERM, SIG_IGN ); signal( SIGINT, SIG_IGN ); signal( SIGPIPE, SIG_IGN ); signal( SIGHUP, SIG_IGN ); signal( SIGTTOU, SIG_IGN ); signal( SIGTTIN, SIG_IGN ); signal( SIGTSTP, SIG_IGN ); signal( SIGCHLD, SIG_IGN );//设置忽略此信号,觉得着子进程退出的时候,不须要父进程处理子进程的僵尸状态,而是由init进程(pid=1)负责收尸 }
#include<unistd.h> #include<signal.h> #include<stdio.h> #include<stdlib.h> #include<sys/param.h> #include<sys/types.h> #include<sys/stat.h> #include<time.h> void init_daemon() { int pid; int i; // for(i=0;i<NOFILE;i++) // close(i); printf("parent\n"); printf("pid[%d]\n",getpid()); printf("ppid[%d]\n",getppid()); printf("gid[%d]\n",getpgrp()); printf("sid[%d]\n",getsid(0)); printf("tcid[%d]\n",tcgetpgrp(0)); printf("\n\n"); pid=fork(); if(pid<0) exit(1);//使得子进程必定不是进程组组长,这样才能调用setsid,创建新的进程组和会话组 else if(pid>0) exit(0); printf("child 1\n"); printf("pid[%d]\n",getpid()); printf("ppid[%d]\n",getppid()); printf("gid[%d]\n",getpgrp()); printf("sid[%d]\n",getsid(0)); printf("tcid[%d]\n",tcgetpgrp(0)); printf("\n\n"); else if(pid>0) exit(0); printf("child 1\n"); printf("pid[%d]\n",getpid()); printf("ppid[%d]\n",getppid()); printf("gid[%d]\n",getpgrp()); printf("sid[%d]\n",getsid(0)); printf("tcid[%d]\n",tcgetpgrp(0)); printf("\n\n"); setsid(); //创建新的进程组和会话组,并成为新的进程组的组长,和回话组的组长 printf("setsid child 1\n"); printf("pid[%d]\n",getpid()); printf("ppid[%d]\n",getppid()); printf("gid[%d]\n",getpgrp()); printf("sid[%d]\n",getsid(0)); printf("tcid[%d]\n",tcgetpgrp(0)); printf("\n\n"); pid=fork(); if(pid<0) exit(1); else if(pid>0) exit(0);//使得孙子进程不在是进程组的组长,即没有权限创建新的与回话组绑定的控制终端 printf("child 2\n"); printf("pid[%d]\n",getpid()); printf("ppid[%d]\n",getppid()); printf("gid[%d]\n",getpgrp()); printf("sid[%d]\n",getsid(0)); printf("tcid[%d]\n",tcgetpgrp(0)); printf("\n\n"); //关闭文件描述符,这样进程不在与文件描述符传递数据,好比printf打印的数据不在如今是终端界面中 for(i=0;i<NOFILE;i++) close(i); printf("close fd\n"); chdir("/home/cz/Desktop/mcs/"); //切换工做目录 printf("cd\n"); umask(0);//清除文件掩膜 printf("umask\n"); } void main() { FILE *fp; time_t t; printf("%s\n","start"); init_daemon();
setupSignal(); while(1) { printf("%s\n","run"); sleep(1); printf("hello\n"); fp=fopen("test.log","a"); //if(fp>=0) //{ time(&t); printf("current time is:%s\n",asctime(localtime(&t))); //} } return 0; }
cz@ubuntu:~/Desktop/mcs$ ./demaontest start parent pid[2724] ppid[1821] gid[2724] sid[1821] tcid[2724] cz@ubuntu:~/Desktop/mcs$ child 1 pid[2725] ppid[1] gid[2724] sid[1821] tcid[1821] setsid child 1 pid[2725] ppid[1] gid[2725] sid[2725] tcid[-1] child 2 pid[2726] ppid[1] gid[2725] sid[2725] tcid[-1]
由以上实验结果就能够清晰的明白,守护进程化过程当中,每一步的做用。
查看/tmp下的test.log的,能够看到守护进程在不断运行。