Linux进程学习总结

合集下载
  1. 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
  2. 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
  3. 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。

Linux进程学习总结
目录
目录 (1)
基本概念 (1)
fork()和vfork()的学习 (9)
孤儿进程和守护进程 (21)
exit()和_exit()函数 (32)
等待进程结束wait()和waitpid()函数 (37)
进程控制函数之exec()函数的学习 (49)
基本概念
最近一周学习了Linux 进程编程的知识,现对其总结如下。

在第一部分中我们先对进程的基本概念以及在Linux 中是如何来现实进程的进行介绍
Tiger-John说明:
许多人在学习中只注重如何编程,却忘了注重原理,不去深究其基本原理。

其实操作系统的原理就好比金庸武侠小说的内功一样,而所有的具体实现如:Linux操作系统,uc/os操作系统都只是武功招式而已。

如果我们内功学的很好的话,再来学习具体的实现过程是很快的。

而且也会对其知识有更加本质的了解。

一、进程的基本概念:
1.为什么计算机操作系统要引进进程:
在操作系统中引入进程的目的是为了使多个程序并发执行,以改善资源利用率及提高系统吞吐量。

2.进程的概念:
进程是程序的一次执行,进程是拥有资源的最小单位和调度单位(在引入线程的操作系统中,
线程是最小的调度单位)
3.进程由什么组成
进程由进程控制块(PCB),数据,程序3部分组成。

其中PCB是进程的灵魂。

4.进程的状态:
进程的三种最基本的状态是:运行态(running),就绪态(readying), 阻塞态(block)
5.进程和程序的区别:
进程和程序的主要区别是进程是动态的,程序是静态的。

进程时运行中的程序,程序是一些保存在硬盘上的可执行的代码。

6.进程的优点和缺点
(任何事物都是有其两面性。

我们在学习的时候要注意其优点和缺点。

人们也就再发现事物缺点的过程中,不断的去改善它,从而引入了新的事物。

在操作系统的学习过程中,我们会发现很多这样的例子。

人们在不断追求完美的过程中,不断的引入新的知识点--进程和线程的出现就足可以说明这一切)
优点:使多个程序并发执行
缺点:程序并发执行时付出了巨大的时空开销,每个进程在进行切换时身上带了过多的“累赘”导致系统效率降低。

于是人们为了解决这个缺点想到让进程在并行时不拥有资源---从而引入了线程的概念:即线程本身不拥有资源或者是很少的资源,进程只是拥有资源的基本单位,线程是调度的基本单位
7.线程的引入:
在操作系统中引入线程则是为了减少程序并发执行时所付出的时空开销,使操作系统具有更好的并发性。

二、Linux中是如何具体实现进程和线程
1.在linux中通过task_struct结构体来描述进程的PCB,我们可以在include/linux/sched.h中看
1>linux中的进程状态
a.运行状态:进程正在运行或在运行队列中等待运行。

b.可中断等待状态:进程正在等待某个事件完成(如等待数据到达)。

等待过程中可以被信号或定时器唤醒。

c.不可中断等待状态:进程正在等待某个事件完成并且等待中不可以被信号或定时器唤醒,必须一直等待到事件发生。

d.僵死状态:进程已终止,但进程描述符依然存在,直到父进程调用wait()函数后释放。

e.停止状态:进程因为收到SINSTOP,SIGSTP,SIGTIN,SGIOU信号后停止运行或者该进程正在被跟踪。

Tiger-john说明:
1在include/linux/sched.h 中我们可以看到Linxu中进程状态的具体实现:
#define TASK_RUNNING 0
#define TASK_INTERRUPTIBLE 1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE 4
#define TASK_STOPPED 8
其中:
TASK_RUNNING是就绪态,进程当前只等待CPU资源。

TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE都是阻塞态,进程当前正在等待除CPU外的其他系统资源;前者可以被信号唤醒,后者不可以。

TASK_ZOMBIE是僵尸态,进程已经结束运行,但是进程控制块尚未注销。

TASK_STOPPED是挂起状态,主要用于调试目的。

进程接收到SIGSTOP信号后会进入该状态,在接收到SIGCONT后又会恢复运行。

2.我们可以在终端中通过命令ps或pstree查看当前系统中的进程
用ps命令可以查看进程的当前状态。

