分类: LINUX
2008-12-09 12:58:22
这一章讲的是UNIX的基本I/O函数:open, write, read, close, lseek、dup、fcntl等。它们又被称为不带缓冲的I/O,这是因为read和write函数直接进行系统调用,而不在进程地址空间中另外开辟缓冲区。
文件描述符是对文件的引用,本身是个int类型的数值。它的取值在进程内是唯一且循环使用的。文件描述符0、1、2(通常使用
标准输入、标准输出和标准出错对应的设备文件注册在目录/dev中,文件名分别为stdin、stdout、stderr。使用命令"ls -l"可以发现,它们实际上分别是指向/proc/self/fd/0、/proc/self/fd/1、/proc/self/fd/2的软链接。
虚拟目录/proc/self/fd中记录了当前进程所打开的文件描述符。通过命令"ls -l"可以看到这些文件描述符分别引用了系统中哪些文件(软链接的目标)。可以看到文件描述符0、1、2会指向tty或者pipe之类的设备,这说明这些进程是和这些设备进行数据读/写的。还可以看到对于守护进程,0、1、2都是链接到/dev/null的,这说明守护进程不会跟任何的接口进行交互。
|
该函数以oflag指定的方式打开字符串filename指定的文件,成功后返回filename对应的文件描述符,失败时返回-1,并设置errno指代失败原因(例如:EACCES——Permission denied)。
oflag包括了O_RDONLY(以只读方式打开)、O_WRONLY(以只写方式打开)、O_RDWR(以读写方式打开),这三个标志只能使用一个。否则使用例如O_RDONLY | O_WRONLY | O_RDWR这样的方式打开文件,在编译时可以通过甚至不会发出警告(我在gcc 4.2,使用-Wall选项时看到也不会有警告),但此时读写方式是不可预料的;
除了读写方式标志外,oflag还可以通过按位或运算方式同时加入其它标志。包括O_APPEND(写时追加到尾端)、O_CREAT(文件不存在的话则创建,否则忽略此标志)、O_EXCL(只用于与O_CREAT结合,此时文件若已存在open调用将失败)、O_TRUNC(用写标志打开且文件存在时将文件长度截为0)、O_NONBLOCK(以非阻塞方式打开文件,如果要求的读写操作不能马上执行的话立即返回失败,常用于管道、字符终端等特殊文件);
还包括三个POSIX可选的同步标志:O_DSYNC、O_RSYNC、O_SYNC。对于Linux,三个标志的含义都与O_SYNC这个标志相同,使用此标志时,write操作将阻塞到内核将内容真正同步到设备,文件在这之前将一直保持打开。
mode为文件创建权限,与进程euid的umask进行“或”操作成为文件的权限位。熟悉chmod(1)命令则自然知道其具体用法。
|
据记载这个函数的名字确实是当年实现时的拼写错误,而一直被后世沿袭。它以只写方式创建并打开一个新文件,如果文件已存在,则文件被截短为0。相当于执行了
|
|
关闭指定的文件描述符。同时,如果进程在此文件上加有记录锁,将释放。
在进程终止时,内核将自动关闭进程打开的文件。
|
lseek执行时,将文件filedes的当前读写位置更改到相对whence指定的位置offset处的地方。whence包括了SEEK_SET(文件开始)、SEEK_END(文件末尾)、SEEK_CUR(当前),后两者的offset可以是负数。offset的类型off_t通常定义为一个机器字的长度( 一般typedef自long类型,因为根据标准C,long在任何机器上都是和机器字的长度相同的。对于32位平台则为4个字节)。
lseek成功时将返回相对于文件开始处的偏移量(可能是负数),失败返回-1并设置errno。在文件是FIFO、管道或者套接字时,lseek将失败并设置errno为ESPIPE(Illegal seek)。
lseek只更改进程打开文件的状态,并不会引起I/O操作。
|
read按指定的字节数nbytes从文件filedes的当前位置处读取数据,输入到缓冲区buf中。
其返回值:
为正数时:为实际读取的字节数,
为0时:已经读到EOF;
为-1时:调用失败,同时errno被设置。
如果文件打开时未指定O_NONBLOCK标志,对其的read调用可能发生阻塞等待可读。阻塞时如果进行了信号捕捉,read将直接失败。
注意参数nbytes的类型为无符号的size_t(即必须为正整数),而返回值是有符号的ssize_t。
|
write按指定的字节数nbytes从buf处取数据,输出到文件filedes的当前位置处,如果已经到文件末尾,将增加文件长度并在最后添加EOF标志。
其返回值:
为正数时:为实际写入的字节数,
为-1时:函数出错,同时errno被设置。
read和write操作一次写入数据的大小将会影响其I/O效率,通常按文件系统的块大小(文件stat结构的st_blksize)设置。
|
这两个函数先将文件位置定位到距开始offset处,然后对其按给定参数进行读/写。这两个步骤是原子操作,这意味这要不这些步骤一次性全部执行,要不就不执行。如果不是原子操作,则可能会由于内核调度或者信号处理等原因,使其他进程插到几个步骤之间更改所操作对象的状态,而引起后续操作发生意外。
|
dup函数使用一个当前进程中可用的最小文件描述符引用filedes所引用的文件,这个新的文件描述符的状态(打开标志、模式、当前位置等)和filedes相同。若成功,返回这个新的文件描述符。失败时返回-1(例如filedes不存在)并设置errno。
dup2可以直接指定dup中新的文件描述符的值为filedes2,如果filedes2已经打开,则先原子的关闭之。如果filedes==filedes2且存在时则直接返回文件描述符而不执行关闭。失败时也返回-1并设置errno。
dup2常用于输入/输出重定向以实现管道操作。例如
dup2(filedes, STDOUT_FILENO); |
则重定向进程的标准输出到filedes,相当于在shell中使用重定向操作符执行了"> file"。
dup2的功能也可以用fcntl(2)实现,但后者不能实现为原子操作且某些errno不同。
通常内核为了考虑吞吐效率等情况,write调用成功后并不马上将数据写到磁盘,而是放在磁盘的缓存区中并通过缓存区交换算法(例如最近最少使用)不定期的同步数据到磁盘,或者通过守护进程定时进行数据同步,也可以通过调用sync使其马上进行数据同步。
|
sync立即将内核缓冲的数据送到磁盘中的写队列,并直接返回。
fsync也立即将内核缓冲的数据送到磁盘中的写队列,等待到磁盘写结束时才返回。
fdatasync类似fync,但除了同步数据外还同步文件的属性(例如stat结构的st_ctime等)。
也可用系统命令sync(1)来同步数据。
|
cmd包括了以下取值:
F_DUPFD: 复制文件描述符并返回新的文件描述符(同dup)
F_GETFD/F_SETFD 获取/重设文件描述符的标志(FD_CLOEXEC,用于指出执行exec调用时是否关闭此文件)
F_GETFL/F_SETFL 获取/重设文件描述符的打开状态标志(O_RDWR、O_NONBLOCK等)
F_GETOWN/F_SETOWN 获取/重设捕捉信号SIGIO(异步I/O时,若I/O已经可用则产生此信号)和信号SIGURG(收到带外数据时产生此信号)的pid;
F_GETLK/F_SETLK 获取/设置记录锁,在并发场合用于同步文件的操作时序。
arg为根据cmd进行不同取值的相关参数,略。
对设备进行指定的操作,适用于read、write、lseek、fcntl等函数不能完成的其它功能。对于Linux,手册中称其用于操作STREAMS设备,但Linux下几乎用不上STREAMS设备。其它平台对ioctl函数的用途不尽相同,略。