网络推荐

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

第五章: 基本I/O

译者:horseman
0O+O6IT7{s+g/~(多谢我的同事HUGH CHEN翻译5.6,5.7跟部分5.4,5.5段落)

8]+k+WW mo"P.d2Od.QBSD爱好者乐园F|DQ&|NE3x]

'S3uo.U(n6?5g本章示范(示例)代码:http://www.khmere.com/freebsd_book/src/05/index.htmlBSD爱好者乐园Y"}bB9^3X"P u

O?+LPZ;Y5.1  基本输入输出
`eDG(a7LBBSD爱好者乐园VM'_o+l\mN
一般来说,Unix信奉简单设计的哲理。"一切都是文件"是个很强大的特征--这就意味着你所编辑的文本文件具有和调制解调器、打印机网卡相同的编程接口,就像编辑文本文件一样,你应该能够对它们(被编辑文件)执行基本的读、写操作等等。尽管这个想法现行的实现不完美,BSD Unix实际上做到非常接近了,这也正是BSD又一个强大的地方--简洁而优雅。有些不是真实的文件而是设备,它们的入口在/dev目录下,有些设备只能用于特殊的操作,如块的读、写,一个极端的例子是以太网设备,甚至它(以太网设备)在FreeBSD 5之前在/dev下面没有入口。
)J${`l8]3S)?4L
wE g2@5m;~9QZ操作系统看待每件事都像文件,一个好的示范是Plan 9,Plan 9用文件实现一切,甚至以太网和网络协议;更详细的信息可以参考Plan 9的主页http://www.cs.bell-labs.com/plan9dist/
@r2LQ'Bx5V)s;HBSD爱好者乐园*i `GM+w iP
一般来说,文件是计算机上数据最基本、最初级的表现形式,本质上是数据一位一位的线性序列。当用exec命令执行编译过的程序时,系统将把二进制文件读入内存,代码将在分配到内存地址空间后被执行,程序被定位在什么位置跟exec命令有关,它可以在软磁盘,硬盘,光驱,甚至是加载的另一半还分布在世界其他角落的网络文件系统上;跟基于网络链路发送数据一样,内容被顺序地一位一位地读入。当一个程序发送数据的时候,数据本身是一位一位的线性序列,有些时候叫做“流”,程序不关心它是否基于网络链路发送数据,它仅仅写数据;这两种最基本的操作,读跟写,是计算鸡用到的最多的。BSD爱好者乐园+F7_l|"UGmf+c
BSD爱好者乐园/_@g | Yg2k%[
本章将涉及最基本的I/O子系统跟进程资源。
B9`B5y6PY{)cy(Ja
a0n&`5G8T:`2yBSD爱好者乐园Sj6H ve
5.2  基本输入输出
2S7x3O!v H&I"LBSD爱好者乐园*U gS;Pwga
UNIX进程打开文件的时候,会保存文件描述符的参考值,该值是一个整数,不论何时在UNIX系统上创建一个进程,都会给它3个文件描述符:BSD爱好者乐园P0t ?)M%o}9qIa
    0   标准输入
qU0d.UKuCk    1   标准输出BSD爱好者乐园'Bu3Z4c`B!r
    2   标准错误BSD爱好者乐园'O+rp ag3Ga1zQ
BSD爱好者乐园2} ]S%wk0_6J@kw
这些值能够用于描述终端、文件的读写,甚至设置其他进程的描述。使shell重定向, cat /etc/hosts >> hosts.out, shell将打开文件hosts.out并且将cat /etc/hosts做为自己的参数执行。不管怎样,当cat进程写到标准输出后(1),结果不会被tty得到,而是输出到hosts.out文件。(cat程序根本不知道写入了文件系统的文件,还是写入到标准输出的文件描述符,我们将在这章的后面一点看到究竟是怎么实现的)
0xa mlm ~,{zBSD爱好者乐园&{;AX@iP
最基本的两个操作描述符是Open和Close函数BSD爱好者乐园_R:r#n-HeA

|? JO^Open函数
7h!B-Oy4Mh%MYXBSD爱好者乐园M_p}6q%n D7U
 
int     open(const char *path, int flags, /*  mode */ );,
BSD爱好者乐园$sm%Vo]5nZ6D J
成功地调用这个函数后,Open函数将返回文件及参数的描述符,这个整型描述符在进程文件描述表内生成索引。该描述符的结构能够让内核知道如何操作这个文件。在BSD上,这个结构叫做filedesc,并且能够在/usr/include/sys/filedesc.h这个头文件中找到。当进程要在这个描述符上执行一些操作的时候,它将要求文件描述符为读、写、可执行操作指定一个整数值。BSD爱好者乐园0m(H/F-B6u

p_{0Y4k内核保存了所有文件描述符的基准值,这些基准值将在时进程打开、复制它或者已经处于打开状态的文件描述符执行exec调用、跨越fork的过程中增加。当这个参考值为0的时候,文件关闭。意思是,如果你有一个程序执行fork或者exec调用,并且close-on-exec位没有指定,基准值将增加;并且当一个新的程序执行了fork或者exec调用,基准值将继续增加。所以文件一直处于打开状态,直到基准值被设置为0,或者直到所有的进程关闭它,或者退出。BSD爱好者乐园{ElP"\UD
BSD爱好者乐园s | N`1P
Close函数
[\? ~M9R/Z*w@*F
r0@Gi)o
 
int        close(int fd);
BSD爱好者乐园1Uw_#sC3}Sl
当进程想移除或者关闭一个打开的文件描述符的时候,它将调用close函数。这将关闭指定的文件描述符,并且减少文件描述符的参考值。这个过程很像 exit————当一个进程执行exit,所有打开的文件描述符跟进程一起被自动地减少,自从文件参考值设为0,内核将释放所有的文件入口的副本.BSD爱好者乐园`)J9MGL2AW ]

6E%V+j){?%?k] Q Rgetdtablesize函数
kq8h1j:K y]*AcPBSD爱好者乐园+^]-s*B3[ynH
 
int        getdtablesize();

.D9@Ss3RMpgetdtablesize函数返回文件描述表的大小,它能用作检查系统限制,你也能够用下面的命令做相同的事情。BSD爱好者乐园 Jl/I H+L!c4V
BSD爱好者乐园.y`W.x0\c*s
 
bash$ sysctl kern.maxfilesperproc
yv0qJ \z8X3fgkern.maxfilesperproc: 3722
BSD爱好者乐园6I*z)Oq|%]~1rdV(rV
依赖于你的系统,你能够在系统运行的时候调整它,或者从新编译你的内核,这个函数不能检查当前进程打开了多少文件,(跟getdtablesize一样)仅仅返回你的进程能够打开文件的最大个数。
k\4w1khj `
cO3T)mU)Lfcntl函数
+^MV7n^{d8EBSD爱好者乐园Z3L2AXhz1?
 
int        fcntl(int fd, int cmd, ...);
BSD爱好者乐园v_W9Y Ih v%G/H
fcntl 函数允许进程操作文件描述符,fcntl函数至少需要指定两个参数,一个有效的文件描述符,一个命令。根据使用的命令,决定fnctl是否需要第三个参数,下面为命令定参数义了一些值。在FreeBSD上,你能在/usr/include/fcntl.h头文件中找到它们。BSD爱好者乐园#Pd!Qv Z\

f/Q@'hbP,ItcJ(e ZH
 
#define F_DUPFD        0

4A a8RSACF_DUPFD用作创建一个新的很像原型的文件描述符,(你能够用dup调用做到相同的事情,将在晚些时候涉及到),当成功执行带F_DUPFD标记的fcntl函数,fcntl将返回下面属性之一的新的文件描述符:BSD爱好者乐园 q;v#x&V|t~&eg Y
BSD爱好者乐园z&Vn-Q9k1l{_S(D

    'q4PMge7bK
  • 假如指定了第三个参数,描述符返回的值比最小可用的描述符,该值等于或者比给定的第三个参数的值大一点,返回的描述符将参照指定给fcntl第一个参数的文件描述符BSD爱好者乐园(l.X3J3ca*tX
  • 假如fcntl指定的文件描述符是一个文件,新文件描述符将有相同的文件偏移量,并且新文件描述符将有相同的访问方式(如:O_RDONLY,O_RDWR,o_WRONLY)
    EZ4o H*e*y
  • 新文件描述符将共享文件状态标记
    !]+N e't)Ap*I
  • 新文件的“close-on-exec”标志将被关闭,即新的文件描述符在exec调用之后仍将保持打开。
    zh!y3XF(AN@9za
BSD爱好者乐园$tle u+r!y&~"_e

9N L#Qv!SF_GETFD命令
$El*U_k%a0G
"h/J1u!dFn2v;}oHM
 
#define  F_GETFD     1

\"Kt i(H6LZdWF_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函数后被关闭。BSD爱好者乐园-Y.U#zSd!Z6e] ^5^y

6|Oo5{.GF_SETFD命令BSD爱好者乐园{ V!Y&]?C
BSD爱好者乐园;C [!|5l2o~V
 
#define  F_SETFD     2

*l4lcBqdaKhF_SETFD命令用作设置文件描述符的close-on-exec标志位。第三个参数要么是FD_CLOEXEC,设置close-on-exec标记,要么是0,取消close-on-exec标记的设置。
.A4K$PIk.M |BSD爱好者乐园;O!f5KZo
F_GETFL和F_SETFL命令
L/^ vs)Jtu+J@%E
vA)^;K6I.GU
 
     #define  F_GETFL    3
O/LW.V!D7~)MC#W        #define  F_SETFL    4

$X z ?9FA&XJF_GETFL命令将使fcntl返回当前文件描述符状态标记,当返回值加上O_ACCMODE(#define O_ACCMODE 0x0003)能够获取到打开的方式,F_SETFL命令将根据第三个参数设置文件状态标记。BSD爱好者乐园D$V4q:pP,e

8]J)G4b {kU'I5Ne公共的标记
/J[| [!y$D#lBSD爱好者乐园5VE7Ph`p
下面这些标记也用作调用open,并且只能被跟上期望的标记调用open函数设置,这是最常见的,检查你系统的头文件能够查到那些值BSD爱好者乐园O%C8m:y]-P
BSD爱好者乐园lZV-fR#rU
 
#define  O_RDONLY 0x0000

i[@(e-d4id如果O_RDONLY标记被设置,那么文件只能以只读方式打开。注意这个O_RDONLY标记只能在打开的时候被设置,它不能被fnctl加F_SETFL命令设置。BSD爱好者乐园$g$\5OHYDq]$Z3\|N

'jl-|%X+_r c:U4q2D
 
#define  O_WRONLY 0x0001
BSD爱好者乐园0} O0K8E:\n t
如果O_WRONLY标记被设置,那么文件只能以只写方式打开。这个标记只能被open设置,不能被fcntl加F_SETFL命令设置。BSD爱好者乐园0Ca4BS;_w

7P YwV)Oc O
 
#define  O_RDWR      0x0002

-D~4Un~!a,@@如果O_RDWR标记被设置,那么文件以可读可写方式打开。这个标记也只能被open调用设置。BSD爱好者乐园:UW ^4uf{

*F;n"eq@_
 
#define  O_NONBLOCK  0x0004
BSD爱好者乐园1C3`*XdN|
如果O_NONBLOCK标记被设置,文件描述符将不被阻塞而被直接返回替代。一个例子是打开tty。如果用户不在终端调用里输入任何东西,read将被阻塞,直到用户有输入,当O_NONBLOCK标记被设置,read调用将直接返回设置到EAGAIN的值BSD爱好者乐园~)_I_:wg
BSD爱好者乐园srK+g!LzX
 
#define  O_APPEND 0x0008

P~&H6v`w如果O_APPEND标记被设置,文件将以追加方式打开,并且将从文件末尾开始写入。BSD爱好者乐园Bw C%K)Ny4B
BSD爱好者乐园'LhtcuE vw*j
 
#define  O_SHLOCK 0x0010

l:UPj Lp如果O_SHLOCK标记被设置,文件描述符将在文件上生成一个共享锁,在文件上设置了共享锁,多个进程能在同一个文件够执行操作,文件共享锁的详细信息,可用看fnctl函数的F_GETLK跟F_SETL命令。BSD爱好者乐园K2C8N J xK!^
 
#define  O_EXLOCK 0x0020

4hwh_s I"_9F如果O_EXLOCK标记被设置,文件描述符将在文件上生成一个可执行锁,一样,更详细的描述可以参照fcntl函数的F_SETLK跟F_GETLK命令。BSD爱好者乐园0u2FM|(z.m[
BSD爱好者乐园.v[uH_p"f({.PL
 
#define  O_ASYNC     0x0040
BSD爱好者乐园0`&k4]H*S
如果O_ASYNC标记被设置,进程集将被发送的SIGIO信号通知,在文件描述符号的IO是可用的。详细的描述请参照信号那一章。
3R$Le2Vx0gsBSD爱好者乐园 B-Q"L8X!hgl\]p k
 
#define  O_FSYNC     0x0080
BSD爱好者乐园s/aSQA
如果O_FSYNC标记被设置,所有写到文件描述符的写操作将不被内核缓存,取而代之的是将被写到介质,并且所有的写调用都将被阻塞,直到内核完成(写操作)。
5j;eEJl@#P
dI9r8I^Z!o
 
#define  O_NOFOLLOW  0x0100

3F\._6n!Uv ~e(y3j如果O_NOFOLLOW标记被设置,假如文件是一个符号连接,open调用将会失败。如果在一个有效的文件描述符上设置了这个标志,那么当前文件就不是一个符号连接。
NJ6|d Ke:[k.Gz7o,ZBSD爱好者乐园k {/e5K I;W
 
#define  O_CREAT     0x0200

6I0Y1fzuv.j4P如果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)BSD爱好者乐园-`@5S*OWs/lB

"f+n)~ E~|@ c
 
#define  O_TRUNC     0x0400
BSD爱好者乐园&TgL*w?m ay+A,a
如果O_TRUNC标记被设置,成功地调用open后文件将被截除。BSD爱好者乐园WC1Uaok:R"r

1QM4[ p Ee
 
#define  O_EXCL      0x0800

Dkv*N:UI当O_EXCL标记被设置,假如文件已经存在,open调用将产生一个错误。
2r:Hc!jo8w$rD#? _o f
C:Q'W:n'j9~+M&\
 
#define F_GETOWN          5

bWd0z5a\+?3J0G9AF_GETOWN命令用于描述符获取当前进程或者进程集收到的SIGIO信号。如果这个值是一个正数,它表示一个进程,负数表示一个进程集。
$sr*?-v-jJ
1['O&uc(iC%] _6W[
 
#define F_SETOWN  6

;u5f$b4O(p M5w当IO就绪的时候,F_SETOWN命令用作设置进程或者进程集使其接收SIGIO信号,指定一个进程,用一个正数(一个进程ID)作为fcntl的第三个参数,否则,用一个负数作为fcntl的第三个参数指定进程集。
3Tm(JXNBSD爱好者乐园?^5J"wO R(`9}
5.3文件上锁
M$kHt}5N"~,[:ZBSD爱好者乐园gT(Vj+a D8t
当多个进程试图写同一个文件,将发生什么?它们相互冲突,已知的事情像文件上锁。结果就是每个文件描都有自己的描述符跟偏移量,当每个进程写自己的文件时,偏移量预先独立导致没有进程知道其他的进程也正在执行写操作。最后的文件将因为多个独立写文件的操作使混合后的文件变得相当于垃圾,直接给文件上锁是解决这个问题的一种方式。在任意时刻只能让一个进程能够写到文件,另一种办法是允许在一个叫做高级文件锁的scheme里的文件内部进行区域锁定。 fcntl函数能够提供这个功能,通常来说,锁有两种,一种是写,另一种是写,不同之处在于读锁不会干扰其它进程读取文件,但是特定的区域只能一个写锁存在。BSD爱好者乐园Qn;L8CO4e
BSD爱好者乐园t x:];O t U%c2u
当使用顾问锁的时候,下面的结构用作fcntl的第三个参数。BSD爱好者乐园 SuBlF7~
BSD爱好者乐园 d*Y-c&\q!@
 
  struct flock {BSD爱好者乐园dv D:qDI#@}d
            off_t   l_start;    /* starting offset */
hw3hL}(L}5S;z            off_t   l_len;      /* len = 0 means until end of file */
_7U'NaT5ND5\            pid_t   l_pid;      /* lock owner */
;]"~7M6n8U BY1j.~ F![            short   l_type;     /* lock type:   */
-jh3p(G6Pb            short   l_whence;   /* type of l_start */
VBM0k!o0aE*~QpX    };

PjPEc*B让我们继续讨论每个元素的细节。BSD爱好者乐园9E1g~C9W

1GD^;n+Q6Ky]l_start
*Q~gY7H p:a)SBSD爱好者乐园+CF&D/|+om+N
这是一个相对于l_whence,单位为字节的偏移量,换句话说,要求的位置实际上是l_whence + l_start
UH,@ @.i{c
\F2Us/t ~l_lenBSD爱好者乐园2mU \k%I%u L
BSD爱好者乐园%Wb {XIf$d,a}
需设置为期望位置的长度,单位为字节,锁将从l_whence + l_start开始锁定l_len字节,如果你想整个文件用一把锁,那么设定l_len的值为0,如果l_len的值是一个负数,结果是不可预测的。BSD爱好者乐园M%b7XU!},N f7}

'e[!QS%Q?!?,C2|l_pid
A$R;D ByG7aD`8]`BSD爱好者乐园8cd0l'W)Q\ByZ2k vy
需要设置为工作在锁上的进程的IDBSD爱好者乐园;o)FMX n&[ W

#[)v^+Y3h!}l_type
b%G}"?-u8M_7s |lBSD爱好者乐园/v Jf)Q s pI3Mf
需要设置为期望的锁的类型,下面是能够使用的值
kO;y0T1W cz_GY
f5^ YQ1t|5@    * F_RDLCK - 读锁定BSD爱好者乐园vV L-G~&H/k8h
    * F_WRLCK - 写锁定
2WKj ?r-nu    * F_UNLCK - 用作清除锁定BSD爱好者乐园y y,f/fTz3]

