Linux——IO

✅<1>主页::我的代码爱吃辣
📃<2>知识讲解:Linux——文件系统
☂️<3>开发环境:Centos7
💬<4>前言:是不是只有C/C++有文件操作呢?python,java,php,go ..... 他们都是有文件操作?他们的文件操作一样吗?他们都有文件操作,且根据语言的语法不同,文件操作也是不同的。有没有一种同意的视角,看待所有语言的文件操作呢?

目录

一.回顾C文件IO相关操作

1.C语言文件写入

2.C语言文件读取

 3.输出信息到显示器有哪些方法

二.系统文件IO

1.open

 2.write

3.close

 4.read

三.对比C库与系统调用

四.如何管理文件

1.操作系统如何管理文件 

2.进程如何管理文件 ——文件描述符

3.文件描述符的分配规则

 三.重定向

1.重定向原理

2.dup2 系统调用

 四.理解FILE


一.回顾C文件IO相关操作

1.C语言文件写入

测试代码:

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "w");if (!fp){printf("fopen error!\n");}const char *msg = "hello Linux!\n";const char *msg2 = "hello C++!\n";int count = 5;while (count--){// 向文件中写入,// 参数1:写入的数据C++// 参数2:写入的字符个数// 参数3:写入的数据元素的个数// 参数4:写入的文件结构体指针fwrite(msg, strlen(msg), 1, fp);}int n = 5;while (n--){// 向文件中写入,// 参数1:写入的文件结构体指针// 参数2:格式化写入fprintf(fp, "[%d]:%s", n, msg2);}fclose(fp);return 0;
}

测试结果:

2.C语言文件读取

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("myfile", "r");if (!fp){printf("fopen error!\n");}char buf[1024];const char *msg = "hello bit!\n";while (1){// 注意返回值和参数,此处有坑,仔细查看man手册关于该函数的说明size_t s = fread(buf, 1, strlen(msg), fp);if (s > 0){buf[s] = 0;printf("%s", buf);}if (feof(fp)){break;}}fclose(fp);return 0;
}

 3.输出信息到显示器有哪些方法

#include <stdio.h>
#include <string.h>
int main()
{const char *msg = "hello fwrite\n";// 1.fwritefwrite(msg, strlen(msg), 1, stdout);// 2.printfprintf("hello printf\n");// 3.fprintffprintf(stdout, "hello fprintf\n");return 0;
}

C库常见IO接口:

    // 1.默认向显示器格式化打印int printf(const char *format, ...);// 2.向指定的文件中格式化输入int fprintf(FILE * stream, const char *format, ...);// 3.向指定的空间中格式化输入int sprintf(char *str, const char *format, ...);// 4.向指定的空间中格式化输入指定个数字符int snprintf(char *str, size_t size, const char *format, ...);

 总结:

  1. C默认会打开三个输入输出流,分别是stdin, stdout, stderr
  2. 仔细观察发现,这三个流的类型都是FILE*, fopen返回值类型,文件指针

二.系统文件IO

操作文件,除了上述C接口(当然,C++也有接口,其他语言也有),我们还可以采用系统接口来进行文件访问,先来直接以代码的形式,实现和上面一模一样的代码:

1.open

隆重介绍一个系统调用:

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

pathname: 要打开或创建的目标文件
flags: 打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,,就是一种位图结构,flags参数:

  1. O_RDONLY: 只读打开
  2. O_WRONLY: 只写打开
  3. O_RDWR : 读,写打开
  4. 这三个常量,必须指定一个且只能指定一个
  5. O_CREAT : 若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
  6. O_APPEND: 追加写

 返回值:

  • 成功:新打开的文件描述符
  • 失败:-1

 2.write

       #include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);

参数介绍:

  1. fd:要写入的文件描述符。
  2. buf:要写入的字符串。
  3. count:写入的个数。

3.close

       #include <unistd.h>int close(int fd);

 关闭指定的文件描述符的文件。

测试代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{// fd:文件描述符// mufile:打开的文件名// O_WRONLY :写方式 | O_CREAT:没有该文件就创建 | O_APPEND : 追加写入int fd = open("myfile", O_WRONLY | O_CREAT | O_APPEND, 0666);if (fd == -1){perror("open");}int count = 5;char *msge = "hello C++ and Linux\n";while (count--){ssize_t n = write(fd, msge, strlen(msge));if (n == -1){perror("write:");}}close(fd);return 0;
}

