尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

【Linux系统】基础IO(上)

【Linux系统】基础IO(上) 磁盘是外设输入输出设备磁盘被归类为外部设备既是输入设备如读取数据也是输出设备如写入数据。因此所有对文件的操作如打开、读取、写入本质上都是对外设的输入和输出简称IOInput/Output。IO的本质文件操作如读写磁盘数据不是直接在应用程序中完成而是通过底层硬件交互实现的IO过程。例如C语言的fopen或fwrite函数最终会转化为对外设的输入输出请求。磁盘作为计算机外设的一种既是输入设备(读取数据)也是输出设备(写入数据)。例如当保存文档时数据从内存输出到磁盘当打开文档时数据从磁盘输入到内存所有对磁盘文件的操作(创建、读取、修改、删除)本质上都是通过操作系统提供的输入输出(IO)接口完成的。总结狭义文件强调磁盘存储的永久性和硬件交互的IO本质所有操作都围绕外设进行。1.2 文件的广义理解在Linux/Unix操作系统中有一个重要的设计理念一切皆文件。这意味着•硬件设备抽象为文件键盘 → /dev/input/显示器 → /dev/tty磁盘 → /dev/sda网卡 → /dev/net•特殊文件类型块设备文件(如磁盘)字符设备文件(如终端)命名管道(FIFO)套接字(Socket)例如在Linux中向/dev/null写入数据会直接被丢弃从/dev/random读取会获取随机数通过/proc文件系统可以查看和修改内核参数这种抽象极大简化了系统编程接口开发者可以使用统一的文件操作API来访问各种资源。1.3 文件操作的分类认知文件存储的实质即使是0KB的空文件也会占用磁盘空间原因包括•文件元数据存储每个文件都需要存储文件名、创建时间、权限等属性信息•文件系统开销大多数文件系统有最小分配单元(如4KB的块大小)•目录条目文件名需要在父目录中建立对应的条目文件的组成结构文件由两个主要部分组成文件属性(元数据)文件名文件大小创建/修改/访问时间所有者/权限信息存储位置等文件内容实际存储的用户数据可以是文本、二进制、多媒体等各种形式例如使用ls -l命令可以查看文件的元数据使用cat命令可以查看文件内容。文件操作的本质所有文件操作都可以归类为•内容操作读取(read)写入(write)追加(append)截断(truncate)•属性操作重命名(rename)修改权限(chmod)更改所有者(chown)设置时间戳(touch)1.4 系统层面的文件操作进程与文件的关系在操作系统中• 文件操作的主体是进程• 每个进程维护一个文件描述符表• 通过文件描述符来引用打开的文件例如标准输入(stdin) → 文件描述符0标准输出(stdout) → 文件描述符1标准错误(stderr) → 文件描述符2对文件的操作本质是进程对文件的操作文件操作由运行中的进程发起进程通过系统调用请求操作系统执行文件任务如打开、读写。磁盘作为硬件资源由操作系统统一管理进程不能直接访问磁盘。操作系统的管理角色操作系统作为磁盘的管理者负责• 文件系统的实现(如ext4, NTFS)• 磁盘空间分配与管理• 文件缓存与IO调度• 权限控制与安全机制磁盘的管理者是操作系统操作系统如Linux内核负责磁盘的底层管理包括文件系统组织、数据存储和安全控制。用户程序不能绕过操作系统直接操作磁盘。系统调用与库函数文件操作的实际执行路径用户程序调用库函数(如fopen/fread)库函数封装系统调用(如open/read)系统调用触发内核文件系统例程内核与磁盘控制器交互完成IO例如C语言的fopen函数底层会调用open系统调用fread会调用read系统调用。这些系统调用才是真正与操作系统内核交互的接口。文件读写通过系统调用接口实现C语言或C的库函数如fopen、fprintf只是用户层封装提供便利性底层实现依赖于文件相关的系统调用接口如open打开文件、read读取数据、write写入数据。这些系统调用涉及模式切换用户态到内核态确保操作安全和高效。所有语言包括C的文件操作最终都调用系统调用因为硬件访问必须通过操作系统。2. C语言文件操作接口回顾1. 文件打开与关闭fopen()- 打开文件功能打开指定路径的文件返回文件指针FILE*。参数filename文件路径绝对或相对路径。mode打开模式如r只读、w只写、a追加等。返回值成功返回文件指针失败返回NULL。fclose()- 文件关闭功能关闭已打开的文件流释放资源。参数FILE*文件指针。返回值成功返回0失败返回EOF-1。代码语言javascriptAI代码解释#include stdio.h int main() { // 参数1: 文件名参数2: 模式r读/w写/a追加 FILE *fp fopen(example.txt, r); if (fp NULL) { // 检查是否成功打开 printf(文件打开失败\n); return 1; } printf(文件打开成功\n); fclose(fp); // 关闭文件 return 0; }2. 字符读写fgetc()/getc()- 读取单个字符功能从文件读取单个字符。参数FILE* stream。返回值成功返回读取的字符转为int文件结束或失败返回EOF。示例代码代码语言javascriptAI代码解释FILE *fp fopen(file.txt, r); if (fp) { char c fgetc(fp); // 从文件读取一个字符 while (c ! EOF) { // EOF 表示文件结束 printf(%c, c); c fgetc(fp); // 继续读取下一个字符 } fclose(fp); }注释fgetc和getc功能相同但getc可能被实现为宏。返回值是int类型可容纳EOF。fputc()/putc()- 写入单个字符功能向文件写入单个字符。参数char c待写入字符。FILE* stream文件指针。返回值成功返回写入的字符失败返回EOF。代码语言javascriptAI代码解释FILE *fp fopen(output.txt, w); if (fp) { fputc(A, fp); // 写入字符 A fclose(fp); }3. 字符串读写fgets()- 读取一行字符串功能从文件读取一行字符串。参数char* str存储读取数据的缓冲区。int n最大读取长度含结尾\0。FILE* stream。返回值成功返回str失败或文件结束返回NULL。代码语言javascriptAI代码解释FILE *fp fopen(data.txt, r); char buf[1024]; if (fp) { // 读取一行最多 sizeof(buf)-1 个字符 while (fgets(buf, sizeof(buf), fp) ! NULL) { printf(%s, buf); } fclose(fp); }注释读取到换行符或缓冲区满时停止末尾自动加\0。需配合feof()或ferror()检查结束/错误。fputs()- 写入字符串功能向文件写入字符串不含自动换行。参数const char* str字符串指针。FILE* stream。代码语言javascriptAI代码解释FILE *fp fopen(log.txt, a); if (fp) { fputs(Hello, World!\n, fp); // 写入字符串不自动加换行 fclose(fp); }注释成功返回非负整数失败返回EOF。4. 格式化读写fprintf()- 格式化写入功能按指定格式向文件写入数据。参数与printf()类似增加文件指针参数。代码语言javascriptAI代码解释FILE *fp fopen(report.txt, w); if (fp) { int num 100; fprintf(fp, 数值: %d\n, num); // 类似 printf但输出到文件 fclose(fp); }fscanf()- 格式化读取功能按指定格式从文件读取数据。参数与scanf()类似增加文件指针参数。代码语言javascriptAI代码解释FILE *fp fopen(data.txt, r); int a; char str[20]; if (fp) { fscanf(fp, %d %s, a, str); // 从文件读取整数和字符串 fclose(fp); }注释返回值是成功匹配的参数个数。5. 块数据读写fread()- 读取数据块功能从文件读取二进制数据块。参数const void* ptr存储目标地址。size_t size每个数据块大小字节。size_t nmemb数据块数量。FILE* stream。代码语言javascriptAI代码解释struct Item { char name[20]; int size; }; struct Item items[3]; FILE *fp fopen(/tmp/data.bin, rb); if (fp) { // 参数缓冲区, 每个元素大小, 元素数量, 文件指针 size_t count fread(items, sizeof(struct Item), 3, fp); if (count ! 3) { // 检查实际读取数量 printf(读取不完整\n); } fclose(fp); }注释返回实际读取的元素数量非字节数。适用于二进制文件模式含b。fwrite()- 写入数据块功能向文件写入二进制数据块。参数const void* ptr数据源地址。size_t size每个数据块大小字节。size_t nmemb数据块数量。FILE* stream。返回值成功写入的数据块数量。代码语言javascriptAI代码解释struct Item items[3] {{Linux, 5}, {C, 1}}; FILE *fp fopen(/tmp/data.bin, wb); if (fp) { fwrite(items, sizeof(struct Item), 2, fp); // 写入2个结构体 fclose(fp); }注释返回实际写入的元素数量需检查是否完整。6. 文件定位fseek()- 移动文件指针功能移动文件位置指针。参数FILE* stream。long offset偏移量字节。int whence起始位置SEEK_SET文件头、SEEK_CUR当前位置、SEEK_END文件尾。返回值成功返回0失败返回非零值代码语言javascriptAI代码解释FILE *fp fopen(large.bin, rb); if (fp) { fseek(fp, 100L, SEEK_SET); // 从文件头偏移100字节 long pos ftell(fp); // 获取当前位置 printf(当前位置: %ld\n, pos); fclose(fp); }rewind()- 重置到文件头代码语言javascriptAI代码解释rewind(fp); // 等价于 fseek(fp, 0L, SEEK_SET)7. 错误处理feof()/ferror()- 检查状态ferror()检查文件操作是否出错非零值表示错误。feof()检查是否到达文件末尾非零值表示结束。代码语言javascriptAI代码解释FILE *fp fopen(file.txt, r); if (fp) { char buf[100]; fgets(buf, sizeof(buf), fp); if (feof(fp)) { printf(已到文件末尾\n); } if (ferror(fp)) { printf(读取错误\n); } fclose(fp); }注释feof()在读到末尾后返回真ferror()检查错误标志。3. 输出信息到显示器你有哪些方法C语言通过标准I/O库提供多种输出方式均基于stdout标准输出流实现printf()最常用的格式化输出函数自动追加换行符。代码语言javascriptAI代码解释printf(Hello World\n); // 输出字符串并换行fprintf()指定输出流如stdout的格式化输出。代码语言javascriptAI代码解释fprintf(stdout, Value: %d\n, 42); // 等同于printfputs()输出字符串并自动换行。代码语言javascriptAI代码解释puts(Hello Linux); // 输出后自动换行fputs()输出字符串但不自动换行。代码语言javascriptAI代码解释fputs(No newline, stdout); // 需手动添加\nputchar()/fputc()单字符输出。代码语言javascriptAI代码解释putchar(A); // 输出字符A fputc(B, stdout); // 等同putcharfwrite()二进制数据块输出也可用于文本。代码语言javascriptAI代码解释const char *msg Binary write\n; fwrite(msg, strlen(msg), 1, stdout); // 直接写入字节流关键点所有方法均通过stdout文件指针指向显示器。4. stdin stdout stderr在C语言中stdin、stdout和stderr是三个预定义的标准I/O流它们在程序启动时由系统自动打开。这些流是C标准库的核心组成部分提供了一种标准化的输入/输出处理方式。1. 基本概念流名称文件描述符默认设备用途缓冲类型stdin0键盘标准输入行缓冲终端stdout1显示器标准输出行缓冲终端stderr2显示器标准错误输出无缓冲关键特性类型均为FILE*文件指针定义在stdio.h中生命周期与程序相同自动打开/关闭2. 底层原理(1) 声明方式代码语言javascriptAI代码解释// stdio.h 中的声明 extern FILE *stdin; extern FILE *stdout; extern FILE *stderr;(2) 与文件描述符的关系代码语言javascriptAI代码解释#include unistd.h // 验证文件描述符 printf(stdin fd: %d\n, fileno(stdin)); // 输出 0 printf(stdout fd: %d\n, fileno(stdout)); // 输出 1 printf(stderr fd: %d\n, fileno(stderr)); // 输出 2(3) 缓冲机制差异流缓冲行为典型场景stdout行缓冲遇到\n或满缓冲才输出printf(Hello\n)stderr无缓冲立即输出perror(Error)stdin行缓冲等待回车键scanf(%s, buf)缓冲验证代码代码语言javascriptAI代码解释#include stdio.h #include unistd.h int main() { // stdout 有缓冲可能不会立即显示 fprintf(stdout, This is stdout); sleep(2); // 等待2秒 // stderr 无缓冲立即显示 fprintf(stderr, \nThis is stderr); return 0; }运行结果 先等待2秒然后同时显示代码语言javascriptAI代码解释This is stderr This is stdout3. 实际应用场景(1) 输入重定向代码语言javascriptAI代码解释#include stdio.h int main() { char buf[100]; // 从stdin读取可以是键盘或重定向文件 while (fgets(buf, sizeof(buf), stdin) ! NULL) { fprintf(stdout, Read: %s, buf); } return 0; }使用方式代码语言javascriptAI代码解释# 键盘输入 $ ./program Hello Read: Hello # 文件重定向 $ ./program input.txt(2) 分离正常输出与错误代码语言javascriptAI代码解释#include stdio.h int main() { // 正常输出到stdout可被重定向 fprintf(stdout, Program started\n); // 错误输出到stderr始终显示在终端 fprintf(stderr, [ERROR] Invalid operation\n); return 0; }重定向效果代码语言javascriptAI代码解释$ ./program output.txt # stdout重定向到文件 [ERROR] Invalid operation # stderr仍在终端显示 $ cat output.txt Program started(3) 错误诊断代码语言javascriptAI代码解释#include stdio.h #include errno.h int main() { FILE *fp fopen(nonexist.txt, r); if (fp NULL) { // 输出到stderr无缓冲确保及时显示 perror(fopen failed); fprintf(stderr, Error code: %d\n, errno); } return 0; }输出代码语言javascriptAI代码解释fopen failed: No such file or directory Error code: 24. 高级用法(1) 流重定向代码语言javascriptAI代码解释#include stdio.h int main() { // 临时重定向stdout到文件 FILE *log fopen(log.txt, w); stdout log; // 重定向 printf(This goes to log.txt); // 写入文件 fclose(log); stdout fdopen(1, w); // 恢复默认 return 0; }(2) 多流协同代码语言javascriptAI代码解释#include stdio.h int main() { int value; // 提示用户输入stderr确保即时显示 fprintf(stderr, Enter a number: ); // 从stdin读取 scanf(%d, value); // 结果输出到stdout printf(Square: %d\n, value * value); return 0; }5. 重要注意事项不要手动关闭这三个流会在程序结束时自动关闭显式关闭可能导致未定义行为代码语言javascriptAI代码解释fclose(stdin); // ❌ 危险操作缓冲同步在混合使用stdout和stderr时使用fflush强制同步代码语言javascriptAI代码解释printf(Processing...); fflush(stdout); // 确保先显示 fprintf(stderr, [WARN] Low memory);重定向安全需要即时显示的提示信息应使用stderr代码语言javascriptAI代码解释// 正确方式重定向时仍显示提示 fprintf(stderr, Enter password: );二进制模式在Windows系统中使用_setmode切换二进制模式以避免换行符转换代码语言javascriptAI代码解释#include io.h #include fcntl.h _setmode(_fileno(stdout), _O_BINARY); // Windows专用5. 打开文件的方式代码语言javascriptAI代码解释r Open text file for reading. The stream is positioned at the beginning of the file. r Open for reading and writing. The stream is positioned at the beginning of the file. w Truncate(缩短) file to zero length or create text file for writing. The stream is positioned at the beginning of the file. w Open for reading and writing. The file is created if it does not exist, otherwise it is truncated. The stream is positioned at the beginning of the file. a Open for appending (writing at end of file). The file is created if it does not exist. The stream is positioned at the end of the file. a Open for reading and appending (writing at end of file). The file is created if it does not exist. The initial file position for reading is at the beginning of the file, but output is always appended to the end of the file.1. 基本模式模式描述文件存在文件不存在缓冲区行为r只读文本文件打开文件返回 NULL输入缓冲区w只写文本文件清空内容创建新文件输出缓冲区a追加文本文件保留内容追加写入创建新文件输出缓冲区rb只读二进制文件打开文件返回 NULL输入缓冲区wb只写二进制文件清空内容创建新文件输出缓冲区ab追加二进制文件保留内容追加写入创建新文件输出缓冲区关键说明r和rb要求文件必须存在否则失败 。w/wb会无条件清空文件易错点。a/ab的写入位置始终在文件末尾 。2. 扩展读写模式号组合模式描述读操作位置写操作位置r读写文本文件文件开头当前指针位置w读写文本文件文件开头先清空内容a读写文本文件文件开头始终在文件末尾rb读写二进制文件文件开头当前指针位置wb读写二进制文件文件开头先清空内容ab读写二进制文件文件开头始终在文件末尾核心特性w和wb会先清空文件再打开 。a/ab的写操作不受fseek()影响永远追加到末尾 。读写切换时需用fseek()或rewind()重定位指针 。换行符差异文本模式无b在 Windows 中自动转换\r\n↔\nLinux/macOS 无此转换 。二进制模式带b在所有平台直接读写原始字节 。权限问题w和a模式需文件可写权限否则返回NULL。Linux 系统可通过chmod()提前设置权限。总结核心口诀r读w写清空a追加b二进制读写。最佳实践明确需求后选择精准模式如避免误用w导致数据清空。二进制文件操作必须加b避免换行符问题。读写混合时用fseek()同步指针位置 。始终检查返回值并处理错误 。
返回列表