news 2026/7/16 13:31:39

【Linux系统编程】基础IO:从文件本质到系统操作

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Linux系统编程】基础IO:从文件本质到系统操作


❤️@燃于AC之乐 来自重庆 计算机专业的一枚大学生
专注 C/C++ Linux 数据结构 算法竞赛 AI
🏞️志同道合的人会看见同一片风景!

👇点击进入作者专栏:

《算法画解》

《linux系统编程》

《C++》

🌟《算法画解》算法相关题目点击即可进入实操🌟
感兴趣的可以先收藏起来,请多多支持,还有大家有相关问题都可以给我留言咨询,希望希望共同交流心得,一起进步,你我陪伴,学习路上不孤单!

文章目录

  • 📖 前言
  • 📁 一、深入理解文件
    • 1.1 文件的独特认识
    • 1.2 文件操作的认识
  • 💻 二、C文件接口使用
    • 2.1 文件的打开与路径
    • 2.2 文件的写入操作
    • 2.3 文件的读取操作
    • 2.4 输出信息到显示器的多种方法
    • 2.5 标准输入输出流
  • 🚗 三、系统文件I/O
    • 3.1 标志位传递方法
    • 3.2 系统级文件写操作
    • 3.3 系统级文件读操作
    • 3.4 系统文件I/O接口详解
    • 3.5 open函数返回值与文件描述符
    • 3.6 综合示例:文件复制工具
  • 🎓 总结

📖 前言

在计算机科学的核心领域,文件系统扮演着承上启下的关键角色——它既是应用程序数据存储的物理载体,也是操作系统资源管理的抽象接口。当我们谈及文件操作时,很多人会停留在C语言库函数的表面应用,却鲜少深入探究其背后的系统机制。从"一切皆文件"的Linux设计哲学,到文件描述符的精妙抽象,再到系统调用与库函数的本质区别,文件I/O的世界远比表面看起来更加深邃。本文将从底层原理到实践应用,层层剖析文件系统的奥秘,带你穿越抽象屏障,直抵操作系统内核,建立对文件I/O的完整认知体系,为后续深入系统编程奠定坚实基础。

📁 一、深入理解文件

1.1 文件的独特认识

1.狭义:
文件在磁盘中;磁盘是永久性的存储介质,从而文件在磁盘上的存储是永久的;磁盘是外设(输入/输出设备);对磁盘上文件的操作本质是对文件的操作,也都是对外设的输入输出:IO。

2,广义:
“Linux下一切皆文件”是核心抽象设计: 将硬件、进程、网络等资源统一抽象为具有路径、可通过标准文件操作(open/read/write/close)访问的对象。

主要体现: 设备文件(/dev/):磁盘(/dev/sda)、终端(/dev/tty)、输入设备等。
进程信息(/proc/):每个进程以目录形式暴露其状态、内存、命令行参数。

系统控制(/sys/):内核参数、设备属性可通过文件读写动态调整。

实现原理: 内核通过虚拟文件系统(VFS) 层统一接口,驱动或子系统实现 read、write 等标准操作,将底层操作转化为文件语义。

1.2 文件操作的认识

文件 = 属性 + 内容
因此,所有对文件的操作本质是对文件内容操作文件属性操作
从系统角度看,对文件的操作本质是进程对文件的操作;而磁盘的管理者是操作系统
文件的读写本质不是通过C语言/C++的库函数(语言层)来实现操作的,而是通过通过文件相关的系统调用接口来实现的,库函数是为了用户提供方便。
库函数也都封装了底层的文件系统调用;并且OS要把被打开的文件管理起来。

💻 二、C文件接口使用

2.1 文件的打开与路径

示例代码:打开文件

#include<stdio.h>intmain(){FILE*fp=fopen("myfile","w");if(!fp){printf("fopen error!\n");}while(1);// 为了让进程持续运行,方便查看信息fclose(fp);return0;}

问题:打开的myfile文件在哪个路径下?

答案: 在程序的当前工作目录下

