ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Linux中基础IO和文件认识

Linux中基础IO和文件认识 文件1文件在磁盘里2磁盘是永久性存储的介质因此文件在磁盘上存储是永久性的3磁盘是一个外设即是输出设备也是输⼊设备4磁盘上的⽂件本质是对⽂件的所有操作都是对外设的输⼊和输出简称IO0文件的认识1对于0KB的文件来说也是占用一定的磁盘空间2文件文件属性文件内容的集合3所有的⽂件操作本质是⽂件内容操作和⽂件属性操作系统角度认识1对文件的操作本质是进程对文件的操作2磁盘的管理者是操作系统3⽂件的读写本质不是通过C语⾔/C的库函数来操作的这些库函数只是为⽤⼾提供⽅便⽽ 是通过⽂件相关的系统调⽤接⼝来实现的fopen和open带f开头C 标准库stdio.h的文件流函数高层 IO不带f操作系统系统调用POSIX如 Linux底层文件描述符 IO特征带 ffopen,fread,fwrite,fclose,fseek不带 fopen,read,write,close,lseek接口来源C 标准库函数跨平台OS 系统调用POSIX 标准Linux/UnixWindows 也有 API 但接口不一样不跨平台操作对象FILE* 文件指针流int 文件描述符 fd小整数缓冲区✅自带用户态缓冲库缓冲❌无库层缓冲直接调用内核只有内核缓冲文本 / 二进制区分文本模式r和二进制rb没有文本模式全部原始二进制字节换行处理Windows 下自动\n - \r\n转换原样读写字节不做换行转换错误获取ferror()、feof()errno全局变量可移植性所有 C 编译器都能用Windows/Linux/macOSUnix 系Windows 不支持这套要用 Win32API典型函数fopen,fclose,fread,fwrite,fgets,fputs,fprintf,fscanf,fseek,ftell,rewindopen,close,read,write,lseek核心概念1、FILE* 流高层fopen返回FILE *fp这个结构体里面封装了文件描述符 fd再加一块内存缓冲区。 当你调用fread/fwrite数据先读写到库的内存缓冲区不是立刻落到磁盘。刷新缓冲区fflush(fp)强制把缓冲区数据交给操作系统。好处减少系统调用次数读写小数据性能更高。2、文件描述符 fd底层系统调用open()返回 int 整数 fd直接跟操作系统内核打交道没有 C 库的缓冲。 调用read/write就直接发起系统调用每一次都进入内核。频繁小 read/write 性能差因为系统调用有开销。3、二者可以互相转换fileno(FILE* fp)从FILE*拿到底层 fdfdopen(int fd, const char *mode)用已有的 fd 包装成 FILE * 流文本模式 vs 二进制模式FILE *fp fopen(a.txt, r); // 文本模式 FILE *fp fopen(a.txt, rb); // 二进制模式Linux 下文本 / 二进制模式没有区别不会转换换行。open/read 这套底层接口永远等价二进制模式没有文本模式。使用场景普通 C 程序追求可移植、简单文件读写→ 用fopen/fread/fwrite带 f自带缓冲简单。Linux 系统编程管道、socket、设备文件、高级标志非阻塞 O_NONBLOCK锁等→ 用open/read/write系统调用。fopen无法直接设置非阻塞这类内核属性。一句话总结带 f 是 C 标准库封装有缓冲、区分文本二进制、跨平台操作 FILE*不带 f 是操作系统原生系统调用操作文件描述符 fd无库缓冲Unix/Linux 专用。fprintf / snprintf它们都属于stdio.h 标准库函数带 f 家族本质是格式化输出函数核心能力把数字、变量按格式转成字符串。fprintfint fprintf(FILE *stream, const char *format, ...);输出目标FILE*文件流可以是文件、stdout、stderr作用格式化直接写入文件流缓冲区。重点fprintf是基于FILE*流走 C 库缓冲区不是直接写磁盘。数据先到用户缓冲区fflush/fclose 才真正交给内核。printf等价于fprintf(stdout, ...)stdout 就是默认的屏幕 FILE * 流。snprintfint snprintf(char *buf, size_t size, const char *format, ...);输出目标内存缓冲区char 数组内存字符串不碰文件作用格式化把结果写到内存字符数组里。不会直接写入文件。char buf[64]; int a 200; snprintf(buf, sizeof(buf), num%d, a); //此时buf里面存放 num200 字符串和 fopen /open/read/write 的关系关系图fprintf格式化 → 写到FILE*流 → C 库缓冲区 →fflush交给内核 write 系统调用。变量 → fprintf → FILE*缓冲区 → fflush → write()系统调用 → 内核snprintf格式化 →写到内存 char []。之后你可以选择用fputs/fwrite把 buf 写到 FILE * 文件或者拿到 fd调用系统调用write(fd, buf, strlen(buf))写入文件。变量 → snprintf → 内存buf ↓ 方案1fputs(buf, fp) //走FILE*库缓冲 方案2write(fd, buf, strlen(buf)) //直接底层系统调用函数输出目的地是否操作文件底层依赖printfstdout屏幕 FILE*是FILE * 流fprintf任意 FILE*文件 /stdout/stderr是FILE * 流、库缓冲sprintf/snprintf内存 char [] 数组❌不碰文件内存操作纯内存无 IOFILE *fp fopen(test.txt,w); int val 666; fprintf(fp, value:%d\n, val); fclose(fp); FILE *fp fopen(test.txt,w); int val 666; char buf[32]; snprintf(buf,sizeof(buf),value:%d\n,val); fputs(buf, fp); fclose(fp); int fd open(test.txt, O_WRONLY|O_CREAT, 0644); int val 666; char buf[32]; snprintf(buf,sizeof(buf),value:%d\n,val); write(fd, buf, strlen(buf)); //直接系统调用write close(fd);总结fprintf格式化后直接输出到 FILE * 文件流做格式化 IOsnprintf格式化输出到内存字符串只做字符串拼装完全不做 IO可以搭配 fwrite/fputs也可以搭配底层 write 系统调用。内核没有格式化能力格式化逻辑全部在 C 标准库。格式化输出简单说把程序里面各种类型的数据整数、小数、字符转换成人类看得懂的字符串按你指定的样子拼接好再输出出去就叫格式化输出。printf(xxx %d, a)格式化后输出到屏幕 (stdout 流)fprintf(fp, xxx %d, a)格式化后输出到FILE* 文件流文件snprintf(buf, size, xxx %d, a)格式化后输出到内存字符数组 buf不输出到屏幕、不写文件。只是在内存拼好字符串后面你想写到哪里再自己写。dprintf(fd, xxx %d, a)Linux POSIX 扩展 格式化后直接输出到底层文件描述符 fd。等价于先用 snprintf 拼字符串再调用 write。再回到你前面的 fopen /open带 f 的库函数fprintf自带格式化 FILE 流缓冲 IOopen/read/write 系统调用只有原始字节 IO没有格式化。想要输出数字必须先用 snprintf 做格式化把数字变成字符串再 write。一句话总结格式化输出 根据模板把内存里的数字、小数等各种变量转换成可读的字符串拼接成一整段文本。格式化是 C 库做的工作操作系统内核完全不懂%d。OPEN的使用三个参数第一个创建文件的文件名第二个创建文件的属性第三个文件的权限设置第二个属性基础必选只能选一个flags含义O_RDONLY只读打开只能读不能写O_WRONLY只写打开只能写不能读O_RDWR读写打开可读可写常用附加标志用|和上面组合标志作用O_CREAT文件不存在就创建用这个标志时 open 必须传第三个参数 modeO_EXCL和O_CREAT一起文件已存在则 open 报错保证新建文件O_TRUNC如果文件存在打开时直接清空文件内容截断为 0 字节O_APPEND追加写每次 write 都写到文件末尾不会覆盖原有内容打印fd: 3动作流向作用读文件 (read/fgets)磁盘 ➜ 程序内存获取文件内容不显示写 stdout (printf/fputs/write (1,..))程序内存 ➜ 屏幕把内存内容打印出来读文件把磁盘上文件里的数据读取到程序内存变量 / 数组里面。读文件不等于直接打印到屏幕 (stdout)。读磁盘 →程序内存读到数组、buf 缓冲区输出到屏幕内存 → stdoutprintf/fputs做这件事流程读文件把内容拿到内存你自己再写代码把内存里的内容打印到屏幕才会看到输出。使用read/fputs/printf等一句话总结O_RDONLYopen 的参数控制打开之后这个文件描述符能不能写只做权限设置不读取数据。read()系统调用真正从磁盘读字节到内存read 必须依靠 open 返回的 fd。O_RDONLY 是开门权限read 是伸手拿东西。开门≠拿东西。配套记忆open开门获取文件描述符设置权限 (O_RDONLY/O_WRONLY/O_RDWR)read拿数据进来磁盘→内存write把数据送出去内存→磁盘close关门为什么打印3因为系统自动打开了stdin 标准输入 键盘 fd: 0stdout 标准输出 显示器 fd: 1stderr 标准错误 显示器 fd: 2fd名字含义默认目标0stdin标准输入键盘1stdout标准输出屏幕2stderr标准错误输出屏幕对Linux一切接文件重定向重定向本质改变文件描述符指向把本来输出到 A 的内容改输出到文件 / 别的地方。输出重定向、覆盖重定向等价O_WRONLY | O_CREAT | O_TRUNCls out.txt # ls本来打印到屏幕(fd1)现在写到 out.txt文件原有内容全部清空追加重定向等价O_WRONLY | O_CREAT | O_APPENDecho hello log.txt # 写到文件末尾不删除旧内容2代表 stderr错误信息#错误信息写入err.txt正常输出依旧屏幕 cat nofile.txt 2 err.txtdup和dup2函数头文件#include unistd.hint dup(int oldfd); int dup2(int oldfd, int newfd);1️⃣dup(int oldfd)函数行为传入一个已经打开的文件描述符oldfd内核会找当前进程里最小的、没有被占用的文件描述符编号让这个新 fd 和oldfd指向同一个struct file返回这个新的文件描述符。示例 进程默认 fd0 (标准输入)、1 (标准输出)、2 (标准错误)都被占用。int fd open(a.txt, O_WRONLY|O_CREAT,0666); // fd3 int new_fd dup(fd);现在最小空闲 fd 是 4dup(3)返回4。fd3 和 fd4两个编号指向同一个打开的文件对象。⚠️注意文件引用计数 1关闭其中一个 fd另一个还可以继续读写文件只有全部 close内核才真正释放文件对象。dup 特点只能由内核帮你选新的 fd 编号你不能指定新 fd 的值。返回值成功返回新 fd失败返回‑1。2️⃣dup2(int oldfd, int newfd)重定向最常用dup2(oldfd, newfd)把 oldfd 复制到 newfd你自己指定 newfd 是几号。执行逻辑分两步如果newfd原本是一个已经打开的有效文件描述符dup2会自动先 close (newfd)把它关掉。然后让newfd这个编号和oldfd指向同一个struct file。返回值成功返回newfd失败返回‑1。int fd open(log1.txt, O_WRONLY|O_CREAT|O_TRUNC,0666); dup2(fd, 1); // oldfdfd(3), newfd1fd1 原本指向显示器stdoutdup2 自动 close (1)关闭显示器连接。让 fd1 现在指向 log1.txt 的文件对象。printf一直往 fd1 写输出就写入文件不再打印屏幕。dup2 重点细节如果oldfd newfd不会做任何操作直接返回 newfd不会 close。newfd可以是任意数字比如dup2(fd,1)、dup2(fd,2)重定向 stderr。函数参数新 fd 编号谁决定dup(oldfd)只传旧 fd内核自动选最小空闲 fddup2(oldfd, newfd)旧 fd 指定新 fd 编号程序员手动指定 newfd小坑dup、dup2 只是复制描述符不复制缓冲区共用同一个文件偏移量。一个 fd 写文件移动偏移另一个 fd 读写也会看到偏移变化。出错记得判断返回值open 失败得到‑1再传给 dup2 会直接出错。头文件必须unistd.h否则编译警告。shell 重定向./a.out log.txtshell 底层就是 fork 子进程在子进程内部调用dup2()把 fd1 指向文件然后 exec 执行程序程序 printf 就输出到文件。和你代码写dup2(fd,1)做的是一模一样。
返回列表