网络推荐



本广告位招租!

推荐给好友 上一篇 | 下一篇

第五章: 基本I/O

译者:horsemanBSD爱好者乐园Yg b%Yx#u U
(多谢我的同事HUGH CHEN翻译5.6,5.7跟部分5.4,5.5段落)

4euMz;LU-K1
J,]J#} X]1BSD爱好者乐园]C G9b~N9}
本章示范(示例)代码:http://www.khmere.com/freebsd_book/src/05/index.html
QA2B}Dnt1
j0Mk5R_(@WkM15.1  基本输入输出BSD爱好者乐园 q3ru9X;e#X_4}
BSD爱好者乐园&wH(sm.W/qpH"f L
一般来说,Unix信奉简单设计的哲理。"一切都是文件"是个很强大的特征--这就意味着你所编辑的文本文件具有和调制解调器、打印机网卡相同的编程接口,就像编辑文本文件一样,你应该能够对它们(被编辑文件)执行基本的读、写操作等等。尽管这个想法现行的实现不完美,BSD Unix实际上做到非常接近了,这也正是BSD又一个强大的地方--简洁而优雅。有些不是真实的文件而是设备,它们的入口在/dev目录下,有些设备只能用于特殊的操作,如块的读、写,一个极端的例子是以太网设备,甚至它(以太网设备)在FreeBSD 5之前在/dev下面没有入口。BSD爱好者乐园7R6D%n1g5Q'Q
BSD爱好者乐园?Pe/H|,tt
操作系统看待每件事都像文件,一个好的示范是Plan 9,Plan 9用文件实现一切,甚至以太网和网络协议;更详细的信息可以参考Plan 9的主页http://www.cs.bell-labs.com/plan9dist/BSD爱好者乐园T5e1T t)OVB
BSD爱好者乐园9^T ] YZ
一般来说,文件是计算机上数据最基本、最初级的表现形式,本质上是数据一位一位的线性序列。当用exec命令执行编译过的程序时,系统将把二进制文件读入内存,代码将在分配到内存地址空间后被执行,程序被定位在什么位置跟exec命令有关,它可以在软磁盘,硬盘,光驱,甚至是加载的另一半还分布在世界其他角落的网络文件系统上;跟基于网络链路发送数据一样,内容被顺序地一位一位地读入。当一个程序发送数据的时候,数据本身是一位一位的线性序列,有些时候叫做“流”,程序不关心它是否基于网络链路发送数据,它仅仅写数据;这两种最基本的操作,读跟写,是计算鸡用到的最多的。BSD爱好者乐园"mCQlF3m
BSD爱好者乐园Rja2c1U;~/yM,g
本章将涉及最基本的I/O子系统跟进程资源。
-ie:qzL1UIr1BSD爱好者乐园.D!a*~!HA@s u dE
BSD爱好者乐园6X+z$S|@
5.2  基本输入输出
)|Zd%Mm1Vu l1BSD爱好者乐园+ay*`] t z?
UNIX进程打开文件的时候,会保存文件描述符的参考值,该值是一个整数,不论何时在UNIX系统上创建一个进程,都会给它3个文件描述符:
:?5_V%_[@5Cl,lxW1    0   标准输入BSD爱好者乐园}$D;l0x-\7X'y
    1   标准输出BSD爱好者乐园\m iFB&c@
    2   标准错误BSD爱好者乐园;BrnGS-c AT c
BSD爱好者乐园 ZdlFeCu8zF
这些值能够用于描述终端、文件的读写,甚至设置其他进程的描述。使shell重定向, cat /etc/hosts >> hosts.out, shell将打开文件hosts.out并且将cat /etc/hosts做为自己的参数执行。不管怎样,当cat进程写到标准输出后(1),结果不会被tty得到,而是输出到hosts.out文件。(cat程序根本不知道写入了文件系统的文件,还是写入到标准输出的文件描述符,我们将在这章的后面一点看到究竟是怎么实现的)BSD爱好者乐园V M/{.b&W:^,m
BSD爱好者乐园wN pX&E&Xc,^
最基本的两个操作描述符是Open和Close函数BSD爱好者乐园#z[5ni lVI3XR2DxW
BSD爱好者乐园q }B/g L#GZ
Open函数
u7S u t(C6Vm1
BvFAd,W/z1
 
int     open(const char *path, int flags, /*  mode */ );,

#`d I"~ f&t5C1成功地调用这个函数后,Open函数将返回文件及参数的描述符,这个整型描述符在进程文件描述表内生成索引。该描述符的结构能够让内核知道如何操作这个文件。在BSD上,这个结构叫做filedesc,并且能够在/usr/include/sys/filedesc.h这个头文件中找到。当进程要在这个描述符上执行一些操作的时候,它将要求文件描述符为读、写、可执行操作指定一个整数值。
-KO].~r(h(PWE1
,Md0_ G| b)l6R QI1内核保存了所有文件描述符的基准值,这些基准值将在时进程打开、复制它或者已经处于打开状态的文件描述符执行exec调用、跨越fork的过程中增加。当这个参考值为0的时候,文件关闭。意思是,如果你有一个程序执行fork或者exec调用,并且close-on-exec位没有指定,基准值将增加;并且当一个新的程序执行了fork或者exec调用,基准值将继续增加。所以文件一直处于打开状态,直到基准值被设置为0,或者直到所有的进程关闭它,或者退出。
O0r;GKJ1
Y w]:x0KJx!T1Close函数BSD爱好者乐园!WP(]4C0Ls'Y\/sf

