孤儿进程和守护进程

通过前面的学习我们了解了如何通过fork()函数和vfork()函数来创建一个进程。现在 我们继续深入来学习两个特殊的进程:孤儿进程和守护进程

一.孤儿进程

1.什么是 孤儿进程
如果一个子进程的父进程先于子进程 结束, 子进程就成为一个孤儿进程,它由 init 进程收养,成为 init 进程的子进程。
2.那么如何让一个进程变为一个孤儿进程呢?
我们可以先创建一个进程,然后杀死其父进程,则其就变成了孤儿进程。
pid =  fork();
if(pid > 0) {
                 exit(0);
}
3. 函数实例:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4 #include<stdlib.h>
5
6 int main()
7 {
8 pid_t pid;
9 pid = fork();
10 if(!pid){
11 while(1){
12 printf("A background process,PID:%d/n,ParentID:%d/n" ,getpid(),getppid());
13 sleep(3);
14
15 }
16 }
17 else if(pid > 0){
18 printf("I am parent process,my pid is %d/n",getpid() );
19 exit(0);
20 }
21 else {
22 printf("Process creation failed!/n");
23 }
24 return 0;
25
26 }
程序运行结果
I am parent process,my pid is 2026
A background process,PID:2027
,ParentID:2026
think@ubuntu:~/work/process_thread/fork2$ A background process,PID:2027
,ParentID:1
A background process,PID:2027
,ParentID:1
A background process,PID:2027
,ParentID:1
A background process,PID:2027
,ParentID:1
A background process,PID:2027
,ParentID:1
A background process,PID:2027
,ParentID:1
Tiger-John说明:
通过以上方法,就可以实现把一个进程变为孤儿进程 。 当要结束一个孤儿进程时只能在终端输入命令: kill  2027(kill 孤儿进程号)来结束其运行。

二守护进程

1 . 什么是守护进程呢?

( daemon) 是指在后台运行,没有控制终端与之相连的进程。它独立于控制终端,通常周期性地执行某种任务 。
Tiger-John说明: 那么,守护进程为什么要脱离后台去运行呢?
守护进程脱离于终端是为了避免进程在执行过程中的信息在任何终端上显示并且进程也不会被任何终端所产生的终端信息所打断
2. 为什么要引入守护进程:
由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。但是守护进程却能够突破这种限制,它从被执行开始运转,直到整个系统关闭时才退出。如果想让某个进程不因为用户或终端或其他地变化而受到影响,那么就必须把这个进程变成一个守护进程
3 .守护进程的特性
1>守护进程最重要的特性是后台运行 。
2>其次,守护进程必须与其运行前的环境隔离开来。这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程(特别是 shell )中继承下来的。
3>最后,守护进程的启动方式有其特殊之处。它可以在 Linux 系统启动时从启动脚本 /etc/rc.d 中启动,可以由作业规划进程 crond 启动,还可以由用户终端(通常是 shell )执行。
4. 守护进程的启动方式有多种:
a. 它可以在 Linux 系统启动时从启动脚本 /etc/rc.d 中启动
b. 可以由作业规划进程 crond 启动;
c. 还可以由用户终端(通常是 shell )执行。
Tiger-John 总结:
 守护进程是 Linux 中的后台服务进程。它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程常常在系统引导装入时启动,在系统关闭时终止。 Linux 系统有很多守护进程,大多数服务都是通过守护进程实现的,同时,守护进程还能完成许多系统任务,例如,作业规划进程 crond 、打印进程 lqd 等(这里的结尾字母 d 就是 Daemon 的意思)。
5. 如何编写守护进程呢

第一步:首先要做的是调用umask将文件模式创建屏蔽字设置为0。由继承得来的文件模式创建屏蔽字可能会拒绝设置某些权限。例如,若守护进程要创建一个组可读、写的文件,而继承的文件模式创建屏蔽字可能屏蔽了这两种权限,于是所要求的组可读、写就不能起作用。
第一步:创建子进程,父进程退出
1>. 由于守护进程是脱离控制终端的,因此,完成第一步后就会在 Shell 终端里造成一程序已经运行完毕的假象。之后的所有工作都在子进程中完成,而用户在 Shell 终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。
2> 在 Linux 中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤儿进程时就会由 1 号进程( init) 收养它。
方法是调用 fork 产生一个子进程,然后使得父进程退出
pid = fork();
if( 0 == pid)
exit(0); // 如果是父进程,就结束父进程,子进程结束。

