74
第 6 第 第第第第

第 6 章

  • Upload
    esme

  • View
    50

  • Download
    0

Embed Size (px)

DESCRIPTION

第 6 章. 进程控制. 本章重点. 进程的基本概念及进程的结构 Linux 环境下进程的相关函数的应用 守护进程的概念、启动和建立 进程操作程序的编写. 6.1 进程简介. 进程是正在执行中的程序。当我们在终端执行命令时, Linux 就会建立一个进程,而当我们的程序执行完成时,这个进程就被终止了。 Linux 是一个多任务操作系统,允许多个用户使用计算机系统,多个进程并发执行。 Linux 环境下启动进程有两种主要途径:手工启动和调度启动。. 6.1 进程简介. ( 1 )手工启动 - PowerPoint PPT Presentation

Citation preview

Page 1: 第  6  章

第 6 章

进程控制

Page 2: 第  6  章

2

本章重点

进程的基本概念及进程的结构 Linux 环境下进程的相关函数的应用 守护进程的概念、启动和建立 进程操作程序的编写

Page 3: 第  6  章

3

6.1 进程简介进程是正在执行中的程序。当我们在终端执行命

令时, Linux 就会建立一个进程,而当我们的程序执行完成时,这个进程就被终止了。

Linux 是一个多任务操作系统,允许多个用户使用计算机系统,多个进程并发执行。

Linux 环境下启动进程有两种主要途径:手工启动和调度启动。

Page 4: 第  6  章

4

6.1 进程简介( 1 )手工启动 前台启动:是手工启动一个进程的最常用方式。

一般地,当用户输入一个命令,如“ gedit” 时,就已经启动了一个进程,并且是一个前台进程。

在后台启动进程的方法是用户在终端输入一个命令时同时在命令尾加上一个“ &” 符号。比如用户要启动一个需要长时间运行文本编辑器,可输入命令“ gedit &” ,表示这个进程在后台运行。在终端中显示的“[ 1 ] 4513” 字样表示在后台运行的进程数及进程号。进程在后台运行,终端中仍可以运行其他进程。

Page 5: 第  6  章

5

6.1 进程简介( 2 )调度启动 有时,系统需要进行一些比较费时而且占用资

源的维护工作,并且这些工作适合在深夜无人值守的时候进行,这时用户就可以事先进行调度安排,指定任务运行的时间或者场合,到时候系统就会自动完成一切工作。

Page 6: 第  6  章

6

6.1 进程简介 例如,输入“ at 17:30 8/8/2011” ,指定在 2011 年 8

月 8 日下午 5:30 执行某命令。[root@localhost root]# at 17:30 8/8/2011warning: commands will be executed using (in order) a) $SHELL

b) login shell c)/bin/shat> service httpd startat> ls -l kkat> <EOT>job 2 at 2011-08-08 17:30

在输入调度命令结束时,按下组合键 Ctrl+D 退出 at 编辑状态,到了进程调度的时间计算机自动启动以上两个进程。

Page 7: 第  6  章

7

6.1 进程简介进程操作包括终止进程、改变优先级、查看进程

属性等 。Linux 环境下常见的进程调用命令

Page 8: 第  6  章

8

6.2 Liunx 进程控制在 Linux 环境下进程创建时,系统会分配一个唯

一的数值给每个进程,这个数值就称为进程标识符( PID )。

在 Linux 中进程标识有进程号( PID )和它的父进程号( PPID )。其中, PID 唯一地标识一个进程。 PID 和 PPID 都是非零的正整数。在 Linux 中获得当前进程的 PID 和 PPID 的系统调用为 getpid 和 getppid 函数。

Page 9: 第  6  章

9

6.2 Liunx 进程控制例 6-1 :设计一个程序,要求显示 Linux 系统分

配给此程序的进程号( PID )和它的父进程号( PPID )。

步骤 1: 编辑源程序代码[root@localhost root]#vim 6-1.c

Page 10: 第  6  章

10

6.2 Liunx 进程控制 步骤 2: 用 gcc 编译程序

[root@localhost root]#gcc 6-1.c –o 6-1

步骤 3: 运行程序[root@localhost root]#./6-1

终端中的显示如下:[root@localhost root]#./6-1

系统分配的进程号 (PID) 是: 4601

系统分配的父进程号 (PPID) 是: 4556

[root@localhost root]#./6-1

系统分配的进程号 (PID) 是: 4602

系统分配的父进程号 (PPID) 是: 4556