~g!bJtw1
 
int        close(int fd);
BSD爱好者乐园%Z6bR_B
当进程想移除或者关闭一个打开的文件描述符的时候,它将调用close函数。这将关闭指定的文件描述符,并且减少文件描述符的参考值。这个过程很像 exit————当一个进程执行exit,所有打开的文件描述符跟进程一起被自动地减少,自从文件参考值设为0,内核将释放所有的文件入口的副本.
G#R7hdH/}@1BSD爱好者乐园4Hy"I5mt,g8[
getdtablesize函数BSD爱好者乐园zq+P)a0bq9hX
BSD爱好者乐园c)R:R5o Y!|-_w?8l1M
 
int        getdtablesize();
BSD爱好者乐园._t r"h;G0D`E~(Z
getdtablesize函数返回文件描述表的大小,它能用作检查系统限制,你也能够用下面的命令做相同的事情。BSD爱好者乐园n kf9cR

.Mu@9k1ZC Gy1
 
bash$ sysctl kern.maxfilesperproc
4x0InS:OS#W;`1kern.maxfilesperproc: 3722

*~y q F+I~.D1依赖于你的系统,你能够在系统运行的时候调整它,或者从新编译你的内核,这个函数不能检查当前进程打开了多少文件,(跟getdtablesize一样)仅仅返回你的进程能够打开文件的最大个数。
Mvr0F@)~1
#Fx5|-T6v:S1fcntl函数BSD爱好者乐园:txg5_&da,b#rR

5uQe(Q$V,nf-W]9Ap1
 
int        fcntl(int fd, int cmd, ...);
BSD爱好者乐园5}/q$E^ L*S!S
fcntl 函数允许进程操作文件描述符,fcntl函数至少需要指定两个参数,一个有效的文件描述符,一个命令。根据使用的命令,决定fnctl是否需要第三个参数,下面为命令定参数义了一些值。在FreeBSD上,你能在/usr/include/fcntl.h头文件中找到它们。
'^:Ky VvP1
#m$MRLacR4y1
 