这样就实现了下面几点:第一,如果该守护进程是作为一个简单shell命令启动的,那么父进程终止使得shell认为这条命令已经执行完毕;第二,子进程继承了父进程的进程组ID,但具有一个新的进程ID,这就保证了子进程不是一个进程组的组长进程。这对于下面就要做的setsid调用是必要的前提条件。
第二步:在子进程中创建新会话:
这个步骤是创建守护进程中最重要的一步,使用系统函数 setsid。使得调用进程:(a)成为新会话的首进程,(b)成为一个新进程组的组长进程,(c)没有控制终端。

Tiger-John 补充: 几个相关概念
a. 进程组:是一个或多个进程的集合。进程组有进程组 ID 来唯一标识。除了进程号( PID )之外,进程组 ID ( GID) 也是一个进程的必备属性。每个进程组都有一个组长进程,其组长进程的进程号等于进程组 ID 。且该进程组 ID 不会因组长进程的退出而受到影响。
b. 会话周期:会话期是一个或多个进程组的集合。通常,一个会话开始与用户登录,终止于用户退出,在此期间该用户运行的所用进程都属于这个会话期。
c. 登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。
Tiger-John 说明: 为什么要涉及它们呢?
因为控制终端,登录会话和进程组通常是从父进程继承下来的。我们就是要摆脱它们,使之不受它们的影响。
那么如何去实现呢,此时我们在第一步的基础上可以调用 setsid ()函数。
1>setsid 函数用于创建一个新的会话,并担任该会话组的组长。调用 setsid 有下面的 3 个作用:
让进程摆脱原会话的控制
让进程摆脱原进程组的控制
让进程摆脱原进程组的控制
让进程摆脱原控制终端的控制
2>. 在创建守护进程时为什么要调用 setsid 函数呢?
由于创建守护进程的第一步调用了 fork 函数来创建子进程,再将父进程退出。由于在调用了 fork 函数时,子进程全盘拷贝了父进程的会话期,进程组,控制终端等,虽然父进程退出了,但会话期,进程组,控制终端等并没有改变,因此,还不是真正意义上的独立开来,而 setsid 函数能够使进程完全独立出来,从而摆脱其他进程的控制。
Tiger-John 说明:
a. 当进程组是会话组长时 setsid() 调用失败。但是通过第一步已经保证了进程不是会话组长。
b.setsid( )调用成功后,进程成为新的会话组长和新的进程组长,并于原来的登录会话和进程组脱离由于会话过程对控制终端的独占性,进程同时与控制终端脱离
c. 此时我们还要禁止进程重新打开控制终端
进程虽然已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端
那么如何实现呢?
我们可以再次建立一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端
pid = fork() ;
exit(0) ;
第三步:改变当前目录为根目录
1>使用 fork 创建的子进程继承了父进程的当前工作目录。由于在进程运行中,当前目录所在的文件系统是不能卸载的,这对以后的使用会造成很多的不便。因此,我们一般是让” /” 作为守护进程的当前工作目录,这样就可以避免上述的问题。如果有特殊需要,也可以把当前工作目录换成其他的路径。
2>改变工作目录的常见函数是 chdir().
第四步:重设文件权限掩码
1>文件权限掩码是指屏蔽掉文件权限中的对应位。由于使用 fork 函数新建的子进程继承了父进程的文件权限掩码,这就给子进程使用文件带来了很多的麻烦。因此,把文件权限掩码设置为 0 ,可以很大程度上增强该守护进程的灵活性。
2>设置文件权限掩码的函数是 umask. 通常使用的方法是 umask(0).
第五步:关闭文件描述符
1> 因为用 fork 函数新建的子进程会从父进程那里继承一些已经打开了的文件。这些被打开的文件可能永远不会被守护进程读写,但它们一样消耗系统资源,而且可能导致所在的文件系统无法卸下。
2> 在上面的第二步之后,守护进程已经与所属的控制终端失去了联系。因此从终端输入的字符不可能到达守护进程,守护进程中常规方法(如 printf )输出的字符也不可能在终端上显示出来。所以,文件描述符为 0 , 1 和 2 的 3 个文件(常说的输入,输出和报错)已经失去了意义,也应该关掉。
3>函数实例:
for(i=0;i<MAXFILE;i++)
close(i);
第六步:处理 SIGCHLD 信号
1>处理 SIGCHLD 信号并不是必须的。但对于某些进程,特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束,子进程将成为僵尸进程( zombie) 从而占用系统资源。如果父进程等待子进程结束,将增加父进程的负担,影响服务进程的并发性能。
2>函数实现:
signal(SIGCHLD,SIG_IGN);
这样,内核在子进程结束时不会产生僵尸进程。