多次运行例 6.1 的程序,从程序运行结果可以看出,每一次运行的结果 PID 值都是不一样的,PID 是唯一地标识一个进程。实际应用中可利用系统分配的 PID值来建立临时文件,以避免临时文件相同带来的问题。

Page 11: 第  6  章

11

6.2 Liunx 进程控制getpid 函数说明

getppid 函数说明

Page 12: 第  6  章

12

6.2.1 进程的相关函数 Linux c 与进程相关的主要函数

Page 13: 第  6  章

13

6.2.2 进程创建1. fork 函数 进程调用 fork 函数创建一个新进程,由 fork 创建的新

进程被称为子进程( child process )。该函数被调用一次,但返回两次,两次返回的区别是子进程的返回值是 0 ,而父进程的返回值则是新子进程的进程 PID 。

子进程和父进程继续执行 fork 之后的指令。子进程是父进程的复制品。例如,子进程获得父进程数据空间、堆和栈的复制品。注意,这是子进程所拥有的拷贝。父、子进程并不共享这些存储空间部分,通常父、子进程共享代码段。

Page 14: 第  6  章

14

6.2.2 进程创建例 6.2 设计一个程序,用 fork 函数创建一个子进

程,在子进程中给变量 n 赋值 3 ,在父进程中给变量 n 赋值 6 , fork 调用之后父进程和子进程的变量 message 和 n 被赋予不同的值,互不影响。请阅读程序,思考程序运行的结果。

步骤 1 设计编辑源程序代码。[root@localhost root]#vi 6-2.c

Page 15: 第  6  章

15

6.2.2 进程创建步骤 2 用 gcc 编译程序。

[root@localhost root]#gcc 6-2.c –o 6-2

步骤 3 运行程序。[root@localhost root]# ./6-2

Page 16: 第  6  章

16

6.2.2 进程创建 思考:1. 在子进程中给变量 n 赋值 6 ,在父进程中给变量 n 赋值 3 ,请分析程序运

行的结果。提示:,当父进程终止时 shell 进程认为命令执行结束了,于是打印 shell提示符,而事实上子进程这时还没结束,所以子进程的消息打印到了 shell提示符后面。最后光标停在 This is the child 的下一行,这时用户仍然可以敲命令,即使命令不是紧跟在提示符后面, Shell也能正确读取。

2. 把程序中的 sleep(1);去掉,查看程序的运行结果如何改变。3. 有如下程序代码:

int main(){

for(;;)fork(); return 0;}

这个程序什么也不做,就是死循环地用 fork 创建子进程,其结果是程序不断产生进程,而这些进程又不断产生新的进程,系统的进程很快就满了,系统就被这些不断产生的进程“撑死了”。当然,只要系统管理员预先给每个用户设置可运行的最大进程数,这个恶意的程序就完成不了它的企图。

Page 17: 第  6  章

17

6.2.2 进程创建sleep 函数说明

Page 18: 第  6  章

18

6.2.2 进程创建fork 函数说明

Page 19: 第  6  章

19

6.2.2 进程创建1. exec 函数 在 Linux 系统中,使程序执行的唯一方法是使用系

统调用 exec() 。 系统调用 exec() 有多种使用形式,称为 exec() 族,

它们只是在参数上不同,而功能是相同的。

Page 20: 第  6  章

20

6.2.2 进程创建

事实上,这六个函数中真正的系统调用函数只有 execve ,其他五个都是库函数,它们最终都会调用 execve这个系统调用。

Page 21: 第  6  章

21

6.2.2 进程创建 exec 调用举例如下:

char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp, session,tpgid, comm", NULL};

char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};

execl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);

execv("/bin/ps", ps_argv);execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", N

ULL, ps_envp);execve("/bin/ps", ps_argv, ps_envp);execlp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NUL

L);execvp("ps", ps_argv);

Page 22: 第  6  章

22

6.2.2 进程创建例 6.3 设计一个程序,用 fork 函数创建一个子

进程,在子进程中,要求显示子进程号与父进程号,然后显示当前目录下的文件信息,在父进程中同样显示子进程号与父进程号。

步骤 1 设计编辑源程序代码。[root@localhost root]#vi 6-3.c

Page 23: 第  6  章

23

6.2.2 进程创建步骤 2 用 gcc 编译程序。

[root@localhost root]#gcc 6-3.c –o 6-3

步骤 3 运行程序。编译成功后,执行 6-3 ,此时系统会出现运行结果,根据 result 的值,先显示 Linux 系统分配给子进程的进程号( PID )和父进程号( PPID ),接着运行 ls 程序,显示当前目录下的文件信息。再等待 10秒钟后,显示父进程的进程号( PID )和父进程号( PPID )。

