news 2026/10/19 11:59:13

linux应用——文件IO和标准IO

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
linux应用——文件IO和标准IO

我们都知道,linux下一切皆文件的概念,那么具体的运行机制是如何的呢,为什么通过操作文件即可操作对应的硬件,而具体的过程如下:

Linux 通过 VFS(虚拟文件系统)将硬件设备抽象为文件接口。当用户通过文件描述符(fd)发起系统调用时,内核通过 fd 找到对应的struct file和inode,从 inode 中提取设备号,并在内核驱动表中找到已注册的struct file_operations。随后,内核调用该结构体中对应的函数(如.read/.write),由该函数通过驱动配置操作硬件控制器进行数据收发。硬件本身的初始化由驱动加载或open时完成。

因为有这么一整套完整的运行机制,所以内核就只需向外暴露一个文件的接口供给用户使用就可以,那么我们接下来就具体说一说对应的文件的操作。

一.文件IO

1.介绍

IO自如其名 指input输入和output输出

文件IO:是操作系统内核直接提供的、基于“文件描述符(fd)”的、无用户态缓冲区的底层输入输出接口,在Linux下通常指POSIX(可移植操作系统接口)标准的系统调用

他是直接基于文件描述符(fd)的接口,是用户能触及内核的最短路径(无用户态缓存),所以实时性会很强,我们在需要高实时性和更底层的操作时会用到很多,比如网络编程。

他有几个特点

  • 不提供缓冲机制,每次读写操作都引起系统调用
  • 核心概念是文件描述符(fd)
  • 访问各种类型文件
  • Linux下,标准IO基于文件IO实现

2.文件描述符(fd)

文件描述符:文件描述符是一个非负整数(如 3, 4, 5...),它是操作系统内核为了高效管理已被打开的文件,而分配给进程的一个索引号。

每一个打开的文件都对应一个文件描述符,文件IO就是通过这个描述符完成的

文件描述符是从0开分配的,依次递增

0, 1, 2表示标准输入,标准输出,错误

3.文件打卡与关闭

下面我们来深入接触一下系统提供的文件IO的函数接口

#include <fcntl.h>

int open(const char *pathname, int flags);

int open(const char *pathname, int flags, mode_t mode);

第一个参数pathname是文件的路径名称

第二个参数flags是指对文件的操作类型,比如只读,只写等

第三个参数是对文件的权限管理(只有在新建文件时有效)

这里插入一下,在linux中通过 ll 命令我们可以查看文件的详细信息,如下

-rw-rw-r--1 kickpi kickpi 0 Jul 31 19:37 test.txt

我们可以看到对于test.txt文件的第一列-rw-rw-r--有此文件的类型,权限信息等,

-rw-:这几个从左到右分别是指文件类型,和用户组(user)权限,r:读取,w:写入 ,最后一个是执行权限(-就是没有执行权限,x就是有执行权限)。

rw-:这几个是所属组(Group)权限,可读可写不可执行

r--:这几个是其他用户权限

我们经常会看到给文件赋予权限的命令chmod 666 test.txt就是改变这几个参数,666对应用户组,所属组合其他用户组。至于数字,比如说用户组rw-就是按二进制来算,-是0,w是010对应2,r是100对应4 ,所以这个数就是0+2+4=6.其他同理。

好了,接着我们回归整体,介绍文件IO的操作

详细说一说第二个参数的flags使用,使用多个参数可以用 | 符号结合

O_RDONLY以只读方式打开文件。
O_WRONLY以只写方式打开文件。
O_RDWR以读写方式打开文件。

O_CREAT

如果文件不存在则创建它。
使用此标志时,必须提供第三个参数mode来指定新文件的权限。
O_TRUNC如果文件已存在且以写方式打开,则将其长度截断为0(清空内容)。
O_APPEND以追加方式打开。每次执行write写入时,数据都自动添加到文件末尾。
O_EXCL与O_CREAT同时使用时,如果文件已存在则打开失败。这可用于测试文件是否存在,且该操作是原子的。
O_NONBLOCK以非阻塞方式打开文件。用于设备文件、FIFO或Socket等。
如果资源不可用,open调用会立即返回,而不会让程序一直等待。
O_SYNC使每次write操作同步进行,即等待物理I/O操作完成后才返回。
这会确保数据被写入磁盘,但会降低性能。
O_NOCTTY如果pathname指向一个终端设备,则不会将该设备分配为进程的控制终端。
O_DIRECTORY如果pathname不是目录,则open调用失败。
O_NOFOLLOW