测试结果:

 4.read

       #include <unistd.h>ssize_t read(int fd, void *buf, size_t count);

参数:

  1. fd:读取文件的文件描述符
  2. buf:存储读取出的数据的缓冲区
  3. count:最大读取个数

返回值:

  • 读取成功:返回读取的字节数。
  • 读取失败:返回-1.

 测试代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{// fd:文件描述符// mufile:打开的文件名// ORDONLY:独方式打开int fd = open("myfile", O_RDONLY);if (fd == -1){perror("open");}char buff[1024];// fd:读取文件的文件描述符// buff:存储读取数据的缓冲区// 1024:最大读取字节数ssize_t n = read(fd, buff, 1024);if (n == -1){perror("write:");}printf(buff);close(fd);return 0;
}

 测试结果:

三.对比C库与系统调用

我们真正理解语言层面的文件操作吗?其实我们并不理解,因为这不是语言问题,这是系统问题。

是不是只有C/C++有文件操作呢?python,java,php,go ..... 他们都是有文件操作?他们的文件操作一样吗?他们都有文件操作,且根据语言的语法不同,文件操作也是不同的。有没有一种同意的视角,看待所有语言的文件操作呢?

在认识返回值之前,先来认识一下两个概念: 系统调用 和 库函数:

 上面的 fopen fclose fread fwrite 都是C标准库当中的函数,我们称之为库函数(libc)。
而, open close read write 都属于系统提供的接口,称之为系统调用接口回忆一下我们讲操作系统概念时,画的一张图:

系统调用接口和库函数的关系,一目了然。
所以,可以认为,f#系列的函数,都是对系统调用的封装,方便二次开发。

只要语言层支持了文件操作,那么语言层对下必然封装了系统调用。

四.如何管理文件

1.操作系统如何管理文件 

文件=内容+属性。

当一个文件没有被操作时,文件一般会被放在磁盘上。

当我们对一个文件进程操作的时候,文件需要被放进内存,因为冯诺依曼体系的限定!

当我们对文件进程操作的时候,文件需要被load到内存,load的是属性还是内容?至少要有属性被load。

当我们对文件进程操作的时候,文件需要被提前放进内存,操作文件的又不是我们一个,所以OS内部移动同时存在大量被打开的文件。那么操作系统如何管理这些被打开的文件呢?创建对应的结构体进行抽象,和数据机构进行组织。

每一个被打开的文件,都要在OS内部对应文件对象的struct结构体,可以将所有的struct_file结构体用某种数据结构连接起来,在OS内部,对被打开的文件进行管理,就转换成对链表的增删查改。

2.进程如何管理文件 ——文件描述符

 文件可以分为两大类,磁盘文件(没有被打开),内存文件(被打开)。

文件被打开,是指文件被以进程为代表的用户让操作系统打开的。

所以之前的文件操作,都是进程与被打开文件之间的关系。在OS的角度,就是PCB与struct_file的关系。

那么进程是如何管理自己打开的文件的呢?

open返回值:

#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{// 打开一个文件int fd = open("testfile", O_WRONLY | O_CREAT, 0666);// 打印文件描述符printf("%d\n", fd);return 0;
}

通过对open函数的学习,我们知道了文件描述符就是一个小整数。

 这里为什么是3?我们多打开几个文件看看:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{// 打开一个文件int fd = open("testfile", O_WRONLY | O_CREAT, 0666);int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);int fd3 = open("testfile3", O_WRONLY | O_CREAT, 0666);// 打印文件描述符printf("%d\n", fd);printf("%d\n", fd1);printf("%d\n", fd2);printf("%d\n", fd3);return 0;
}

我们发现打印出的是连续的整数。但是没有还是从3开始的,那么会不会有0,1,2呢?

