Apue.2e学习笔记chapter3

本章学习文件I/O。

不带缓冲指的是每个读写操作都会调用内核中的一个系统调用。

File descriptor

    相当于win下的文件句柄,一般0:stdin,1:stdout,2:stderr,在依从POSIX标准的系统中,使用宏STDIN_FILENO,STDOUT_FILENO和STDERR_FILENO来确定,最好不用硬编码。

    文件描述符的取值范围是0~OPEN_MAX。

open function

    #include <fcntl.h>

    int open(const char *pathname, into flag, … /*mode_t mode */)

flag包括O_RDONLY, O_WRONLY和O_RDWR(以上3选1),和O_APPEND, O_CREAT, O_EXCL(测试是否存在),O_TRUNC, O_NOCITY(终端相关),O_NONBLOCK(非阻塞,对FIFO、块文件或字符设备文件有效),SUS扩展选项:O_DYSNC(每次write等待物理I/O完成,属性除外),O_RSYNC(读同步),O_SYNC(写操作,包括属性,在不同系统上实现不太一样)。

    open操作返回的文件描述符一定是未用的最小文件描述符。

    如果文件名长度大于NAME_MAX有的系统会直接截断,有的会返回出错。_POSIX_NO_TRUNC确定了遵从哪种行为,如果=1,那么出错,设置errno;否则直接截断。

creat function

    令人蛋碎的create函数,别问为啥没有最后的e,这个问题去问ken大神。

    int creat(const char *pathname, mode_t mode)

    这个函数现在一般不用了,等价于open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode),之所以存在,是对旧系统的兼容。

close function

    #include <unistd.h>

    int close(int filedes),同上面的一样,失败返回-1

lseek function

    off_t lseek(int filedes, off_t offset, int whence)

    通过whence参数描述具体的行为,可以是SEEK_SET, SEEK_CUR, SEEK_END,具体意思和ISO C中一致。如果是确定当前偏移量,可以用lseek(fd,0,SEEK_CUR)

    off_t的实现根据平台有所变化,一般有32位和64位两种。可以使用sysconf确认,这里标准有些混乱。

    注意:某些设备的偏移量可能是负值,因此对结果,确认失败与-1比较是否相等比确认负值更为妥当。

    如果写入内容超出文件末端,文件大小会被扩大,但是两块内容之间的空洞是否占用空间是由具体实现决定的。

read function

    sszie_t read(int filedes, void *buf, size_t nbytes);//posix.1

    int read(int filedes, char *buf, unsigned nbytes);//经典

    返回读到的字节数(或-1),读到EOF则返回0

write function

    sszie_t write(int filedes, const void *buf, size_t nbytes)

    类似read,失败的原因多半是磁盘空间已经写满,或文件大小超限。

对于Unix系统,字符和二进制没有区别…换句话说,一切皆字符。

buf大小的选择,一般与文件系统的block长度相关,可以通过实验得出恰当值。

文件共享

    系统有一张进程表,每个进程在其中有一个记录项,记录项包含一张该进程打开的所有文件的描述符表,这个表格主要包含两项内容:文件描述符标志和对应的文件表项指针;后者包含文件状态标志、文件偏移量和指向该文件v节点表项的指针。v节点包含了文件类型和对此文件进行各种操作的函数的指针,此外还可能包含了文件的i节点(索引节点)。i节点是文件的固有属性,包含文件的所有者、文件长度、文件所在的设备和指向文件实际数据块在磁盘所在位置的指针。

    以上描述根据实现不同而有所不同,Linux没有v节点,采用了一个独立于文件系统的i节点和一个依赖于文件系统的i节点。

    一个给定的文件只有一个v节点表项,但是打开该文件的每个独立进程都有自己的文件表项(以维持各自的偏移量)。当写入的偏移量超出文件长度,i节点中的对应信息也会更新为文件偏移量。

    可能有多个文件描述符项指向同一个文件表项;文件状态标志会影响所有指向该文件表项的任何进程中的描述符。

原子操作

    所谓原子操作,意思就是不能被打断的操作,要么执行,要么不执行,不能被暂停的操作,主要用于多线程同步。

    SUS的扩展原子I/O,包括:

    ssize_t pread(int filedes, void *buf, size_t nbytes, off_t offset)

    ssize_t pwrite(int filedes, const void *buf, size_t nbytes, off_t offset)

    注意这里直接将偏移量做为参数,因此相当于将lseek和read/write合并为一个原子(当然,不更新文件偏移量指针)

    open(fd, O_CREAT | O_EXCL)也可以作为一个原子,意思是如果不存在就创建文件,否则返回-1,O_TRUNC也有类似的效果。

dup和dup2

    复制文件描述符的函数(命名方式非常糟糕)。

    int dup(int filedes);

    int dup(int fiedes1, int filedes2)

    前者一定返回可用的最小文件描述符,后者可以通过filedes2指定(如果filedes2已经打开,需要先将其关闭,如果两个参数相等就不用关闭了)。

    dup后的描述符共享文件表项,所以需要使用原子函数读写。

    另外,可以使用万用函数fcntl进行复制,dup1: fcntl( fd, P_DUPFD, 0); dup2: close(fd2); fcntl(fd1, F_DUPFD,fd2),注意后者不是原子操作。

sysc, fsync和fdatasync

    用于高速缓存的读写同步。

    sync最简单,会将所有修改过的块缓冲区排入写队列,然后返回(作用于全局),并不等待写完成。系统守护进程会周期地调用该函数维护系统。

    fsync(fd),作用于单一文件描述符,等待写操作结束。

    fdatasync类似于fsync,但是只更新数据,不更新属性。(并非所有系统都支持)

fctnl function

    字面意思就是文件操作函数,万用函数。主要用来改变已经打开文件的性质。

    #include <fctnl.h>

    int fctnl(int filedes, int cmd, … /* int arg */ )

    行为依赖于cmd,包括:

  • F_DUPFD,复制文件描述符,前面有相关描述,从略;
  • F_GETFD, F_SETFD,获得/设置文件描述符标记,现在只有一个:FD_CLOEXEC(执行时是否关闭);
  • F_GETFL, F_SETFL,获得/设置文件状态标志,即O_xxx一系列open函数使用的标志;
  • F_GETOWN, F_SETOWN,获得/设置异步I/O所有权,见14章;
  • F_GETLK, F_SETLK, F_SETLKW,获得/设置记录锁;

对于F_GETFL/F_SETFL,有个比较蛋碎的地方,它们共占一位,如果需要取得具体的,先要把结果与O_ACCMODE相与,然后再和3个读取方式逐一比较;其他的就直接位与就行了,=1说明对应标志置位。

ioctl function

    字面意思是I/O控制函数,万用函数。

    #include <unistd.h> /*system V*/

    #include <sys/ioctl.h> /*BSD and Linux*/

    #include <stropts.h> /*XSI STREAMS*/

    int ioctl(int filedes, int request, …)

    这货是SUS的一个扩展,功能非常多。通常最后一个参数是指向一个变量或结构的指针。除了这个头文件之外,还需要其操作设备的头文件。每个设备驱动都可以自定义自身的ioctl命令,因此它的用法非常多。

/dev/fd

    非POSIX.1特性。打开/dev/fd/n,相当于复制描述符n(假设n是打开的),主要用于shell。

    有些系统可能直接提供/dev/stdin, /dev/stdout和/dev/stderr来表述标准流。

    有些bash shell中,可以使用 '-'来表述标准输入,但是如果有/dev/fd/0或/dev/stdin,显然使用后者更清晰。

posted @ 2013-01-14 19:59  生无所息  阅读(267)  评论(0编辑  收藏  举报