6具体函数实现:
编写一个守护进程要包括两部分:主程序 test.c 和初始化程序 init.c 。
初始化程序中的 init_daemon 函数负责生成守护进程。利用 init_daemon 函数可以生成自己的守护进程。

daemon.c

1 #include<stdio.h>
2 #include<signal.h>
3 #include<sys/param.h>
4 #include<sys/types.h>
5 #include<sys/stat.h>
6 #include<stdlib.h>
7
8 int init_daemon(void)
9 {

10         pid_t pid;
11         int i;

12
13         pid = fork();
14         if(pid > 0){          //第一步,结束父进程,使得子进程成为后台
15                 exit(0);
16         }
17         else if(pid < 0){
18                 return -1;
19         }
20 /*第二步建立一个新的进程组,在这个新的进程组中,子进程成为这个进程组的首进程,以使该进程脱离所用终端*/   
21         setsid();
22 /*再次新建一个子进程,退出父进程,保证该进程不是进程组长,同时让该进程无法再打开一个新的终端*/
 23         pid = fork();
 24         if(pid > 0){
 25                 exit(0);
 26         }
 27         else if(pid < 0){
 28                 return -1;
 29         }
 30 //第三步:关闭所用从父进程继承的不再需要的文件描述符
 31         for(i = 0;i < NOFILE;close(i++));
 32 //第四步:改变工作目录,使得进程不与任何文件系统联系
 33         chdir("/");
 34 //第五步:将文件屏蔽字设置为0
 35         umask(0);
 36 //第六步:忽略SIGCHLD信号
 37         signal(SIGCHLD,SIG_IGN);
 38         return 0;
 39 }

test.c
 1 #include<stdio.h>
 2 #include<time.h>
 3 #include<syslog.h>
 4 extern int init_daemon(void);
 5
 6 int main()
 7 {
 8         time_t now;
 9         init_daemon();//初始化Daemon
 10         syslog(LOG_USER | LOG_INFO,"测试守护进程!/n");
 11         while(1){
 12                 sleep(8);//睡眠一分钟
 13                 time(&now);
 14                 syslog(LOG_USER | LOG_INFO,"系统时间:/t%s/t/t/n",ctime(&now    ));
 15                 }
 16 }
 17

程序在 ubuntu 2 .6 版本上进过调试
think@ubuntu:/etc$ gcc -g -o test daemon.c test.c
think@ubuntu:/etc$ ./test
编译成功后可以用 ps -ef 查看进程状态,
think@ubuntu:/etc$ ps -ef
UID PID   PPID C   STIME   TTY TIME   CMD
think 2995 1    0   11:05    ? 00:00:00   ./test
从此处可以看出该进程具备守护进程的所用特征
查看系统日志
think@ubuntu:~$ cat /var/log/syslog
Nov 13 11:05:37 ubuntu test: 测试守护进程!
Nov 13 11:05:45 ubuntu test: 系统时间: #011Sat Nov 13 11:05:45 2010#012#011#011
Nov 13 11:05:53 ubuntu test: 系统时间: #011Sat Nov 13 11:05:53 2010#012#011#011
Nov 13 11:06:01 ubuntu test: 系统时间: #011Sat Nov 13 11:06:01 2010#012#011#011
Nov 13 11:06:09 ubuntu test: 系统时间: #011Sat Nov 13 11:06:09 2010#012#011#011
Nov 13 11:06:17 ubuntu test: 系统时间: #011Sat Nov 13 11:06:17 2010#012#011#011
Nov 13 11:06:25 ubuntu test: 系统时间: #011Sat Nov 13 11:06:25 2010#012#011