0 & 1 & 2 :

  1. Linux进程默认情况下会有3个缺省打开的文件描述符,分别是标准输入0, 标准输出1, 标准错误2.
  2. 0,1,2对应的物理设备一般是:键盘,显示器,显示器

 所以输入输出还可以采用如下方式:

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main()
{char buf[1024];// 0:标准输入的文件描述符——键盘文件ssize_t s = read(0, buf, sizeof(buf));if (s > 0){buf[s] = 0;// 写入1号文件描述符的文件中——显示器文件// 写入2号文件描述符的文件中——显示器文件write(1, buf, strlen(buf));write(2, buf, strlen(buf));}return 0;
}

而现在知道,文件描述符就是从0开始的小整数。当我们打开文件时,操作系统在内存中要创建相应的数据结构来描述目标文件,于是就有了file结构体。表示一个已经打开的文件对象。而进程执行open系统调用,所以必须让进程和文件关联起来。每个进程都有一个指针*files, 指向一张表files_struct,该表最重要的部分就是包涵一个指针数组,每个元素都是一个指向打开文件的指针!所以,本质上,文件描述符就是该数组的下标。所以,只要在进程PCB中拿着文件描述符,就可以找到对应的文件。

3.文件描述符的分配规则

 测试代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{close(0);int fd = open("testfile", O_WRONLY | O_CREAT, 0666);close(2);int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);// 打印文件描述符printf("%d\n", fd);printf("%d\n", fd1);printf("%d\n", fd2);return 0;
}

测试结果:

 说明:

  1. 当我们关闭0,2号文件描述符,0,2文件描述符空着,新打开的文件描述符不再从3开始。
  2. fd: 0 或者 fd 2 可见,文件描述符的分配规则:在files_struct数组当中,找到当前没有被使用的最小的一个下标,作为新的文件描述符。

 三.重定向

1.重定向原理

 上述代码如果我们关闭的是1号文件描述符:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{close(0);int fd = open("testfile", O_WRONLY | O_CREAT, 0666);// 如果关闭1号文件描述符close(1);int fd1 = open("testfile1", O_WRONLY | O_CREAT, 0666);int fd2 = open("testfile2", O_WRONLY | O_CREAT, 0666);// 打印文件描述符printf("%d\n", fd);printf("%d\n", fd1);printf("%d\n", fd2);return 0;
}

测试结果:

说明:

  1. 本应该输出到显示器的内容,却输出到了文件中。这种现象就叫做重定向。
  2. 常见的重定向有:>, >>, <,输出重定向,追加重定向,输入重定向。

重定向的本质:

说明:

原本输入到显示器的数据输入到了其他文件,仅仅通过更改struct file*fdarray[ ]对应下标的存储的指针。

2.dup2 系统调用

#include <unistd.h>int dup2(int oldfd, int newfd)

说明:

  • oldfd:需要重定向的文件描述符。
  • newfd:被重定向的文件描述符。

 测试代码:

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
int main()
{int fd = open("./log", O_CREAT | O_RDWR, 0666);if (fd < 0){perror("open");return 1;}close(1);// 将fd对应的文件,重定向到1号文件描述符dup2(fd, 1);for (;;){char buf[1024] = {0};ssize_t read_size = read(0, buf, sizeof(buf) - 1);if (read_size < 0){perror("read");break;}printf("%s", buf);fflush(stdout);}return 0;
}

测试结果:

printf是C库当中的IO函数,一般往 stdout 中输出,但是stdout底层访问文件的时候,找的还是fd:1, 但此时,fd:1下标所表示内容,已经变成了./log的地址,不再是显示器文件的地址,所以,输出的任何消息都会往文件中写入,进而完成输出重定向。

 四.理解FILE

因为IO相关函数与系统调用接口对应,并且库函数封装系统调用,所以本质上,访问文件都是通过fd访问的。所以C库当中的FILE结构体内部,必定封装了fd。

测试代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>int main()
{int fd = open("testfile", O_CREAT | O_WRONLY, 0666);int fd1 = open("testfile1", O_CREAT | O_WRONLY, 0666);printf("%d\n", stdin->_fileno);printf("%d\n", stdout->_fileno);printf("%d\n", stderr->_fileno);printf("%d\n", fd);printf("%d\n", fd1);return 0;
}

测试结果:

看一段代码:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{const char *msg0 = "hello printf\n";const char *msg2 = "hello write\n";printf("%s", msg0);write(1, msg2, strlen(msg2));fork();return 0;
}

