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