|
|
EDA365欢迎您登录!
您需要 登录 才可以下载或查看,没有帐号?注册
x
9 o! G, o* Y+ z# `4 |5 D
进程是程序的一次执行, 是运行在自己的虚拟地址空间的一个具有独立功能的程序. 进程是分配和释放资源的基本单位, 当程序执行时, 系统创建进程, 分配内存和 CPU 等资源; 进程结束时, 系统回收这些资源。 进程由PCB(进程控制块)来描述:
% B1 X; r& D* i) c, d
6 X! y; g8 ~# I8 D0 p( `0 e- 进程id。系统中每个进程有唯一的id,在C语言中用pid_t类型表示,其实就是一个非负整数。
4 J; n6 Y: e$ \. z3 i* [ " \+ z7 \0 ^5 c0 t7 {/ P* ^
- 进程的状态,有运行、挂起、停止、僵尸等状态。+ {' d+ w+ W# J4 |7 r1 l
0 e9 k; v; n4 g/ f9 R" B) x( ~
- 进程切换时需要保存和恢复的一些CPU寄存器。# \- S, W2 [6 j9 {) ?# ]
5 W7 T$ s5 A% E: h! I* }) y9 `- 描述虚拟地址空间的信息。
8 _/ V+ @! j& V! [" W( @5 H
" X$ c" {8 x+ z" ]1 ^8 v& l- 描述控制终端的信息。2 O' V) I+ ~2 K# a. G# o
: O# P- }) O% P" W1 B
- 当前工作目录(Current Working Directory)。
( @2 s) a- d! C 9 W/ o* I6 a0 s! P: c7 X
- umask掩码。
( z) p" f% ?& k, {/ [' S5 A
) C$ Y2 G8 I+ C- 文件描述符表,包含很多指向file结构体的指针。! N; \9 _5 g3 F8 B5 Z
0 e2 \( _+ I1 K! i- 和信号相关的信息。8 N4 T- F! j3 J8 b
* I! b+ U5 E/ n0 {1 s
- 用户id和组id。
1 r& C% {0 r0 x# u2 { % p6 e) ], i/ n# p6 w
- 控制终端、Session和进程组。
/ [+ d) C m# f% g
5 a! |9 _ {: Q* O* w) v- 进程可以使用的资源上限(Resource Limit)。 c! f4 a$ l0 {- [
% ~$ S' ^9 _& z, S8 w6 T# K4 T3 Z& L
/ l. h3 w \; K2 G 线程与进程+ L$ c W: H$ E: l% x v
- 线程又名轻负荷进程, 它是在进程基础上程序的一次执行, 一个进程可以拥有多个线程.
- 线程没有独立的资源, 它共享进程的 ID, 共享进程的资源.
- 线程是 UNIX 中最小的调度单位, 目前有系统级调度和进程级调度两种线程调度实行方式: 系统级调度的操作系统以线程为单位进行调度; 进程级调度的操作系统仍以进程为单位进行调度, 进程再为其上运行的线程提供调度控制.' R7 Z0 S6 e4 w0 P
) d2 _% a/ |8 I# v2 h9 I! Z+ e0 @
守护进程:常驻后台执行的特殊进程,如sysproc init) T- S. L8 j: `* D6 L
! v* y+ k4 d' e) Y4 }/ O% I读取PID号:getpid getpgrp getppid <unistd.h> <sys/types.h>
- f0 E% W4 s( I* u, i1 H& Q" p* o, G8 ?$ h
读取用户标识号:getuid geteuid getgid getegid
5 S& c$ ]! C6 @( [4 e) f7 ]' x; e$ z0 s! J3 u7 Q
例子:
" @0 ~6 ]& H$ W! l1 n1 k
! R; v" W* u# A# U. V#include<unistd.h>
9 c' O8 d6 P* s0 a
( y9 p+ R! x$ k" J7 Dvoid main()
, y$ S3 k5 v6 x1 T8 c4 l$ G6 {& K( q) J# V1 a
{% Q2 L! L6 y b2 v. g+ \$ w; z
0 w, s! N* }5 D. N printf("pid=[%d], gid=[%d], ppid=[%d]\n", getpid(), getpgrp(), getppid());
: b, I5 v/ f, f. l2 J4 M; v5 N0 _$ `2 I: _
printf("uid=[%d], euid=[%d], gid=[%d], egid=[%d]\n", getuid(), geteuid(), getgid(), getegid());
6 q2 L" |2 d) ^8 f5 l# u' y0 I/ Y' B
}
$ T) J6 |) W% N0 ^# v6 M2 F1 r8 x6 w M: e& c9 V7 ~
# ./id1
3 z- G& ^( @9 l" m* [- q3 J: t& F) H2 A9 t0 d4 A5 h
pid=[3311], gid=[3311], ppid=[2925]
' @) l2 F0 q. P+ n7 H w2 v; J( a8 Y1 q% e" H9 G+ W
uid=[0], euid=[0], gid=[0], egid=[0]
% d2 G1 `; v' A
1 m# t9 }" a7 \0 a& M) y 环境变量( R. T/ ?# T; p6 W5 M5 m5 R8 d
UNIX 中, 存储了一系列的变量, 在 shell 下执行'env'命令, 就可以得到环境变量列表. : O$ C* U- @) R
# w, b) l+ u9 U* B
环境变量分为系统环境变量和用户环境变量两种. 系统环境变量在注册时自动设置, 大部分具有特定- I! w9 |; C! y7 S
2 V7 W% j+ ~! K) y. g的含义; 用户环境变量在 Shell 中使用赋值命令和 export 命令设置. 如下例先设置了变量 XYZ, 再将其转化
8 m k5 E) l' D6 A! \4 T k2 c0 w/ r) h3 J' I4 \1 A/ Y
为用户环境变量: - ?! t+ P) w+ }" i$ Q
+ F1 A: [3 W4 [/ a3 D6 @+ e* E ^
[bill@billstone Unix_study]$ XYZ=/home/bill
x1 S9 L$ P( x: O% ~) Z7 ^8 U2 D/ Y, i* M) e; ?5 V
[bill@billstone Unix_study]$ env | grep XYZ
1 [: E$ @) s% m, F
) n# S& ?. l' ?+ T! [0 W1 m/ @' s( Y# s[bill@billstone Unix_study]$ export XYZ " s/ ^6 H, C. E3 k) T+ Q
8 Z7 x1 `" C" x& |7 l: O/ r% m8 p) l
[bill@billstone Unix_study]$ env | grep XYZ
1 _8 K0 W0 q# e
0 H! w' K; \ g+ {* E, [- L# TXYZ=/home/bill : m5 @9 t: D# ~
3 b. [) l2 Y+ A% S
[bill@billstone Unix_study]$ * N+ K& m& {, a# S
- P+ _0 _" T" O- G+ V) z9 x UNIX 下 C 程序中有两种获取环境变量值的方法: 全局变量法和函数调用法 ' ?6 c; _2 P. q8 P
2 h5 x- R, W& m& w: L7 J6 K (a) 全局变量法+ t, w' G* s( q* n
UNIX 系统中采用一个指针数组来存储全部环境值:
+ V8 t- B; P: P* `2 t$ J5 Q
9 n3 q! z1 Z" c# M0 v( J1 yExtern char **environ;
) s' u: W* V, b- s+ L
0 O) h4 F/ \9 c' v0 p: q 该法常用于将 environ 作为参数传递的语句中, 比如后面提到的 execve 函数等.
2 V2 e7 D6 [5 Z) g
, M* [& D8 f8 Y* S5 V% Y* R 1: #include <stdio.h>
# e) u# a+ x s( |+ V 2: : o' h! ]0 e% _& C( L0 m a0 N
3: extern char **environ;
" q8 P/ I% J, ^* I 4: 8 V# o% [1 v: G# @5 c+ q7 Z
5: int main()
1 S2 A" g Q+ m- M 6: + q$ @8 E3 C# d% S
7: {
( M5 g% j" W$ }" G s$ r 8: $ v. z# G$ U3 Y9 [( P4 [
9: char **p = environ; % J4 e8 b0 D0 U) b
10: 0 J. z% V: [2 E9 L: D1 t/ m
11: while(*p){
: b1 X& {( u" a: Q" _+ q+ K 12:
! M5 @. }! j y 13: fprintf(stderr, "%s\n", *p);
2 V6 B; m0 ?1 S# A( y. ~) X 14: ) ?) I- w& r; ^, U7 A3 }3 `
15: p++;
7 ^9 |1 ^% m+ v$ S3 _' i. x 16:
5 K4 {5 `% `3 ?- X 17: } # |3 h2 k$ K9 j" c# h
18: , i0 Y" r \/ L0 Q
19: return 0;
0 Z+ _4 a1 f/ X4 N+ P# ? 20:
, t" e- r/ R- \' d; y6 N& s 21: } 0 E& {6 q# U& F; I( X2 y6 O
(b) 函数调用法
9 D- o, e( y V: K' d. B+ W- ] UNIX 环境下操作环境变量的函数如下:8 y( Z. {- N5 g9 s+ V; P5 i
4 p9 O- ~$ q1 Y
#include <stdlib.h>$ x/ k" M! _( n" _) }: J
char *getenv(const char *name);
# s/ G+ \) M% r9 z, Wint setenv(const char *name, const char *value, int rewrite);
# I) J) n- {2 |: Q. A7 k4 Uvoid unsetenv(const char *name);
7 Q* G4 b. S( m) l1 u- X& ?+ f, z 函数 getenv 以字符串形式返回环境变量 name 的取值, 因此每次只能获取一个环境变量的值; 而且要使用该函数, 必须知道要获取环境变量的名字.
1 X+ Q. ]! s2 A
* D5 a$ V1 j# h. N
4 h6 l2 u; l+ T- x在进程中执行新程序的三种方法
( o5 ?; r* D1 r& z, u 进程和人类一样, 都有创建, 发展, 休眠和死亡等各种生命形态. % ?; C- `; Z+ o
6 H) r0 V' F. \8 @
函数 fork 创建新进程,( } b7 `$ f3 |! u
函数exec 执行新程序,
4 L D- m8 z; J( O, G2 ]函数 sleep 休眠进程, ' }9 a8 L* @: |0 V1 y
函数 wait 同步进程和函数
1 z1 L" S2 F+ Z* I( Texit 结束进程.$ v+ l1 C) U, Z9 X9 x
创建子进程的两个用途: 1.复制代码 2.执行新程序2 ~& Y2 F# R- x" x- o
: t2 I( l) D3 m: g6 B
(1) fork-exec
$ ~' Y+ h9 _# r& V9 u8 n 调用 fork 创建的子进程, 将共享父进程的代码空间, 复制父进程数据空间, 如堆栈等. 调用 exec 族函数将使用新程序的代码覆盖进程中原来的程序代码, 并使进程使用函数提供的命令行参数和环境变量去执行 K( \. ?# [% ]* @) z* ~0 w
( y( A: P! ?6 Y: ?新的程序.4 q# ~. J% T+ \- T7 W1 @! e( e/ J
1 j! Z! |/ e. E
#include <sys/types.h>( u' r3 s' s8 l) {
#include <unistd.h>
) Q0 N7 F2 u+ Q4 rpid_t fork(void);5 A7 I: ? `, o0 B% [; P
& ^. T0 E5 c8 Tfork函数的特点概括起来就是“调用一次,返回两次”,在父进程中调用一次,在父进程和子进程中各返回一次。一开始是一个控制流程,调用fork之后发生了分叉,变成两个控制流程,这也就是“fork”(分叉)这个名字的由来了。子进程中fork的返回值是0,而父进程中fork的返回值则是子进程的id(从根本上说fork是从内核返回的,内核自有办法让父进程和子进程返回不同的值),这样当fork函数返回后,程序员可以根据返回值的不同让父进程和子进程执行不同的代码。fork的返回值这样规定是有道理的。fork在子进程中返回0,子进程仍可以调用getpid函数得到自己的进程id,也可以调用getppid函数得到父进程的id。在父进程中用getpid可以得到自己的进程id,然而要想得到子进程的id,只有将fork的返回值记录下来,别无它法。0 C+ J& {( X* B" H
6 Z; S, S- \0 D$ Y
fork的另一个特性是所有由父进程打开的描述符都被复制到子进程中。父、子进程中相同编号的文件描述符在内核中指向同一个file结构体,也就是说,file结构体的引用计数要增加。/ V( ~! Z. Z: m
1 V2 [- ]7 B, t4 t# [
exec 函数族有六个函数如下:5 n* |) u" d! }; ]) T& \
" J0 V# F$ l" H8 N
#include <unistd.h>+ i# R% b7 V0 {4 x
$ O n1 g$ A& ]6 w
int execl(const char *path, const char *arg0, ..., (char *)0);# `; W- o, g- e: G! j- Q
8 L6 q$ C% x5 H, W$ `& w/ X mint execle(const char *path, const char *arg0, ..., (char *)0, char *const envp[]);
6 p- e! H2 E. F2 a, m: }* s
2 {) {3 V2 v0 ?+ W1 hint execlp(const char *file, const char *arg0, ..., (char *)0);
# o$ ?1 \+ q% S W/ J9 _6 u& e* C# v$ U& Y
int execv(const char *path, const char *argv[]);
0 c& {1 @* ?( s, R7 k5 r6 W: i) w
) {& x" P9 c7 p4 v- f' K( P' cint execve(const char *path, const char *argv[], const char *envp[]);
; u- w! S; h2 \7 B7 x: H$ }* [; z+ `
int execvp(const char *file, const char *argv[]);
) q8 L t6 {; z( q# [# a9 o1 n
, Y" o* L' c* H. zextern char **environ;
! J8 W, j ?) G6 S7 G5 ^" d1 x: K0 p- d3 g" n K1 o ?! B
这些函数如果调用成功则加载新的程序从启动代码开始执行,不再返回,如果调用出错则返回-1,所以exec函数只有出错的返回值而没有成功的返回值。" Y9 R. }# B( Z% ^ c$ S$ ~
' r$ s# p5 c6 K5 A这些函数原型看起来很容易混,但只要掌握了规律就很好记。不带字母p(表示path)的exec函数第一个参数必须是程序的相对路径或绝对路径,例如"/bin/ls"或"./a.out",而不能是"ls"或"a.out"。对于带字母p的函数:
$ P2 p7 R# ^# M7 f6 `4 Q
; `( w4 x, Y, H3 h" H( ~* C# U如果参数中包含/,则将其视为路径名。
# u; e' Q) b& Y7 j- U
3 G4 ^+ z: c1 \, }1 M0 p* }否则视为不带路径的程序名,在PATH环境变量的目录列表中搜索这个程序。
# j; {) t; ]+ Z6 G" A5 _* m1 O3 O2 B/ ?. h, T+ Q
带有字母l(表示list)的exec函数要求将新程序的每个命令行参数都当作一个参数传给它,命令行参数的个数是可变的,因此函数原型中有...,...中的最后一个可变参数应该是NULL,起sentinel的作用。对于带有字母v(表示vector)的函数,则应该先构造一个指向各参数的指针数组,然后将该数组的首地址当作参数传给它,数组中的最后一个指针也应该是NULL,就像main函数的argv参数或者环境变量表一样。2 p! y0 k9 h- M# M
% N9 L* u3 q8 R# J" `7 k对于以e(表示environment)结尾的exec函数,可以把一份新的环境变量表传给它,其他exec函数仍使用当前的环境变量表执行新程序。5 |, J! [( b" I1 q6 C# J5 l _. u
5 p( L& P& F" {9 H4 J+ b7 r# sexec调用举例如下:# } ^* U) v W4 O3 V X& P3 Z
0 i& W; f5 D: n4 Y
char *const ps_argv[] ={"ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL};
" Z# v3 c W7 u/ o9 _& |char *const ps_envp[] ={"PATH=/bin:/usr/bin", "TERM=console", NULL};
; ~+ H; J4 w+ t1 Xexecl("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
2 g3 F4 Q/ ]- U7 ~5 s |7 I8 qexecv("/bin/ps", ps_argv);3 d% U- H8 u, Q5 q1 H
execle("/bin/ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL, ps_envp);
% V: a8 c1 V$ G2 R% Q+ w8 {execve("/bin/ps", ps_argv, ps_envp);
! M% t" e2 V2 pexeclp("ps", "ps", "-o", "pid,ppid,pgrp,session,tpgid,comm", NULL);
4 {7 J0 Y" k! x3 t! `3 Z8 i& Y( xexecvp("ps", ps_argv);
0 {: M6 k2 V" Q$ a. G(2) vfork-exec
. Z& h6 I. [' E9 s) }, jvfork 比起 fork 函数更快, 二者的区别如下: 8 b, n. _' t8 w2 _* @
. q6 ?' W9 ?3 x! a a) vfork 创建的子进程并不复制父进程的数据, 在随后的 exec 调用中系统会复制新程序的数据到内存, 继而避免了一次数据复制过程& h5 o' ^! N, [- w* k; W
b) 父进程以 vfork 方式创建子进程后将被阻塞, 知道子进程退出或执行 exec 调用后才能继续运行. 当子进程只用来执行新程序时, vfork-exec 模型比 fork-exec 模型具有更高的效率, 这种方法也是 Shell创建新进程的方式.
1 k' r5 t& K! | X2 ?! |#include <sys/types.h> 3 n5 C7 s1 C5 {' ^& ?
- d1 O- W5 F/ `" d1 B/ ^
#include <unistd.h> 3 ] J, c) q) ~' \
2 c o* l- x7 g4 j8 k5 O#include <stdio.h> ; J* D4 k% r3 {9 C: c: S) n* T! l
4 s$ i; Z8 f8 K9 g4 T5 D- B0 x8 bint main()
- @) A' s9 E' C/ ?2 K: d7 C. k! B; t& M
{
6 b6 }, D8 X3 Y/ W3 c+ f8 s- }3 s4 g: f5 y% X; I
pid_t pid; ' j( ?6 N2 D! _2 Y: Q% Z
: r5 H, m9 M5 \# h( @- B# T% t
if((pid = vfork()) == 0){
6 i$ U* K0 v- n3 w5 y; D
8 n# h9 X! t T7 K# j% e( _ fprintf(stderr, "---- begin ----\n"); + y( q' v r6 |4 f" U, O# s3 t& ~
" [) t* X4 W5 g
sleep(3);
* f8 V- n5 f: S
0 R+ T& T% Y, o6 h& @ execl("/bin/uname", "uname", "-a", 0);
, d) S; w% G) P9 c3 K# Z9 K' \
3 m2 @+ |6 {. ~( U6 p0 C# H: j" Y fprintf(stderr, "---- end ----\n");
4 h& x6 i( c9 [( ], ~3 m ]6 l+ T+ R6 P3 u6 y- E4 Z( l! U7 d
} 8 c+ Q z# W3 }, e/ K) h/ h
# M. |9 d: n5 G# w8 v4 y) \ else if(pid > 0)
5 \& C8 V- Z% b6 R$ w" Y$ A5 o/ e7 z6 M- p/ h' e8 c, K
fprintf(stderr, "fork child pid = [%d]\n", pid); + ~/ Y2 ?* k1 k' U/ J
* A9 \% { v2 L
else " ?9 ]. p* k' z. d! v8 ?
% I* W" T- ~: o! `; P4 u4 ^
fprintf(stderr, "Fork failed.\n"); % X. @7 u7 u0 Y
4 F4 r# M% w" {; O z
return 0; 9 ]8 L6 T4 b {! E3 J
5 [( h/ S9 I9 C4 P}
: f, R. k/ R: n; {% g: Y
. B1 w0 m, ^8 h1 B$ y# X, d[bill@billstone Unix_study]$ make exec2 ' p3 r$ u4 ^' e& |, d
0 S R5 K- k- J/ X) K! Cmake: `exec2' is up to date.
# i6 {8 G; z v5 i' M8 J/ Z: M1 m. b3 B4 y' @: h% F
[bill@billstone Unix_study]$ ./exec2 ' i( y6 n* N( _
8 U5 f( S j+ i2 C
---- begin ----
7 _3 F$ ?/ c9 Z, c
$ b' j) b. p) U, yfork child pid = [13293]
6 ~: y* O" d5 C4 ]# Q ~) }
; d; |9 _9 E! f) C W[bill@billstone Unix_study]$ Linux billstone 2.4.20-8 #1 Thu Mar 13 17:18:24 EST 2003 i686 athlon i386 GNU/Linux
# k2 r* s1 y+ n# v (3) system; _1 `/ j! i6 b; P3 _9 I# l, T
在 UNIX 中, 我们也可以使用 system 函数完成新程序的执行.
& z, Z& ?. B0 l* i( h; u
* D$ R& J/ ?7 c: U4 [9 V& V( B 函数 system 会阻塞调用它的进程, 并执行字符串 string 中的 shell 命令.
/ ^. y0 `# @% _" V0 N! K8 n) \* q9 e5 e
[bill@billstone Unix_study]$ cat exec3.c
6 q! @- C6 T8 `0 k, A
3 N9 z2 I9 g9 ]2 G; Q#include <unistd.h>
! ]$ y) z3 a7 c4 k, L; H6 u {( g1 D4 w$ @
#include <stdio.h>
2 P' @. D% Q( e2 l) o
{% Y. c W. I% E+ K- fint main() + F1 _% M, i! Y5 K# s
3 u6 X- g0 n4 l) s
{
. @+ H1 a* y6 P/ Z
' A& x3 p) v& a* { char cmd[] = {"/bin/uname -a"}; * l. [/ t7 x: v
* {1 v5 X1 Z# n+ T* ]; o8 J
system(cmd);
$ G1 D! `6 L6 n0 \8 Y, ?' [% L; I/ l6 d8 j8 i. p' ~. G
return 0; ( j# r7 a$ f2 G6 g3 l& G8 G
, C) J& q- k9 V4 [" W- r
}
3 X+ E3 s1 k! ] j4 O" t( o) }' u# Q
, ~& }+ J- m- ]0 p- j6 S[bill@billstone Unix_study]$ make exec3 * e. D" ?# D/ ?6 E. w E
! x6 e4 b) l' [8 M; Q
cc exec3.c -o exec3
. I6 c% c! K' N V6 t+ G1 G2 r. n
[bill@billstone Unix_study]$ ./exec3
2 z! b J; z2 |& E/ S; I r8 ?
) {$ j3 e% r8 ], ^: uLinux billstone 2.4.20-8 #1 Thu Mar 13 17:18:24 EST 2003 i686 athlon i386 GNU/Linux . J* ^% v* d; c2 B$ [% r5 S
2 I7 u% x) v% y6 q f
* H- s) R2 H+ b1 Q* x( e9 G2 x0 y; N. v' B1 l! x3 U% p5 I
进程休眠:sleep1 R8 G, w% n5 L4 m: C/ z
进程终止:exit abort+ b! b, i% O5 J8 g+ W
进程同步(等待):wait
, L* H1 U% }, b) [3 R一个进程在终止时会关闭所有文件描述符,释放在用户空间分配的内存,但它的PCB还保留着,内核在其中保存了一些信息:如果是正常终止则保存着退出状态,如果是异常终止则保存着导致该进程终止的信号是哪个。这个进程的父进程可以调用wait或waitpid获取这些信息,然后彻底清除掉这个进程。我们知道一个进程的退出状态可以在Shell中用特殊变量$?查看,因为Shell是它的父进程,当它终止时Shell调用wait或waitpid得到它的退出状态同时彻底清除掉这个进程。4 G: X4 R9 N: ^2 e' P! q: ~& n
) T& y# l: g) v' l. v2 H% A$ {
如果一个进程已经终止,但是它的父进程尚未调用wait或waitpid对它进行清理,这时的进程状态称为僵尸(Zombie)进程。 E4 J' [$ U- ]5 H( S- q
2 P# ^& Q5 R1 H; ]1 l
ps -ef | grep 13707 9 o8 p- ]9 n1 [- i. L- i9 H
8 |( Z1 R1 c! U# @bill 13707 1441 0 04:17 pts/0 00:00:00 ./szomb1 8 A& q2 A( O3 {% Z6 ^
4 s+ i, ?! m$ E' b1 Jbill 13708 13707 0 04:17 pts/0 00:00:00 [szomb1 <defunct>] // 僵死进程
: X7 Q7 U$ T( ^- R r8 @2 W0 K8 P% D/ M9 L' s: g- E
bill 13710 1441 0 04:17 pts/0 00:00:00 grep 13707 + D7 x7 y& J4 Q) Q$ k, \- x5 A$ y
7 T2 t' z' C% _. m8 I
[bill@billstone Unix_study]$
. s3 _8 T F, E( l4 x* Q. Z* l! A
其中, 'defunct'代表僵死进程. 对于僵死进程, 不能奢望通过 kill 命令杀死之, 因为它已经'死'了, 不再接收任何系统信号. * n$ r- R' c# ^2 S! s
8 x D% k r9 n. ~0 |1 m2 v
当子进程终止时, 它释放资源, 并且发送 SIGCHLD 信号通知父进程. 父进程接收 SIGCHLD 信号,调用wait 返回子进程的状态, 并且释放系统进程表资源. 故如果子进程先于父进程终止, 而父进程没有调用 wait接收子进程信息,则子进程将转化为僵死进程, 直到其父进程结束.
* Y1 s) z" L/ k) Q
2 S9 k" h% o. _: p- {一旦知道了僵死进程的成因, 我们可以采用如下方法预防僵死进程: 1 N9 @, l* b. L2 o. {
`7 N1 G; e2 B/ q# q
(1) wait 法 / p \7 Y! b" s3 S" R
- m7 V: I' a2 b# N
父进程主动调用 wait 接收子进程的死亡报告, 释放子进程占用的系统进程表资源. / @% m" n1 x, x
2 R' D5 E( g4 W (2) 托管法 - |8 M' X1 k+ O, T6 u
7 @8 H& |( w7 x) ` 如果父进程先于子进程而死亡, 则它的所有子进程转由进程 init 领养, 即它所有子进程的父进程 ID 号变为 1. 当子进程结束时 init 为其释放进程表资源.
; y ~) I+ l6 W+ U) k6 Q m* h! b2 f0 v( X" |( @
托管法技巧:两次fork,子进程退出,则子子进程的父进程变为init。
* Z: ]. h/ _- H1 O( k/ w, z+ ?! h3 q4 ]- F
(3) 忽略 SIGC(H)LD 信号
! d1 K, i3 v. C
7 B/ u) p: M0 [! [9 O1 T$ X% z2 Q 当父进程忽略 SIGC(H)LD 信号后, 即使不执行 wait, 子进程结束时也不会产生僵死进程.
4 S5 l& [$ `% k) w% P5 T5 |: t
- g( u- g6 ]- k7 ]" f: Q7 { (4) 捕获 SIGC(H)LD 信号
7 F: }0 e( ^3 a" @7 C1 }3 Z* v' V2 B8 r( Y
当父进程捕获 SIGC(H)LD 信号, 并在捕获函数代码中等待(wait)子进程
. ~+ b$ L: |' f5 K5 y* M& m# P: P
0 P' t" Q' ^1 {5 h) p* pwait和waitpid函数的原型是:3 N2 j- Z, s$ }3 d: Q
# q* N; i& L8 F& s0 z$ r#include <sys/types.h>
' I+ _+ p1 }- B" c$ `9 s3 i#include <sys/wait.h>
0 o& m" r: _: s! G& X7 l ) u$ ^' K+ k) W- D& q
pid_t wait(int *status);
+ v6 X. {. w8 J: Q! C+ Ypid_t waitpid(pid_t pid, int *status, int options);
) s. {5 G3 b9 z4 Q* }若调用成功则返回清理掉的子进程id,若调用出错则返回-1。父进程调用wait或waitpid时可能会:8 q p2 g. ^# k! U
7 v' X- W% K! J! B阻塞(如果它的所有子进程都还在运行)。
3 R6 M' q) } y* m" o& g + U% D5 N: C5 V2 a- I9 [" Z
带子进程的终止信息立即返回(如果一个子进程已终止,正等待父进程读取其终止信息)。
* r- B8 g) y5 v3 h( M & j2 J& p9 R% [( N: f, Y& F
出错立即返回(如果它没有任何子进程)。
1 D# f+ |2 v, \) {4 E$ K
! q V7 l/ Q3 ?& P这两个函数的区别是:
|1 ]3 L, ~5 `8 x' s5 }' C# H7 Y , v" u( K Y7 ~3 H( Q, T
如果父进程的所有子进程都还在运行,调用wait将使父进程阻塞,而调用waitpid时如果在options参数中指定WNOHANG可以使父进程不阻塞而立即返回0。5 n" @ I: a+ d' b/ ~7 G
$ Y( e( j6 P4 |5 @* G( I8 C/ uwait等待第一个终止的子进程,而waitpid可以通过pid参数指定等待哪一个子进程。
6 V( [& k' B$ F: `; b6 u) { b 0 ?' ^$ L8 l. F
可见,调用wait和waitpid不仅可以获得子进程的终止信息,还可以使父进程阻塞等待子进程终止,起到进程间同步的作用。如果参数status不是- C& M* n1 i! v. B/ a7 y
3 p: b$ r$ Y* q) ], _+ h
空指针,则子进程的终止信息通过这个参数传出,如果只是为了同步而不关心子进程的终止信息,可以将status参数指定为NULL。
1 _; {* _+ Z4 |$ w
& H1 [$ ?! _. M4 a5 m $ X4 y& U6 S5 y* u
例 30.6. waitpid
, p* v6 h; P9 r' w: g: ]
& m- T1 Y m; y) ]9 R#include <sys/types.h>/ }& T5 B6 D* y( O. o, I) d
#include <sys/wait.h>- i3 L2 a; t% `" C6 U& ]8 G
#include <unistd.h>7 o, u- x9 H3 Z* x' j, F/ }
#include <stdio.h>
0 b5 C! B( M7 ~, T) k* E+ l#include <stdlib.h>3 G; M; I1 N9 v5 b4 y0 A: G( n% a) _
2 j+ @" ^& I9 B
int main(void)
3 l2 d4 M* q- ] e{
7 y5 f% u1 z- F# i" g/ m pid_t pid;
" Q8 i, o" g0 \6 ]8 j- j) Z. ? pid = fork();. u* U: k B' Z3 P4 t
if (pid < 0) {9 i' R0 s2 x2 p+ Y
perror("fork failed");1 g- ~3 M) x* |4 L
exit(1);
0 z$ G5 b" G, ~' M5 W }3 m1 R5 j( u: ]8 y+ D
if (pid == 0) {
2 _1 S5 `0 z( v- y ^ int i;
1 O0 }$ O" q5 k. Q5 p for (i = 3; i > 0; i--) {/ [1 B, M4 b- X2 n- {1 Q5 V3 M
printf("This is the child\n");0 W8 M/ [& m' q: o
sleep(1);+ M% p" D4 c: Q" L
}) s" u/ E; ] e9 V
exit(3);
& |0 [, W$ j ]' D% g3 A& B. ` } else {7 r6 e: h7 F5 n; r9 A/ R$ ]
int stat_val;
1 U" `( w/ _% T waitpid(pid, &stat_val, 0);
4 w. R: L7 n) U, ] if (WIFEXITED(stat_val))
/ T7 Y6 D. ~" b printf("Child exited with code %d\n", WEXITSTATUS(stat_val));- S' T' C* a9 \+ O x3 B
else if (WIFSIGNALED(stat_val)): Z, S" J/ B \
printf("Child terminated abnormally, signal %d\n", WTERMSIG(stat_val));
, Q- b1 t/ E9 T }3 F9 t# t1 E7 \( j8 H( w9 V
return 0;( p# y8 C5 U2 l, V5 p
}
" U# _/ E' S; O* C5 G$ w# c - }8 |. m$ E+ s5 t6 y' V1 x
子进程的终止信息在一个int中包含了多个字段,用宏定义可以取出其中的每个字段:如果子进程是正常终止的,WIFEXITED取出的字段值非零,1 ?* ^$ P' [, q3 l5 [% N$ e9 J
, [) j E# E8 L. c3 A: o2 JWEXITSTATUS取出的字段值就是子进程的退出状态;如果子进程是收到信号而异常终止的,WIFSIGNALED取出的字段值非零,WTERMSIG取出的, }0 n+ g9 x4 a- e
/ \8 p7 f, t) O z
字段值就是信号的编号。作为练习,请读者从头文件里查一下这些宏做了什么运算,是如何取出字段值的。. h( l, F9 L. t( W' Y
5 V4 l0 Y7 j3 v 守护进程
+ w5 A3 e/ y) V. [ S ^& w; a1 ~2 S 所谓守护进程是一个在后台长期运行的进程, 它们独立于控制终端, 周期性地执行某项任务, 或者阻塞直到事件发生, 默默地守护着计算机系; `6 X- h: Z: N L5 t0 _) C& `2 z
3 n) d0 H0 X: B G& k7 {% s! v
统的正常运行. 在 UNIX 应用中, 大部分 socket 通信服务程序都是以守护进程方式执行.
; b% \# G. {0 J f5 F0 G) s" r6 b: |- }
完成一个守护进程的编写至少包括以下几项:
) C1 i K+ l; `5 j: G6 }- ~0 v
( y7 e) T2 e- }9 D) l (1) 后台执行
' d( }9 X' N7 w0 D1 C [
/ O2 R' K& P0 f! B 后台运行的最大特点是不再接收终端输入, 托管法可以实现这一点
% [/ c' k, _& w+ E3 ~ k$ v; D1 b. p; [, ]) g
pid_t pid; 0 L2 ^. ?8 V- s9 S
( n" p# _, f0 q* p; k9 d5 q
pid = fork(); # _6 t$ @) X+ j! u
0 X6 i. r5 C1 {' J( p; |# O4 X: }if(pid > 0) exit(0); // 父进程退出
- S6 U' j5 ?6 q; Z. C+ u" p/ O6 e' U- k1 c5 A6 {5 {
/* 子进程继续运行 */
! c" W/ Z0 s; A( B5 H* M
. H# w3 J8 n( R父进程结束, shell 重新接管终端控制权, 子进程移交 init 托管 ) ]0 C) d1 U* y- Z5 \1 ?
6 N% q3 W% g; m' m8 Z1 z$ U, M
(2) 独立于控制终端
" ^1 u& Q4 v: j& f/ H4 l2 F6 o. U
6 Y* `4 V+ b7 T, b p0 C# p 在后台进程的基础上, 脱离原来 shell 的进程组和 session 组, 自立门户为新进程组的会话组长进程, 与原终端脱离关系
/ b8 ]( a. {# j/ b) |) [2 Y/ q# l3 n# L6 Q
#include <unistd.h>
( C2 x4 {7 c- c
( P1 X: Y+ i$ i* y: C, {; W6 ]pid_t setsid(); 1 Z6 A4 r! L8 j1 G" @% k0 B2 h" {8 g
$ p7 Y9 V9 q8 J: x, ^% f0 s6 N
函数 setsid 创建一个新的 session 和进程组.
* N4 G/ [! t3 H
& M3 H, s3 y" s" H (3) 清除文件创建掩码
8 A% ?& E7 Q5 a4 @3 M$ Y; z, o
7 J w- d/ j% {+ S* g 进程清除文件创建掩码,代码如下: % t: E ^# y- k5 P- `
# x( |* m8 ~1 ~8 Humask(0); 4 ~# E |; |" L. m* F5 W C
7 x" t) z0 S/ O& l: n) S (4) 处理信号
& e# `/ Q, A& U5 x3 C
! ^+ @" ~# C/ \" J. ]1 V$ h 为了预防父进程不等待子进程结束而导致子进程僵死, 必须忽略或者处理 SIGCHLD 信号, 其中忽略该信号的方法为:
6 i) S# S0 ]- w& {3 C" U" z1 e6 z; A5 n: W" L; }" @
signal(SIGCHLD, SIG_IGN);
9 d. \( S2 V3 u1 f4 V
- ]+ U4 q$ i# W( k U5 ]( F% D8 N4 o 守护进程独立于控制终端, 它们一般以文件日志的方式进行信息输出. Syslog 是 Linux 中的系统日志管理服务,通过守护进程 syslogd 来维护。该守护进程在启动时会读一个配置文件“/etc/syslog.conf”。该文件决定了不同种类的消息会发送向何处。例如,紧急消息可被送向系统管理员并在控制台上显示,而警告消息则可记录到一个文件中。 该机制提供了 3 个 syslog 函数,分别为 openlog、syslog 和 closelog。
# Z- w9 |2 ?7 D$ T/ q/ @" V$ y) Y) I
下面是一个简单的守护进程实例 InitServer
9 g' ~# _8 D. B2 I9 K
5 K( h2 C1 M( K* n/ ]) O[bill@billstone Unix_study]$ cat initServer.c
4 D$ s- h# \7 k2 C+ p/ w
1 H7 B7 d* \/ }& L2 p- Q 1: #include <assert.h> " @- S0 l* S/ j/ I+ d
2: - ~/ i9 K2 a: O. |+ z0 j
3: #include <signal.h> 2 Q- z+ S, {3 Z4 Q" y# n5 c. k( V
4:
) e2 h3 A% I$ J: W3 n 5: #include <sys/wait.h>
& |! T4 s8 K$ L) }9 z* H3 Y 6:
1 { D1 l/ [: ?7 H0 a" W 7: #include <sys/types.h>
) V/ ^/ u, D4 r9 n 8: * {2 ?4 B" Z+ a# @6 q8 x& p
9: void ClearChild(int nSignal){ , v" v& d4 l( {# I, Y, o
10:
& y- w& S/ m' g0 H$ V# S 11: pid_t pid;
' ~1 e- p. U( {, i. y7 c5 G0 }, n 12: ! l. R: O0 C$ {" F% e
13: int nState; : |7 T8 l2 |/ w% Y& j! h
14:
! f K/ B2 p& T: E: L 15: // WNOHANG 非阻塞调用 waitpid, 防止子进程成为僵死进程
% M5 n/ U# n: f, v) z 16: & P! \0 L: R# Z
17: while((pid = waitpid(-1, &nState, WNOHANG)) > 0); 5 p6 E6 h1 ~1 x9 q
18:
E5 t( }* i) F5 \ 19: signal(SIGCLD, ClearChild); // 重新绑定 SIGCLD 信号
# D$ y# k0 T" {* m3 l 20: & _9 e2 r L1 b3 g
21: }
* D$ O& x2 d9 L2 g 22: + x1 ^+ w4 U6 b: l# V
23: int InitServer(){
/ `& h( e" G' `1 p, g5 }7 H; _: D 24:
! m. @: e5 R% P3 r, o3 C' v 25: pid_t pid; $ h1 k& T3 F+ A& S& q
26: 7 g8 \# E! X5 I3 S1 B% b% e
27: assert((pid = fork()) >= 0); // 创建子进程
4 E$ d5 c4 G4 V7 m3 U! i% l 28:
1 e6 \9 @$ f2 i6 T2 @* H( e9 a 29: if(pid != 0){ // 父进程退出, 子进程被 init 托管 3 Z- t% @7 R6 D1 H _' J6 K1 Y
30:
% F" G$ T4 e+ ~8 F$ r' o2 o 31: sleep(1);
4 o$ ^3 o6 t& ^+ ~ 32: 8 L' x, v: T f/ ^
33: exit(0); 6 v' ^2 t% r/ I/ n
34:
( e* r+ A0 ^8 U+ n( ?) X 35: }
6 e& j, _+ ?7 O* {9 g5 E 36:
2 k! r; k5 D4 D& q2 g% n 37: assert(setsid() >= 0); // 子进程脱离终端
6 [" l6 W& w" O) }. c 38:
6 b" n3 Z. u+ ?- u2 G9 [ 39: umask(0); // 清除文件创建掩码 3 |5 L+ n+ l" O) l
40:
2 k& m! Q; V8 t* S3 \+ t 41: signal(SIGINT, SIG_IGN); // 忽略 SIGINT 信号
M1 s2 v [( @6 y; d1 N 42: : t! p, N3 a( W& E
43: signal(SIGCLD, ClearChild); // 处理 SIGCLD 信号,预防子进程僵死 + w! f5 v" Y, q0 m6 w- I
44: 9 [ {4 {- i+ m p- F
45: return 0;
- T$ A3 m* c8 D( R 46: ' b: ], [. R- O. A$ B
47: }
3 m( D3 O5 i" O8 [+ s: ^ 48: % p G0 x+ n; ?$ c; }9 W
49: int main()
% g c# _5 P4 ]8 ?" r 50: 0 m" j3 A8 y8 j7 |8 v" s
51: { ) A8 K' J1 C, Y6 u; g" n$ ~% y2 D% Z
52: , K6 {# h7 q7 g
53: InitServer(); 9 T; b. A3 p7 l( y8 Q6 t5 f
54:
2 Q+ `) l1 ?& S' g6 F! Z$ J* P 55: sleep(100); s& `- C W; R0 P, S: q
56:
5 g' R! z* q7 D# s 57: return 0; / ^2 l5 a4 i3 K6 x/ c
58:
* H3 a6 N9 v: ?* }1 E# _ 59: }
5 I c+ j! Z ~( K0 c8 B[bill@billstone Unix_study]$ make initServer
5 ]) n O4 [- B7 P U* Y
0 b$ x; y/ m6 R# \; xcc initServer.c -o initServer 5 G. `: j1 L9 B3 u2 ] K5 P& K( D
# e) W+ b" N/ B& m$ o
[bill@billstone Unix_study]$ ./initServer # s) k9 m) W1 z' g! H* e" G5 u/ {
( P! o3 D( S2 M+ \: k2 N[bill@billstone Unix_study]$ ps -ef | grep initServer 2 i! v0 V T" `$ ?2 P6 O9 `
% L- J% h' F& U) f, j2 Z# \
bill 13721 1 0 04:40 ? 00:00:00 ./initServer // '?'代表 initServer 独立于终端
. I4 `6 l2 M' E) q, X& ?* |& w. L0 t3 F0 l* s8 _
bill 13725 1441 0 04:41 pts/0 00:00:00 grep initServer
8 o* E4 P4 v- e: \. D
; W4 {9 z1 Q1 m! s9 R2 X, E 程序在接收到 SIGCLD 信号后立即执行函数 ClearChild, 并调用非阻塞的 waitpid 函数结束子进程结束$ ?: I! f N q0 K5 z" f2 o, T
2 L# f& @* b$ L- m/ J$ B' W
信息, 如果结束到子进程结束信息则释放该子进程占用的进程表资源, 否则函数立刻返回. 这样既保证了不增加守护进程负担, 又成功地预防了僵死进程的产生. ) W* Z3 ^% `& N5 m7 h e4 G
' N2 m T/ Z3 l! r* ^
2 T9 H/ x8 u! q+ B/ W3 Y6 Y* q4 F& \# ]: b6 f( Z: Y+ a) f% a
自己编写的一个程序:
3 h' W$ A k: A. }2 k2 q( \7 V% r) z' }9 |- J4 Z" O
# cat test.c' K& p: A ?8 L4 t, f6 r* n, |5 o
, a6 N% I$ t% V+ R, j/ @$ Y' X 1: #include <unistd.h>3 O6 y, m2 }& M+ S0 Y- {( K+ g
2: #include <stdio.h>, z4 k. x) ]% ^
3: #include <sys/types.h>+ o$ Q) X# L; s
4: 2 y* C' h( t9 t9 `& l1 P
5: int cal ()& p S/ f, R- f# O: q
6: {
& v, s" ^$ E! \, p 7:
" d& p; w" H3 s8 k& P% Z( d+ x( q 8: int i = 0, sum = 0;
3 ]4 G3 j9 U v1 [% x5 Z6 O' W 9: + O2 _- X4 Z' e& W! r v
10: for (i = 0; i <= 100; i++)' Y$ Z" i4 I3 z- I
11: 2 Q+ z# V% k0 k% T( M
12: {: z5 k# P0 D3 j4 N& |2 h
13:
( q2 e& I. g' ] 14: sum += i;1 I+ Q F2 {/ T8 Z7 l4 C. `$ @7 N
15:
) ^% g4 M H8 e+ s# V& }+ T 16: }
( R! \/ P: K3 m1 }0 z' }; p7 |! W3 k7 u 17: ; N# W0 X; \0 U: O; ~: Y: C0 j
18: return sum;- T$ n: U/ l, F' P+ h( W. P; B
19: + I3 m# s8 z$ [9 E
20: }
5 Q' b: y# F+ K9 [" I* E 21: 0 u2 M) E' J, {4 n# C- m
22: int
& o' r3 }# b' c 23: 6 G9 Z5 H' U2 Z3 d2 O
24: main ()
7 d( c' A! V8 Z% b% w 25:
5 D* F7 |' U7 ?8 V( V 26: {
- _' j: c4 _2 q" W) { 27:
9 A5 ?7 E) G6 b: U5 V# a 28: int num=1, status;* f$ H* \% b6 K" c! y, c# ] I
29:
# w4 o1 K: i& n 30: int *s=#" A3 o# s# P" |3 {3 w. F/ R
31: 9 H& ]7 Z: O8 I
32: pid_t pid;
: ]1 P1 h6 p# \, e, D% j$ y 33: ; T! _; o7 [% X3 V+ U8 g" O
34: if ((pid = fork ()) == 0)
5 X1 r0 Y1 B, X6 f; N9 w2 U' N" e 35:
% X# W4 @8 m& O: E( D 36: {6 T4 ^. @; W6 W3 V, M) M, p
37: 8 o7 M' K5 t2 E
38: *s = cal ();
9 b4 U: e! L5 d) m& A6 Y* B! \ 39: 9 K4 h2 ^# W4 S O9 n% i0 x
40: printf ("1+..+100=%d\n", *s);% H2 b7 Y- }6 o6 ?5 f: p! l
41: 8 l- H- V( A7 C# G2 y( @% j
42: exit (0);% x! D; {! K1 n, J0 g
43: ! I! m/ h c7 |
44: }2 `4 z2 g# N% q/ a
45:
. X8 }1 p8 ^! M o) t 46: else if (pid < 0)% v* K2 C) I( |# @! \+ P9 n9 P
47:
# u' e% G( |: s0 [5 y 48: {! T7 c# \$ ^' p+ t! n* x; ]" j
49: , \# t. \( x/ x j
50: exit (0);6 S. x8 i S/ c% w" `& P1 @
51: % m0 B4 K1 {7 Q% W, f' n8 F
52: }
/ N; k, N6 w, C 53:
* k$ c3 x w: t0 F9 f* B 54: //pid = wait (&status);
) e% d% v/ G; a0 _ 55:
6 p* {3 D$ n: B1 l* ^; D 56: //if (status == 0)
- Z1 V/ O: P+ [ 57:
L' Z& p/ \7 H" D2 [1 ]8 t1 u/ w 58: // {
* v! R- X. H @2 w& N5 L 59:
! r* w) E4 w. b; t2 U; @ 60: wait ();
4 c: U. h/ I& ?( `. E 61: 1 T* E9 p4 x$ {' ]) S% W# c
62: printf ("1+2+...+100=%d\n", *s);
5 i9 n1 X+ }5 s7 b6 {. w 63: 5 l( s, ]8 d/ i+ c1 x, L6 A1 z
64: // }3 q, s8 y o3 c" X
65:
9 S+ ]& z; m& O6 ^ 66: //else
0 `1 F8 C' m9 @0 m1 c' P/ p' t. P 67: 9 L. O# Y, o6 T% s6 T" u
68: // {
: N: ^' t9 P6 V9 ] e* D: P 69:
1 {' a* Z" p7 ^) ?- J 70: // printf ("error!\n");8 G, ]. w7 r) l% w
71:
! J% x! n. s' r( P& V, M 72: // }
9 X* o/ }6 h1 r! T 73: h4 h7 i5 p' o7 I
74: }6 w* z3 i% W) N4 {/ W! N
75: $ ^' }% i& t" [( t5 k* {3 g
76: [root@localhost chapter9]# ./test
+ X- C. c8 |& Y+ B' N 77: 8 k7 M# N- {2 {5 W% I! Z7 q2 W
78: 1+..+100=5050$ U" }5 E5 T* |5 r* v* S$ N! Q2 e
79:
# e5 S& p2 w8 l+ j) I 80: 1+2+...+100=1
$ X# t1 s* B7 N0 \ 81:
" S& J2 j, v7 h& g2 F7 y程序的本意是用子进程来执行函数,而fork子进程完全复制父进程数据空间,这样子进程会获得父进程的所有变量的拷贝,尽管父子进程变量名相同,但却存在了不同的地方,因此不能通过内存变量完成父子进程之间的信息传递。
8 H7 [* Y+ H4 G0 N% h1 S( I1 z% m |
|