news 2026/9/13 9:59:21

【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/9/12 5:39:45

孩子近视度数一路狂飙?看看是不是这些原因

‍  孩子近视后&#xff0c;家长最揪心的莫过于看着度数一次次复查都往上涨&#xff0c;明明已经叮嘱了少看电子屏幕&#xff0c;也简单做了护眼措施&#xff0c;却还是挡不住度数“狂飙”。很多家长把原因归结为户外活动少、用眼环境差等常见问题&#xff0c;却忽略了那些藏…

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

2026 年 Google Ads 广告投放风险点:为什么需要指纹浏览器?

随着 2026 年的到来&#xff0c;Google Ads 的风控已经从传统的 IP 检查升级为更高级的行为分析系统。普通的代理 IP 或单纯的浏览器操作&#xff0c;已经难以完全规避风险。账户封号、广告限流、审核延迟等问题&#xff0c;越来越频繁地发生在广告投放团队中。本文将从 Google…

作者头像 李华
网站建设 2026/9/4 7:04:19

阿里2026版Spring全家桶高级笔记

不知道各位Java好大哥们闲的时候会不会去关注Spring目前的官网&#xff0c;你会发现他的slogan是: Spring makes Java Simple。它让Java的开发变得更加简单。某种意义上来说&#xff1a;是Spring成就了Java&#xff01;但随之而来的就是&#xff1a;由他之后诞生出来的各种组件…

作者头像 李华
网站建设 2026/9/12 6:40:52

悬鉴与“养护人叙事环”的建构:算法治理的微观政治学

悬鉴与“养护人叙事环”的建构&#xff1a;算法治理的微观政治学笔者&#xff1a;岐金兰摘要当代算法治理陷入了“批判愈多&#xff0c;治理愈空”的实践困境。其根源在于主流范式困于“主体-客体”框架&#xff0c;将“算法”视为待规制的静态客体&#xff0c;而系统性忽视了算…

作者头像 李华
网站建设 2026/9/5 18:57:36

基于深度学习的图像风格迁移系统[python]-计算机毕业设计源码+LW文档

摘要&#xff1a;图像风格迁移作为计算机视觉和图形学中的热门研究方向&#xff0c;旨在将一幅图像的艺术风格应用到另一幅图像的内容上&#xff0c;创造出具有特定风格的新图像。本文深入探讨了基于深度学习的图像风格迁移技术&#xff0c;阐述了其基本原理、常见方法&#xf…

作者头像 李华