如何查看程序的当前路径?
在Linux系统中,可以通过查看/proc/[进程id]目录来获取进程的相关信息:

首先获取进程ID:

psaux|grephello 查看进程信息:
ls/proc/[进程id]-l 关键符号链接:

cwd:指向当前进程运行目录的符号链接(current working directory)

exe:指向启动当前进程的可执行文件的完整路径

理解:
当进程创建时,它会继承父进程的当前工作目录。当进程使用相对路径打开文件时,操作系统会将该相对路径与进程的当前工作目录拼接,形成完整的文件路径。

2.2 文件的写入操作

示例代码:hello.c写文件

#include<stdio.h>#include<string.h>intmain(){// 以写入模式("w")打开文件,如果文件不存在则创建,存在则清空FILE*fp=fopen("myfile","w");if(!fp){printf("fopen error!\n");return1;}constchar*msg="hello bit!\n";intcount=5;// 循环写入5次while(count--){// fwrite参数:数据指针,每个元素大小,元素个数,文件指针fwrite(msg,strlen(msg),1,fp);}fclose(fp);return0;}

关键点:

"w"模式:如果文件存在则清空内容,不存在则创建

fwrite()函数将数据写入文件

写入完成后必须调用fclose()关闭文件,释放资源

2.3 文件的读取操作

示例代码:hello.c读文件

#include<stdio.h>#include<string.h>intmain(){// 以只读模式("r")打开文件FILE*fp=fopen("myfile","r");if(!fp){printf("fopen error!\n");return1;}charbuf[1024];constchar*msg="hello bit!\n";while(1){// fread参数:缓冲区,每个元素大小,元素个数,文件指针// 注意:这里使用strlen(msg)作为读取大小,可能不是最优选择ssize_ts=fread(buf,1,strlen(msg),fp);if(s>0){buf[s]='\0';// 添加字符串结束符printf("%s",buf);}// 检查是否到达文件末尾if(feof(fp)){break;}}fclose(fp);return0;}

改进版:实现简单cat命令

#include<stdio.h>#include<string.h>intmain(intargc,char*argv[]){if(argc!=2){printf("Usage: %s <filename>\n",argv[0]);return1;}FILE*fp=fopen(argv[1],"r");if(!fp){printf("fopen error!\n");return2;}charbuf[1024];while(1){// 改进:使用sizeof(buf)确保读取完整缓冲区ints=fread(buf,1,sizeof(buf),fp);if(s>0){buf[s]='\0';printf("%s",buf);}if(feof(fp)){break;}}fclose(fp);return0;}

2.4 输出信息到显示器的多种方法

#include<stdio.h>#include<string.h>intmain(){constchar*msg="hello fwrite\n";// 方法1:使用fwrite向stdout写入fwrite(msg,strlen(msg),1,stdout);// 方法2:使用printf函数printf("hello printf\n");// 方法3:使用fprintf向stdout写入fprintf(stdout,"hello fprintf\n");// 方法4:使用puts函数(自动添加换行符)puts("hello puts");// 方法5:使用putchar逐个字符输出constchar*str="hello putchar\n";while(*str){putchar(*str++);}return0;}

2.5 标准输入输出流

标准流的声明

#include<stdio.h>// 这三个是C语言默认打开的标准流externFILE*stdin;// 标准输入流(默认连接到键盘)externFILE*stdout;// 标准输出流(默认连接到显示器)externFILE*stderr;// 标准错误流(默认连接到显示器)

标准流的特点 自动打开:程序启动时自动打开,无需手动fopen

不可关闭:尝试关闭这些流可能导致未定义行为

可重定向:可以通过shell重定向到文件或其他设备

./program>output.txt# 标准输出重定向到文件./program2>error.txt# 标准错误重定向到文件./program<input.txt# 标准输入重定向从文件读取
模式描述文件存在文件不存在读写位置
“r”只读打开成功打开失败文件开头
“r+”读写打开成功打开失败文件开头
“w”只写清空内容创建新文件文件开头
“w+”读写清空内容创建新文件文件开头
“a”追加打开成功创建新文件文件末尾
“a+”读和追加打开成功创建新文件读-开头,写-末尾

