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