Page 24: 第  6  章

24

6.2.2 进程创建1. execv 函数的应用,要在程序中执行命令 :ps -ef, 命令

ps 在 "/bin"目录下。在这一函数中,参数 v 表示参数传递 (含命令 ) 为构造指针数组方式:

char *arg[]={"ps","-ef",NULL};函数的使用为:execv("/bin/ps",arg);参考程序:#include<stdio.h> /* 文件预处理,包含标准输入输出库 */#include<unistd.h> /* 文件预处理,包含 getpid 、 getppid 函

数库 */int main () /*C 程序的主函数,开始入口 */{ char *arg[]={"ls","-al",NULL}; execv("/bin/ls",arg); return 1;}

Page 25: 第  6  章

25

6.2.2 进程创建2. execlp 函数的应用,要在程序中执行命令 :ps -ef, 命

令 ps 在 "/bin"目录下。在这一函数中,参数 l 表示命令或参数逐个列举,参数 p 为文件查找方式(不需要给出路径)。因而此函数的调用形式为:

execlp("ps","ps","-ef",NULL);

请编写一程序进行调试。

3. execl 函数的应用,要在程序中执行命令 :ps -ef, 命令ps 在 "/bin"目录下。在这一函数中,参数 l 表示命令或参数逐个列举,文件需给定路径。因而此函数的调用形式为: execl("/bin/ps","ps","-ef",NULL);请编写一程序进行调试。

Page 26: 第  6  章

26

6.2.2 进程创建4. execle 函数的应用 :

execle("/bin/login", "login", "-p", username, NULL, envp); 上述语句运行时, login 程序提示用户输入密码(输入密码期

间关闭终端的回显),然后验证帐号密码的正确性。如果密码不正确, login 进程终止, init 会重新 fork/exec 一个 getty 进程。如果密码正确, login 程序设置一些环境变量,设置当前工作目录为该用户的主目录。

设计一个程序,在子进程中调用函数 execl("/bin/ps","ps","-ef",NULL) ,而在父进程中调用函数 execle("/bin/env","env",NULL,envp) ,其中定义: char *envp[]= {"PATH= /tmp","USER=liu",NULL};请编写并进行调试。

Page 27: 第  6  章

27

6.2.3 进程终止(1) 正常终止:

(a) 在 main 函数内执行 return语句,这等效于调用 e x i t 。

(b) 调用 exit 函数。此函数由 ANSI C 定义,其操作包括调用各终止处理程序,然后关闭所有标准 I/O流等。

(c) 调用 _exit 系统调用函数,此函数由 exit 调用。(2) 异常终止:

(a) 调用 abort 。(b) 由一个信号终止。

Page 28: 第  6  章

28

6.2.3 进程终止例 6.4 设计一个程序,要求子进程和父进程都在

显示输出一些文字后分别用 exit 和 _exit 函数终止进程。

步骤 1: 设计编辑源程序代码 [root@localhost root]#vim 6-4.c

Page 29: 第  6  章

29

6.2.3 进程终止步骤 2: 用 gcc 编译程序

[root@localhost root]#gcc 6-4.c –o 6-4

步骤 3: 运行程序[root@localhost root]#./6-4

测试终止进程的 _exit 函数 !

测试终止进程的 exit 函数 !

目前为父进程,这一行我们用缓存 !

[root@localhost root]#

可以看出,调用 exit 函数时,缓冲区中的记录能正常输出;而调用 _exit 时,缓冲区中的记录无法输出。

Page 30: 第  6  章

30

6.2.3 进程终止_exit() 函数作用:直接使进程停止运行 ,清除其使

用的内存空间 , 并清除其在内核中的各种数据结构;

exit() 函数则在执行退出之前加了若干道工序, exit函数在调用 exit 系统之前要查看文件的打开情况,把文件缓冲区中的内容写回文件。

Page 31: 第  6  章

31

6.2.3 进程终止exit 函数说明

_exit 函数说明

Page 32: 第  6  章

32

6.2.4 僵尸进程 一个已经终止运行、但其父进程尚未对其进行善后处理(获取终止子进程的有关信息、释放它仍占用的资源)的进程被称为僵死进程( zombie )。 Linux 的ps 命令将僵死进程的状态显示为 Z 。

使用 fork 函数创建子进程时,由于子进程有可能比父进程晚终止,父进程终止后,子进程还没终止,子进程就会进入一种无父进程的状态,此时子进程就成为了僵尸进程。为避免这种情况,可以在父进程中调用 wait 或 waitpid 函数,使子进程比父进程早终止,而让父进程有机会了解子进程终止时的状态,并自动清除僵尸进程

