APUE第三章 文件I/O

1、概述

UNIX系统中,可用的文件I/O函数——打开文件、读文件、写文件等。而大多数文件I/O只需用到5个函数:open、read、write、lseek以及close。

本章所描述的函数常被称为不带缓冲的I/O。其中不带缓冲指的是每个read和write都调用内核中的一个系统调用

多进程间共享资源,原子操作。以及多个进程间如何共享文件,以及所涉及的内核有关数据结构。

最后将说明dup、fcntl、sync、fsync和ioctl函数。(注意:APUE有许多函数具有同类型版本,例如open,其还有openat函数。为了打笔记简单起见,有关APUE出现的函数,只列举最常见常用的函数,其同类函数可以通过书籍查阅,此笔记条款以后章节笔记均适用。)

2、文件描述符

值得注意的是,UNIX系统中,所有设备均以文件形式出现,而所打开的文件都通过文件描述符引用。

所以当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用open或creat返回的文件描述标识该文个把,然后将其作为参数传送给read或write。

惯例,UNIX系统shell把文件描述符0(STDIN_FILENO)与进程的标准输入关联、1(STDOUT_FILENO)与进程的标准输出关联、2(STDERR_FILENO)与进程的标准错误关联。其中STDIN_FILENO、STDOUT_FILENO和STDERR_FILENO在头文件<unistd.h>中定义。

3、大多数文件I/O操作需用到的5个函数:open、read、write、lseek以及close

open函数:打开或创建一个文件

#include<fcntl.h>
int open(const char* path, int oflag, .../*mode_t mode*/);
				//返回值:若成功,返回文件描述符;若出错,返回-1

对于open函数而言,仅当创建新文件时才使用最后mode参数。

path参数是要打开或创建文件的名字。oflag参数可用来说明此函数的多个选项。用以下一个或多个常量进行“或”运算构成oflag参数(这些常量在头文件<fcntl.h>中定义)。

O_RDONLY 只读打开

O_WRONLY 只写打开

O_RDWR 读、写打开

O_EXEC 只执行打开

O_SEARCH 只搜索打开(应用于目录)

以上5个常量中必须指定一个且只能指定一个。以下常量是可选的。

O_APPEND 每次写时都追加到文件的尾端。

O_CLOEXEC 把FD_CLOEXEC常量设置为文件描述标志。

O_CREAT 若此文件不存在则创建它。使用此选项时,open函数需同时说明第3个参数mode,用mode指定该新文件的访问权限位。

O_DIRECTORY 如果path引用的不是目录,则出错。

O_EXCL 如果同时指定了O_CREAT,而文件已经存在,则出错。用此可以测试一个文件是否存在,如果不存在,则创建此文件,这使测试和创建两者成为一个原子操作。

O_NOCTTY 如果path引用的是终端设备,则不将该设备分配作为此进程的控制终端。

O_NOFOLLOW 如果path引用的是一个符号链接,则出错。

O_NONBLOCK 如果path引用的是一个FIFO、一个块特殊文件或一个字符特殊文件,则此选项为文件的本次打开操作和后续的I/O操作设置非阻塞方式。

O_SYNC 使每次write等待物理I/O操作完成,包括由该write操作引起的文件属性更新所需的I/O。

O_TRUNC 如果此文件存在,而且为只写或读-写成功打开,则将其长度截断为0。

O_TTY_INIT 如果打开一个还未打开的终端设备,设置非标准termios参数值,使其符合Single UNIX Specification。

O_DSYNC 使每次write要等待物理I/O操作完成,但是如果该写操作并不影响读取刚写入的数据,则不需等待文件属性被更新。

O_RSYNC 使每一个文件描述符作为参数进行的read操作等待,直至所有对文件同一部分挂起的写操作都完成。

close函数:关闭一个打开文件。

#include<fcntl.h>
int close(int fd);
				//返回值:若成功,返回0;若出错,返回-1

关闭一个文件时还会释放该进程加在该文件上的所有记录锁。

当一个进程终止时,内核自动关闭它所有的打开文件。从而可以不显式的close关闭打开文件。

lseek函数:显式地为一个打开文件设置偏移量。

#include<unistd.h>
off_t lseek(int fd, off_t offset, int whence);
				//返回值:若成功,返回新的文件偏移量;若出错,返回-1

通常,读、写操作都从当前文件偏移量处开始,并使偏移量增加所读写的字节数。按系统默认的情况,当打开一个文件时,除非指定O_APPEND选项,否则该偏移量被设置为0。

对参数offset的解释与参数whence的值有关:

i、若whence是SEEK_SET,则将该文件的偏移量设置为距文件开始处offset个字节。

ii、若whence是SEEK_CUR,则将该文件的偏移量设置为其当前值加offset,offset可为正或负。

iii、若whence是SEEK_END,则将将文件的偏移量设置为该文件长度加offset,offset可正可负。

若lseek成功执行,则返回新的文件偏移量,为此可以用下列方式确定打开文件的当前偏移量:

off_t currpos;

currpos=lseek(fd,0,SEEK_CUR);//获取当前文件的偏移量