运行状态为R,可中断等待状态为S,不可中断等待状态为D,僵死状态为Z,停止状态为T。

实例:
think@Ubuntu:~$ ps -eo pid,stat
PID STA T
1 Ss
2 S
3 S
37 SN
364 Ss
371 S<
442 S<s
1060 Sl
1081 Ssl
1085 Ssl
1203 Ss+
3782 Ss
3803 R+
Tiger-John说明:
在运行结果中有一些后缀字符,其意义分别为< (高优先级进程),N(低优先级进程),L(内存锁页,即页不可以被换出内存),s(该进程为会话首进程),l(多线程进程),+(进程位于前台进程组)。

例如:Ssl说明该进程处于可中断等待状态,且该进程为会话首进程,而且是一个多线程的进程。

2.linux系统的进程间通信有哪几种方式
1>管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。

进程的亲缘关系通常是指父子进程关系。

2>有名管道(named pipe) :有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。

3>信号量( semophore ) :信号量是一个计数器,可以用来控制多个进程对共享资源的访问。

它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。

因此,主要作为进程间以及同一进程内不同线程之间的同步手段。

4>消息队列( message queue ) :消息队列是消息的链表,存放在内核中并由消息队列标识符标识。

消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。

5> 信号( sinal ) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。

6> 共享内存( shared memory ) :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。

共享内存是最快的IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。

它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。

7>套接字( socket ) :套解口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

3.进程控制
1>linux进程控制包括创建进程,执行进程,退出进程以及改变进程优先级等。

在linux系统中,用于对进程进行控制的系统调用有:
a.fork:用于创建一个新进程。

b.exit :用于终止进程
c.exec :用于执行一个应用程序
d.wait :将父进程挂起,等待子进程终止
e.getpid :获取当前进程的进程ID
f.nice :该变进程的优先级
4.进程标识
1>Linux操作系统中,每个进程都是通过唯一的进程ID标识的。

进程ID 是一个非负数。

每个进程除了进程ID外还有一些其它信息,都可以通过相应的函数获得。

2>主要的函数有:
pid_t getpid(void) :获得进程ID
pid_t getppid(void):获得进程父进程的ID
pid_t getuid(void) :获得进程的实际用户ID
pid_t geteuid(void) :获得进程的有效用户ID
pid_t getgid(void) : 获得进程的实际组ID
pid_t getegid(void) 获得进程的有效组ID
Tiger-Johen说明:
这些函数的声明在sys/types.h和unistd.h 头文件中。

a.实际用户ID(uid) :标识运行该进程的用户
b.有效用户ID(euid): 标识以什么用户身份来运行进程。

例如:一个普通用户A,运行了一个程序,而这个程序是以root 身份来运行的,着程序运行时就具有root 权限。

此时,实际用户ID时A用户的ID,而有效用户ID是root用户ID 3>函数实例:
表头文件:#include <unistd.h>
#include<sys/types.h>
函数定义:pid_t getpid(void)
函数说明:getpid()用来取得目前进程的进程识别码,许多程序利用取到的此值来建立临时文件,以避免临时文件相同带来的问题
返回值:目前进程的进程识别码
函数实例:
#include<stdio.h>
#include<sys/types.h>
#include <unistd.h>
main()
{
printf("pid = %d\n", getpid());
}
三.进程的内存映像
1.Linux下程序转化成进程
a.Linux下C程序的生成分为4个阶段:
预编译
编译
汇编
链接
Tiger-Johen说明:
编译器gcc进过预编译,编译,汇编3个步骤将源程序文件转换为目标文件。

b.当程序执行时,操作系统将可执行程序复制到内存中。

程序转化为进程通常需要经过以下步骤:
内核将程序读入内存,为程序分配内存空间
内核为该进程分配进程标识符(PID)和其他资源
内核为该进程保存PID及相应的状态信息,把进程放到运行队列中等待执行。

程序转化为进程后就可以被操作系统的调度程序执行了。

2.进程的内存映像
a.进程的内存映像是指内核在内存中如何存放可执行程序文件。

在将程序转化为进程的过程中,操作系统将可执行程序由硬盘复制到内存中。

b.linux下程序映像的一般布局如下:(从低地址到高地址)
1>代码段:代码段是只读的,可被多个进程共享。