如果pathname是一个符号链接,则open调用失败

关闭文件的函数

#include <unistd.h>

int close(int fd);

参数fd是文件描述符。成功时返回0,错误时返回-1。

下面给出示例代码:

// 示例代码:使用文件IO打开、创建、关闭文件 #include <stdio.h> // 标准输入输出头文件 #include <unistd.h> // POSIX操作系统API头文件,包含close函数 #include <errno.h> // 错误号定义头文件 #include <fcntl.h> // 文件控制选项头文件,包含open函数和标志 int main() { // 打开(或创建)文件"./test.txt",以读写模式,如果文件不存在则创建,权限为0666 int fd = open("./test.txt", O_RDWR | O_CREAT, 0666); // 检查文件描述符是否有效(open失败返回-1) if (fd == -1) { // 打印错误信息 printf("%s", strerror(errno)); return -1; // 返回错误代码 } // 打印成功打开的文件描述符值 printf("fd = %d\n", fd); // 关闭文件描述符,释放资源 close(fd); return 0; // 程序正常退出 }

输出结果如下
fd=3

4.文件的写入,读取等

#include <unistd.h>

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

这是文件的写入,其中buf是写入数据的缓存区,count是写入数据的大小

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

这是文件的读取,其中buf是接受数据的缓存区,count是读取数据大小,count要小于buf。其次是读取的时候要留一位给换行符。

读取和写入都设计要一个概念就是文件偏移量后移,就是指无论你写入数据还是读取数据,都会把指向文件内容的位置移动到最后一个处理的字符那里,所以要使用文件定位函数

#include <unistd.h>

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

第三个参数whence是锁定开始移动的位置,第二个参数offset是移动多少字节,这个函数可以帮助我们调整目前文件内容所在的位置。下面是第三个参数的具体值和含义

SEEK_SET0文件开头绝对定位。offset必须 >= 0。把书签直接插到指定的页数。
SEEK_CUR1当前位置相对定位。offset可正可负。书签从当前页开始,往前或往后翻指定的页数。
SEEK_END2文件末尾相对定位。offset通常为负数(往后退),也可以为正数(制造空洞)。书签从最后一页的末尾开始往前退。

看代码

#include <stdio.h>

#include <unistd.h>

#include <errno.h>

#include <fcntl.h>

int main(){

int fd = open("./test.txt",O_RDWR|O_CREAT,0666);

char buf[12] = {0};

if(fd==-1)

{

printf("%s",strerror(errno));

return -1;

}

if(write(fd,"hello world",sizeof("hello world"))<0){

printf("%s",strerror(errno));

return -1;

}

lseek(fd, 0, SEEK_SET);

int ret = read(fd,buf,sizeof(buf));

if(ret<=0){

printf("%s",strerror(errno));

return -1;

}

printf("fd=%d\n",fd);

printf("buf=%s\n",buf);

close(fd);

return 0;

}

输出

$ make run
fd=3
buf=hello world

这就是完整的打开,写入,调整位置,读取,关闭文件的流程

了解这些就差不多了,还有一些其他的函数有特定的功能,我放在文章最后的链接里,可以自行查看

二.标准IO

标准IO和文件IO是有区别的,他是基于文件IO用ANSI C标准进行定义的,加入了用户缓存区这一环节,正是有这个缓存机制,减少了系统的调用环节,能实现更高的效率,c语言中的stdio.h就是其标准库。

标准IO的操作是用一个FILE结构体来存放文件信息的,他会将信息先存入用户态缓存区,到达一定的数据量后再推送出去,这样的操作就像流操作,所以标准IO又被称为 流 。如下FILE结构体的定义:

#ifndef _FILE_DEFINED

struct _iobuf {

char *_ptr;

int _cnt;

char *_base;

int _flag;

int _file;

int _charbuf;

int _bufsiz;

char *_tmpfname;

};

typedef struct _iobuf FILE;

下面我们说一说关于多出来的这个缓存区

缓冲区不是虚无缥缈的概念,它就是 C 库(glibc)为每个FILE *流在用户态内存(堆上)分配的一块连续的字符数组(比如 8192 字节)。