测试所涉及文件是否可以设置偏移量的方法:如果文件描述符指向的是一个管道,FIFO或网络套接字,则lseek返回-1,并errno设置为ESPIPE。如下代码所示:

#include "apue.h"

int
main(void)
{
	if (lseek(STDIN_FILENO, 0, SEEK_CUR) == -1)
		printf("cannot seek\n");
	else
		printf("seek OK\n");
	exit(0);
}

read函数:从打开文件中读数据。

#include<unistd.h>
ssize_t read(int fd, void* buf, size_t nbytes);
				//返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1

注意,不要超出所限制读的字节数。

write函数:向打开文件写数据。

#include<unistd.h>
ssize_t read(int fd,const void* buf, size_t nbytes);
				//返回值:读到的字节数,若已到文件尾,返回0;若出错,返回-1

对于普通文件,写操作从文件的当前偏移量处开始。如果在打开该文件时,指定了O_APPEND选项,则在每次写操作之前,将文件偏移量设置在文件的当前结尾处。在一次成功写之后,该文件偏移量增加实际写的字节数。

4、文件共享及原子操作

内核使用3种数据结构表示打开文件,它们之间的关系决定了在文件共享方面一个进程对另一个进程可能产生的影响。

i、每个进程在进程表中都有一个记录项(进程表项),该记录项包含一张打开文件描述符表,可将其视为一个矢量,每个描述符占用一项。与每个文件描述符相关联的是:

a、文件描述符标志

b、指向一个文件表项的指针

ii、内核为所有打开文件维持一张文件表。每个文件表项包含:

a、文件状态标志

b、当前文件偏移量

c、指向该文件v节点表项的指针

iii、每个打开文件(unix的设备都是用文件表示)都有一个v节点结构。v节点包含文件类型和对此文件进行各种操作函数的指针。大多数文件,v节点还包含该文件的i节点。i节点包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等信息。

具体描述,见下图:

两个独立进程各自打开同一个文件,内核数据结构图表示如下:

注意,使用O_APPEND打开文件,修改的是文件状态标志。使用lseek函数修改的是当前文件偏移量。

原子操作:由多步组成的一个操作。如果操作原子地执行,则要么执行完所有步骤,要么一步也不执行,不可能只执行所有步骤的一个子集。

5、dup、sync、fsync、fcntl和ioctl函数

dup函数可用来复制一个现有的文件描述符。

#include<unistd.h>
int dup(int fd);
				//返回值:若成功,返回新的文件描述符;若出错,返回-1

由dup返回的新文件描述符一定是当前可用文件描述符中的最小值。

此函数返回的新文件描述符与参数fd共享同一个文件表项,如下图所示:

sync与fsync函数是用来保证实际文件系统与缓冲区中内容的一致性。

#include<unistd.h>
int fsync(int fd);
void sync(void);
				//返回值:若成功,返回0;若出错,返回-1

sync只是将所有修改过的块缓冲区排入写队列,然后就返回,它并不等待实际写磁盘操作结束。

fsync函数只对由文件描述符fd指定的一个文件起作用,并且等待写磁盘操作结束才返回。fsync可用于数据库这样的应用程序,这种应用程序需要确保修改过的块立即写到磁盘上。

fcntl函数可以改变已经打开文件的属性。

#include<fcntl.h>
int fcntl(int fd, int cmd, .../*int arg*/);
				//返回值:若成功,则依赖于cmd;若出错,返回-1

第3个参数总是一个整数,与上面所示函数原型中的注释部分对应。只有当设置记录锁时,第3个参数则是指向一个结构的指针。

fcntl函数有以下5种功能:

i、复制一个已有的描述符(cmd=F_DUPFD或F_DUPFD_CLOEXEC)

ii、获取/设置文件描述标志(cmd=F_GETFD或F_SETFD)

iii、获取/设置文件状态标志(cmd=F_GETFL或F_SETFL)

iv、获取/设置异步I/O所有权(cmd=F_GETOWN或F_SETOWN)

v、获取/设置记录锁(cmd=F_GETLK、F_SETLK或F_SETLKW)

以后cmd的参数功能细节见APUE书上P66。

ioctl函数:是I/O打操作的杂物箱。终端I/O是使用ioctl最多的地方。

#include<unistd.h>/* System V */
#include<sys/ioctl.h>/* BSD and linux */
int ioctl(int fd, int request, ...);
				//返回值:若出错,返回-1;若成功,返回其他值

此处只是列举了ioctl函数本身所要求的头文件。通常,还要求另外的设备专用头文件。如,终端I/O的ioctl命令都需要头文件<termios.h>。

每个设备驱动程序可以定义它自己专用的一组ioctl命令,系统则为不同种类的设备提供通用的ioctl命令。下图总结FreeBSD支持的通用ioctl命令的一些类别。

类别 常量名 头文件 ioctl数
盘标号 DIOxxx <sys/disklabel.h> 4
文件I/O FIOxxx <sys/filio.h> 14
磁带I/O MTIOxxx <sys/mtio.h> 11
套接字I/O SIOxxx <sys/sockio.h> 73
终端I/O TIOxxx <sys/ttycom.h> 43

猜你喜欢

转载自blog.csdn.net/TT_love9527/article/details/82759830