#define F_DUPFD        0
BSD爱好者乐园5GR:v9]~+@$\
F_DUPFD用作创建一个新的很像原型的文件描述符,(你能够用dup调用做到相同的事情,将在晚些时候涉及到),当成功执行带F_DUPFD标记的fcntl函数,fcntl将返回下面属性之一的新的文件描述符:
v&kE Hq x n]1BSD爱好者乐园y"]Tynr [

    +G7x0PkDcl1
  • 假如指定了第三个参数,描述符返回的值比最小可用的描述符,该值等于或者比给定的第三个参数的值大一点,返回的描述符将参照指定给fcntl第一个参数的文件描述符BSD爱好者乐园[mE8@)T5d%pXn
  • 假如fcntl指定的文件描述符是一个文件,新文件描述符将有相同的文件偏移量,并且新文件描述符将有相同的访问方式(如:O_RDONLY,O_RDWR,o_WRONLY)
    /c;Dz ?/`%W1
  • 新文件描述符将共享文件状态标记
    #na0N!qPR U o1
  • 新文件的“close-on-exec”标志将被关闭,即新的文件描述符在exec调用之后仍将保持打开。BSD爱好者乐园;Ke#KE^TQ
BSD爱好者乐园6hu1sz/hs(i
BSD爱好者乐园,OOm*{bIu+M@N
F_GETFD命令BSD爱好者乐园-Z2h0h ~a0b
BSD爱好者乐园!v }e:Y:V%@
 
#define  F_GETFD     1
BSD爱好者乐园*Ssj6I$q5xi@
F_GETFD 命令用作获取"close-on-exec"标记的状态,跟FD_CLOSEXEC相与后返回值要么为0,要么为1。如果返回0,close-on- exec标记没有被设置过,那么文件描述符将保持调用交叉执行调用(so the file descriptor will remain open across exec calls.),假如是1,close-on-exec标志被设置过,文件描述符将在成功调用一个exec函数后被关闭。
8\:l*ua&U3P!c p4z a.S1BSD爱好者乐园eI([(z2Y,s0O:Vy.r)[
F_SETFD命令
:U1n5J;F&h$b:H6k1
0Cad+l$^I!k1
 
#define  F_SETFD     2
BSD爱好者乐园4YX,jZ[Qv
F_SETFD命令用作设置文件描述符的close-on-exec标志位。第三个参数要么是FD_CLOEXEC,设置close-on-exec标记,要么是0,取消close-on-exec标记的设置。
)y%op Gt"O2v1
1e)w"RjN)oPc}2ix1F_GETFL和F_SETFL命令
{,Wx2l9p/e1BSD爱好者乐园-V$^R0YZJ,j
 
     #define  F_GETFL    3
l`,r(F%La1        #define  F_SETFL    4

's,k%in.J:h0q@1F_GETFL命令将使fcntl返回当前文件描述符状态标记,当返回值加上O_ACCMODE(#define O_ACCMODE 0x0003)能够获取到打开的方式,F_SETFL命令将根据第三个参数设置文件状态标记。BSD爱好者乐园5To |V1x}

} X2h,rb{%q1公共的标记
s7^T_#iv[ C1BSD爱好者乐园"up[;OsmBV:k&U
下面这些标记也用作调用open,并且只能被跟上期望的标记调用open函数设置,这是最常见的,检查你系统的头文件能够查到那些值BSD爱好者乐园U/PXh gv

&gg.\%T3L:Ag%O1
 
#define  O_RDONLY 0x0000
BSD爱好者乐园` ^)D)@ p&B"S
如果O_RDONLY标记被设置,那么文件只能以只读方式打开。注意这个O_RDONLY标记只能在打开的时候被设置,它不能被fnctl加F_SETFL命令设置。
!iE$Qh"?bk1
0~$dN Z3y/P S S*Tta1
 
#define  O_WRONLY 0x0001

+r|2wM+]8vF{1如果O_WRONLY标记被设置,那么文件只能以只写方式打开。这个标记只能被open设置,不能被fcntl加F_SETFL命令设置。BSD爱好者乐园Jo"zY:[]
BSD爱好者乐园M&SM7y7O#K3r
 
#define  O_RDWR      0x0002
BSD爱好者乐园yGy,e%Q+za3rx
如果O_RDWR标记被设置,那么文件以可读可写方式打开。这个标记也只能被open调用设置。BSD爱好者乐园 yZ-` f+l"W
BSD爱好者乐园v]Jl%~0`2lB9^V
 
#define  O_NONBLOCK  0x0004
BSD爱好者乐园~\ dxr
如果O_NONBLOCK标记被设置,文件描述符将不被阻塞而被直接返回替代。一个例子是打开tty。如果用户不在终端调用里输入任何东西,read将被阻塞,直到用户有输入,当O_NONBLOCK标记被设置,read调用将直接返回设置到EAGAIN的值BSD爱好者乐园~/G8R jVO w/D\)n

~sM g ~:f|1
 
#define  O_APPEND 0x0008

b'e'p c%g4lS1如果O_APPEND标记被设置,文件将以追加方式打开,并且将从文件末尾开始写入。
c{"d4f h]Q6l1BSD爱好者乐园n qbdz,ac ]Est
 
#define  O_SHLOCK 0x0010
BSD爱好者乐园EZu?7j*}g
如果O_SHLOCK标记被设置,文件描述符将在文件上生成一个共享锁,在文件上设置了共享锁,多个进程能在同一个文件够执行操作,文件共享锁的详细信息,可用看fnctl函数的F_GETLK跟F_SETL命令。
S[4L(b x1
 
#define  O_EXLOCK 0x0020
BSD爱好者乐园5uM7u-g&E7e(WE'W`
如果O_EXLOCK标记被设置,文件描述符将在文件上生成一个可执行锁,一样,更详细的描述可以参照fcntl函数的F_SETLK跟F_GETLK命令。BSD爱好者乐园 Za/W/i `w z
BSD爱好者乐园%~8dx&i:V2_g,@
 
#define  O_ASYNC     0x0040
BSD爱好者乐园pDuN%P]/NAu
如果O_ASYNC标记被设置,进程集将被发送的SIGIO信号通知,在文件描述符号的IO是可用的。详细的描述请参照信号那一章。
g@hYu1BSD爱好者乐园 K@oY7T
 
#define  O_FSYNC     0x0080
BSD爱好者乐园 BK YWnT5l1F
如果O_FSYNC标记被设置,所有写到文件描述符的写操作将不被内核缓存,取而代之的是将被写到介质,并且所有的写调用都将被阻塞,直到内核完成(写操作)。
u&cf4y QHU2N5m M+t\1
GY?3H @wa1
 
#define  O_NOFOLLOW  0x0100

Td k%U/sy%{*c1如果O_NOFOLLOW标记被设置,假如文件是一个符号连接,open调用将会失败。如果在一个有效的文件描述符上设置了这个标志,那么当前文件就不是一个符号连接。
;V:? {h3m'bc%g%R1
%bWP xr^B1
 
#define  O_CREAT     0x0200

.^~/C7tF1如果O_CREAT标记被设置,假如执行open调用的时候文件不存在,那么文件可用被创建。(这个错误的拼写很有趣;when one of the original creators of C was asked "What one thing would you change about C?" he replied, "I would change O_CREAT to O_CREATE!", or at least how the rumor goes)
-J;kd yo7P"D8N1
R(|h AF]x7U1
 
#define  O_TRUNC     0x0400
BSD爱好者乐园O)d!bB4E:M)^)t
如果O_TRUNC标记被设置,成功地调用open后文件将被截除。BSD爱好者乐园j(onMz)A s Df

ud\4M%J.E7zi1
 
#define  O_EXCL      0x0800
BSD爱好者乐园!h(f!r] zqSa\s
当O_EXCL标记被设置,假如文件已经存在,open调用将产生一个错误。
;R4IX!b_ViT*]1
(I$Eo un-z1
 
#define F_GETOWN          5
BSD爱好者乐园1xo i?l;|1m/`(L`
F_GETOWN命令用于描述符获取当前进程或者进程集收到的SIGIO信号。如果这个值是一个正数,它表示一个进程,负数表示一个进程集。BSD爱好者乐园 ^Ao i4nZ%_,~
BSD爱好者乐园VY }g0{A
 
#define F_SETOWN  6
BSD爱好者乐园(@K)e | |e
当IO就绪的时候,F_SETOWN命令用作设置进程或者进程集使其接收SIGIO信号,指定一个进程,用一个正数(一个进程ID)作为fcntl的第三个参数,否则,用一个负数作为fcntl的第三个参数指定进程集。BSD爱好者乐园+LjTCf4zX2nSC

;f[#X Oj3Ok15.3文件上锁
usb(eI Lyq X.S1BSD爱好者乐园.k W;\YO)xT3G,AI
当多个进程试图写同一个文件,将发生什么?它们相互冲突,已知的事情像文件上锁。结果就是每个文件描都有自己的描述符跟偏移量,当每个进程写自己的文件时,偏移量预先独立导致没有进程知道其他的进程也正在执行写操作。最后的文件将因为多个独立写文件的操作使混合后的文件变得相当于垃圾,直接给文件上锁是解决这个问题的一种方式。在任意时刻只能让一个进程能够写到文件,另一种办法是允许在一个叫做高级文件锁的scheme里的文件内部进行区域锁定。 fcntl函数能够提供这个功能,通常来说,锁有两种,一种是写,另一种是写,不同之处在于读锁不会干扰其它进程读取文件,但是特定的区域只能一个写锁存在。
OC4C)S(Ua1BSD爱好者乐园kME0Hqy
当使用顾问锁的时候,下面的结构用作fcntl的第三个参数。BSD爱好者乐园3j;i(O|gSA;Dn

K8~wn5W)tk5P1
 
  struct flock {BSD爱好者乐园2uM2W*\"\:T
            off_t   l_start;    /* starting offset */
l{v!M0in1            off_t   l_len;      /* len = 0 means until end of file */BSD爱好者乐园%X;d _ r.E/C
            pid_t   l_pid;      /* lock owner */
_]4o|+D,_7HdfH3w1            short   l_type;     /* lock type:   */BSD爱好者乐园 a)F:z9c8Ah9bW4d4I
            short   l_whence;   /* type of l_start */
9vK3IF'Xi4L1G1    };

f!Z cRjt&e1让我们继续讨论每个元素的细节。
%]G |uv}y4W1
@)d+g G q^ i5d(\7c1l_startBSD爱好者乐园}E@qO"GJ

*[ ]n+h ~ P4Z1这是一个相对于l_whence,单位为字节的偏移量,换句话说,要求的位置实际上是l_whence + l_start
|l8^N,Ev1
)^_*g _6|"u s1l_lenBSD爱好者乐园 O%Cf }#{ti.E
BSD爱好者乐园UF3Y4PUT/L
需设置为期望位置的长度,单位为字节,锁将从l_whence + l_start开始锁定l_len字节,如果你想整个文件用一把锁,那么设定l_len的值为0,如果l_len的值是一个负数,结果是不可预测的。BSD爱好者乐园7V3l#sE*j'l5|T&|
BSD爱好者乐园CH/J)h#m@,\ U
l_pidBSD爱好者乐园 h Ij\:g!Q
BSD爱好者乐园 R*_-Xy?S3d
需要设置为工作在锁上的进程的IDBSD爱好者乐园E,lA$z!q%N'p8L&k

BP8r+q`8}G^B1l_typeBSD爱好者乐园4HaoxP ? I!f*pw

