当前位置: 首页 > news >正文

Linux系统编程----文件IO

一、文件描述符

  • 本质:内核为进程打开的文件分配的非负整数索引,是进程访问文件的唯一标识。
  • 分配规则:优先使用当前最小未被使用的文件描述符编号。
  • 默认关联:每个进程启动时默认打开 3 个文件描述符:
    • 0:标准输入(stdin)
    • 1:标准输出(stdout)
    • 2:标准错误(stderr)

二、open 函数

头文件#include <fcntl.h>

函数原型:int open(const char *pathname, int flags, mode_t mode);

核心参数

  • pathname: 要打开的文件路径(字符串形式)。
  • flags: 指定打开文件的方式,必须包含以下必选项之一
标志含义
O_RDONLY只读
O_WRONLY只写
O_RDWR读写

此外,还可以通过按位或组合以下常用附加标志:

标志含义
O_APPEND追加模式,每次写入都定位到文件末尾
O_CREAT若文件不存在则创建(需配合mode指定权限)
O_TRUNC若文件存在且以写方式打开,则将其长度截断为0(清空内容)
O_EXCLO_CREAT一起使用时,若文件已存在则返回错误(防止竞争)
  • mode
  1. 仅当使用了O_CREAT时,第三个参数才有效,用于指定新创建文件的访问权限(八进制表示,如0666)。

  2. 最终权限还会受到进程的umask影响:

    实际权限 = mode & ~umask

返回值

  • 成功:返回一个文件描述符(非负整数,如3、4、5等)。

  • 失败:返回-1,并设置errno(可用perrorstrerror查看错误原因)。

*open与fopen的对应关系

标准IO的fopen实际上就是对open的系统调用进行了封装,并增加了缓冲区管理。下面是常用fopen模式与open标志的对应关系:

fopen模式open标志组合
"r"O_RDONLY
"r+"O_RDWR
"w"O_WRONLY | O_CREAT | O_TRUNC
"w+"O_RDWR | O_CREAT | O_TRUNC
"a"O_WRONLY | O_CREAT | O_APPEND
"a+"O_RDWR | O_CREAT | O_APPEND

示例:

#include <stdio.h> #include <fcntl.h> #include <unistd.h> int main(int argc, const char *argv[]) { if (argc != 2) { printf("Usage: %s <filename>\n",argv[0]); return -1; } // 打开文件:只写模式 + 创建(如果不存在)+ 截断(如果文件已存在,打开时会清空原有内容) // 0666:权限设置为 rw-rw-rw- int fd = open(argv[1],O_WRONLY|O_CREAT|O_TRUNC,0666); if (fd < 0) { perror("open fail"); return -1; } printf("success\n"); return 0; }

三、read/write函数

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

功能 :

从文件描述符fd指向的文件中读取最多count个字节的数据,并存储到buf指向的缓冲区中。

参数说明 :

参数含义
fd文件描述符(由open返回)
buf指向缓冲区的指针,用于存放读取的数据
count请求读取的最大字节数

返回值

  • 成功:返回实际读取的字节数(可能小于count,如到达文件末尾或剩余数据不足)。

  • 到达文件末尾:返回0(表示没有更多数据可读)。

  • 失败:返回-1,并设置errno

  • 注意read不保证一次能读到count字节,因此通常需要循环读取直到满足需求或遇到文件结束。

ssize_t write(int fd, const void *buf, size_t count);

功能 :

buf指向的缓冲区中的count个字节写入到文件描述符fd指向的文件中。

参数说明

参数含义
fd文件描述符
buf指向要写入数据的缓冲区
count要写入的字节数

返回值

  • 成功:返回实际写入的字节数(通常等于count,但可能小于,如磁盘满或信号中断)。

  • 失败:返回-1,并设置errno

示例 : 用read/write实现cp命令

#include <stdio.h> #include <fcntl.h> #include <unistd.h> //./a.out src dest int main(int argc, const char *argv[]) { if (argc != 3) { printf("Usage: %s <src> <dest>\n",argv[0]); return -1; } int fd_s = open(argv[1],O_RDONLY); int fd_d = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666); if (fd_s < 0 || fd_d < 0) { perror("open fail"); return -1; } //拷贝 char buf[100] = {0}; int ret = 0; while (1) { ret = read(fd_s,buf,sizeof(buf)); if (ret == 0) break; write(fd_d,buf,ret); } //关闭 close(fd_s); close(fd_d); return 0; }