2>数据段:存储已被初始化的变量,包括全局变量和已被初始化的静态变量。

3>未初始化数据段:存储未被初始化的静态变量,它也被称为bss段
4>堆:用于存放程序运行中动态分配的变量
5>栈:用户函数调用,保存函数的返回地址,函数的参数,函数内部定义的局部变量。

Tiger-Johen说明:
可执行程序和内存映像的区别:
a.可执行程序位于磁盘中而内存映像位于内存中;
b.可执行程序没有堆栈,因为程序被加载到内存中才会分配堆栈;
c.可执行程序虽然也有未初始化数据段但它并不被储存在位于硬盘中的可执行文件中;
d.可执行程序时静态的,不变的,而内存映像随着程序的执行时在动态变化的
fork()和vfork()的学习
通过上一部分的学习,我们了解了进程的概念以及在Linux中进程的实现,此部分我们将具体学习如何在Linux中创建一个进程。

一、前言:
通过原理知识的学习,我们知道每个进程由进程ID号标识。

进程被创建时系统会为其分配一个唯一的进程ID号。

当一个进程向其父进程(创建该进程的进程)传递其终止消息时,意味这个进程的整个生命周期结束。

此时,该进程占用的所用资源包括进程ID被全部释放。

那么在Linux中如何创建一个进程呢?
创建进程有两种方式:一是由操作系统创建,二是由父进程创建的进程(通常为子进程)。

系统调用fork是创建一个新进程的唯一方式。

vfork也可创建进程,但它实际上还是调用了fork函数。

Tiger-John 说明:
1.由操作系统创建的进程它们之间是平等的不存在资源继承关系。

2.由父进程创建的进程通常为子进程它们之间有继承关系。

3. 在系统启动时,OS会创建一些进程,它们承担着管理和分配系统资源的任务,即系统进程。

如0号idle进程,它是从无到有诞生的第一个线程,主要用于节能;关于idle进程,系统最初引导0号进程,对应的PCB为init_task(),要说明下它是0号进程PCB的头,并不是1号init进程,在引导结束后即成为cpu 上的idle进程。

在每个cpu上都有一个idle 进程,这些进程登记在init_tasks[]数组中。

idle进程不进入就绪队列,系统稳定后,仅当就绪队列为空的时候idle 进程才会被调度到,在没有其它进程运行的情况下,它大量时间占用cpu 。

1号进程(init进程)它是一个由内核启动的用户级进程,它是所有用户进程的父进程。

实际上,Linux2.6在初始化阶段首先把它建立为一个内核线程kernel_init:
kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
参数CLONE_FS | CLONE_FILES | CLONE_SIGHAND表示0号线程和1号线程分别共享文件系统(CLONE_FS)、打开的文件(CLONE_FILES)和信号处理程序(CLONE_SIGHAND)。

当调度程序选择到kernel_init内核线程时,kernel_init就开始执行内核的一些初始化函数将系统初始化。

那么,kernel_init()内核线程是怎样变为用户进程的呢?
实际上,kernel_init()内核函数中调用了execve()系统调用,该系统调用装入用户态下的可执行程序init(/sbin/init)。

注意,内核函数kernel_init()和用户态下的可执行文件init是不同的代码,处于不同的位置,也运行在不同的状态,因此,init是内核线程启动起来的一个普通的进程,这也是用户态下的第一个进程。

init进程从不终止,因为它创建和监控操作系统外层所有进程的活动。

二、fork()函数和vfork()函数的学习
1.fork()函数
调用fork函数后,当前进程分裂为两个进程,一个是原来的父进程,另一个是刚创建的子进程。

父进程调用fork后返回值是子进程的ID,子进程中返回值是0,若进程创建失败,
只返回-1。

失败原因一般是父进程拥有的子进程个数超过了规定限制(返回EAGAIN)或者内存不足(返回ENOMEM)。

我们可以依据返回值判断进程,一般情况下调用fork函数后父子进程谁先执行是未定的,取决于内核所使用的调度算法。

一般情况下os让所有进程享有同等执行权,除非某些进程优先级高。

若有一个孤儿进程,即父进程先于子进程死去,子进程将会由init进程收养。

