第2章:文件I/O

本文详细解析Linux中文件系统I/O操作的基本原理,包括如何打开和关闭文件,如何通过read()和write()函数读写文件,以及如何使用lseek()和定位读写。同时介绍了I/O多路复用技术,帮助开发者更高效地进行文件操作。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1. 在对文件进行读写操作之前,首先需要打开文件。内核会为每个进程维护一个打开文件的列表,该列表称为文件表(file table)。文件表是由一些非负整数进行索引,这些非负整数称为文件描述符(file descriptor)。列表的每一项是一个打开文件 的信息,包含指向该文件索引节点(inode)内存拷贝的指针以及关联的元数据,如文件位置指针和访问模式。

默认情况下,子进程会维护一份父进程的文件表副本。在该副本中,打开文件列表及其访问模式、当前文件位置以及其他元数据,都和父进程维护的文件表相同,但存在一点区别:即当子进程关闭一个文件时,不会影响到父进程的文件表


2. 通过read()读文件

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t len);

每次调用read()函数,会从fd指向的文件的当前偏移开始读取len字节到buf所指向的内存中。执行成功时,返回写入buf中的字节数;出错时,返回-1,并设置errno值。fd的文件位置指针会向前移动,移动的长度由读取到的字节数决定。如果fd所指向的对象不支持seek操作(比如字符设备文件),则读操作总是从“当前”位置开始。

对于read()而言,返回小于len的非零正整数是合法的。实际上,调用read()有很多可能结果:

  • 调用返回值等于len。读取到的所有len个字节都被存储在buf中。结果和预期的一致。
  • 调用返回值小于len,大于0。读取到的字节被存储在buf中。比如在读取过程中信号中断,再次执行read(分别更新buf和len值)
  • 调用返回0,表示EOF,没有更多可读的数据。
  • 由于当前没有数据可用,调用阻塞。在非阻塞模式下,不会发生这种情况。
  • 调用返回-1,并把errno设置成EINTR。这表示在读取任何字节前收到信号。调用可以重新执行。
  • 调用返回-1,并把errno设置成EAGAIN。这表示当前没有任何数据可用,请求应该稍后再重新执行。这种情况只在非阻塞模式下发生。

  • 调用返回-1,并把errno设置成非EINTR或EAGAIN的一个值。这表示更严重的错误。重新执行读操作不会成功。
读入所有字节可以这样:

ssize_t ret;
while (len !=0 &&(ret = read(fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror("read");
break;
}

len -= ret;
buf += ret;
}

3. 调用write()写

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);
write()调用会从文件描述符fd指向的文件的当前位置开始,将buf中至多count个字节写入到文件中。不支持seek的文件(如字符设备)总是从起始位置开始写。write()指向成功时,会返回写入的字节数,并更新文件位置。出错时,返回-1,并设置errno值。
对于普通文件(不太可能会返回部分写),不需要执行循环写操作。

但是,对于其他的文件类型,比如socket,需要循环来保证写了所有请求的字节:

ssize_t ret;
while (len != 0 && (ret = write(fd, buf, len)) != 0) {
if (ret == -1) {
if (errno == EINTR)
continue;
perror("write");
break;
}

len -= ret;
buf += ret;
}
当write()调用返回时,内核已经把数据从提供的缓冲区拷贝到内核缓冲区中。但不保证数据已经写到目的地。

4. 关闭文件
#include <unistd.h>

int close(int fd);
系统调用close()会取消当前进程的文件描述符fd与其关联的文件之间的映射。关闭文件操作并非意味着该文件的数据已经被写到磁盘。

5. 用lseek()查找
一般情况下,I/O是线性的,由于读写引发 的隐式文件位置更新都需要seek操作。lseek()系统调用能够将文件描述符的位置指针设置成指定值。lseek()只更新文件位置,没有执行其他操作,也并不初始化任何I/O:

#include <sys/types.h>
#include <unistd.h>
off_t lseek(int fd, off_t pos, int origin);

6. 定位读写
Linux提供了两种read()和write()系统调用的变体来代替lseek(),每次读写操作时,都把文件位置作为参数,在完成时,不会更新文件位置。

ssize_t pread(int fd, void *buf, size_t count, off_t pos);从文件描述符的pos位置开始读取,共读取count个字节到buf中
ssize_t pwrite(int fd, const void *buf, size_t count, off_t pos);从文件描述符fd的pos位置开始,从buf中写count字节到文件中
定位读写只适用于可查找的文件描述符,包括普通文件。pread()和pwrite()调用的语义相当于在read()和write()之前调用lseek()调用,但存在以下三点区别:
  1. pread()和pwrite()调用在结束时不会修改文件位置指针。
  2. pread()和pwrite()调用避免了在使用lseek()时出现的竞争。
7. I/O多路复用
内核一旦发现进程指定的一个或多个I/O条件就绪,它就通知进程。

select  poll 



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值