CSDN 上的 APUE 读书笔记第三章 -- 文件 I/O

20 篇文章 0 订阅

第三章 文件 I/O


这一章讲的是Unix 的基本I/O 函数:open, write, read, close, lseek、dup、fcntl 等。它们又被称为不带缓冲的I/O,这是因为这些函数直接执行系统调用,而不在进程地址空间中另外开辟缓冲区。


1、文件描述符 file descriptor

文件描述符是对文件的引用,本身是个 int 类型的数值。它的取值在进程内是唯一且循环使用的。文件描述符 0、1、2 ( 通常使用<unistd.h>中定义的 STDIN_FILENO, STDOUT_FILENO, STDERR_FILENO) 则用作进程的标准输入文件、标准输出文件和标准出错文件。

标准输入、标准输出和标准出错对应的设备文件注册在目录/dev中,文件名分别为 stdin, stdout, stderr。使用命令"ls -l"可以发现,它们实际上分别是指向/dev/self/fd/0, /dev/self/fd/1, /dev/self/fd/2 的软链接。

虚拟目录/proc/self/fd 中记录了当前进程所打开的文件描述符。通过命令"ls -l"可以看到这些文件描述符分别引用了系统中哪些文件(软链接的目标)。可以看到文件描述符 0、1、2 会指向 tty 或者 pipe之类的设备,这说明这些进程是和这些设备进行数据读/写的。还可以看到对于守护进程,0、1、2 都是链接到/dev/null 的,这说明守护进程不会跟任何的接口进行交互。


2、打开文件:open 函数

#include <fcntl.h>
int open(const char *filename, int oflag);int open(const char *filename, int oflag, mode_t mode);

该函数以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(2)操作将阻塞到内核将内容真正同步到设备,文件在这之前将一直保持打开。

mode 为文件创建权限,与进程 EUID 的 umask 进行“或”操作成为文件的权限位。熟悉 chmod(1)命令则自然知道其具体用法。


3、创建新文件:creat 函数

#include <fcntl.h>
int creat(const char *filename, mode_t mode);

据记载这个函数的名字确实是当年实现时的拼写错误,而一直被后世沿袭。它以只写方式创建并打开一个新文件,如果文件已存在,则文件被截短为 0。相当于执行了

open(pathname, O_WRONLY | O_CREAT | O_TRUNC, mode);

4、关闭文件:close 函数

#include <unistd.h>
int close(int filedes);

关闭指定的文件描述符。同时,如果进程在此文件上加有记录锁,将释放。在进程终止时,内核将自动关闭进程打开的文件。


5、定位文件:lseek 函数

#include <unistd.h>
off_t lseek(int filedes, off_t offset, int whence);

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 操作。


6、读文件数据到缓冲区(输入操作):read 函数

#include <unistd.h>
ssize_t read(int filedes, void *buf, size_t nbytes);

read 按指定的字节数 nbytes 从文件 filedes 的当前位置处读取数据,输入到缓冲区 buf 中。其返回值:

为正数时:为实际读取的字节数,

为0 时:已经读到EOF;

为-1时:调用失败,同时errno 被设置。

如果文件打开时未指定O_NONBLOCK标志,对其的read 调用可能发生阻塞等待可读。阻塞时如果进行了信号捕捉,read 将直接失败。

注意参数nbytes 的类型为无符号的size_t(即必须为正整数),而返回值是有符号的ssize_t。


7、从缓冲区写数据到文件(输出操作):write 函数

#include <unistd.h>
ssize_t write(int filedes, const void *buf, size_t nbytes);

write 按指定的字节数 nbytes 从 buf 处取数据,输出到文件 filedes 的当前位置处,如果已经到文件末尾,将增加文件长度并在最后添加EOF 标志。

其返回值:

为正数时:为实际写入的字节数,

为-1时:函数出错,同时errno 被设置。

read 和 write 操作一次写入数据的大小将会影响其 I/O 效率,通常按文件系统的块大小(文件 stat 结构的 st_blksize)设置。


8、定位同时读写文件的原子操作:pread 和 pwrite 函数

#include <unistd.h>
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);

这两个函数先将文件位置定位到距开始offset 处,然后对其按给定参数进行读/写。这两个步骤是原子操作,这意味这要不这些步骤一次性全部执行,要不就不执行。如果不是原子操作,则可能会由于内核调度或者信号处理等原因,使其他进程插到几个步骤之间更改所操作对象的状态,而引起后续操作发生意外。


9、复制文件描述符:dup 和 dup2 函数

#include <unistd.h>
int dup(int filedes);
int dup2(int filedes, int filedes2);

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 不同。

如果使用dup2(2)重定向描述符后,要关闭原来的文件描述符。则在dup2之前应比较两个参数是否相等,以避免误关闭。


10、更新到实际文件:sync、fsync 和 fdatasync 函数

通常内核为了考虑吞吐效率等情况,write 调用成功后并不马上将数据写到磁盘,而是放在磁盘的缓存区中并通过缓存区交换算法(例如最近最少使用)不定期的同步数据到磁盘,或者通过守护进程定时进行数据同步,也可以通过调用 sync 使其马上进行数据同步。

#include <unistd.h>
int fsync(int filedes);
int fdatasync(int filedes);
void sync(void);

sync 立即将内核缓冲的数据送到磁盘中的写队列,并直接返回。

fsync 也立即将内核缓冲的数据送到磁盘中的写队列,等待到磁盘写结束时才返回。

fdatasync 类似 fync,但除了同步数据外还同步文件的属性(例如 stat 结构的 st_ctime 等)。也可用系统命令sync(1)来同步数据。


11、已打开文件状态的更改:fcntl 函数

#include <fcntl.h>
int fcntl(int filedes, int cmd, /* int arg*/);

cmd 包括了以下取值:

F_DUPFD: 复制文件描述符并返回新的文件描述符(同 dup)

F_GETFD/F_SETFD 获取/重设文件描述符的标志(FD_CLOEXEC,用于指出执行 exec(3)调用时是否关闭此文件)

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 进行不同取值的相关参数,略。


12、ioctl 函数

对设备进行指定的操作,适用于 read、write、lseek、fcntl 等函数不能完成的其它功能。对于Linux,手册中称其用于操作 STREAMS 设备,但 Linux 下几乎用不上 STREAMS 设备。其它平台对 ioctl函数的用途不尽相同,略。






评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值