基础IO(上)

前言

文件 = 内容 + 属性

  1. 所有对文件的操作就是对内容操作和对属性操作。
  2. 内容是数据,属性也是数据。存储文件,必须既存储内容又存储数据。创建文件默认就是在磁盘中的。
  3. 我们要访问一个文件的时候,都是要先把这个文件打开的。访问文件的本质就是进程在打开文件,打开之后就要把磁盘中的文件加载到内存中去。
  4. 一个进程可以打开多个文件,多个进程也可以打开多个文件。换言之,加载到内存中被开的文件,可能会存在多个。

操作系统在运行中,可能会打开很多个文件,那么操作系统要不要管理这些被打开的文件呢,如果需要的话如何管理呢?六字真言~

先描述,再组织!

一个文件要被打开,一定要先内核中形成被打开的文件对象。

  1. 文件按照是否被打开分为:被打开的文件和没有被打开的文件。被打开的文件在内存中,没有被打开的文件在磁盘中。
  2. 研究被打开的文件本质就是:进程和被打开文件的关系。

1. 回顾C文件接口

老规矩,首先打开man手册学习下fopen接口

image-20240520115440635

  • 文件以"w"的方式打开,会先清空文件内容,如果文件不存在则会自动创建!
  • 文件以"a"的方式打开,也是写入,但不会清空文件,而是从文件结尾追加新内容!

那么如何向文件写入内容呢?可以使用fputs或者fwrite,依旧查看一下man手册:

fputs

image-20240523120704077

fwrite:

image-20240523120748496

这里演示一下以"w"的方式打开,并向文件内写入一些内容

#include <stdio.h>
#include <string.h>
int main()
{FILE *fp = fopen("log.txt", "w");if (NULL == fp){perror("fopen");return 1;}const char *msg = "hello linux!\n";int count = 10;while (count--){fputs(msg, fp);}fclose(fp);return 0;
}

image-20240523121325425

成功写入~!

2. 系统文件I/O

用man手册查看一下open

image-20240521163005375

  • pathname:要打开或创建的目标文件
  • flags:打开文件时,可以传入多个参数选项,用下面的一个或者多个常量进行“或”运算,构成flags。
  • 参数:
    • O_RDONLY:只读打开
    • O_WRONLY:只写打开
    • O_RDWR:读,写打开这三个常量,必须指定一个且只能指定一个
    • O_CREAT:若文件不存在,则创建它。需要使用mode选项,来指明新文件的访问权限
    • O_APPEND:追加写
    • O_TRUNC:清空文件
  • 返回值:
    • 成功:新打开的文件描述符
    • 失败:-1
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd = open("log.txt", O_WRONLY | O_CREAT, 0666);if (fd < 0){perror("open");return 1;}const char *msg = "hello file system-call\n";// 操作这个文件write(fd, msg, strlen(msg));// 关闭这个文件close(fd);return 0;
}

当我们想向一个文件中写入字符串的时候,不需要strlen()+1,因为\0是C语言的规定,而不是文件的规定!

参数O_WRONLY默认写入不是完全覆盖式写入,而是从文件开始依次覆盖写入,剩余的保留。如果需要修改写的方式,则需要上段代码中的第十行代码。

  • 按照写方式打开,如果文件不存在就创建它,如果有内容会先清空文件的内容!

int fd = open("log.txt", O_WRONLY | O_CREAT | O_TRUNC, 0666);
与之对应的C语言接口,fopen以"w"方式打开:
FILE *fp = fopen("log.txt", "w");

  • 按照写方式打开,如果文件不存在就创建它,如果有内容则从文件结尾处开始写入,追加但不清空文件!

int fd = open("log.txt", O_WRONLY | O_CREAT | O_APPEND, 0666);
与之对应的C语言接口,fopen以"a"方式打开:
FILE *fp = fopen("log.txt", "a");

所以说,我们学习的C语言打开文件的接口,在底层一定封装了系统调用!

3. 文件描述符fd

通过对open函数的学习,我们知道了文件描述符就是一个小整数。其实文件描述符fd本质也就是数组的下标!