四、lseek函数

off_t lseek(int fd, off_t offset, int whence);

功能 :

重新定位文件描述符fd关联的文件偏移量(即下一次读写操作开始的位置)。它不进行实际的读写操作,只移动文件内部的位置指针。

参数说明

参数含义
fd文件描述符(由open返回)
offset偏移量(单位:字节),可正可负
whence参考起点,决定offset的基准位置

whence的取值

含义说明
SEEK_SET文件开头offset必须 ≥ 0
SEEK_CUR当前位置offset可正可负
SEEK_END文件末尾offset可正可负

返回值

  • 成功:返回从文件开头到新位置的字节数(即新的文件偏移量)。

  • 失败:返回-1,并设置errno(如fd无效、whence非法等)。

常用 :

off_t pos = lseek(fd, 0, SEEK_SET); // 返回0(文件开头) off_t end = lseek(fd, 0, SEEK_END); // 返回文件大小(字节数) off_t pos = lseek(fd, 100, SEEK_CUR); // 从当前位置向后移动100字节 off_t pos = lseek(fd, -50, SEEK_END); // 从文件末尾向前移动50字节

获取文件大小 :

off_t file_size = lseek(fd, 0, SEEK_END); // 此时文件偏移量已变为文件末尾,若需后续读写,应重新定位 lseek(fd, 0, SEEK_SET); // 可选:移回开头

示例 :根据源文件的大小,给目标文件创建一个一样大的空洞文件。

#include <stdio.h> #include <fcntl.h> #include <unistd.h> //./a.out src dest int main(int argc, const char *argv[]) { if (argc != 3) { printf("Usage: %s <src> <dest>\n",argv[0]); return -1; } int fd_s = open(argv[1],O_RDONLY); int fd_d = open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0666); if (fd_s < 0 || fd_d < 0) { perror("open fail"); return -1; } // 1. 获取源文件总大小(把指针移到末尾,返回值就是文件字节数) off_t len = lseek(fd_s,0,SEEK_END); // 2. 把目标文件的指针 移动到 【文件大小 - 1】的位置 // 比如源文件是 100 字节,就移动到第 99 个字节的位置 lseek(fd_d,len-1,SEEK_SET); // 3. 在这个位置写入 1 个空字节 write(fd_d,"",1); return 0; }

五、目录操作与文件状态获取

5.1 opendir:打开目录

#include <dirent.h>

DIR *opendir(const char *name);

  • 功能:打开名为name的目录,并返回一个目录流指针(DIR*)。

  • 参数name— 目录路径(字符串)。

  • 返回值:成功返回DIR*指针;失败返回NULL,并设置errno

5.2 readdir:读取目录项

struct dirent *readdir(DIR *dirp);

  • 功能:从目录流dirp中读取下一个目录项,返回包含该目录项信息的结构体指针。

  • 参数dirp— 由opendir返回的目录流指针。

  • 返回值:成功返回struct dirent*指针;失败或读到目录末尾时返回NULL

struct dirent结构体(不同系统略有差异,但核心成员如下):

struct dirent { ino_t d_ino; /* inode号 */ off_t d_off; /* 偏移量(非文件偏移) */ unsigned short d_reclen; /* 记录长度 */ unsigned char d_type; /* 文件类型(某些文件系统可能不支持) */ char d_name[256]; /* 文件名(以'\0'结尾) */ };
  • d_name:最重要的字段,存储文件名。

  • d_type:可快速判断文件类型,其值可以是:

含义
DT_BLK块设备
DT_CHR字符设备
DT_DIR目录
DT_FIFO命名管道
DT_LNK符号链接
DT_REG普通文件
DT_SOCK套接字
DT_UNKNOWN类型未知(需用stat进一步判断)

5.3 closedir:关闭目录流

int closedir(DIR *dirp);

  • 功能:关闭目录流,释放资源。

  • 返回值:成功返回0;失败返回-1

