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