XHq]gml_whence
BM(f(m(Bn?$[WS
pV[*RK/[ DN*Az这是这个系统调用里面最混乱的部分,这个字段将决定l_start位置的偏移量,需要设为:
l q |#\4JSTT|    * SEEK_CUR - 在当前位置BSD爱好者乐园)\yK.^j3X5S@F+zB
    * SEEK_SET - 在文件开始
L#ADuGGk [    * SEEK_END - 在文件末尾BSD爱好者乐园&n%_pZuwX5_

oTEIY:qYfcntl的命令BSD爱好者乐园s9G FF#t r+TJ4v

rM(R+ZB下面的可用作fcntl的命令BSD爱好者乐园'y)D6e2w9D0Y b} k
BSD爱好者乐园!_V.VD @@N3Z*WU&u
 
#define  F_GETLK      7

a#K6@gx t@1iF_GETLK 命令尝试检查否能上锁,当使用这个命令,fnctl将检查是否有相冲突的锁,如果存在相冲突的锁,fnctl将改写flock结构,用冲突锁消息通过检查,如果没有相冲突的锁,那么在flock结构最初的信息将被保留,除非l_type字段被设成F_UNLCK
qp{mF&N%UBSD爱好者乐园i4e H/IR-l/I
 
#define  F_SETLK      8

wq KOoM"n\ bF_SETLK命令试图获得flock结构描述的锁,如果锁不被承认,本次调用将不被阻塞。不管怎样,fcntl将直接返回EAGAIN,同时将设置相应的errno,当flock结构的l_type被设置为F_UNLCK时,你能够使用这个命令清除一个锁。BSD爱好者乐园?1P'ihY2Q }X

q8|M_Kx@
 
#define  F_SETLKW     9

(@ Y,^(R0{F_GETLK命令试图获得flock结构描述的锁,它将命令fnctl阻塞,直到赋予一个锁
Rk].IS1q9^x
g!Y6Oc+r5.4为什么用FLOCK  BSD爱好者乐园x P#b/I}+Mgv
BSD爱好者乐园,v6hbbR3dU#o
对大部分情况来说,高级文件锁定机制是有好处的。然而,POSIX.1接口有几个缺点。第一,当一个文件的任何一个文件描述符被关闭时,与该文件关联的所有锁定必须被删除。换句话说,如果你有一个进程打开一个文件,接着它调用一个函数打开同一个文件,读取然后关闭它,这样先前你对这个文件的所有锁定都会被删除。如果你并不确定一个库例程会做什么,这将会引起严重问题。第二,锁定是不会传递给子进程的,所以一个子进程必须独立地创建属于它自己的锁定。第三,所有在一个exec调用之前获得的锁定在由exec启动的进程释放,关闭这个文件或结束之前不会被释放。所以,如果你需要锁定文件的某一部分,那么调用exec不需要释放锁定或者关闭文件描述符,那部分区域将被锁定直到进程结束,你想要的或许不是预期的结果,不论如何,BSD的设计者用许多flock创建了非常简单的优先级文件上锁的接口.BSD爱好者乐园9oA'WZ+d\ix)K;}#i
BSD爱好者乐园.To o:}+g!t#V
flock用于锁定整个文件,是BSD优先选择的方法,跟fcntl高级锁定相反,flock机制允许锁定进入子进程,使用flock调用的其他好处是可以在文件级并且不在文件描述符级别完成锁定,在某些情况下可用优先选择,意味着多个参照同一个文件的文件描述符,例如DUP()调用,或者多个OPEN()调用,希望每一个参照相同的文件锁,带flock的文件锁跟一个写多个读的fcntl锁很类似,不论如何,当下面的操作被定义,调用flock时,锁的优先级能够被提升:BSD爱好者乐园|\SS-s;R5DdQ;d~;a

HyhN'Y.`
 
#define   LOCK_SH        0x01      /* shared file lock */

e t0a8iOLOCK_SH操作用于在文件上(类似fcntl的读锁)创建共享锁,在一个文件上,多个进程能够共享一把锁。BSD爱好者乐园` |c;x8Ae'iI

/?0ZkKE b3k
 
#define   LOCK_EX        0x02      /* exclusive file lock */
BSD爱好者乐园.F3w8}z0m Z8jY+bS
LOCK_EX操作用于在文件上创建互斥锁,当互斥锁生效时,文件上不能存在其他的共享锁,包括已共享的锁。
.e8@$@K K:w-~}$YBSD爱好者乐园dq{k3aJGu9] f
 
#define   LOCK_NB        0x04      /* don't block when locking */

ba9l#Iy]o使用这个,flock的调用将阻塞,直到锁定生效,不论如何,假如期望的操作LOCK_NB是ORed,flock的调用将向EWOULDBLOCK返回错误号成功(0)或者失败(1).BSD爱好者乐园7K ]B5zs)F Kg oq

W$?c |/s
 
#define   LOCK_UN        0x08      /* unlock file */

aDgq$r XLOCK_UN用于移除文件上的锁
~0x*l`:|]uaBSD爱好者乐园3Q6T j;J'K
通过调用期望的flock,flock锁的优先级能够被提升或者降低。新的成功的调用将使最近生效的锁替换先前的锁。BSD爱好者乐园;YouHW.VH EV

)P+}6d0~5^uSDUP函数
WM9m6S(Kt4Gh
c"ZF'HE~7W C[
 
int dup(int old);

w'@ \j`4J)r7R像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.BSD爱好者乐园#U$Ej w,ERm
BSD爱好者乐园f)HAL fawo8r
DUP2函数
'I_}!l$s5@ YBSD爱好者乐园\7m_kpB_
 
int dup2(int old, int new);

o P0{&U/leut)v;]除了new参数是希望的目标值,dup2函数跟dup函数很相似。假如新参数已经参照了一个有效打开的描述符,并且它的值跟老参数的值不一样,那么新文件描述符将先被关闭。假如新参数等于老参数,那么函数不会做任何操作。成功调用DUP2的返回值将等于新参数。假如DUP2调用失败,将返回-1并且相应地设置errno.BSD爱好者乐园Wj pF ?
BSD爱好者乐园(K@T z]&eD
5.5  进程间通信BSD爱好者乐园^/WgW ?[/vnR

hJ C Cd+ma$K来自于System V的主要的功能是基本的进程间通信,或者说IPC,这些一直在BSD里面被非常广泛地使用。IPC机制允许程序之间相互共享数据。这个很像我们已经介绍过的重定向,但是重定向是单向处理而不是双向处理。在示范程序中,重定向可以跟设置过STDIN_FILENO参数的CAT命令共享数据,但是有一个问题:cat命令不能跟重定向的程序共享数据,假设一个算法是从其他文件描述符读但是用open不灵活的时候,我们可以修改他们使其可以双向地相互共享数据。BSD提供了很多更好的用于进程间通信的方法。
6DvSWz!S*UBSD爱好者乐园jiy'R3T2Kr
PIPE函数BSD爱好者乐园ox9@l)K8V(s4x
BSD爱好者乐园7t*g%q.R"F9`,Z
 
int pipe(int *array);

~3o?le+X w{9E通过给定一个有效的二维数组调用pipe函数(如:int array[2]),pipe函数将分配两个文件描述符,假如成功,数组将包含两个有区别的并且允许单向通信的文件描述符, 打开的第一个文件描述符(array[0])用于读,打开的另一(array[1])个用于写,所以,自从成功调用pipe函数后,你能在这两个描述符之间得到一个单向通信通道,当写入其中一个时,你也将能够从另一个读取输出,管道函数比重定向好的地方是你不必使用文件,
I{T.HH A iBSD爱好者乐园l j*?B Of;JWN
这些文件描述符的行为与普通的文件描述符完全一样,然而它们没有任何文件与之关联,管道功能使unix shell加到其他命令的管道变得很有用,例如
clh/pxt'D-CLV
Kcl;~$E0Ho
 
bash$ find /  -user frankie  | grep -i jpg  | more

-_D&y)lc @这个例子里面,find命令将其输出通过管道传送给grep命令,grep再将其输出通过管道传送给more命令。当建立这个顺序的时候,shell将处理管道实际的设置,这些程序本身没有写到其他程序的想法,因为他们真的不需要知道。从这个例子你能看见自从调用了管道,正常来说进程就会fork, 之后进程就能通信,假如要达到双向通信的目标,你需要创建两个管道,一个用于父进程到子进程的通信,另一个用作子进程到父进程的通信。
3T!S$}(f:cG&W
MF ilf,b管道有下面两条通信规则:
%_-F"Q5?b3D    1.如果管道读的这边关闭了,试图写到这个管道将会导致一个SIGPIPE信号发送给试图写的进程。
D}"B,{$zy+r    2.如果管道写的这边关闭了,试图从这个管道读将导致读返回0或文件结束。关闭写端是发送文件结束到该管道读端的唯一方法。BSD爱好者乐园X2t3hY3M}+su}A

m3A5C8a#S-vwB}R成功地调用pipe函数后将返回0,假如调用失败,将返回-1并且errno将被相应地设置,
!{1u-f2z2|UKBSD爱好者乐园 `;D6nm^6}n
备注:在更多的现代的BSD上,单个描述符上pipe函数支持双向通信,不论如何,这个特性不是很轻便,也因为如此,这个方式不建议使用。
|$\:Swc*pT Lk,G7\BSD爱好者乐园%Vt,i_En s PY

L$N D'xl1C0oT6?^OMkfifo函数BSD爱好者乐园!lo bm(L NW%SL
BSD爱好者乐园Z@H \8ypE
 
int mkfifo(const char *path, mode_t mode);
BSD爱好者乐园OH'vlB(d8Tw YE
在相关的进程间通信时,管道是有用的,然而在没有关联关系的进程间通信时,使用mkfifo函数。Mkfifo函数实际上在文件系统创建一个文件。这个文件只是其他文件在通信时使用的一个标志。这些文件就叫做FIFO管道(先进先出管道)。当一个进程创建一个FIFO管道,写到这个FIFO管道并不会写到这个文件,而是被另一个进程读取。这种行为与管道非常相似,所以FIFO也被叫做命名管道。Mkfifo函数有两个参数。第一个参数是一个以null结尾的字符串,声明路径和文件名。第二个参数是该文件的存取模式。该存取模式是标准的unix文件所有者读写权限(参考/usr/include/sys/stat.h中的S_IRUSR, S_IRGRP等)。BSD爱好者乐园L Eh h'P"{
BSD爱好者乐园uhp z&D/q(r:s
一旦mkfifo函数成功调用,需要用open函数打开创建的fifo管道进行读写。如果调用失败,会返回-1而且错误代码会被相应地设置。BSD爱好者乐园/nq k)`6B},j,z"M(f5D
BSD爱好者乐园nxO'a+F_v
创建fifo管道与创建文件相类似,进程也必须有足够的权限来创建fifo管道,因为该fifo的用户ID会被设置为该进程的有效用户ID,而组ID会被设置为该进程的有效组ID。BSD爱好者乐园w!zL)VFXo
BSD爱好者乐园/A @9ThlT7hm
关于FIFO重要的一点是,在缺省情况下,它们是阻塞的。因此,读取一个fifo管道会被阻塞,直到另一端把数据写进来,反之亦然。为了避免这种情况,可以使用O_NOBLOCK参数来打开。在这种情况下,你会获得以下行为:对读的调用会立即返回,返回值为0;或者对写的调用会导致一个SIGPIPE信号。
6d-G L5H7G,L:d
mL(?D_S5.6消息队列
Fk~h d L1?YBSD爱好者乐园-` i9a`\%B x\x!]8]
另一种进程间通信机制,消息队列,为进程间通信提供另一种方式。然而和我提到的其他通信机制不同,你应该尽量避免使用这种方式。如果你的程序使用消息队列,试着用fifo或者甚至Unix Domain Socket重新实现它。在我讨论原因之前,以下是一个快速概述。
FtB'I8M1|L
KQ;{2]6sY1]7U:IV消息队列与FIFO相似,但它们使用一个键而不是一个文件作为引用。这个键是一个无符号整数。一旦一个消息队列被创建,发送到这个消息队列的数据会被内核缓存。内核分配给消息队列的内存是有限的。一旦一个消息队列被填满,那么在一个进程从这个消息队列把数据读走之前不能再往这个队列发送数据。在这种情况下,如果两个进程以不同的速度读写,队列是可靠的而且在大多数情况下是非堵塞的。这与FIFO不同。在FIFO的机制中,一个慢的读进程事实上是会拖慢一个比较快的写进程的(除非设置了O_NONBLOCK参数)。另一个好处是即使写进程退出了,写到消息队列中的数据也会被保存起来直到另一个进程读取它。而在FIFO的机制下,如果写进程退出了,这个FIFO会被关闭,而读进程会收到一个文件结束的标志。BSD爱好者乐园or;y-[q,WP(d
BSD爱好者乐园m"o W|9O
以上这些所有的消息队列的好处看起来不错,但让我们再仔细看看。假定一个进程打开一个消息队列,往里面写了一大块数据,填满了内核的缓冲区然后退出,内核将不得不保存这些数据直到另一个进程来读取它,而其他任何想创建和写消息队列的进程将会被拒绝。这种情况将会持续直到一个进程读走这些数据或者系统重启。在这种情况下,创建一个简单的针对消息队列的拒绝服务是可能的。
+qd0X0MLlx"g3@ ZBSD爱好者乐园1a} |C'S4L(c
另一个问题是键并不确保是唯一的。换句话说,一个进程无法确立一种方式来确定它是否是使用一个特定消息队列的唯一进程。而在FIFO机制中,当一个进程创建一个FIFO时,它有比较好的机会知道这个FIFO是否唯一因为可以声明一个针对文件路径的预先约定(比如:/usr/local/myapp/fifo_dir)。你的应用程序可以在安装时创建一个唯一的目录,这样几乎可以确保一个唯一的FIFO路径。有效的消息队列键值可以通过调用一个函数ftok来生成以帮助减少相同的键值,但并不能确保唯一。这个问题的副作用难于确定—你的程序可能读到它并不想读的数据,或者你的程序写入的数据正被其他并不想读取的进程读取。简短来说,当你使用消息队列时,可能导致难于调试的奇怪行为和错误。BSD爱好者乐园9Nn'z2U gL/J Ss

sv2a m v%g C/L#N如果你仍然坚持使用消息队列,参考以下的指南页:ftok(3), msgget(3), msgctl(3), msgrcv(3), 和msgsnd(3)。BSD爱好者乐园+V2|b\I#A5YD
BSD爱好者乐园 ~ m?;^r'oPZ4p3K
5.7 结论
)XzMc@BSD爱好者乐园sI?R6j"x\g
这一章介绍了几个操作打开文件描述符的系统调用,包含在我们fork和exec之前必须关闭文件描述符的场景。我们也讨论了文件锁定,设置和移除文件锁定,以及一些特殊的文件描述符如FIFO和Queue等,在那种情况下根本不在文件系统上存储数据。这些系统调用为BSD增加了很大的可编程性和灵活性。但当一个进程有多个打开的文件描述符时会怎么样?下一章将会讨论高效地处理多个文件描述符。因为使用范围的限制,BSD方面文章更新速度不快,站长会坚持每天更新博客,欢迎访问!
[版权声明]BSD爱好者乐园站内文章,如来源不是互联网,则均系原创或翻译之作,可随意转载,或以此为基础进行演译,但务必以链接形式注明原始出处和作者信息,否则属于侵权行为。另对本站转载他处文章,俱有说明,如有侵权请联系本人,本人将会在第一时间删除侵权文章。
TAG: freebsd FreeBSD 系统编程
 

评分:0

我来说两句

seccode