文件IO
文章目录
系统调用IO与标准IO
1、系统调用IO是操作系统提供的底层接口,用于直接操作文件。
POSIX标准为类Unix系统定义了一系列的系统调用IO函数,如open()
, read()
, write()
, close()
, lseek()
等。
c
#include <unistd.h>
#include <sys/types.h> // for off_t
// 打开文件
int open(const char *pathname, int flags, mode_t mode);
// 读取文件
ssize_t read(int fd, void *buf, size_t count);
// 写入文件
ssize_t write(int fd, const void *buf, size_t count);
// 关闭文件
int close(int fd);
// 文件定位(偏移量)
off_t lseek(int fd, off_t offset, int whence);
这些函数直接与内核交互,提供了对文件的低级控制。
2、系统调用IO的特点
应用程序每次调用系统调用IO函数时,都会从用户态切换到内核态,这种切换会带来一定的性能开销
3、标准IO是在系统调用IO的基础上构建的高级IO接口。
标准IO由C语言标准库提供,在系统调用IO接口的基础上进行封装,提供了缓冲机制,可以减少系统调用的次数,提高IO效率。
4、标准IO的特点
标准IO通过缓冲机制减少系统调用的次数;在用户态维护缓冲区,减少用户态和内核态之间的切换次数。
当读文件时,调用标准IO进行读文件时,首先会通过系统调用IO读取一定数量的数据存入到缓冲区,然后再从缓冲区里面去读取数据,不必每次都通过系统调用去读取数据
当写文件时,首先会将数据写入到缓冲区,当缓冲区存满或者触发某些条件后,才会调用系统调用IO将缓冲区里的数据写入到文件
深入系统调用IO
1、原子操作
原子操作是一种不可分割的操作,在执行过程中不会被任何外部因素打断,包括中断和线程调度等
原子操作要么不执行,要么一次性全部执行完毕
原子操作可以保证在并发过程中的数据一致性
2、文件IO中实现原子操作
可以通过O_EXCL
以及O_APPEND
等标志来实现文件的原子操作:
O_EXCL
:与 O_CREAT
结合使用时,确保文件不存在时才创建,避免竞态条件(如多进程同时创建同一文件)。
****O_APPEND
:保证每次写入都追加到文件末尾,避免多进程/线程写入时的数据覆盖问题。
示例:
c
int fd = open("file.txt", O_WRONLY | O_CREAT | O_EXCL, 0644); // 原子创建
int fd_append = open("file.log", O_WRONLY | O_APPEND); // 原子追加
3、与文件相关的三个数据结构
在Linux系统中,每个打开的文件都关联着三个重要的数据结构:
(1)文件描述符表(进程级)
每个进程独立维护,存储该进程打开的文件描述符及其指向的文件表项的指针;open()
返回的fd即为此表的索引。
(2)文件表(系统级)
内核维护的全局结构,包含:
文件状态标志(O_RDONLY等)、 当前文件偏移量、指向v-node表的指针
不同fd可能指向同一文件表(如通过dup()
复制fd)。
(3)v-node表(文件系统)
存储文件的元信息:
文件类型、文件大小、文件操作函数指针、实际数据块位置
多个文件表项可能共享同一v-node(如硬链接)。
4、文件描述符fd与物理文件之间的关联
当进程通过文件描述符(fd)访问文件时,内核会依次查找以下数据结构:
1. 进程级的文件描述符表,获取指向系统级文件表的指针;
2. 系统级文件表,读取状态标志和当前偏移量,并获取v-node表指针;
3. 文件系统的v-node表,定位文件元信息和物理存储位置;
4. 最终通过文件系统驱动访问磁盘上的实际数据块。
5、fcntl函数
fcntl()
系统调用主要用于对一个已经打开的文件描述符执行一系列控制操作,包括获取/设置文件状态标志、文件锁、文件描述符复制等,其原型为:
c
#include <fcntl.h>
int fcntl(int fd,int cmd,...);
(1)获取/设置文件状态标志
将cmd参数设置为F_GETFL
可以获取文件状态标志 ,设置为F_SETFL
可以修改文件状态标志 (如O_APPEND/O_NONBLOCK等)。注意:部分标志(如O_RDONLY)创建后不可修改。
示例:
c
int flags = fcntl(fd, F_GETFL); // 获取当前标志
fcntl(fd, F_SETFL, flags | O_APPEND); // 追加O_APPEND标志
fcntl()
返回的文件状态标志是一个位掩码,可以通过与预定义的宏进行按位与(&)操作来判断已打开文件的状态和权限,例如:
c
if(fcn & O_WRONLY){/*判断描述符为fd的文件是否是只写模式*/
printf("%s write only!\n",argv[1]);
}
(2)复制文件描述符
6、dup函数
(1)dup()
复制一个已有的文件描述符,返回新的未使用的最小文件描述符,可用于实现文件描述符重定向,如将stdout重定向到文件
c
int dup(int oldfd); /*成功返回新文件描述符,失败返回-1*/
(2)dup2()
复制文件描述符到指定值,若指定值已打开则先关闭;可用于精确控制文件描述符的值,如管道通信中重定向标准输入/输出
c
int dup2(int oldfd, int newfd); /*成功返回newfd,失败返回-1*/
(3)dup3()
类似dup2但可附加标志(如O_CLOEXEC);用于需要设置文件描述符标志的复制操作,如多线程安全编程
c
int dup3(int oldfd, int newfd, int flags); /*成功返回newfd,失败返回-1*/
msp; 类似dup2但可附加标志(如O_CLOEXEC);用于需要设置文件描述符标志的复制操作,如多线程安全编程
c
int dup3(int oldfd, int newfd, int flags); /*成功返回newfd,失败返回-1*/