Page 33: 第  6  章

33

6.2.4 僵尸进程wait 函数是用于使父进程阻塞,直到一个子进

程终止或者该进程接到了一个指定的信号为止。如果该父进程没有子进程或者他的子进程已经终止,则 wait 就会立即返回。

waitpid 的作用和 wait 一样,但它并不一定要等待第一个终止的子进程,它还有若干选项,也能支持作业控制。实际上 wait 函数只是 waitpid 函数的一个特例,在 Linux内部实现 wait函数时直接调用的就是 waitpid 函数。

Page 34: 第  6  章

34

6.2.4 僵尸进程 例 6.5 僵尸进程产生。

#include <sys/types.h>#include <sys/wait.h>#include <unistd.h>#include <stdlib.h>

int main (){        pid_t pc,pr;        pc = fork();        if (pc < 0)          printf("error ocurred!\n");        else          if(pc == 0)

{             printf("This is child process with pid of %d\n",getpid());             }          else{            sleep(20);            printf("This is partent with pid of %d\n",getpid());            }        exit(0);}

Page 35: 第  6  章

35

6.2.4 僵尸进程 编译后运行

[root@localhost root]#./6-5 This is child process with pid of 2594 This is partent with pid of 2593 程序执行后显示子进程的进程号 2594 ,在父进程输出“ This i

s partent with pid of 2593” 前,在另一终端输入命令: [root@localhost root]# ps 2594 PID TTY STAT TIME COMMAND 2594 pts/2 Z 0:00 [6-5 <defunct>]

ps 命令显示 2594 进程的属性,其状态( STAT )为“ Z” ,为进程僵尸状态,如果在 sleep(20); 前添加语句wait(NULL); 结果表明消除了僵尸进程。

Page 36: 第  6  章

36

6.2.4 僵尸进程如果 6-5 进程退出而没有调用 wait 会出现什么情况?僵尸进程会停留在系统中吗? 不 ,试着再次运行 ps ,你会发现两个 6-5 进程都消失了。

当一个程序退出,它的子进程被一个特殊进程继承,这就是 init 进程,它是 Linux 启动后运行的第一个进程。

init 进程会自动清理所有它继承的僵尸进程。

Page 37: 第  6  章

37

6.2.4 僵尸进程 例 6.6 通常在父进程中用 wait 函数等待子进程,父进

程直到接收到子进程结束的信号后,父进程结束等待。请设计一个程序,要求创建一个子进程,子进程显示自己的进程号( PID )后暂停一段时间,父进程等待子进程正常结束,打印显示等待的进程号( PID )和等待的进程退出状态。

分析 先用 fork 函数创建子进程,在子进程中返回值为 0 ,子进程用 getpid 函数显示自己的进程号( PID ),用 sleep 函数暂停 5秒;父进程中返回值大于 0 ,父进程用wait 函数等待子进程正常终止,避免子进程变成僵尸进程,父进程打印等待的进程的进程号( PID )和它的终止状态。

Page 38: 第  6  章

38

6.2.4 僵尸进程步骤 1: 设计编辑源程序代码

[root@localhost root]#vim 6-6.c

Page 39: 第  6  章

39

6.2.4 僵尸进程

此例中的子进程运行时间,明显比父进程时间长。为了避免子进程成为僵尸进程,父进程调用 wait ,阻塞父进程的运行,等待子进程正常结束,父进程才继续运行,直到正常结束。

步骤 2: 用 gcc 编译程序[root@localhost root]#gcc 6-6.c –o 6-6

步骤 3: 运行程序[root@localhost root]#./6-6

这是子进程 , 进程号 (PID) 是 :4998

这是父进程 , 正在等待子进程……等待的进程的进程号 (PID) 是 :4998 , 结束状态 :6

Page 40: 第  6  章

40

6.2.4 僵尸进程 程序中也可使用语句: wpid=waitpid(pid,&status,0); 子进程的结束状态返回后存于 status ,然后使用下列几个宏可判别子进程结束情况:

WIFEXITED(status) ,如果子进程正常结束则为非 0 值。 WEXITSTATUS(status) ,取得子进程 exit() 返回的结束代码,

一般先用 WIFEXITED 来判断是否正常结束才能使用此宏。 WIFSIGNALED(status) ,如果子进程是因为信号而结束则此宏值为真。

WTERMSIG(status) ,取得子进程因信号而中止的信号代码,即信号的编号,一般会先用 WIFSIGNALED 来判断后才使用此宏。