当使用标准IO的写入函数fwrite时,会先将写入的数据存入缓存区,达到一定条件时,才会调用系统IO函数来进行系统调度,这时候有几种机制,有几个指针

  • _IO_write_base:指向缓冲区的起始地址。

  • _IO_write_ptr:指向当前可写入的位置。数据从这里开始存放。

  • _IO_write_end:指向缓冲区的末尾地址

模式 场景 解释

全缓冲(_IOFBF)_IO_write_ptr == _IO_write_end(写满)
或者程序正常exit/fclose
满了才调用write。默认用于普通磁盘文件。
行缓冲(_IOLBF)

写入的数据里包含\n(换行符)
或者写满

遇到回车就调用write。默认用于终端(stdout)。
注意:如果缓冲区满了但没换行,也会刷。
无缓冲(_IONBF)_IO_write_ptr根本不用(缓冲区长度为 0)每次fwrite直接调用write,数据不经过用户态缓存。默认用于stderr。

stdin/stdout默认是行缓冲
stderr没有缓冲

1.文件的打开关闭

FILE *fopen (const char *path, const char *mode);

path是文件路径和文件名

mode是和文件IO一样指定打开文件的形式

模式字符串文件不存在时文件存在时读写位置(偏移量)核心用途
"r"(只读)报错返回 NULL正常打开文件开头必须文件已存在,只读不写(如读取配置文件)。
"w"(只写)创建新文件清空为 0 字节(内容全丢!)文件开头从头开始写,覆盖旧内容(危险操作,类似O_TRUNC)。
"a"(追加)创建新文件保留原有内容文件末尾所有写入的数据自动追加到文件末尾(类似O_APPEND,不能回头改旧数据)。

在基础后面加上+ 表示可读可写

"r+"报错返回 NULL正常打开(不清空)文件开头用于“修改现有文件”的场景(读旧数据,写新数据覆盖)。
注意:读写切换时必须fseek或fflush,否则容易混乱。
"w+"创建新文件清空为 0 字节文件开头用于“从头创建并读写”。相当于先清空,再读写。
"a+"创建新文件保留原有内容文件末尾(读时从开头读,写时强制末尾)读可以从开头看,但写永远跳到末尾追加

还有一个"b"参数 ,他是为了阻止 C 库在\n和\r\n之间做多余的事,可以保证读取写入的正确性

在windows系统中\r 代表回到行首 \n代表向下平移到下一行,\r\n结合就是到下一行行首,如果没用参数b,windows系统下可能对\n做处理把他转换成\r\n,会引起歧义。linux只有\n不会引起歧义。做移植的时候就必须加这个参数了

int fclose(FILE *stream)

成功返回0,失败返回EOF(-1)

2.读写文件操作

读取:

#include <stdio.h>

int fgetc(FILE *stream);

int getc(FILE *stream); //宏

int getchar(void);

fgetc是读取流的一个字符,

getc也是读取流的一个字符,但是他是宏

getchar是获取输入的字符与fgetc(stdin)一样

char *fgets(char *s, int size, FILE *stream);

这个是读取整行数据的

写入:

int fputc(int c, FILE *stream);

int putc(int c, FILE *stream);

int putchar(int c);

  • 成功时返回写入的字符;出错时返回EOF(-1)
  • 这几个函数都是写入单个字符的

int puts(const char *s); int fputs(const char *s, FILE *stream);

  • puts将缓冲区s中的字符串输出到stdout,并追加’\n’
  • fputs将缓冲区s中的字符串输出到stream,不追加 ‘\n’

再介绍这两个函数,高效读写:

size_t fread(void *ptr, size_t size, size_t n, FILE *fp);

size_t fwrite(const void *ptr, size_t size, size_t n, FILE *fp);

参数含义通俗理解
ptr内存缓冲区指针读取时:数据放这里(fread);写入时:数据从这里取(fwrite)。
size每个元素的大小(字节数)比如你要存int,这里就填sizeof(int);要存char,就填1。
nmemb要读/写的元素个数你想一次性处理多少个元素。
stream文件流指针fopen返回的FILE*。

fread和fwrite是标准I/O中“批量数据搬运工”。相比于fgetc(一次一个字符)和fputs(一次一行),它们直接操作内存块,非常适合读写二进制数据(如结构体、数组、图片数据)或大块文本。

接着补充对于文件内容位置操作的函数