Qh/S)?[1需要设置为期望的锁的类型,下面是能够使用的值BSD爱好者乐园t/|A T-BC? ^U }H

_ PlrD3w7c&E1    * F_RDLCK - 读锁定BSD爱好者乐园n5VV+j;]8|F@8zJ
    * F_WRLCK - 写锁定BSD爱好者乐园 F"M]/|v;g G:@a
    * F_UNLCK - 用作清除锁定
2~x;Nw]:S*a6\!fT1BSD爱好者乐园^-LK v x2J q8z,Z
l_whence
@3R#H ~,MEm1BSD爱好者乐园4R E@[0p!M.E
这是这个系统调用里面最混乱的部分,这个字段将决定l_start位置的偏移量,需要设为:
{Y2VJ c0u2? x1    * SEEK_CUR - 在当前位置BSD爱好者乐园6ifg? G8`4YR
    * SEEK_SET - 在文件开始BSD爱好者乐园xU*``F K
    * SEEK_END - 在文件末尾BSD爱好者乐园 v\^*kb!Q

[IV5M?y ~D(q6W1fcntl的命令
8JP1PT-Hh1
Gw.YiT6P1下面的可用作fcntl的命令
r`@c-}Q1
TJ$Y n6i1
 
#define  F_GETLK      7
BSD爱好者乐园3i2J,Q0~ kmbk"W
F_GETLK 命令尝试检查否能上锁,当使用这个命令,fnctl将检查是否有相冲突的锁,如果存在相冲突的锁,fnctl将改写flock结构,用冲突锁消息通过检查,如果没有相冲突的锁,那么在flock结构最初的信息将被保留,除非l_type字段被设成F_UNLCK
|%W8q:wx y"^d`1
,w1j({$m]1
 
#define  F_SETLK      8

5|3@j BanL2WZ-iR1F_SETLK命令试图获得flock结构描述的锁,如果锁不被承认,本次调用将不被阻塞。不管怎样,fcntl将直接返回EAGAIN,同时将设置相应的errno,当flock结构的l_type被设置为F_UNLCK时,你能够使用这个命令清除一个锁。BSD爱好者乐园}7fF:W T0k1jN
BSD爱好者乐园(nrLE DK.A
 
#define  F_SETLKW     9

`5[N3j M?3icPF1F_GETLK命令试图获得flock结构描述的锁,它将命令fnctl阻塞,直到赋予一个锁
8|y Y Sz-jeL1BSD爱好者乐园J4`,ES%x T
5.4为什么用FLOCK  
$j]5K"M}t)k5R1
*]:w6F(i0G1对大部分情况来说,高级文件锁定机制是有好处的。然而,POSIX.1接口有几个缺点。第一,当一个文件的任何一个文件描述符被关闭时,与该文件关联的所有锁定必须被删除。换句话说,如果你有一个进程打开一个文件,接着它调用一个函数打开同一个文件,读取然后关闭它,这样先前你对这个文件的所有锁定都会被删除。如果你并不确定一个库例程会做什么,这将会引起严重问题。第二,锁定是不会传递给子进程的,所以一个子进程必须独立地创建属于它自己的锁定。第三,所有在一个exec调用之前获得的锁定在由exec启动的进程释放,关闭这个文件或结束之前不会被释放。所以,如果你需要锁定文件的某一部分,那么调用exec不需要释放锁定或者关闭文件描述符,那部分区域将被锁定直到进程结束,你想要的或许不是预期的结果,不论如何,BSD的设计者用许多flock创建了非常简单的优先级文件上锁的接口.
6z)u/Z9K9JSqM1BSD爱好者乐园4c3z tKiH7[
flock用于锁定整个文件,是BSD优先选择的方法,跟fcntl高级锁定相反,flock机制允许锁定进入子进程,使用flock调用的其他好处是可以在文件级并且不在文件描述符级别完成锁定,在某些情况下可用优先选择,意味着多个参照同一个文件的文件描述符,例如DUP()调用,或者多个OPEN()调用,希望每一个参照相同的文件锁,带flock的文件锁跟一个写多个读的fcntl锁很类似,不论如何,当下面的操作被定义,调用flock时,锁的优先级能够被提升:BSD爱好者乐园)p/lU AOO|0jb