WIFSTOPPED(status) ,如果子进程处于暂停执行情况则此宏值为真。一般只有使用 WUNTRACED 时才会有此情况。

WSTOPSIG(status) ,取得引发子进程暂停的信号代码,

Page 41: 第  6  章

41

6.2.4 僵尸进程例 6.7 在父进程中用 waitpid 函数等待子进程,

子进程结束时通过调用函数 exit(3)向父进程发送结束信号,父进程直到接收到子进程结束的信号后,父进程结束等待,并且通过宏WIFEXITED(stat_val) ,取得子进程 exit(3) 返回的结束代码。

步骤 1 设计编辑源程序代码。[root@localhost root]#vi 6-7.c

Page 42: 第  6  章

42

6.2.4 僵尸进程步骤 2 用 gcc 编译程序。

[root@localhost root]#gcc 6-7.c –o 6-7步骤 3 运行程序。

[root@localhost root]# ./6-7This is the childThis is the childThis is the childChild exited with code 3[root@localhost root]# ps PID TTY TIME CMD32201 pts/2 00:00:00 bash32416 pts/2 00:00:00 ps

Page 43: 第  6  章

43

6.2.4 僵尸进程wait 函数说明

思考:改写程序 6-6.c ,程序设计要体现子进程与父进程迸发执行的效果,并且子进程退出后父进程才退出。

思考题:在例 6.6 中如果不用 wait 函数,子进程变成了僵尸进程,如何在系统找出这个僵尸进程?

Page 44: 第  6  章

44

6.2.4 僵尸进程 例 6.8 设计一个程序,要求用户可以选择是否创建子进

程,子进程模仿思科( Cisco ) 1912交换机的开机界面,以命令行的方式让用户选择进入,父进程判断子进程是否正常终止。

分析 先让用户选择是否创建子进程,如果不创建,打印子进程结束状态后退出;如果创建子进程,子进程模仿思科( Cisco ) 1912交换机的开机界面,以命令行的方式让用户选择进入,父进程用 waitpid 函数等待子进程正常终止,防止子进程变成僵尸进程,父进程打印子进程的终止状态。

Page 45: 第  6  章

45

6.2.4 僵尸进程步骤 1: 设计编辑源程序代码

[root@localhost root]#vim 6-8.c

Page 46: 第  6  章

46

6.2.4 僵尸进程步骤 2: 用 gcc 编译程序

[root@localhost root]#gcc 6-8.c –o 6-8步骤 3: 运行程序

[root@localhost root]#./6-81. 创建子进程2. 不创建子进程

请输入您的选择 :2这是父进程 ( 进程号: 5028 ,父进程号: 4739)子进程非正常终止,子进程终止状态: 0

Page 47: 第  6  章

47

6.2.4 僵尸进程再次运行程序

[root@localhost root]#./6-81. 创建子进程2. 不创建子进程请输入您的选择 :1这是子进程 ( 进程号: 5044 ,父进程号: 5043): 进入思科 (Cisc

o)1912交换机开机界面。 1 user(s) now active on Management Console. User Interface Menu [0] Menus [1] Command Line [2] IP ConfigurationEnter Selection : 0您选择进入了菜单模式这是父进程 ( 进程号: 5043 ,父进程号: 4739)子进程正常终止,子进程终止状态: 1

Page 48: 第  6  章

48

6.2.4 僵尸进程 步骤 4:修改程序试着不用 waitpid 函数,如下所示 ://waitpid(result,&status,0); /* 父进程调用 waitpid 函数,消除僵尸进程 *

/ 再次运行程序

[root@localhost root]#./6-81. 创建子进程2. 不复创建子程请输入您的选择 :1这是子进程 ( 进程号: 5067 ,父进程号: 5064): 进入思科 (Cisco)1912交换机开机界面。

1 user(s) now active on Management Console. User Interface Menu [0] Menus [1] Command Line [2] IP ConfigurationEnter Selection: 这是父进程 ( 进程号: 5064 ,父进程号: 4739)子进程非正常终止,子进程终止状态: 0

此例可以看出,在没有语法、语义等错误的情况下,程序还是没有完成设计要求。可见,在多进程程序设计时,除了养成使用完后就终止的良好习惯,还要让子进程工作完成后再终止,这个时候父进程就得灵活使用 wait 函数和 waitpid 函数。

Page 49: 第  6  章

49

6.2.4 僵尸进程waitpid 函数说明

Page 50: 第  6  章

50

6.2.4 僵尸进程思考题: waitpid 函数的应用,要求子进程用 sl