函数原型移动基准返回值是否清除feof/ferror是否支持超大文件 (>2GB)
rewindvoid rewind(FILE *fp);固定到开头无 (void)是(隐式清除)是(内部使用fsetpos或 64位偏移)
fseekint fseek(FILE *fp, long offset, int whence);SEEK_SET/CUR/END+ 任意偏移量成功0,失败-1否(标志保留)否(long在32位系统限制2GB)
ftelllong ftell(FILE *fp);获取当前位置成功返回偏移量,失败返回-1L不影响标志否(同样受long大小限制)
fgetposint fgetpos(FILE *fp, fpos_t *pos);获取当前位置(存入pos)成功0,失败非0不影响标志是(fpos_t设计支持大文件)
fsetposint fsetpos(FILE *fp, const fpos_t *pos);设置到pos指定的位置成功0,失败非0否(标志保留)是(fpos_t设计支持大文件)

到这里就差不多了,这里在附上基本操作的代码

#include <stdio.h>

#include <unistd.h>

#include <errno.h>

#include <fcntl.h>

int main(){

FILE *fp=fopen("test.txt","r+");

if(fp==NULL){

perror("fopen");

return -1;

}

fputs("wsyunnand",fp);

rewind(fp);

int buf;

buf=fgetc(fp);

if(buf!=EOF){

printf("read:%c\n",buf);

}

char read[100] = {0};

int i=fread(read,1,100,fp);

printf("read all:%d",i);

printf("read all:%s",read);

fclose(fp);

return 0;

}

文件内容是这个

wsyunnand
yeyeyessssssssssss
ddddddddd

输出

$ make run
read:w
read all:39read all:syunnand
yeyeyessssssssssss
ddddddddd

最后把一些其他标准IO和文件IO的一些其他函数放在这个链接:
linux标准IO和文件IO函数-CSDN博客

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/19 11:56:19

逆序与冒泡排序:双指针原理、稳定性与优化详解

带过几个新入行的同事之后&#xff0c;我发现一个很有意思的现象&#xff1a;逆序和冒泡排序这两个东西&#xff0c;几乎每个学编程的人都写过&#xff0c;但真到了面试现场或者代码评审里&#xff0c;能一次写对并且把原理讲清楚的人&#xff0c;比例其实不高。逆序是把一个数…

作者头像 李华
网站建设 2026/10/19 11:55:18

xxx信息RAC 6200端口漏洞_分析

1.分析及解决方案概述分析原因 通过对现有信息的分析&#xff0c;可以看到主机漏洞是由于RAC的ons引起。 ons使用6100、6200端口进行集群内部通信。解决方案 针对现有情况&#xff0c;建议如下&#xff1a; 1&#xff09;启用防火墙&#xff0c;确保只有RAC集群节点机器可以访问…

作者头像 李华
网站建设 2026/10/19 11:46:25

OpenClaw 自托管部署全指南:从环境配置到日常运维

1. OpenClaw 部署前&#xff0c;先想清楚它到底帮你解决什么问题OpenClaw 部署这个关键词&#xff0c;最近在自托管圈子里出现得挺频繁。乍一看又是一个开源 AI 助手项目&#xff0c;但真要把这坨东西跑在自己机器上&#xff0c;跟用别人搭好的在线服务完全是两码事。它本质上是…

作者头像 李华
网站建设 2026/10/19 11:45:56

本体驱动AI原生应用开发:从提示词堆砌到知识结构化

1. 从“提示词堆砌”到“知识结构化”&#xff1a;为什么需要本体驱动过去一年我接触了不少AI原生应用项目&#xff0c;发现一个很普遍的现象&#xff1a;大部分开发者把精力花在调提示词、换模型、加工具调用上&#xff0c;但真正决定一个AI应用能不能从Demo走向生产环境的&am…

作者头像 李华
网站建设 2026/10/19 11:41:31

树莓派开源技术手册实战:从碎片笔记到可持续知识工程

开篇先说实话&#xff1a;“Pi 蓝皮书”这个名字听起来挺唬人&#xff0c;但拆开来看&#xff0c;它就是一个开源的树莓派技术手册项目。我最初做这件事的动机很简单——树莓派的资料太散了。今天翻论坛找到一段踩坑记录&#xff0c;明天从某个中文教程复制一段代码&#xff0c…

作者头像 李华