我们发现我们所创建的文件描述符是从3开始的,那么0、1、2去哪里了呢?

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

image-20240523205321668

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

无论读写,都要先把数据加载到文件缓冲区中去!

我们在应用层进行的数据读写,本质是什么?

本质就是将内核缓冲区中的数据,来回进行拷贝!

文件描述符的分配规则

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{char buf[1024];ssize_t s = read(0, buf, 1024);if (s > 0){buf[s] = 0;// 也就是buf这个字符串以'\0'结尾write(1, buf, strlen(buf));}return 0;
}

效果如下:

image-20240523223202939

可以看到我们不用scanfprintf也能实现输入和打印,同时也证明了一点

进程默认已经打开了0、1、2,我们可以直接使用0、1、2进行数据的访问!

当我们一上来就关闭默认打开的0或2号文件:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int main()
{close(0); // close(2);int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}printf("fd:%d\n", fd);close(fd);return 0;
}

image-20240524111604866image-20240524111622955

我们再次创建文件时会发现,我们所创建的新文件fd被分配为了0或者2,于是得到一个结论:

文件描述符的分配规则是,寻找最小的,没有被使用的数据位置,分配给指定的打开文件!

但是如果我们关闭1号文件,屏幕上就什么都不会显示了,因为1号文件是默认输出文件。

4. 重定向

针对上段代码,做出点稍稍的修改~

int main()
{close(1); // close(2);int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}printf("fd:%d\n", fd);printf("stdout->fd:%d\n", stdout->_fileno);fflush(stdout);close(fd);return 0;
}

当然我们执行完程序后,屏幕依旧什么都不显示,但是我们惊奇的发现,在log.txt文件内出现了这样的情况:

image-20240524114519427

printf默认是打印到显示器中去的,但是为什么会打印到我们的文件中去呢?

image-20240524115707669

printf默认写入stdout中,也就是1号文件,凡是往1号文件写入的内容,都不再写入stdout中了,而是写入了新建的log.txt文件中去。这种现象叫做输出重定向。

重定向的本质,其实就是修改特定文件fd下标的内容。也就是上层fd不变,底层fd指向的内容在改变。

如果我们每次重定向都需要关闭文件,那这样太麻烦,那么有没有一个接口可以帮助我们进行重定向呢?

dup2系统调用

image-20240524164444259

以输出重定向为例:

dup2() makes newfd be the copy of oldfd, closing newfd first if necessary.

newfd成为oldfd的拷贝,oldfd被保留下来了,如果传参,应该这样传:dup2(fd,1)

int main()
{int fd = open("log.txt", O_CREAT | O_WRONLY | O_TRUNC, 0666);if (fd < 0){perror("open");return 1;}dup2(fd, 1);printf("hello printf\n");fprintf(stdout, "hello fprintf\n");close(fd);return 0;
}

image-20240524170810256

在命令行中常见的重定向有:>,>>,<

  • >:输出重定向
  • >>:追加重定向
  • <:输入重定向

image-20240524172122758

5. 标准错误流

int main()
{fprintf(stdout, "hello stdout!\n");fprintf(stderr, "hello stderr!\n");return 0;
}

形成一个可执行程序文件,把标准输出和标准错误流打印在屏幕上。

当我们用可执行程序文件进行输出重定向时,标准错误流依旧打印在屏幕上:

image-20240524212232185

这并不难理解,因为我们只是做了输出重定向,也就是把1号文件替换成了log.txt,此时stderr没有改变,依旧会将内容输出在屏幕上,如果想要把stderr也就是2号文件重定向,需要如下操作:

./myfile > log.txt 2>&1(取1号文件的地址,使2号文件重定向到该地址中)

上面重定向的写法其实是简写,完整的写法应该是这样的:

./myfile 1> log.txt

为什么要有标准错误流呢

当我们在日常开发中,有些消息是常规信息,有些消息是错误信息。在语言上我们向标准错误打印就是向2号文件打印,所有我们用标准错误打印的信息都是错误信息,将常规信息和错误信息分别打印到不同的文件中,方便我们日后统一排查,如有错误直接查看错误信息所在的文件即可。(在C语言中perror就是向标准错误打印~!)

6. 缓冲区

6.1 前言

我们所理解的缓冲区:就是一部分内存

  • 缓冲区的主要作用是提高效率——提高使用者的效率。
  • 因为有缓冲区的存在,我们可以积累一部分数据再统一发送,从而提高了发送的效率。

缓冲区因为能够暂存数据,必定要有一定的刷新方式:

  1. 无缓冲(立即刷新)
  2. 行缓冲(行刷新)
  3. 全缓冲(缓冲区满了再刷新)

这些是缓冲区刷新的一般方式,但是还有特殊情况:

  1. 强制刷新
  2. 进行进程退出的时候,一般要进行缓冲区的刷新

一般对于显示器文件,行刷新(行缓冲)
对于磁盘上的文件,全缓冲(缓冲写满再刷新)

6.2 一个样例

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main()
{fprintf(stdout, "hello fprintf!\n");printf("hello printf!\n");fputs("hello fputs!\n", stdout);const char* str = "system call!\n";write(1, str, strlen(str));fork();return 0;
}

可以看到,直接运行程序和将可执行程序重定向到log.txt文件中的运行结果是不一样的!

image-20240604203500804

理解样例:

  1. 当我们直接向显示器打印的时候,显示器文件的刷新方式是行刷新!而且这段代码中输出的所有字符串都有\n,在fork之前数据已经全部被刷新了,包括systemcall。
  2. 重定向到log.txt,本质是向磁盘文件中写入(不是显示器了),我们系统对于数据的刷新方式已经由行缓冲刷新变成了全缓冲刷新了!
  3. 全缓冲意味着缓冲区变大,实际我们写入的简单数据已经不足以把缓冲区写满了,fork在执行的时候,数据依旧在缓冲区中!
  4. 我们目前所谈的“缓冲区”和操作系统是没有关系的~只能和C语言本身有关!

我们日常所用的最多的其实是C/C++提供的语言级别的缓冲区!也就是用户缓冲区。

  1. C/C++提供的缓冲区,里面保存的一定是用户的数据,属于当前进程在运行时自己的数据。但是如果我们把数据交给了OS,那么这个数据就属于OS,而不属于我自己了。
  2. 当进程退出的时候,一般要进行缓冲区的刷新,即便你的数据没有满足缓冲区的刷新条件!

刷新缓冲区属不属于清空或者写入操作呢?算!

fork执行完之后退出,任意一个进程(父进程或者子进程)在退出的时候刷新缓冲区,并发生写时拷贝。

我们发现write系统调用并没有打印打两份,说明:

write并没有使用C语言的缓冲区,而是直接写入到操作系统中去,已经不属于进程了,所以不发生写时拷贝!

那么什么是刷新呢?

刷新就是把C/C++的缓冲区里的内容写入OS中去,这个工作就叫做刷新

C/C++语言缓冲区存在的意义是什么呢?

是为了提高printffprintf等这些函数的调用效率!一个函数的调用时间越短意味着后面的代码执行越快,也就意味着程序运行的整体效率都提高了。

(顺便提一下,printf这些函数在格式化输出的时候,就是在拷贝到缓冲区的时候做的~)

任何情况下,我们输入输出的时候,都要有一个FILE类型,FILE是一个结构体,在这个结构体里包含了fd,那么在这个结构体里再包含一段缓冲区也未尝不可。

6.3 FILE

我们来看下FILE结构体:

首先使用命令:

grep -n 'typedef struct _IO_FILE FILE' /usr/include/stdio.h

image-20240604210029406

FILE结构体在第48行,我们直接将文件定位到48行

vim /usr/include/stdio.h +48

image-20240604210356150

对_IO_FILE结构体的重命名为FILE,也就是我们平常所看到的FILE类型。

再来找一下具体的定义:

grep -n 'struct _IO_FILE {' /usr/include/libio.h

image-20240604210932254

FILE结构体具体定义在第246行,我们直接将文件定位到246行

vim /usr/include/libio.h +246

image-20240604211142169

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

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

相关文章

无人机EasyDSS推拉流视频直播技术在农业植保中的精准应用与展望

随着科技的飞速发展&#xff0c;无人机在农业领域的应用越来越广泛&#xff0c;特别是在农业植保方面&#xff0c;无人机以其独特的优势&#xff0c;为农业生产带来了革命性的改变。 无人机在农业植保中的应用主要体现在两个方面&#xff1a;提高工作效率和精准喷洒药物。在以…

每天认识一家仪器仪表厂商 | 横河测试测量官网 - Yokogawa

横河Yokogawa工商信息&#xff1a; 横河测量技术(上海)有限公司于2000年08月09日成立。法定代表人山崎正晴(YAMAZAKI MASAHARU)&#xff0c;公司经营范围包括&#xff1a;从事测量科技、机电科技领域内技术开发、技术转让、技术咨询、技术服务&#xff0c;仪器仪表、通讯设备、…

专业130+总分400+四川大学951信号与系统考研经验川大电子信息与通信工程,真题,大纲,参考书。教材。

今年四川大学951信号与系统专业课130&#xff08;据我所知没有140以上的今年&#xff09;&#xff0c;总分400&#xff0c;顺利上岸川大&#xff0c;回顾一下自己这一年的复习&#xff0c;希望自己的经历可以对大家复习有所借鉴&#xff0c;也是对自己的考研画上句话。专业课&a…

使用from…import语句导入模块

自学python如何成为大佬(目录):https://blog.csdn.net/weixin_67859959/article/details/139049996?spm1001.2014.3001.5501 在使用import语句导入模块时&#xff0c;每执行一条import语句都会创建一个新的命名空间&#xff08;namespace&#xff09;&#xff0c;并且在该命名…

mysql中InnoDB存储引擎的Buffer Pool

大家好。众所周知&#xff0c;对于使用InnoDB作为存储引擎的表来说&#xff0c;不管是用于存储用户数据的索引&#xff08;包括聚簇索引和二级索引&#xff09;&#xff0c;还是各种系统数据&#xff0c;都是存储在磁盘上的。在处理客户端的请求时&#xff0c;当需要访问某个页…

思维导图——幕布

一、前言 幕布是一款专注于简化和组织信息的大纲笔记应用&#xff0c;它旨在帮助用户高效地整理知识点、优化工作流程以及规划个人生活。 二、软件特点 幕布工具的核心优势在于其能够快速将用户的输入转换成清晰的思维导图&#xff0c;便于视觉化地理解和记忆信息。 幕布还具…

插入mysql报错:Incorrect string value: ‘\xF0\xAC\x8C\x97\xE5\x9E...‘

原因分析 这个错误通常发生在使用MySQL数据库时&#xff0c;尝试将包含四字节UTF-8字符&#xff08;通常表示为Unicode码点大于UFFFF的字符&#xff09;插入到一个不支持这种字符的字符集列中。一般在插入睡眠emoji表情时容易遇到 解决 -- 设置数据库编码utf8mb4 ALTER DAT…

TrollInstallerX小白一键安装巨魔商店 分分钟安装成功

概述 TrollInstallerX 是一款通用的 TrollStore 安装程序。它注重可靠性和易用性。它的速度也非常快&#xff0c;能够在几秒钟内将 TrollStore 和/或其持久性助手安装到最新设备上。 TrollInstallerX 支持所有运行 iOS 14.0 - 16.6.1 的设备&#xff0c;包括 arm64 和 arm64e。…

若依分离版—增加通知公告预览及缩放功能

若依分离版—增加通知公告预览及缩放功能 前言开发通知公告 前言 若依分离版的通知公告没有预览功能&#xff0c;想开发通知公告功能 开发通知公告 效果如下 具体开发内容 修改若依notice代码如下。 <template><div class"app-container"><el-…

103.网络游戏逆向分析与漏洞攻防-ui界面的设计-加速功能的开关设计

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 如果看不懂、不知道现在做的什么&#xff0c;那就跟着做完看效果&#xff0c;代码看不懂是正常的&#xff0c;只要会抄就行&#xff0c;抄着抄着就能懂了 内容…

李廉洋:6.4-6.5黄金原油再次走低,美盘行情分析及最新策略。

黄金消息面分析&#xff1a;全球债券周二上涨&#xff0c;呼应美债隔夜的涨势。美联储或早降息的押注增强了主权债务的吸引力。澳大利亚和新西兰10年期债券收益率下跌至少8个基点&#xff0c;先前数据显示&#xff0c;美国5月份工厂活动萎缩的速度加快。日本10年期债券收益率下…

Nginx location 与 Rewrite

Nginx正则表达式 location 通过前缀或正则匹配用户的URL访问路径做页面跳转、访问控制和代理转发 location 大致可以分为三类&#xff1a; 精准匹配&#xff1a;location / {...} 一般匹配&#xff1a;location / {...} 正则匹配&#xff1a;location ~ / {...} location…

ctfshow解题,知识点学习

1.easy_zip&#xff08;misc&#xff09; 1&#xff09;打开环境后是一个压缩包&#xff0c;解压里面有个flag.txt文件需要密码&#xff0c; 2&#xff09;直接用工具爆破&#xff0c;即可找到密码 2.easy_eval 1&#xff09;进入题目环境&#xff0c;先进行代码审计 首先说是…

让你的博客实现负载均衡

零、缘起 有时候博客突然挂了&#xff0c;发现服务器厂商出了问题&#xff0c;很忧伤&#xff0c;我正在写着或查阅自家博客那种不可xx的内容。这时想着&#xff0c;如果这个博客有负载均衡就好了&#xff0c;空了想着实现下。 一分钟了解负载均衡的一切 选择第二种【反向代…

MoE 大模型的前世今生

节前&#xff0c;我们星球组织了一场算法岗技术&面试讨论会&#xff0c;邀请了一些互联网大厂朋友、参加社招和校招面试的同学。 针对算法岗技术趋势、大模型落地项目经验分享、新手如何入门算法岗、该如何准备、面试常考点分享等热门话题进行了深入的讨论。 合集&#x…

Vue3整合Tailwindcss之padding样式类

04 常用基础样式 padding 样式类 什么是内边距 基础样式 ClassPropertiesp-0padding: 0px;px-0padding-left: 0px; padding-right: 0px;py-0padding-top: 0px; padding-bottom: 0px;ps-0padding-inline-start: 0px;pe-0padding-inline-end: 0px;pt-0padding-top: 0px;pr-0pa…

kafka-生产者监听器(SpringBoot整合Kafka)

文章目录 1、生产者监听器1.1、创建生产者监听器1.2、发送消息测试1.3、使用Java代码创建主题分区副本1.4、application.yml配置----v1版1.5、屏蔽 kafka debug 日志 logback.xml1.6、引入spring-kafka依赖1.7、控制台日志 1、生产者监听器 1.1、创建生产者监听器 package co…

图片批量纵向拼接神器:轻松插入间隔像素,生成真彩绚丽长图,让你的创意无限延伸!

在数字艺术的世界里&#xff0c;图片拼接早已不再是简单的组合&#xff0c;而是创意与技术的融合。如果你正在寻找一款能够快速将图片进行纵向拼接&#xff0c;并且能轻松插入间隔像素&#xff0c;同时保证色彩绚丽的神器&#xff0c;那么&#xff0c;我们首助编辑高手的长图拼…

如何实现单例模式及不同实现方法分析-设计模式

这是 一道面试常考题&#xff1a;&#xff08;经常会在面试中让手写一下&#xff09; 什么是单例模式 【问什么是单例模式时&#xff0c;不要答非所问&#xff0c;给出单例模式有两种类型之类的回答&#xff0c;要围绕单例模式的定义去展开。】 单例模式是指在内存中只会创建…

React 中的 ForwardRef的使用

React 中的 forwardRef Hooks 是指将子组件的 Dom 节点暴露给给父组件&#xff0c;在 React 中如果想要访问 Dom 节点是通过 useRef 这个 hooks&#xff0c;而 forwardHook 在 useRef 做了扩展。useRef 是当前组件中间中的节点&#xff0c;而 forwardRef 相当于做了一层封装将父…