Linux进程学习(孤儿进程和守护进程)的更多相关文章

  1. Linux进程学习 - 孤儿进程和守护进程

    孤儿进程和守护进程 通过前面的学习我们了解了如何通过fork()函数和vfork()函数来创建一个进程.现在 我们继续深入来学习两个特殊的进程:孤儿进程和守护进程 一.孤儿进程 1.什么是 孤儿进程如 ...

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

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

  3. linux第1天 fork exec 守护进程

    概念方面 文件是对I/O设备的抽象表示.虚拟存储器是对主存和磁盘I/O设备的抽象表示.进程则是对处理器.主存和I/O设备的抽象表示 中断 早期是没有进程这个概念,当出现中断技术以后才出现进程这个概念 ...

  4. Linux 的进程组、会话、守护进程

    一.进程组ID 每个进程都属于一个进程组.每个进程组有一个领头进程.进程组是一个或多个进程的集合,通常它们与一组作业相关联,可以接受来自同一终端的各种信号.每个进程组都有唯一的进程组ID(整数,也可以 ...

  5. Linux系统编程(26)——守护进程

    Linux系统启动时会启动很多系统服务进程,比如inetd,这些系统服务进程没有控制终端,不能直接和用户交互.其它进程都是在用户登录或运行程序时创建,在运行结束或用户注销时终止,但系统服务进程不受用户 ...

  6. Python之路(第三十七篇)并发编程:进程、multiprocess模块、创建进程方式、join()、守护进程

    一.在python程序中的进程操作 之前已经了解了很多进程相关的理论知识,了解进程是什么应该不再困难了,运行中的程序就是一个进程.所有的进程都是通过它的父进程来创建的.因此,运行起来的python程序 ...

  7. LINUX编程学习笔记(十四) 创建进程与 父子进程内存空间

    1什么是进程:进程是一个执行中的程序 执行的程序: 代码->资源->CPU 进程有很多数据维护:进程状态/进程属性 所有进程属性采用的一个树形结构体维护 ps  -a//所有进程 ps - ...

  8. Docker学习笔记 - Docker的守护进程

    学习目标:  查看Docker守护进程的运行状态 启动.停止.重启Docker守护进程 Docker守护进程的启动选项 修改和查看Docker守护进程的启动选项 1.# 查看docker运行状态  方 ...

  9. 基于Linux环境,创建PHP后台守护进程(转载)

    应用场景:某些情况下,我们需要持续的周期性的提供一些服务,比如监控内存或cpu的运行状况,这些应用与客户端是没有关系的,不是说客户端(如web界面,手机app等)关闭了,我们就不监控内存或cpu了,为 ...

  10. Linux+Nginx+Asp.net Core及守护进程部署

    上篇<Docker基础入门及示例>文章介绍了Docker部署,以及相关.net core 的打包示例.这篇文章我将以oss.offical.site站点为例,主要介绍下在linux机器下完 ...

随机推荐

  1. Mac Pro的HDMI接口与WI-FI可能存在冲突的解决方法

    当我将Mac Pro通过HDMI->DVI转接头接上一台显示器时,正在使用的WI-FI网络立马不能使用,重新连接网络也不行,但断开HDMI连接后,WI-FI立马恢复正常. 在网上查,在apple ...

  2. JavaScript 自定义单元测试

    <!doctype html> <html> <head> <meta charset="utf-8"> <script> ...

  3. Noah的学习笔记之Python篇:函数“可变长参数”

    Noah的学习笔记之Python篇: 1.装饰器 2.函数“可变长参数” 3.命令行解析 注:本文全原创,作者:Noah Zhang  (http://www.cnblogs.com/noahzn/) ...

  4. Linux内核监控模块-3-系统调用的截获

    上一章,我们获取了系统调用表的地址,这里我们来搞点所谓“截获”的事情.所谓“截获”即是将系统调用表里的地址指向我们自己写的一个函数,系统调用先执行我们自己写的函数,处理完后,再返回原来系统调用的执行函 ...

  5. Data guard RAC配置【二】

    2. 利用duplicate配置容灾端 1.配置容灾端oracle用户的环境变量,这里以192.166.1.61为例. export ORACLE_BASE=/opt/oracle export OR ...

  6. java cpu缓存

    众所周知, CPU是计算机的大脑, 它负责执行程序的指令; 内存负责存数据, 包括程序自身数据. 同样大家都知道, 内存比CPU慢很多. 其实在30年前, CPU的频率和内存总线的频率在同一个级别, ...

  7. python 大文件以行为单位读取方式比对

    http://www.cnblogs.com/aicro/p/3371986.html 先前需要做一个使用python读取大文件(大于1G),并逐条存入内存进行处理的工作.做了很多的尝试,最终看到了如 ...

  8. 14.8.3 Physical Row Structure of InnoDB Tables InnoDB 表的物理行结构

    14.8.3 Physical Row Structure of InnoDB Tables InnoDB 表的物理行结构 一个InnoDB 表的物理行结构取决于在创建表指定的行格式 默认, Inno ...

  9. latch free

    latch free 等待事件: latch: cache buffers chains 这个等待事件其实还有另外一个重要的原因,那么就是逻辑读太高,SQL执行计划走错了导致的. 当进程想要获取锁存器 ...

  10. 译文:TransactionScope 与 Async/Await

    你可能不知道这一点,在 .NET Framework 4.5.0  版本中包含有一个关于 System.Transactions.TransactionScope 在与 async/await 一起工 ...