文件位置操作函数:

#include<stdio.h>intmain(){FILE*fp=fopen("test.txt","r+");if(!fp)return1;// ftell:获取当前文件位置longpos=ftell(fp);printf("Current position: %ld\n",pos);// fseek:移动文件位置指针// SEEK_SET:从文件开头// SEEK_CUR:从当前位置// SEEK_END:从文件末尾fseek(fp,10,SEEK_SET);// 移动到离文件开头10字节处fseek(fp,-5,SEEK_END);// 移动到离文件末尾5字节处// rewind:将文件位置指针重置到文件开头rewind(fp);fclose(fp);return0;}

🚗 三、系统文件I/O

3.1 标志位传递方法

在深入系统文件I/O之前,我们需要先理解一种在系统编程中常用的技巧:通过位运算传递多个标志位。这种方法在系统文件IO接口中被广泛使用。

#include<stdio.h>// 使用二进制位定义标志位(注意:这里应该使用16进制或8进制)#defineONE0x01// 0000 0001#defineTWO0x02// 0000 0010#defineFOUR0x04// 0000 0100#defineEIGHT0x08// 0000 1000voidfunc(intflags){// 使用位与(&)运算检查特定标志位是否被设置if(flags&ONE)printf("flags has ONE! ");if(flags&TWO)printf("flags has TWO! ");if(flags&FOUR)printf("flags has FOUR! ");if(flags&EIGHT)printf("flags has EIGHT! ");printf("\n");}intmain(){// 测试单一标志位func(ONE);// 输出: flags has ONE!// 测试另一个单一标志位func(FOUR);// 输出: flags has FOUR!// 测试多个标志位组合(使用位或|运算)func(ONE|TWO);// 输出: flags has ONE! flags has TWO!// 测试三个标志位的组合func(ONE|FOUR|EIGHT);// 输出: flags has ONE! flags has FOUR! flags has EIGHT!return0;}

关键点解析:

使用二进制位表示不同的标志位,每个位代表一个独立的选项

通过|(位或)运算组合多个标志位

通过&(位与)运算检查特定标志位是否被设置

这种方法可以高效地传递多个布尔选项,节省内存和参数传递开销

3.2 系统级文件写操作

#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>intmain(){// 设置文件创建时的权限掩码// umask(0)表示不清除任何权限位umask(0);// 打开文件:O_WRONLY只写模式 | O_CREAT如果不存在则创建// 第三个参数0644表示文件权限:rw-r--r--intfd=open("myfile",O_WRONLY|O_CREAT,0644);if(fd<0){perror("open");// 打印系统错误信息return1;}intcount=5;constchar*msg="hello bit!\n";intlen=strlen(msg);// 循环写入5次while(count--){// write系统调用:// fd: 文件描述符(后面详细介绍)// msg: 要写入数据的缓冲区首地址// len: 期望写入的字节数// 返回值: 实际写入的字节数(可能小于len)ssize_tret=write(fd,msg,len);if(ret<0){perror("write");break;}}close(fd);// 关闭文件描述符return0;}

3.3 系统级文件读操作

#include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>#include<string.h>intmain(){// 以只读模式打开文件intfd=open("myfile",O_RDONLY);if(fd<0){perror("open");return1;}constchar*msg="hello bit!\n";charbuf[1024];while(1){// read系统调用:// fd: 文件描述符// buf: 读取数据的缓冲区// strlen(msg): 期望读取的字节数// 返回值: 实际读取的字节数(0表示文件末尾,-1表示错误)ssize_ts=read(fd,buf,strlen(msg));if(s>0){buf[s]='\0';// 确保字符串正确结束printf("%s",buf);}elseif(s==0){// 到达文件末尾break;}else{// 读取错误perror("read");break;}}close(fd);return0;}

3.4 系统文件I/O接口详解

open函数

#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>// open函数的两种形式intopen(constchar*pathname,intflags);intopen(constchar*pathname,intflags,mode_tmode);