运行结果:

看到这里一切正常,如果我们将输出到显示器的数据,重定向到其他文件中:

 我们发现 printf 输出了2次,而 write 只输出了一次(系统调用)。为什么呢?肯定和fork有关!

  •  一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。
  • printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。
  • 而我们放在缓冲区中的数据,就不会被立即刷新,fork之后。
  • 但是进程退出之后,会统一刷新,写入文件当中。
  • 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的一份数据,随即产生两份数据。
  • write 没有变化,说明没有所谓的缓冲,而是直接写入文件。

 综上:

  1. printf fwrite 等库函数会自带缓冲区,而 write 系统调用没有带缓冲区。
  2. 另外,我们这里所说的缓冲区,都是用户级缓冲区。其实为了提升整机性能,OS也会提供相关内核级缓冲区,不过不再我们讨论范围之内。
  3. 那这个缓冲区谁提供呢? printf fwrite 是库函数, write 是系统调用,库函数在系统调用的“上层”, 是对系统调用的“封装”,但是 write 没有缓冲区,而 printf fwrite 有,足以说明,该缓冲区是二次加上的,又因为是C,所以由C标准库提供。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/83101.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

长尾关键词挖掘软件-免费的百度搜索关键词挖掘

嗨&#xff0c;大家好&#xff01;今天&#xff0c;我想和大家聊一聊长尾关键词挖掘工具。作为一个在网络世界里摸爬滚打多年的人&#xff0c;我对这个话题有着一些个人的感悟和见解&#xff0c;希望能与大家分享。 首先&#xff0c;让我坦白一点&#xff0c;长尾关键词挖掘工具…

《计算机视觉中的多视图几何》笔记(3)

3 Projective Geometry and Transformations of 3D 这章主要讲的是3D的射影几何&#xff0c;与2D的射影几何差不多。主要区别是&#xff1a; 3D射影几何对偶的是点和平面&#xff0c;直线是自对偶的。3D空间中直线有4个自由度&#xff0c;这一现象并不是那么容易直接得出。一…

2023最新安装微信小程序开发软件安装教程

一&#xff0c;安装开发者工具 我们在开发小程序之前&#xff0c;首先需要安装小程序开发者工具&#xff0c;今天就来教大家安装小程序开发者工具。 微信开放文档 (qq.com)https://developers.weixin.qq.com/miniprogram/dev/framework/ 官网工具下载地址&#xff1a; 微信…

在windows下持续ping ip,将返回结果及时间记录到文件中

在纯英文路径下创建文件ping.txt 在txt中写入 Dim args, flag, unsuccOut args"" otherout"" flag0If WScript.Arguments.count 0 Then WScript.Echo "Usage: cscript tping.vbs [-t] [-a] [-n count] [-l size] [-f] [-i TTL] [-v TOS]" WScr…

NDK (ndk)报错 Unity requires NDK r19 (64-bit)(19.0.05232133)

一、介绍 在 Android 添加 NDK ndk 的时候&#xff0c;出现 Unity requires NDK r19 (64-bit)(19.0.05232133)。 二、环境 1、Unity 2020.3.48f1c1 2、Android NDK 配置 三、报错信息 NDK (ndk)报错 Unity requires NDK r19 (64-bit)(19.0.05232133) 四、解决方法 1、下…

uniapp 在父组件中使用ref属性调用子组件中的方法 报错undefined

项目背景&#xff1a; 项目是自定义底部tabBar&#xff0c;所以这个页面是index(首页的一个子组件ref"pageC") 现在要求是不管页面滚动到第几页数据&#xff0c;或者每次从详情页以及其他页面回到这个页面时&#xff08;子组件pageC)内容都要回到顶部 所以我在index的…

