linux编程-守护进程编写

守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很有用的进程。

Linux的大多数服务器就是用守护进程实现的。比如,Internet服务器inetd,Web服务器httpd等。同时,守护进程完成许多系统任务。比如,作业规划进程crond,打印进程lpd等。

守护进程的编程本身并不复杂,复杂的是各种版本的Unix的实现机制不尽相同,造成不同 Unix环境下守护进程的编程规则并不一致。

需要注意,照搬某些书上的规则(特别是BSD4.3和低版本的System V)到Linux会出现错误的。

守护进程及其特性 
  守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之相似。其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是shell)中继承下来的。最后,守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动,可以由作业规划进程crond启动,还可以由用户终端(通常是shell)执行。 
总之,除开这些特殊性以外,守护进程与普通进程基本上没有什么区别。因此,编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果读者对进程有比较深入的认识就更容易理解和编程了。

基本概念及特性

进程:

系统进行资源分配和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

  1. void setupSignal(void)
  2. {
  3. signal( SIGTERM, SIG_IGN );
  4. signal( SIGINT, SIG_IGN );
  5. signal( SIGPIPE, SIG_IGN );
  6. signal( SIGHUP, SIG_IGN );
  7. signal( SIGTTOU, SIG_IGN );
  8. signal( SIGTTIN, SIG_IGN );
  9. signal( SIGTSTP, SIG_IGN );
  10. signal( SIGCHLD, SIG_IGN );//设置忽略此信号,以为着子进程退出的时候,不需要父进程处理子进程的僵尸状态,而是由init进程(pid=1)负责收尸
  11. }
  1. #include<unistd.h>
  2. #include<signal.h>
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<sys/param.h>
  6. #include<sys/types.h>
  7. #include<sys/stat.h>
  8. #include<time.h>
  9.  
  10. void init_daemon()
  11. {
  12. int pid;
  13. int i;
  14.  
  15. // for(i=0;i<NOFILE;i++)
  16. // close(i);
  17.  
  18. printf("parent\n");
  19. printf("pid[%d]\n",getpid());
  20. printf("ppid[%d]\n",getppid());
  21. printf("gid[%d]\n",getpgrp());
  22. printf("sid[%d]\n",getsid());
  23. printf("tcid[%d]\n",tcgetpgrp());
  24.  
  25. printf("\n\n");
  26. pid=fork();
  27.  
  28. if(pid<)
  29. exit();//使得子进程一定不是进程组组长,这样才能调用setsid,建立新的进程组和会话组
  30. else if(pid>)
  31. exit();
  32.  
  33. printf("child 1\n");
  34. printf("pid[%d]\n",getpid());
  35. printf("ppid[%d]\n",getppid());
  36. printf("gid[%d]\n",getpgrp());
  37. printf("sid[%d]\n",getsid());
  38. printf("tcid[%d]\n",tcgetpgrp());
  39. printf("\n\n");
  40. else if(pid>)
  41. exit();
  42.  
  43. printf("child 1\n");
  44. printf("pid[%d]\n",getpid());
  45. printf("ppid[%d]\n",getppid());
  46. printf("gid[%d]\n",getpgrp());
  47. printf("sid[%d]\n",getsid());
  48. printf("tcid[%d]\n",tcgetpgrp());
  49. printf("\n\n");
  50. setsid(); //建立新的进程组和会话组,并成为新的进程组的组长,和回话组的组长
  51. printf("setsid child 1\n");
  52. printf("pid[%d]\n",getpid());
  53. printf("ppid[%d]\n",getppid());
  54. printf("gid[%d]\n",getpgrp());
  55. printf("sid[%d]\n",getsid());
  56. printf("tcid[%d]\n",tcgetpgrp());
  57. printf("\n\n");
  58. pid=fork();
  59. if(pid<)
  60. exit();
  61. else if(pid>)
  62. exit();//使得孙子进程不在是进程组的组长,即没有权限建立新的与回话组绑定的控制终端
  63.  
  64. printf("child 2\n");
  65. printf("pid[%d]\n",getpid());
  66. printf("ppid[%d]\n",getppid());
  67. printf("gid[%d]\n",getpgrp());
  68. printf("sid[%d]\n",getsid());
  69. printf("tcid[%d]\n",tcgetpgrp());
  70. printf("\n\n");
  71. //关闭文件描述符,这样进程不在与文件描述符传递数据,比如printf打印的数据不在现在是终端界面中
  72. for(i=;i<NOFILE;i++)
  73. close(i);
  74.  
  75. printf("close fd\n");
  76. chdir("/home/cz/Desktop/mcs/"); //切换工作目录
  77. printf("cd\n");
  78. umask();//清除文件掩膜
  79. printf("umask\n");
  80. }
  81.  
  82. void main()
  83. {
  84. FILE *fp;
  85. time_t t;
  86.  
  87. printf("%s\n","start");
  88. init_daemon();
         setupSignal();
  89. while()
  90. {
  91.  
  92. printf("%s\n","run");
  93. sleep();
  94. printf("hello\n");
  95. fp=fopen("test.log","a");
  96. //if(fp>=0)
  97. //{
  98. time(&t);
  99. printf("current time is:%s\n",asctime(localtime(&t))); //}
  100. }
  101. return ;
  102. }
  1. cz@ubuntu:~/Desktop/mcs$ ./demaontest
  2. start
  3. parent
  4. pid[]
  5. ppid[]
  6. gid[]
  7. sid[]
  8. tcid[]
  9.  
  10. cz@ubuntu:~/Desktop/mcs$ child
  11. pid[]
  12. ppid[]
  13. gid[]
  14. sid[]
  15. tcid[]
  16.  
  17. setsid child
  18. pid[]
  19. ppid[]
  20. gid[]
  21. sid[]
  22. tcid[-]
  23.  
  24. child
  25. pid[]
  26. ppid[]
  27. gid[]
  28. sid[]
  29. tcid[-]