参数解析:

pathname: 要打开或创建的目标文件路径

flags: 打开文件的标志位(使用位或运算组合)

常用flags标志位:

// 基本访问模式(必须且只能指定一个)O_RDONLY// 只读打开O_WRONLY// 只写打开O_RDWR// 读写打开// 可选标志位(可以组合使用)O_CREAT// 文件不存在时创建O_APPEND// 追加模式(总是在文件末尾写入)O_TRUNC// 如果文件存在且为普通文件,将其长度截断为0O_EXCL// 与O_CREAT一起使用,如果文件存在则失败O_NONBLOCK// 非阻塞模式O_SYNC// 同步写入(数据立即写入磁盘)mode:当使用O_CREAT创建新文件时,指定文件的访问权限

权限模式说明:

权限使用8进制表示,如0644

0644表示:所有者rw-(6),组用户r–(4),其他用户r–(4)

实际权限 = mode & ~umask(umask为权限掩码)

返回值:

成功:返回新的文件描述符(非负整数)

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

其他系统文件I/O接口

#include<unistd.h>// 写入数据ssize_twrite(intfd,constvoid*buf,size_tcount);// 读取数据ssize_tread(intfd,void*buf,size_tcount);// 关闭文件描述符intclose(intfd);// 移动文件读写位置off_tlseek(intfd,off_toffset,intwhence);// whence取值:// SEEK_SET:从文件开头偏移// SEEK_CUR:从当前位置偏移// SEEK_END:从文件末尾偏移

3.5 open函数返回值与文件描述符

系统调用 vs 库函数:

特性系统调用)库函数)
定义操作系统内核提供的接口编程语言标准库提供的函数
示例open, read, write, closefopen, fread, fwrite, fclose
位置位于操作系统内核空间位于用户空间库中
开销较大(需要上下文切换)较小(在用户空间执行)
封装关系库函数通常封装系统调用系统调用是更底层的接口

文件描述符(File Descriptor)