⚠️注意:一个目录流只能关闭一次,多次关闭可能导致未定义行为。

六、获取文件状态:stat系列函数

目录操作只能获得文件名和简单的文件类型(d_type),若需要文件的详细属性(大小、权限、时间戳等),必须使用statlstatfstat

6.1 stat:获取文件状态

#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *statbuf);

  • 功能:获取pathname指定文件的属性,存入statbuf指向的结构体。

  • 参数

    • pathname:文件路径。

    • statbuf:指向struct stat的指针,用于接收信息。

  • 返回值:成功返回0;失败返回-1,并设置errno

注意stat会跟随符号链接(即获取链接指向的目标文件的信息)。若需获取符号链接本身的信息,使用lstat

6.2 struct stat 结构体

struct stat { dev_t st_dev; /* 设备ID */ ino_t st_ino; /* inode号 */ mode_t st_mode; /* 文件类型和权限 */ nlink_t st_nlink; /* 硬链接数 */ uid_t st_uid; /* 所有者用户ID */ gid_t st_gid; /* 所有者组ID */ dev_t st_rdev; /* 特殊设备ID(仅对设备文件有效) */ off_t st_size; /* 总大小(字节) */ blksize_t st_blksize; /* 文件系统IO块大小 */ blkcnt_t st_blocks; /* 分配的512B块数 */ time_t st_atime; /* 最后访问时间 */ time_t st_mtime; /* 最后修改时间 */ time_t st_ctime; /* 最后状态改变时间 */ };

七、解析文件类型与权限

st_mode字段是一个位掩码,同时包含了文件类型和权限信息。

7.1 获取文件类型

方法一:使用S_IFMT掩码提取类型

mode_t type = st_mode & S_IFMT; switch (type) { case S_IFREG: printf("普通文件"); break; case S_IFDIR: printf("目录"); break; case S_IFLNK: printf("符号链接"); break; case S_IFCHR: printf("字符设备"); break; case S_IFBLK: printf("块设备"); break; case S_IFIFO: printf("FIFO"); break; case S_IFSOCK: printf("套接字"); break; default: printf("未知类型"); break; }

方法二:使用预定义的宏(更简洁)

含义
S_ISREG(st_mode)是否为普通文件
S_ISDIR(st_mode)是否为目录
S_ISCHR(st_mode)是否为字符设备
S_ISBLK(st_mode)是否为块设备
S_ISFIFO(st_mode)是否为FIFO
S_ISLNK(st_mode)是否为符号链接
S_ISSOCK(st_mode)是否为套接字

示例:

if (S_ISREG(st.st_mode)) printf("普通文件");

7.2 获取文件权限

st_mode的低9位(或12位,含setuid等)表示权限。常用宏:

含义
S_IRUSR/S_IRGRP/S_IROTH读权限
S_IWUSR/S_IWGRP/S_IWOTH写权限
S_IXUSR/S_IXGRP/S_IXOTH执行权限
S_ISUID/S_ISGIDsetuid / setgid 位
S_ISVTX粘滞位

权限可像ls -l那样输出,通常用掩码运算。

八、标准IO与文件IO的互转

在Linux系统编程中,标准IO(FILE*流)和文件IO(文件描述符)两套接口各有优势:标准IO提供缓冲和格式化方便,文件IO更底层、更灵活。通过以下两个函数,可以在两者之间自由转换。

8.1 从文件描述符到流:fdopen

FILE *fdopen(int fd, const char *mode);

  • 功能:将一个已打开的文件描述符转换为标准IO流(FILE*)。

  • 参数

    • fd:已打开的文件描述符。

    • mode:与fopen相同的模式字符串(如"r","w","a"等),必须与fd原有的访问模式兼容

  • 返回值:成功返回FILE*指针,失败返回NULL

注意事项

  • 转换后,对流的操作(如fprintffgets)会作用于原文件描述符。

  • 关闭流(fclose)时,底层的文件描述符也会自动关闭。

  • 转换后不要再单独close(fd),避免重复关闭。

8.2 从流到文件描述符:fileno

