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