,w&cwa_D+P1
 
#define   LOCK_SH        0x01      /* shared file lock */
BSD爱好者乐园&TL}5k dR,]X
LOCK_SH操作用于在文件上(类似fcntl的读锁)创建共享锁,在一个文件上,多个进程能够共享一把锁。BSD爱好者乐园p1[.xE M xU!A;JlC
BSD爱好者乐园.Y9Em$R;W#B
 
#define   LOCK_EX        0x02      /* exclusive file lock */
BSD爱好者乐园j l"O~h|
LOCK_EX操作用于在文件上创建互斥锁,当互斥锁生效时,文件上不能存在其他的共享锁,包括已共享的锁。BSD爱好者乐园FCwnB$Cz

'g p$|alc o1
 
#define   LOCK_NB        0x04      /* don't block when locking */
BSD爱好者乐园1|d$^k0^9n&N
使用这个,flock的调用将阻塞,直到锁定生效,不论如何,假如期望的操作LOCK_NB是ORed,flock的调用将向EWOULDBLOCK返回错误号成功(0)或者失败(1).BSD爱好者乐园 Q(Y6C7ndH4d;d

v*J5LS_ S,]1{2w1C1
 
#define   LOCK_UN        0x08      /* unlock file */

F#p*E+gN1LOCK_UN用于移除文件上的锁BSD爱好者乐园t@:o4TONK.E9v

aPYq_7{ @i1通过调用期望的flock,flock锁的优先级能够被提升或者降低。新的成功的调用将使最近生效的锁替换先前的锁。BSD爱好者乐园S/ZV h5Dz#o

weBLHQ%@j{)v&f,h1DUP函数BSD爱好者乐园4LC:v7a n7r
BSD爱好者乐园)PYx0Bi-J3H
 
int dup(int old);

,|"hF \&oo[&_.c1像fcntl调用能够用作为现有的文件描述符复制描述符一样,dup函数也能复制文件描述符,dup调用能够返回一个跟old参数无法区别的新的文件描述符,这就意味着所有的read(),write()跟lseek()调用都会操作两个描述符(复制出来的新的跟原来的描述符),同时,所有fcntl的选项将保留,close on exec位出外, close on exec位会被关闭,所以你可以复制一个文件描述符,然后允许子进程去调用一个exec函数,这是dup函数非常普遍的一个用法。old参数用作复制,并且必须是一个有效的参数,描述目标描述符,新文件符至少应该是没使用过的文件描述符,它由成功调用dup函数后返回。它意味着如果你关闭STDIN_FILENO(该值为0)那么dup直接调用的新文件描述符的值将是STDIN_FILENO,假如dup函数调用因一些原因失败,将返回-1并且相应地设置errno.
'me Pm0}*f1BSD爱好者乐园 @8? qr*q8d+y
DUP2函数BSD爱好者乐园9}~ to mUQ
BSD爱好者乐园a2L0V4D|
 
int dup2(int old, int new);
BSD爱好者乐园{;@lw!IV
除了new参数是希望的目标值,dup2函数跟dup函数很相似。假如新参数已经参照了一个有效打开的描述符,并且它的值跟老参数的值不一样,那么新文件描述符将先被关闭。假如新参数等于老参数,那么函数不会做任何操作。成功调用DUP2的返回值将等于新参数。假如DUP2调用失败,将返回-1并且相应地设置errno.
e$BG u#o!{qn1BSD爱好者乐园U]!bY}L'w
5.5  进程间通信BSD爱好者乐园"Qrt#pP

w c3JX#M_1来自于System V的主要的功能是基本的进程间通信,或者说IPC,这些一直在BSD里面被非常广泛地使用。IPC机制允许程序之间相互共享数据。这个很像我们已经介绍过的重定向,但是重定向是单向处理而不是双向处理。在示范程序中,重定向可以跟设置过STDIN_FILENO参数的CAT命令共享数据,但是有一个问题:cat命令不能跟重定向的程序共享数据,假设一个算法是从其他文件描述符读但是用open不灵活的时候,我们可以修改他们使其可以双向地相互共享数据。BSD提供了很多更好的用于进程间通信的方法。
0eH+Hna%R$r'j.a ym1BSD爱好者乐园 iWkC`l&]N(I
PIPE函数
yu#`q2s ew1BSD爱好者乐园:M~YwA8N'xcL
 
int pipe(int *array);
BSD爱好者乐园!`~B8C a`]8b
通过给定一个有效的二维数组调用pipe函数(如:int array[2]),pipe函数将分配两个文件描述符,假如成功,数组将包含两个有区别的并且允许单向通信的文件描述符, 打开的第一个文件描述符(array[0])用于读,打开的另一(array[1])个用于写,所以,自从成功调用pipe函数后,你能在这两个描述符之间得到一个单向通信通道,当写入其中一个时,你也将能够从另一个读取输出,管道函数比重定向好的地方是你不必使用文件,BSD爱好者乐园 L#l!kmj
BSD爱好者乐园V;z`%D4dC(V-q
这些文件描述符的行为与普通的文件描述符完全一样,然而它们没有任何文件与之关联,管道功能使unix shell加到其他命令的管道变得很有用,例如BSD爱好者乐园l iul2i.Ij@
BSD爱好者乐园 k3R*v9Ud]
 
bash$ find /  -user frankie  | grep -i jpg  | more

aJ.Y ~^` x`4@1这个例子里面,find命令将其输出通过管道传送给grep命令,grep再将其输出通过管道传送给more命令。当建立这个顺序的时候,shell将处理管道实际的设置,这些程序本身没有写到其他程序的想法,因为他们真的不需要知道。从这个例子你能看见自从调用了管道,正常来说进程就会fork, 之后进程就能通信,假如要达到双向通信的目标,你需要创建两个管道,一个用于父进程到子进程的通信,另一个用作子进程到父进程的通信。BSD爱好者乐园n6o/qj.C Rgh

(ZRC%k/`l9v_1管道有下面两条通信规则:
!f$ro3NA#t4k(R&L1    1.如果管道读的这边关闭了,试图写到这个管道将会导致一个SIGPIPE信号发送给试图写的进程。
s5zXW v1    2.如果管道写的这边关闭了,试图从这个管道读将导致读返回0或文件结束。关闭写端是发送文件结束到该管道读端的唯一方法。
i L(b8w%?E1
6l'O/{5\;l)~ w1成功地调用pipe函数后将返回0,假如调用失败,将返回-1并且errno将被相应地设置,BSD爱好者乐园A&eJJ_2UY
BSD爱好者乐园/\A&l2@9s A4~b3\~L5a
备注:在更多的现代的BSD上,单个描述符上pipe函数支持双向通信,不论如何,这个特性不是很轻便,也因为如此,这个方式不建议使用。BSD爱好者乐园 m:q#t ] D+\C"vW*d
BSD爱好者乐园8s&^2k0S.uXv

B"T~f~"GW Pi1Mkfifo函数
GiG/^-`%f;f M6c$H1BSD爱好者乐园4dCK N+f4T
 
int mkfifo(const char *path, mode_t mode);
BSD爱好者乐园,\%\1I? bc
在相关的进程间通信时,管道是有用的,然而在没有关联关系的进程间通信时,使用mkfifo函数。Mkfifo函数实际上在文件系统创建一个文件。这个文件只是其他文件在通信时使用的一个标志。这些文件就叫做FIFO管道(先进先出管道)。当一个进程创建一个FIFO管道,写到这个FIFO管道并不会写到这个文件,而是被另一个进程读取。这种行为与管道非常相似,所以FIFO也被叫做命名管道。Mkfifo函数有两个参数。第一个参数是一个以null结尾的字符串,声明路径和文件名。第二个参数是该文件的存取模式。该存取模式是标准的unix文件所有者读写权限(参考/usr/include/sys/stat.h中的S_IRUSR, S_IRGRP等)。BSD爱好者乐园V~A i+~3f z_ ]

Q#[-[4L@(|X1一旦mkfifo函数成功调用,需要用open函数打开创建的fifo管道进行读写。如果调用失败,会返回-1而且错误代码会被相应地设置。BSD爱好者乐园Eh3Q-U5Z*E`T

(U3D z.d%o4cA^1创建fifo管道与创建文件相类似,进程也必须有足够的权限来创建fifo管道,因为该fifo的用户ID会被设置为该进程的有效用户ID,而组ID会被设置为该进程的有效组ID。
n'i DJC5~1
m,Ink9_$O1关于FIFO重要的一点是,在缺省情况下,它们是阻塞的。因此,读取一个fifo管道会被阻塞,直到另一端把数据写进来,反之亦然。为了避免这种情况,可以使用O_NOBLOCK参数来打开。在这种情况下,你会获得以下行为:对读的调用会立即返回,返回值为0;或者对写的调用会导致一个SIGPIPE信号。BSD爱好者乐园 c2W F5pl_1s

Kl(rk5~,l4p15.6消息队列
#zm.v%c-V;x1
9Oz'v"z'b+l1另一种进程间通信机制,消息队列,为进程间通信提供另一种方式。然而和我提到的其他通信机制不同,你应该尽量避免使用这种方式。如果你的程序使用消息队列,试着用fifo或者甚至Unix Domain Socket重新实现它。在我讨论原因之前,以下是一个快速概述。BSD爱好者乐园 y:Pp }q"hd,^T ["v
BSD爱好者乐园]o%U LB&o3r
消息队列与FIFO相似,但它们使用一个键而不是一个文件作为引用。这个键是一个无符号整数。一旦一个消息队列被创建,发送到这个消息队列的数据会被内核缓存。内核分配给消息队列的内存是有限的。一旦一个消息队列被填满,那么在一个进程从这个消息队列把数据读走之前不能再往这个队列发送数据。在这种情况下,如果两个进程以不同的速度读写,队列是可靠的而且在大多数情况下是非堵塞的。这与FIFO不同。在FIFO的机制中,一个慢的读进程事实上是会拖慢一个比较快的写进程的(除非设置了O_NONBLOCK参数)。另一个好处是即使写进程退出了,写到消息队列中的数据也会被保存起来直到另一个进程读取它。而在FIFO的机制下,如果写进程退出了,这个FIFO会被关闭,而读进程会收到一个文件结束的标志。BSD爱好者乐园Or8uAj;ru0Z1eM)O+F
BSD爱好者乐园9@,w`,iJ;]
以上这些所有的消息队列的好处看起来不错,但让我们再仔细看看。假定一个进程打开一个消息队列,往里面写了一大块数据,填满了内核的缓冲区然后退出,内核将不得不保存这些数据直到另一个进程来读取它,而其他任何想创建和写消息队列的进程将会被拒绝。这种情况将会持续直到一个进程读走这些数据或者系统重启。在这种情况下,创建一个简单的针对消息队列的拒绝服务是可能的。BSD爱好者乐园lIv"hH_)s,N
BSD爱好者乐园+`u}FX'G
另一个问题是键并不确保是唯一的。换句话说,一个进程无法确立一种方式来确定它是否是使用一个特定消息队列的唯一进程。而在FIFO机制中,当一个进程创建一个FIFO时,它有比较好的机会知道这个FIFO是否唯一因为可以声明一个针对文件路径的预先约定(比如:/usr/local/myapp/fifo_dir)。你的应用程序可以在安装时创建一个唯一的目录,这样几乎可以确保一个唯一的FIFO路径。有效的消息队列键值可以通过调用一个函数ftok来生成以帮助减少相同的键值,但并不能确保唯一。这个问题的副作用难于确定—你的程序可能读到它并不想读的数据,或者你的程序写入的数据正被其他并不想读取的进程读取。简短来说,当你使用消息队列时,可能导致难于调试的奇怪行为和错误。BSD爱好者乐园 U Q0q^u/Rz
BSD爱好者乐园H/OQ8[d#S#K
如果你仍然坚持使用消息队列,参考以下的指南页:ftok(3), msgget(3), msgctl(3), msgrcv(3), 和msgsnd(3)。BSD爱好者乐园$G8s`+jLO{

@'Z(S*d)}5w,] u6i15.7 结论BSD爱好者乐园1v2}M+h t'yv5bD
BSD爱好者乐园K-O:t[(B.U2t
这一章介绍了几个操作打开文件描述符的系统调用,包含在我们fork和exec之前必须关闭文件描述符的场景。我们也讨论了文件锁定,设置和移除文件锁定,以及一些特殊的文件描述符如FIFO和Queue等,在那种情况下根本不在文件系统上存储数据。这些系统调用为BSD增加了很大的可编程性和灵活性。但当一个进程有多个打开的文件描述符时会怎么样?下一章将会讨论高效地处理多个文件描述符。
[重要提醒]对本篇资料有疑问,请到论坛讨论,尽量使文章准确无误>>>
[版权声明]BSD爱好者乐园站内文章,如来源不是互联网,则均系原创或翻译之作,可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。
TAG: freebsd FreeBSD 系统编程
 

评分:0

我来说两句

seccode