eep 等待 10秒,父进程用 waitpid 函数等待子进程正常结束,父进程在等待的时候不阻塞,每1秒在屏幕上输出一行文字,若发现子进程退出,打印出等待进程的进程号( PID )和退出状态。请编写一程序进行调试。

Page 51: 第  6  章

51

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

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

Page 52: 第  6  章

52

6.3.1 守护进程及其特性守护进程最重要的特性是后台运行。其次,守护进程必须与其运行前的环境隔离开来。

这些环境包括未关闭的文件描述符,控制终端,会话和进程组,工作目录以及文件创建掩码等。这些环境通常是守护进程从执行它的父进程(特别是 shell )中继承下来的。

最后,守护进程的启动方式有其特殊之处。它可以在 Linux 系统启动时从启动脚本 /etc/rc.d 中启动,也可以由作业规划进程 crond 启动,还可以由用户终端(通常是 shell )执行。

Page 53: 第  6  章

53

6.3.1 守护进程及其特性 在 Linux 环境下的有哪些内核守护进程,通过 ps –aux 命

令就可查看 Linux 环境下的守护进程:1. Init 系统守护进程:它是进程 1 ,负责启动各运行层次特

定的系统服务。这些服务通常是在它们自己拥有的守护进程的帮助下实现的。

2. Keventd 守护进程:为在内核中运行计划执行的函数提供进程上下文。

3. Kswapd 守护进程:也称为页面调出守护进程。它通过将脏页面以低速写到磁盘上从而使这些页面在需要时仍可回收使用,这种方式支持虚存子系统。

4. bdflush 和 kupdated 守护进程: Linux内核使用两个守护进程 bdflush 和 kupdated将调整缓存中的数据冲洗到磁盘上。当可用内存达到下限时, bdflush 守护进程将脏缓冲区从缓冲池中冲洗到磁盘上,每隔一定时间间隔, kupdated 守护进程将脏页面冲洗到磁盘上,以便在系统失效时减少丢失的数据。

Page 54: 第  6  章

54

6.3.1 守护进程及其特性5. portmap 端口映射守护进程:提供将 RPC (远程过程调

用)程序号映射为网络端口号的服务。6. syslogd 守护进程:可由帮助操作人员把系统消息记入日志的任何程序使用。

7. inetd 守护进程( xinetd ):它侦听系统网络接口,以便取得来自网络的对各种网络服务进程的请求。

8. nfsd 、 lockd 、 rpciod 守护进程:提供对网络文件系统( NFS ) 的支持。

9. cron 守护进程:在指定的日期和时间执行指定的命令。许多系统管理任务是由 cron 定期地执行相关程序而实现的。

10. cupsd 守护进程:是打印假脱机进程,它处理对系统提出的所有打印请求。

Page 55: 第  6  章

55

6.3.2 编写守护进程的要点1. 创建子进程,终止父进程 由于守护进程是脱离控制终端的,因此首先创建子进程,

终止父进程,使得程序在 shell 终端里造成一个已经运行完毕的假象。之后所有的工作都在子进程中完成,而用户在 shell 终端里则可以执行其他的命令,从而使得程序以僵尸进程形式运行,在形式上做到了与控制终端的脱离。

创建子进程,终止父进程的代码如下:pid=fork();

if(pid>0)

{exit(0);} /* 终止父进程 */

Page 56: 第  6  章

56

6.3.2 编写守护进程的要点2. 在子进程中创建新会话 这是创建守护进程中最重要的一步; setsid 函数用于创建了一个新的会话,并担任该会话组

的组长。调用 setsid 有 3 个作用:让进程摆脱原会话的控制,让进程摆脱原进程组的控制和让进程摆脱原控制终端的控制。

在调用 fork 函数时,子进程全盘拷贝了父进程的会话期、进程组、控制终端等,虽然父进程退出了,但原先的会话期、进程组、控制终端等并没改变,因此,还不是真正意义上独立开来,而 setsid 函数能够使进程完全独立出来,从而脱离所有其他进程的控制。

Page 57: 第  6  章

57

6.3.2 编写守护进程的要点3. 改变工作目录 使用 fork 创建的子进程也继承了父进程的当前工作目录。由于在进程运行过程中,当前目录所在的文件系统不能卸载。因此,把当前工作目录换成其他的路径,如“ /” 或“ /tmp” 等。改变工作目录的常见函数是 chdir 。

4. 重设文件创建掩码 文件创建掩码是指屏蔽掉文件创建时的对应位。由于