华为OD机试 - 求最多可以派出多少支团队 - 双指针(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

【 Tkinter界面-练习05】 event和bind

一、说明 事件和动作有关&#xff1b;所有的界面都与运动有关&#xff0c;本篇将对事件、事件触发、绑定回调函数等&#xff0c;其实是一系列部件配合的复杂的过程&#xff0c;这些过程牵扯到系统如何设计&#xff0c;线程、消息队列循环等。本篇将详细介绍各种因素的关系。 二…

vue3创建的官网提示方法

创建: npm create vuelatest 或许会出现提示: 根据官网提示: 意味着我们需要安装或者更新create-vue的版本 后面就可以跟随提示了

【2023年11月第四版教材】第13章《资源管理》(第三部分)

第13章《资源管理》&#xff08;第部分&#xff09; 4 规划资源管理4.1 数据表现★★★4.2 资源管理计划★★★4.2 团队章程★★★ 5 估算活动资源 4 规划资源管理 组过程输入工具和技术输出规划1.规划资源管理1.项目章程2.项目管理计划&#xff08;质量管理计划、范围基准&am…

VOP —— Noise

目录 Turbulent Noise —— 计算1D/3D类型的Noise Anti-Aliased Flow Noise —— 生成抗锯齿噪波 Anti-Aliased Noise —— 生成抗锯齿噪波 Curl Noise —— 创建divergence-free 3D噪波 Curl Noise 2D —— 创建divergence-free 2D噪波 Flow Noise —— 生成1D/3D Perli…

下载安装nvm教程(附带下载切换node.js版本实操)

目录 一、介绍 二、下载 三、安装步骤 四、配置淘宝源 五、测试 六、常用的nvm命令 七、下载切换node版本实操 node版本参考 一、介绍 node版本管理&#xff1a;nvm就是可以切换你的node版本&#xff0c;特别是当node版本过高或者过低时候&#xff0c;就可以用nvm进行…

036:vue导出页面生成pdf文件

第036个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

GIF动图怎么变成jpg动图?一键分解GIF动画

GIF格式图片怎么转换成jpg格式图片&#xff1f;在日常生活中jpg、png转GIF格式非常的常见&#xff0c;那么gif转换成jpg格式应该怎么操作呢&#xff1f;很简单&#xff0c;给大家分享一款gif动态图片制作&#xff08;https://www.gif.cn/giffenjie&#xff09;工具&#xff0c;…

计算机视觉与深度学习-卷积神经网络-纹理表示卷积神经网络-卷积神经网络-[北邮鲁鹏]

这里写目录标题 参考文章全连接神经网络全连接神经网络的瓶颈全连接神经网络应用场景 卷积神经网络卷积层(CONV)卷积核卷积操作卷积层设计卷积步长(stride)边界填充特征响应图组尺寸计算 激活层池化层(POOL)池化操作定义池化操作作用池化层超参数常见池化操作 全连接层(FC)样本…

算法通关村 | 透彻理解动态规划

1. 斐波那契数列 1&#xff0c;1&#xff0c;2&#xff0c;3&#xff0c;5&#xff0c;8&#xff0c;13&#xff0c;..... f(n) f(n-1) f(n-2) 代码实现 public static int count_2 0;public int fibonacci(int n){if (n < 2){count_2;return n;}int f1 1;int f2 2;i…

【鸽鸽送书第一期】 | 实现可观测性平台的技术要点是什么?文末参与送书哦!

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏:《粉丝福利》 《C语言进阶篇》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 文章目录 &#x1f4cb; 前言实现可观测性平台的技术要点是什么&#xff1f;1.兼容全域信号量2.所谓全域信号量有哪些&#x…

基于AI视觉的表面缺陷检测设备优势显著,加速制造业数智化转型

作为生产制造过程中不可缺少的一步&#xff0c;表面缺陷检测广泛应用于工业领域&#xff0c;包括3C电子、芯片半导体、食品医药、木材等行业。但随着智能化进程加快&#xff0c;制造工厂生产线的质量检测压力加剧&#xff0c;传统人工表面缺陷检测已经无法满足当前社会较高的检…

YOLOv8『小目标』检测指南

前言 目前博主课题组在进行物体部件的异常检测项目&#xff0c;项目中需要先使用 YOLOv8 进行目标检测&#xff0c;然后进行图像切割&#xff0c;最后采用 WinCLIP 模型 进行部件异常检测 但是在实际操作过程中出现问题&#xff0c; YOLOv8 模型目标检测在大目标精确度不错&a…

Mybatis深度解析:从起源到现代应用的全景视角

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…