intmain(){// 打开一个文件,返回文件描述符intfd=open("test.txt",O_RDONLY);printf("File descriptor: %d\n",fd);// 通常为3// 标准文件描述符(始终存在)printf("stdin fd: %d\n",STDIN_FILENO);// 0printf("stdout fd: %d\n",STDOUT_FILENO);// 1printf("stderr fd: %d\n",STDERR_FILENO);// 2close(fd);return0;}

文件描述符的本质:

数组索引:文件描述符是进程文件描述符表(file descriptor table)的索引

进程私有:每个进程有自己的文件描述符表

从0开始:0,1,2已被标准输入、输出、错误占用

资源管理:操作系统通过文件描述符管理打开的文件

文件描述符表结构:

// 伪代码表示进程的文件描述符表structprocess_fd_table{structfile_descriptor*entries[1024];// 通常1024个条目intnext_free_fd;// 下一个可用的文件描述符};// 当open成功时intopen_file(constchar*pathname,intflags){// 1. 在内核中创建或找到对应的文件对象// 2. 在进程的文件描述符表中分配一个空闲位置// 3. 将文件对象指针存储到该位置// 4. 返回该位置的索引(文件描述符)}

3.6 综合示例:文件复制工具

#include<stdio.h>#include<stdlib.h>#include<fcntl.h>#include<unistd.h>#include<string.h>#defineBUFFER_SIZE4096intmain(intargc,char*argv[]){if(argc!=3){fprintf(stderr,"Usage: %s <source> <destination>\n",argv[0]);return1;}// 打开源文件(只读)intsrc_fd=open(argv[1],O_RDONLY);if(src_fd<0){perror("open source file");return1;}// 打开目标文件(创建或截断,读写权限)intdst_fd=open(argv[2],O_WRONLY|O_CREAT|O_TRUNC,0644);if(dst_fd<0){perror("open destination file");close(src_fd);return1;}charbuffer[BUFFER_SIZE];ssize_tbytes_read,bytes_written;// 循环读取和写入while((bytes_read=read(src_fd,buffer,sizeof(buffer)))>0){bytes_written=write(dst_fd,buffer,bytes_read);if(bytes_written!=bytes_read){perror("write");close(src_fd);close(dst_fd);return1;}}if(bytes_read<0){perror("read");}// 关闭文件描述符close(src_fd);close(dst_fd);printf("File copied successfully!\n");return0;}

🎓 总结

本文系统性地构建了对Linux文件操作的全景认知。从“一切皆文件”的核心抽象哲学出发,揭示了文件不仅是数据存储单元,更是操作系统统一管理各类资源的接口。通过对比C语言标准库函数与系统调用,我们深入理解了文件操作从用户空间到内核空间的完整流程。文件描述符机制作为进程与文件之间的桥梁,完美体现了操作系统的资源管理智慧。无论是基础的读写操作,还是高级的标志位组合使用,系统文件I/O都展现出比语言层接口更强大的灵活性和控制力。掌握这些知识,不仅是学习系统编程的基础,更是理解操作系统工作原理的关键一步。

加油!志同道合的人会看到同一片风景。
看到这里请点个赞关注,如果觉得有用就收藏一下吧。后续还会持续更新的。 创作不易,还请多多支持!

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

Qt之多线程和并发_P2

在使用多线程时需要时刻注意一点的时,多个线程在访问同一个资源时会抢夺资源,造成数据不一致,严重影响程序结果甚至崩溃。为了防止竞态条件的发生,使用多线程时需要实现线程同步,也即确保多个线程在同时使用共享资源时不会发生冲突或数据不一致。 Qt提供了互斥锁、信号量、…

作者头像 李华
网站建设 2026/7/9 18:26:12

科研党收藏!自考必备的AI论文工具 —— 千笔写作工具

你是否在论文写作中经常感到无从下手&#xff1f;选题纠结、思路混乱、查重率高、格式出错……这些难题是否让你夜不能寐&#xff1f;对于自考学生来说&#xff0c;论文不仅是毕业的关键&#xff0c;更是对学术能力的终极考验。而如今&#xff0c;有了千笔AI&#xff0c;这一切…

作者头像 李华
网站建设 2026/7/14 20:55:34

Redis与MySQL回写中的数据类型存储设计

一、前置 在 Redis 与 MySQL 数据回写场景中&#xff0c;Redis 核心定位是缓存 / 高效存储层&#xff0c;MySQL 是持久化存储层&#xff0c;数据回写通常是「Redis 更新→同步 / 异步更新 MySQL」或「MySQL 更新→刷新 Redis」。KV 设计需遵循 3 个通用原则&#xff1a; 1. 键…

作者头像 李华
网站建设 2026/7/14 20:54:28

寒假集训7——STL

P3370 【模板】字符串哈希 题目描述 如题&#xff0c;给定 N 个字符串&#xff08;第 i 个字符串长度为 Mi​&#xff0c;字符串内包含数字、大小写字母&#xff0c;大小写敏感&#xff09;&#xff0c;请求出 N 个字符串中共有多少个不同的字符串。 友情提醒&#xff1a;如…

作者头像 李华
网站建设 2026/7/14 20:12:49

智能火灾报警系统的设计

智能火灾报警系统的设计 第一章 绪论 传统火灾报警系统多依赖单一烟雾传感器&#xff0c;存在响应滞后、误报率高、无法精准定位等问题&#xff0c;难以满足现代建筑对火灾预警的及时性与可靠性需求。智能火灾报警系统融合多传感器协同、智能识别、无线通信技术&#xff0c;构建…

作者头像 李华
网站建设 2026/7/11 16:12:55

Java源码:道路救援与同城货运一体化平台

以下是一套基于Java技术栈的道路救援与同城货运一体化平台源码方案&#xff0c;涵盖系统架构、核心功能、技术实现与代码示例&#xff1a; 一、系统架构 采用微服务架构设计&#xff0c;基于Spring Boot 2.7 Spring Cloud Alibaba构建&#xff0c;将系统拆分为用户服务、订单…

作者头像 李华