int fileno(FILE *stream);

  • 功能:获取标准IO流底层关联的文件描述符。

  • 参数stream— 已打开的FILE*流。

  • 返回值:成功返回文件描述符(非负整数),失败返回-1

注意事项

  • 获取的文件描述符可用于系统调用(如readwritelseekfcntl等)。

  • 不要手动关闭该文件描述符,因为关闭流(fclose)时会自动关闭它。

  • 若同时使用标准IO和系统调用操作同一文件,需注意缓冲问题,必要时调用fflush确保数据一致

混合使用示例 :

// 用open创建文件,转换为流,再用fprintf格式化写入 int fd = open("test.txt", O_WRONLY | O_CREAT, 0644); FILE *fp = fdopen(fd, "w"); fprintf(fp, "Hello, world!\n"); fclose(fp); // 自动关闭底层fd // 用fopen打开文件,获取fd,再用lseek定位 FILE *fp2 = fopen("data.txt", "r+"); int fd2 = fileno(fp2); off_t size = lseek(fd2, 0, SEEK_END); printf("文件大小: %ld\n", size); fclose(fp2);

四、总结

  • fdopen:文件描述符 → 标准IO流,适合需要格式化输出或行缓冲的场景。

  • fileno:标准IO流 → 文件描述符,适合需要系统调用精细控制的场景。

  • 两者配合,既能享受标准IO的便捷,又能利用系统调用的强大功能。

关键点:转换后只关闭流(fclose),不要重复关闭文件描述符;混合使用时注意缓冲区同步。

http://www.jsqmd.com/news/539723/

相关文章:

  • CSS 命名规范:从混乱到有序的代码艺术
  • AI 模型推理的 GPU 性能调优方法
  • tcp buffer和socket buffer - 小镇
  • Seed 2.0编程能力实测:全球第7水准,Python/Java代码生成实操
  • 迷宫传送[最短路径]
  • 集合对象的绑定
  • 在Vscode中使用Claude code(接智普或KIMI)
  • MCP 服务开发笔记
  • Javascript提高:JavaScript Promise 超通俗解释-由Deepseek产生
  • 别再死记ResNet结构了!用PyTorch手把手复现ResNet34,搞懂残差连接为什么能解决‘退化’问题
  • 2026想申港大本科?专业港大本科申请中介推荐(附联系方式) - 品牌2026
  • C++的std--ranges适配器视图元素修改与原数据可变性在算法中的保证
  • AI 开发实战:异常处理怎么设计,AI 才能帮你真正找出薄弱点
  • CI2451实战指南:一款2.4G无线SoC芯片,如何让遥控玩具和灯控设计更简单?
  • 设置Linux命令行提示符shell prompt的前缀颜色,区分命令和输出结果(重连、重启都不会消失)
  • LuckyLilliaBot实战指南:从零构建NTQQ机器人系统
  • 天梯赛L2题解(029-032)
  • 像素幻梦创意工坊实战:为Unity游戏项目批量生成像素资源包
  • Markdown Viewer浏览器插件:快速预览Markdown文档的终极指南
  • 拖拽生成!这款编辑器做到了!告别代码妥妥的!
  • 下载 | Win11 25H2 官方正式版ISO映像!(3月更新、消费者版/专业版、商业版/企业版、26200.8037)
  • CSS 渐变的高级应用:色彩的流动艺术
  • 保姆级教程:用C语言数组手算1000的阶乘,解决PTA编程题(附完整代码)
  • 2026深圳美国留学申请中介推荐,高端美国留学中介服务流程与口碑盘点 - 品牌2026
  • 如何快速掌握茉莉花插件:面向中文文献管理者的终极Zotero优化指南
  • OpenClaw QQ 插件 v0.6.0 发布:率先适配OpenClaw新版本Plugin-SDK
  • 优麦云亚马逊营销云AMC功能与作用精准解析 | 最新优惠码速领 - 麦麦唛
  • 滚动轴承故障诊断系统设计:基于凯斯西储大学数据
  • 别等 Sora 了!一代神话陨落?OpenAI 这一手“弃车保帅”我看懂了...
  • 自适应模型预测控制在无人驾驶汽车轨迹跟踪中的应用