使用 fork 函数新建的子进程继承了父进程的文件创建掩码,这就给该子进程使用文件带来了了诸多的麻烦。因此,把文件创建掩码设置为 0 ,可以大大增强该守护进程的灵活性。设置文件创建掩码的函数是 umask 。在这里,通常的使用方法为 umask(0) 。

Page 58: 第  6  章

58

6.3.2 编写守护进程的要点5. 关闭文件描述符 用 fork 函数新建的子进程会从父进程那里继承一些已

经打开了的文件。这些被打开的文件可能永远不会被守护进程读或写,但它们一样消耗系统资源,而可能导致所在文件系统无法卸载。

通常按如下方式关闭文件描述符:for(i=0;i<NOFILE;i++)

close(i);

或者也可以用如下方式:for(i=0;i<MAXFILE;i++)

close(i);

Page 59: 第  6  章

59

6.3.3 守护进程的编写例 6.9 设计两段程序,主程序 6-9.c 和初始化程

序 init.c 。要求主程序每隔 10秒钟向 /tmp目录中的日志 6-9.log报告运行状态。初始化程序中的 init_daemon 函数负责生成守护进程。

分析 把生成守护进程的部分写成独立的函数 init_daemon ,放在程序 init.c 中,方便调用,程序 init.c 中要编写的是前面守护进程的五步。主程序先调用 init_daemon 函数,使得主程序运行后成为守护进程,接着主程序用 while语句无限循环,每隔 10秒钟往 6-9.log 文件中写入一行文字和当前时间。

Page 60: 第  6  章

60

6.3.3 守护进程的编写步骤 1: 设计编辑源程序代码

[root@localhost root]#vim 6-9.c

[root@localhost root]#vim init.c

Page 61: 第  6  章

61

6.3.3 守护进程的编写 步骤 2: 用 gcc 编译程序

[root@localhost root]#gcc 6-9.c init.c –o 6-9 步骤 3: 运行程序

编译成功后,执行可执行文件 6-9 ,程序运行后,没有任何提示,等待一段时间后,查看一下 6-9.log 文件中有没有文字写入,输入“ tail -6 /tmp/6-9.log” ,显示 6-9.log 文件中的最后 6 行,从时间上看,说明守护进程在暗地里每隔 10秒写入一串字符,如下所示:

[root@localhost root]#./6-9[root@localhost root]#tail -6 /tmp/6-9.log守护进程还在运行,时间是: Fri Oct 21 14:30:12 2011守护进程还在运行,时间是: Fri Oct 21 14:30:22 2011守护进程还在运行,时间是: Fri Oct 21 14:30:32 2011守护进程还在运行,时间是: Fri Oct 21 14:30:42 2011守护进程还在运行,时间是: Fri Oct 21 14:30:52 2011

Page 62: 第  6  章

62

6.3.3 守护进程的编写可见, 6-9确实一直在运行,而且看到“ ?” ,

结合 Linux 环境下进程的知识,知道确实有了一个守护进程。结束此守护进程可用 kill 命令,如kill 5108撤销 PID 为 5108 的进程 6-9 。

父进程创建了子进程,而父进程退出之后,此时该子进程就变成了“孤儿进程”。在 Linux 中,每当系统发现孤儿进程,就会自动由 1 号进程(也就是 init 进程)收养它,原先的子进程就会变成 init 进程的子进程了。

Page 63: 第  6  章

63

6.3.3 守护进程的编写setsid 函数说明 :

Page 64: 第  6  章

64

6.3.3 守护进程的编写思考题:1. 例 6-9 中,如果不先终止父进程就调用 setsid

函数会发生什么?守护进程运行后,注销当前用户后再登录进去,守护进程还在运行吗?

2. 编写一程序,要求运行后成为守护进程,每隔5 分钟修改一次本机的 IP 地址。 所有的 IP 地址放在一个文本文件中,每隔 5 分钟随

机读取一个, Linux 中可以用“ ioctl” 和“ sysctl”函数实现,也可以调用系统命令“ ifconfig” ,例如“ ifconfig eth0 192.168.0.20 netmask 255.255.255.0” 。能否利用例 6.9 中的初始化程序 init.c?

Page 65: 第  6  章

65

6.3.3 守护进程的编写在守护进程的编写、调试过程中会发现,程序运

行成为守护进程后,完全脱离了终端的控制,调试的时候没法像普通程序调试那样,在终端中看到错误信息,甚至用 gdb也无法正常调试。

