a我考网

 找回密码
 立即注册

QQ登录

只需一步,快速开始

扫一扫,访问微社区

查看: 58|回复: 0

[综合辅导] Linux认证辅导:形象的理解dup和dup2函数

[复制链接]
发表于 2012-8-4 12:07:07 | 显示全部楼层 |阅读模式
Linux认证辅导:形象的理解dup和dup2函数/ n5 W$ T8 Z* ?8 o
相信大部分在Unix/Linux下编程的程序员手头上都有《Unix环境高级编程》(APUE)这本超级经典巨著。作者在该书中讲解dup/dup2之前曾经讲过“文件共享”,这对理解dup/dup2还是很有帮助的。这里做简单摘录以备在后面的分析中使用:) [- r) d* \2 y; d) x* }# h5 ?  `
Stevens said:
7 J9 _8 w) O& [! v: d, D) N9 S5 Q(1) 每个进程在进程表中都有一个记录项,每个记录项中有一张打开文件描述符表,可将视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:
- L/ c* V) i5 @  G, Z(a) 文件描述符标志。( o7 G) E4 x& u8 b
(b) 指向一个文件表项的指针。+ J5 c) B* K# I% {4 v/ u" s- C' c
(2) 内核为所有打开文件维持一张文件表。每个文件表项包含:
' t7 C3 e( N) D$ x% r  x(a) 文件状态标志(读、写、增写、同步、非阻塞等)。( o4 B$ u; X3 z" M
(b) 当前文件位移量。
) R3 B' Q2 ], }% x0 F(c) 指向该文件v节点表项的指针。
4 u% n& O, N) U6 _* y图示:
/ ~( Y1 f: a4 _- H+ ~文件描述符表1 }$ W: |) e& A! @
------------- l7 _. Q" v: @$ D9 {1 V$ X' w+ u
fd0 0 | p0 -------------》 文件表0 ---------》 vnode0
% C5 Q; o# M2 |2 E0 E------------
, `. l( E0 X1 e- s- s# B9 m2 wfd1 1 | p1 -------------》 文件表1 ---------》 vnode1
8 {  e% f0 k8 {9 }% n------------
% j$ o3 [3 h" lfd2 2 | p2- o& s! T* r% b8 M# t6 C  b- K' z: P
------------$ j2 C- B  V9 s/ t
fd3 3 | p3
8 a! {: p- u- e% J7 I# f------------
' r' I" l, S  g' q+ |4 o7 ?! F0 F。。. 。。.5 `' D) J; M% ~6 ]- A( A2 |# C
。。. 。。.9 C% Q) f4 r- D7 n; j* |
------------
$ t3 u! D. e8 y$ w* U' r一、单个进程内的dup和dup2
5 W+ a" r8 `9 h- [假设进程A拥有一个已打开的文件描述符fd3,它的状态如下:
- ^5 Z$ J9 o' _& r进程A的文件描述符表(before dup2)7 E4 b7 H1 O# }, R3 n: e
% S- z5 i5 G+ \0 x: ?- k8 t
! G" ^2 k7 Q* h
------------
( l) p7 `7 W  Q; T7 ufd0 0 | p0+ N+ h+ t- S7 i" g" [
------------
/ q8 p6 n% U3 _, S. J5 yfd1 1 | p1 -------------》 文件表1 ---------》 vnode16 b; i8 z) h2 d9 O" e
------------! s) o7 N- A/ R. A3 L: W
fd2 2 | p2
1 n" i) N) s6 Y# Y0 @2 P" {6 j9 k------------
  @3 @& K: W+ W3 g( vfd3 3 | p3 -------------》 文件表2 ---------》 vnode2
) C7 E# D0 f, y9 V5 p2 y6 P' `2 R5 W------------( ]* q6 s1 W1 _" n5 f  \' D
。。. 。。.
2 a7 m, n; @7 h。。. 。。.7 G0 x7 `, g7 L$ k- w( g9 |, g1 `* z
------------
6 q; f2 j( \/ c% I; B, `1 [经下面调用:6 [7 v: |4 W7 J3 P" h/ A: p  D$ A
n_fd = dup2(fd3, STDOUT_FILENO);后进程状态如下:
9 S1 V1 d( M3 I5 `& t5 f进程A的文件描述符表(after dup2)( L9 E& \; n, C8 }
------------  \5 R: Q' x& H6 e; T$ ?( v
fd0 0 | p0
( C2 Q+ P% M: R6 q1 R. o/ R4 X. Q------------
# _, D6 ^! V! h, q9 nn_fd 1 | p1 ------------
: r: H2 |8 F3 }8 V9 \! }' x------------ \) F, Z2 h$ @, z  ^5 z8 S% N6 O
fd2 2 | p2 \
6 ?" x3 S  V# o------------ _\|
; f6 v5 R: v; M0 L' bfd3 3 | p3 -------------》 文件表2 ---------》 vnode2
' z. R! K+ i! ]------------* J9 D5 n8 Z/ Z1 }* u2 `
。。. 。。.  w' l; [  A$ W$ h% W
。。. 。。.# @3 F! m, n0 @) i6 t, Q7 U! W
------------
8 Y0 f7 e' X/ Z3 r2 H  r' v' h解释如下:
& A( v- C* r+ yn_fd = dup2(fd3, STDOUT_FILENO)表示n_fd与fd3共享一个文件表项(它们的文件表指针指向同一个文件表项),n_fd在文件描述符表中的位置为 STDOUT_FILENO的位置,而原先的STDOUT_FILENO所指向的文件表项被关闭,我觉得上图应该很清晰的反映出这点。按照上面的解释我们就可以解释CU中提出的一些问题:" \3 [# @/ ]4 w$ `6 V+ s, h
(1) “dup2的第一个参数是不是必须为已打开的合法filedes?” -- 答案:必须。
/ o; L& B2 [- P6 j(2) “dup2的第二个参数可以是任意合法范围的filedes值么?” -- 答案:可以,在Unix其取值区间为[0,255]。
. |/ c* R% w: V/ X  p另外感觉理解dup2的一个好方法就是把fd看成一个结构体类型,就如上面图形中画的那样,我们不妨把之定义为:
( x" I( A0 o; e: k; hstruct fd_t {0 ?/ H3 q% C2 f; \, a5 C6 ?
int index;
# H- v. o9 D* ^# z0 g4 Dfilelistitem *ptr;: x' t) R# F5 g; G
};  a( i/ X0 R# @/ Q4 N3 T
然后dup2匹配index,修改ptr,完成dup2操作。
3 X& z# K' l  a  f& [7 j7 ~在学习dup2时总是碰到“重定向”一词,上图完成的就是一个“从标准输出到文件的重定向”,经过dup2后进程A的任何目标为STDOUT_FILENO的I/O操作如printf等,其数据都将流入fd3所对应的文件中。下面是一个例子程序:) l6 I# m+ d+ o% D2 t" r
#define TESTSTR “Hello dup2\n”
' ?: @4 z7 r4 o0 \9 t- @int main() {4 Q7 o9 K* I6 w+ E
int fd3;" k" Q6 T7 z6 w+ T6 L# b1 J
fd3 = open(“testdup2.dat”, 0666);8 A  f) e& h: \1 I8 Y9 M/ [
if (fd 《 0) {, t+ B1 J; `! ^* p' ]
printf(“open error\n”);/ D& S4 g0 P/ P2 u
exit(-1);7 M' J, r& v( }% ^0 @5 Y
}' N$ A9 ]# z* P" g' r
' N3 I: J: ^$ s) p( [+ u$ ]

. ~1 [1 P5 w( E8 M/ ]8 z% [' Wif (dup2(fd3, STDOUT_FILENO) 《 0) {
' w& H! X2 x2 M# y. d3 qprintf(“err in dup2\n”);5 f" }  @- D; p/ O7 n% N! x- l9 {3 }4 L
}8 i' F/ m3 |/ Y9 s. E4 z
printf(TESTSTR);
: I3 K+ l) ]1 t1 P" preturn 0;
$ F. |0 @  p4 _' ^}
& p$ F- Q' L& T9 X- N其结果就是你在testdup2.dat中看到“Hello dup2”。& X( O8 s3 O: J
二、重定向后恢复6 J, D! {( i8 r- r+ Y7 d3 g
CU上有这样一个帖子,就是如何在重定向后再恢复原来的状态?首先大家都能想到要保存重定向前的文件描述符。那么如何来保存呢,象下面这样行么?
7 A" ~+ H- s$ x3 b4 l6 S  Cint s_fd = STDOUT_FILENO;
$ S# @5 r! }) _* d1 S' Fint n_fd = dup2(fd3, STDOUT_FILENO);
: P4 _0 V0 j4 k还是这样可以呢?
! K6 f1 N% f) c$ u) ?. w/ [& t5 qint s_fd = dup(STDOUT_FILENO);5 w! f! n; M& L8 M* `3 ]0 S2 N7 Q
int n_fd = dup2(fd3, STDOUT_FILENO);1 }4 h' F  k, \% U- J- h7 S
这两种方法的区别到底在哪呢?答案是第二种方案才是正确的,分析如下:按照第一种方法,我们仅仅在“表面上”保存了相当于fd_t(按照我前面说的理解方法)中的index,而在调用dup2之后,ptr所指向的文件表项由于计数值已为零而被关闭了,我们如果再调用dup2(s_fd, fd3)就会出错(出错原因上面有解释)。而第二种方法我们首先做一下复制,复制后的状态如下图所示:0 W7 ]0 k# _! a7 G
进程A的文件描述符表(after dup)
; {+ Z! B& n/ G1 s0 N2 p------------
, f: f& Q- G7 s$ Z$ q8 jfd0 0 | p0/ I1 K/ _" b0 q% `! J! X2 o+ f+ {
------------
2 N$ J1 ?+ i+ Z6 p. x8 k7 C! tfd1 1 | p1 -------------》 文件表1 ---------》 vnode1
( V' ]8 ~1 c& |------------ /|
% I% c' W  l- I7 Kfd2 2 | p2 /
0 h0 r/ `+ ?/ G' s5 H1 l2 i------------ /
$ K3 [/ e3 @- @, tfd3 3 | p3 -------------》 文件表2 ---------》 vnode27 \/ v; x7 S9 ?; l; s" }3 K
------------ /
  K$ V+ ]. }+ h) @& F5 As_fd 4 | p4 ------/
& g' L& z" l: ^6 {------------; e* i9 M0 n! ~( N' i! O
。.. 。../ q: ?6 a3 Y; K$ C! I( {$ }/ {
。.. 。..% I" o: H7 _1 u# D
------------( F. [( Q9 W* E9 ]
调用dup2后状态为:
. G% x# X4 `( }- ^2 D* Q1 v! z# \进程A的文件描述符表(after dup2)) g( _1 _$ U5 ]7 z% \
------------2 u- w/ u2 Q; S7 R1 P3 [8 P
fd0 0 | p0
6 w% X" Z9 X5 c------------) O' G" K& w1 h# r% Q
n_fd 1 | p1 ------------: r2 ?# q  L% i" ^. E& u. c
------------ \0 w# t5 v. `! p- d1 G0 l
fd2 2 | p2 \
' z. A# A# J% _------------ _\|
6 h5 [/ W' I9 D2 H, F1 L/ `2 sfd3 3 | p3 -------------》 文件表2 ---------》 vnode2
  u2 ?( k1 u, d* d3 l/ p+ j& j* `------------/ _1 A5 Z) f1 R9 K) G
s_fd 4 | p4 -------------》文件表1 ---------》 vnode1
' X7 `# f2 ?; m! N% C+ k( E------------$ n9 U; v* G+ U6 J' j
。.. 。..
7 ]  e% C( Q9 x1 V9 e, V! A。.. 。..
/ l* B1 P, E) p% V& k! f------------+ V! W/ c% N! R$ i) H* g
dup(fd)的语意是返回的新的文件描述符与fd共享一个文件表项。就如after dup图中的s_fd和fd1共享文件表1一样。
/ ^$ Y5 [( M, S; ~2 S( A确定第二个方案后重定向后的恢复就很容易了,只需调用dup2(s_fd, n_fd);即可。下面是一个完整的例子程序:5 ^% ^" q5 k0 o  x# p  [2 S+ q
#define TESTSTR “Hello dup2\n”, L) v' q/ O0 n% I: ^+ L
#define SIZEOFTESTSTR 112 p$ M  r9 z/ e* L8 `( v
int main() {
" f) N8 e- M- Z+ g6 M4 S2 C8 S% yint fd3;
; `; H: L. n9 t! g* Gint s_fd;4 m8 Q& k% Y/ B* w) L
int n_fd;
) v6 y) Y2 k$ ]4 a( Y6 ?5 Vfd3 = open(“testdup2.dat”, 0666);
4 J& X3 i) v* L, Z9 Eif (fd3 《 0) {$ b9 w3 Z/ x+ t, F
printf(“open error\n”);
. R! w8 r. Q; n- Xexit(-1);
* d# h7 y  t  d, t4 O}
' J# L# i" ~, F3 p* g2 F( \/ z: ]6 ]! Z- m- y
0 x' I) q7 x  \  O. F
/* 复制标准输出描述符 */
, ?- G0 b! V4 h* K2 f% X  I! Fs_fd = dup(STDOUT_FILENO);
5 O0 @0 Z( y; Sif (s_fd 《 0) {
; R. o( X7 f0 k/ _) S9 i, Xprintf(“err in dup\n”);
$ [# [" S9 i2 j2 L9 o}1 Q1 N* v% C1 b+ y
/* 重定向标准输出到文件 */
& b# ~( G  d, V* x* S' p# B& }n_fd = dup2(fd3, STDOUT_FILENO);$ y4 c/ j; \7 @! K
if (n_fd 《 0) {# F4 `8 f4 X! G3 F; d
printf(“err in dup2\n”);
' {9 V! ^% \' s/ F}+ G3 F( k; T7 f5 r- T
write(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR); /* 写入testdup2.dat中 */0 m- U! D1 J6 y( }7 c: O. y
/* 重定向恢复标准输出 */
0 N% b; B+ F" X- C2 I) Iif (dup2(s_fd, n_fd) 《 0) {
0 s- y  y: W) q' V2 q0 S  \printf(“err in dup2\n”);
: W/ \6 `6 r: L5 N3 t5 A9 D  r}
0 Y, b" L  q* S% L1 z0 c% Uwrite(STDOUT_FILENO, TESTSTR, SIZEOFTESTSTR); /* 输出到屏幕上 */7 n. N/ `* ]9 ?; [* p1 d" @/ t$ H7 s
return 0;& z% i; p8 e; P) s) T3 s" s
}% E% Q4 q4 G; Q- V- `. V# s
注意这里我在输出数据的时候我是用了不带缓冲的write库函数,如果使用带缓冲区的printf,则最终结果为屏幕上输出两行“Hello dup2”,而文件testdup2.dat中为空,原因就是缓冲区作怪,由于最终的目标是屏幕,所以程序最后将缓冲区的内容都输出到屏幕。
1 X) S6 ], S  M) E三、父子进程间的dup/dup2: U/ G) t' J6 f9 s1 ?; N) t
由fork调用得到的子进程和父进程的相同文件描述符共享同一文件表项,如下图所示:
7 g( k; H! Q! e# |父进程A的文件描述符表0 r; a- |; C4 p, w( N1 g2 ~# Q. i
------------1 Z. x4 Y+ R& |9 y
fd0 0 | p09 a$ b% m5 p! l3 {$ v
------------$ o+ ^; M6 w' R0 Z% V+ D  T
fd1 1 | p1 -------------》 文件表1 ---------》 vnode1
% }; J. `; w; m) Z$ M% F------------ /|\
& P& g2 F9 N) {: O4 Zfd2 2 | p2 |- D6 d; g% j! Z9 j0 |
------------ |/ t  u, @5 |* D8 G# W0 ]; _
|
( v% d; l& w  ]! I9 _3 T3 l# ]( ]子进程B的文件描述符表 |0 K9 @" V, L1 T1 p
------------ |' p  |4 f/ w7 m9 Y3 X3 c
fd0 0 | p0 |
; u1 s# e5 l. z6 P: A+ c5 [8 T------------ |
. [$ |6 `) J& M+ k: Dfd1 1 | p1 ---------------------|% H/ y7 y4 u( {% ?
------------0 g/ v" h* d! V3 Z
fd2 2 | p2; U& y# ^6 v, J
------------: Q- \8 }1 ^, a- O5 e
所以恰当的利用dup2和dup可以在父子进程之间建立一条“沟通的桥梁”。这里不详述。( I& `8 E9 T6 b# m, [4 i
四、小结
; D3 q6 \) Q! {, T. i$ `/ }  M灵活的利用dup/dup2可以给你带来很多强大的功能,花了一些时间总结出上面那么多,不知道自己理解的是否透彻,只能在以后的实践中慢慢探索了。
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|Woexam.Com ( 湘ICP备18023104号 )

GMT+8, 2024-5-17 20:25 , Processed in 0.682769 second(s), 21 queries .

Powered by Discuz! X3.4 Licensed

© 2001-2017 Comsenz Inc.

快速回复 返回顶部 返回列表