由以上实验结果就可以清晰的明白,守护进程化过程中,每一步的作用。

查看/tmp下的test.log的,可以看到守护进程在不断运行。

经典C/S服务器模型之守护进程的更多相关文章

  1. Linux进程实践(5) --守护进程

    概述 守护进程是在需要在后台长期运行不受终端控制的进程,通常情况下守护进程在系统启动时自动运行,在服务器关闭的时候自动关闭:守护进程的名称通常以d结尾,比如sshd.xinetd.crond.atd等 ...

  2. day34 python学习 守护进程,线程,互斥锁,信号量,生产者消费者模型,

    六 守护线程 无论是进程还是线程,都遵循:守护xxx会等待主xxx运行完毕后被销毁 需要强调的是:运行完毕并非终止运行 #1.对主进程来说,运行完毕指的是主进程代码运行完毕 #2.对主线程来说,运行完 ...

  3. hadoop不能互相访问和linux防火墙守护进程

    前言——作为装过几次集群的菜鸟,对于hadoop集群的安装还是比较有心得的:只要配置文件够好,集群配置就非常容易,否则也容易出现莫名其妙的问题!总结了一份3台机器搭建较完好的集群的一份配置文件. 在我 ...

  4. Linux Supervisor 守护进程基本配置

    supervisor:C/S架构的进程控制系统,可使用户在类UNIX系统中监控.管理进程.常用于管理与某个用户或项目相关的进程. 组成部分supervisord:服务守护进程supervisorctl ...

  5. Linux学习笔记(9)-守护进程

    明天学这个!! ---------------------------------------------------------- 守护进程(Daemon)是运行在后台的一种特殊进程.它独立于控制终 ...

  6. ASP.NET Core Linux下为 dotnet 创建守护进程(必备知识)

    前言 在上篇文章中介绍了如何在 Docker 容器中部署我们的 asp.net core 应用程序,本篇主要是怎么样为我们在 Linux 或者 macOs 中部署的 dotnet 程序创建一个守护进程 ...

  7. linux系统编程之进程(八):守护进程详解及创建,daemon()使用

    一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...

  8. linux下的守护进程daemon

    什么是守护进程?其实感觉守护进程并没有什么明确的定义,只是守护进程有一些特征,这是它需要遵循的. 守护进程的第一个特征是长时间在后台运行的程序,并且主要是为了提供某种服务,而为了能够让服务尽可能随时都 ...

  9. cloudera learning3:Hadoop配置和守护进程logs

    Services:Haddoop cluster上可以部署的组件,比如HDFS,YARN,HBase等. Roles:在service配置时,由Cloudera Manager创建.比如NameNod ...

随机推荐

  1. 修复Thinkphp框架5.0和5.1版本的远程代码执行安全漏洞

    由于框架对控制器名没有进行足够的检测会导致在没有开启强制路由的情况下可能的getshell漏洞.最直接的影响为index.php直接被篡改成首页html的内容! 5.0版本 thinkphp/libr ...

  2. PAT_A1125#Chain the Ropes

    Source: PAT A1125 Chain the Ropes (25 分) Description: Given some segments of rope, you are supposed ...

  3. C#第十三节课

    冒泡排序 using System;using System.Collections.Generic;using System.Linq;using System.Text;using System. ...

  4. 三剑客基础详解(grep、sed、awk)

    目录 三剑客基础详解 三剑客之grep详解 1.通配符 2.基础正则 3.grep 讲解 4.拓展正则 5.POSIX字符类 三剑客之sed讲解 1.sed的执行流程 2.语法格式 三剑客之Awk 1 ...

  5. ldap 用户组和用户(4)

    Posixgroup用户组属性 默认情况下openldap的用户组属性是Posixgroup,Posixgroup用户组属性和用户没有实际的对应关系.如果我们一定要把Posixgroup和user对应 ...

  6. 2.Git可视化操作

    1.在本地新建版本库 首先,我们打开Git GUI是这样的一个界面,选择第一项,新建版本库. 然后选择你需要进行版本管理的项目路径,我选择了一个LoginDemo的项目. 当你创建了版本库的时候,你可 ...

  7. 转载 - 汇编--INT 10H功能

    出处:http://www.cnblogs.com/magic-cube/archive/2011/10/19/2217676.html INT 10H 是由 BIOS 对屏幕及显示器所提供的服务程序 ...

  8. 0809MySQL-InnoDB Compact 行记录格式

    InnoDB存储引擎提供了compact(5.1后的默认格式)和redundant两个格式来存放行记录数据.redundant格式是为了兼容之前的版本而保留. mysql> show table ...

  9. java中类的路径为什么这么长

  10. Linux Storage Stack Diagram 4.0

    https://www.thomas-krenn.com/en/wiki/Linux_Storage_Stack_Diagram