在 Linux 系统中,编写调试守护进程,一般是利用系统的日志服务,通过系统守护进程 syslogd控制自己编写的守护进程的告警信息。 Linux C语言中,只要调用 syslog 函数,将守护进程的出错信息写入“ /var/log/messages” 系统日志文件,就可以做到这一点。

Page 66: 第  6  章

66

6.3.3 守护进程的编写例 6.10 设计一个程序,要求运行后成为守护进

程,守护进程又创建一个子进程,守护进程和它的子进程都调用 syslog 函数,把结束前的状态写入系统日志文件。

分析 根据守护进程的编程要点,原程序运行后的进程要退出,它的子进程变成守护进程,称为“第一子进程”,守护进程创建的子进程称为“第二子进程”。

Page 67: 第  6  章

67

6.3.3 守护进程的编写步骤 1: 设计编辑源程序代码

[root@localhost root]#vim 6-10.c

Page 68: 第  6  章

68

6.3.3 守护进程的编写 步骤 2: 用 gcc 编译程序

[root@localhost root]#gcc 6-10.c –o 6-10

步骤 3: 运行程序 [root@localhost root]#./ 6-10 程序运行后,没有任何提示,等待一段时间后,查看一下 /var/l

og/messages 文件中有没有文字写入,使用命令“ tail -3 /var/log/messages” 显示最后 3 行内容,说明守护进程通过系统日志管理服务,在暗地里写入一串字符,而且从时间上看出,第二子进程确实是在暂停 5秒钟后退出的;

此时用 ps 命令查看一下进程: [root@localhost root]#ps -ef|grep 6-10 可见, 6-10 确实一直在运行,而且看到“ ?” ,是一个守护进

程。守护进程的调试,通常就用这种写入系统文件的方法 注意,调用 openlog 、 syslog 函数,操作的系统日志文件“ /var/log/message” ,必须具有 root权限。

Page 69: 第  6  章

69

6.3.3 守护进程的编写openlog 函数说明

Page 70: 第  6  章

70

6.3.3 守护进程的编写syslog 函数说明

Page 71: 第  6  章

71

6.3.3 守护进程的编写思考题:编写一程序,要求运行后成为守护进程,

复制守护进程的子进程,子进程往某个文件里写入字符串“测试守护进程”,守护进程的错误信息输出到系统日志文件“ /var/log/messages” ,程序以普通用户权限编译后运行调试会有什么结果?请把产生守护进程的部分分割成独立的程序文件。

Page 72: 第  6  章

72

思考与实验1. 什么是进程?进程与作业有何区别?2. 进程启动的方式有哪几种?3. 用 exec 函数创建一个进程,显示当前目录下的文件信息。

4. execle 函数的应用,要在程序执行时设定环境变量,路径为 tmp ,用户为 liu ,执行命令 env 时把这些环境变量传递给系统,在这一函数中,参数 e 表示可传递新进程环境变量,参数 l 表示命令或参数逐个列举,文件查找需给出路径。命令 env 在 "/bin"目录下。把环境变量设定为:char *envp[]={"PATH=/tmp","USER=liu",NULL};因而此函数的调用形式为:execle("/bin/env","env",NULL,envp);

请编写一程序进行调试。

Page 73: 第  6  章

73

思考与实验5. execve 函数的应用,要在程序执行时设定环境变量,路径为 tmp ,用户为 liu ,执行命令 env 时把这些环境变量传递给系统,在这一函数中,参数 e 表示可传递新进程环境变量,参数 v 表示传递的参数 (含命令 ) 为构造指针数组,文件查找需给出路径。命令 env 在 "/bin"目录下。把环境变量设定为:char *envp[]={"PATH=/tmp","USER=liu",NULL};参数的构造指针数组为:char *arg[]={"env",NULL};因而此函数的调用形式为:execve("/bin/env","env",NULL,envp);

请编写一程序进行调试。

Page 74: 第  6  章

74

思考与实验6. execvp 函数的应用,要在程序中执行命令 :ps -ef, 命

令 ps 在“ /bin”目录下。在这一函数中,参数 v 为构造指针数组,参数 p 为文件查找方式(不需要给出路径)。因而构造的指针数组为:char *arg[]={"ps","-ef",NULL};此函数的调用形式为:execvp("ps",arg);

请编写一程序进行调试。7. 思考例 6.6 中的子进程有没有变成僵尸进程,为什么? 8. 编写一个后台检查邮件的程序,这个程序每隔一个指

定的时间会去检查邮箱,如果发现有邮件了,会不断的通过机箱上的小喇叭来发出声音报警 (Linux 的默任个人的邮箱地址是 /var/spool/mail/ 用户的登录名 ) 。