函数实例:通过fork()函数创建一个进程:
1 #include<sys/types.h>
2 #include<unistd.h>
3 #include<stdio.h>
4
5 main()
6 {
7 pid_t pid;
8 printf("PID before fork() :%d\n",(int)getpid());
9
10 pid = fork();
11 if(pid < 0){
12 printf("error in fork!\n");
13 }
14 else if(0 == pid){
15 printf("I'm the child process, CurpPID is %d,ParentPid is %d \n",pid,(int)getppid());
16 }
17 else{
18 printf("I'm the parent process,child PID is %d,ParentPID is %d\n",pid,(int)getpid());
19 }
20
程序经过调试后结果如下:
think@ubuntu:~/work/process_thread/fork$ ./fork
PID before fork() :4566
I'm the parent process,child PID is 4567,ParentPID is 4566
I'm the child process, CurpPID is 0,ParentPid is 4566
从程序执行结果可以看出:调后fork()函数后返回两个值,子进程返回值为0,而父进程的返回值为创建的子进程的进程ID。

Tiger-John说明:
1> Linux进程一般包括代码段,数据段和堆栈段。

代码段存放程序的可执行代码;数据段存放程序的全局变量、常量、静态变量;堆存放动态分配的内存变量,栈用于函数调用,存放函数参数、函数内部定义的局部变量。

2>有人说调用fork函数后,fork()函数返回了两个值,这是一中错误的说法。

其实,当系统调用fork()函数后fork()会将调用进程的所有内容原封不动的拷贝到新产生的子进程中去,当前进程分裂成了两个进程分别在执行,互不干扰。

3>.看一个函数实例来仔细体会一下系统调用fork()函数后是如何执行的。

1 #include<stdio.h>
2 #include<unistd.h>
3 #include<sys/types.h>
4
5 int main()
6 {
7 pid_t pid;
8 int count = 0;
9 pid = fork();
10
11 printf("This is first time,pid = %d\n",pid);
12 printf("This is the second time,pid = %d\n",pid);
13 count++;
14 printf("count = %d\n",count);
15
16 if(pid > 0){
17 printf("This is the parent process,the child has the pid :%d \n",pid);
18 }
19 else if(!pid){
20 printf("This is the child process.\n");
21 }
22 else{
23 printf("fork failed.\n");
24 }
25
26 printf("This is third,pid = %d\n",pid);
27 printf("This is four time,pid = %d\n",pid);
28 return 0;
29 }
程序经过调试后结果
think@ubuntu:~/work/process_thread/fork1$ ./fork This is first time,pid = 4614
This is the second time,pid = 4614
count = 1
This is the parent process,the child has the pid :4614 This is third,pid = 4614
This is four time,pid = 4614
This is first time,pid = 0
This is the second time,pid = 0
count = 1
This is the child process.
This is third,pid = 0
This is four time,pid = 0
think@ubuntu:~/work/process_thread/fork1$
Tiger-John说明:
从上面的程序的执行结果我们看到一个奇怪的现象:为什么printf的语句执行两次,而那句“count++;”的语句却只执行了一次?
系统在调用fork()后分裂成了两个函数分别执行,互不干扰。

2.Vfork和fork的区别:
1> vfork也可创建进程,但它实际上还是调用了fork函数。

2>在说明他们的区别之前我们先看两个程序和执行结果
函数实例1:用fork()函数创建进程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 int globV ar = 5;
6
7 int main(void)
8 {
9 pid_t pid;
10 int var = 1;
11 int i;
12 printf("fork is different with vfork \n");
13
14 pid = fork();
15 if(!pid){
16 i=3;
17 while(i-- > 0){
18 printf("Child process is running\n");
19 globV ar++;
20 var++;
21 sleep(1);
22 }
3 printf("Child's globV ar = %d,var = %d\n",globV ar,var);
24 }
25 else if(pid){
26 i=5;
27 while(i-- > 0){
28 printf("Parent process is running\n");
29 globV ar++;
30 var++;
31 sleep(1);
32 }
33 printf("Parent's globV ar = %d,var %d\n",globV ar,var);
34 exit(0);
35 }
36 else{
37 perror("Process creation failed\n");
38 exit(-1);
39 }
40 }
程序经过调试后;
think@ubuntu:~/work/process_thread/fork3$ ./fork
fork is different with vfork
Parent process is running
Child process is running
Child process is running
Parent process is running
Child process is running
Parent process is running
Child's globV ar = 8,var = 4
Parent process is running
Parent process is running
Parent's globV ar = 10,var = 6
函数实例2:用vfork()函数创建一个进程
1 #include<stdio.h>
2 #include<sys/types.h>
3 #include<unistd.h>
4 #include<stdlib.h>
5 int globV ar = 5;
6 int main(void)
7 {
8 pid_t pid;
9 int var = 1;
10 int i;
11
12 printf("fork is different with vfork!\n");
13
14 pid = vfork();
15 if(!pid){
16 i=3;
17 while(i-- > 0)
18 {
19 printf("Child process is running\n");
20 globV ar++;
21 var++;
22 sleep(1);
23 }
24 printf("Child's globV ar = %d,var =%d\n",globV ar,var);
25 }
26 else if(pid){
27 i = 5;
28 while(i-- > 0)
29 {
30 printf("Parent process is running\n");
31 globV ar++;
32 var++;
33 sleep(1);
34 }
35 printf("Parent's globV ar = %d,var %d\n",globV ar,var);
36 exit(0);
37 }
38 else {
39 perror("Process creation failed\n");
40 exit(0);
41 }
42
43 }
程序经过调试后结果为:
think@ubuntu:~/work/process_thread/fork3$ ./vfork
fork is different with vfork!
Child process is running
Child process is running
Child process is running
Child's globV ar = 8,var = 4
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent process is running
Parent's globV ar = 13,var = 5
Tiger-John说明:
我们通过以上两个函数的执行可以看到一些区别:
1. 使用fork 创建子进程时,子进程继承了父进程的全局变量和局部变量。

在子进程中,最后全局变量globV ar 和局部变量var 的值均递增 3 ,分别为8 和 4. 不管是全局变量还是局部变量,子进程与父进程对它们的修改互不影响。

2. 父进程中两者分别递增5. 最后结果为10 和6
通过以上程序的运行结果可以证明fork 的子进程有自己独立的地址空间。

3. 子进程和父进程的执行顺序是很随意的,没有固定的顺序。

父子进程的输出是混杂在一起的。

--------------------------------------------
1.用vfork()函数创建子进程后,父进程中globV ar和var最后均递增了8.这是因为vfork的子进程共享父进程的地址空间,子进程修改变量对父进程是可见的。

2.使用vfork()函数子进程后打印的结果是子进程在前,父进程在后,说明vfork()保证子进程先执行,在子进程调用exit获exec之前父进程处于阻塞等待状态。

3>那么现在来看看fork()和vfork()函数之间的区别:
(1)fork():使用fork()创建一个子进程时,子进程只是完全复制父进程的资源。

这样得到的子进程独立于父进程具有良好的并发性。

vfork():使用vfor创建一个子进程时,操作系统并不将父进程的地址空间完全复制到子进程。

而是子进程共享父进程的地址空间,即子进程完全运行在父进程的地址空间上。

子进程对该地址空间中任何数据的修改同样为父进程所见。

(2)fork():父子进程执行顺序不定;
vfork():保证子进程先运行,在调用exec或exit之前与父进程共享数据,在它调用exec 或exit之后父进程才可能被调度运行。

(3)vfork保证子进程先运行,在它调用exec或exit后父进程才可能被调度运行。

如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。

孤儿进程和守护进程
通过前面的学习我们了解了如何通过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. 如何编写守护进程呢
第一步:创建子进程,父进程退出
1>. 由于守护进程是脱离控制终端的,因此,完成第一步后就会在Shell 终端里造成一程序已经运行完毕的假象。

之后的所有工作都在子进程中完成,而用户在Shell 终端里则可以执行其他命令,从而在形式上做到了与控制终端的脱离。

2> 在Linux 中父进程先于子进程退出会造成子进程成为孤儿进程,而每当系统发现一个孤
儿进程时就会由 1 号进程(init) 收养它。

方法是调用fork 产生一个子进程,然后使得父进程退出
pid = fork();
if( 0 == pid)
exit(0); // 如果是父进程,就结束父进程,子进程结束。

第二步:在子进程中创建新会话:
这个步骤是创建守护进程中最重要的一步,使用系统函数setsid
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().。

相关文档
最新文档