目录

一、进程和文件的关系

二、背景补充

三、打开文件接口

(1) FILE *fopen(const char* filename , const char *mode)

(2)open 系统调用

文件描述符

open和fopen的关系

(3)size_t fwrite(const void * ptr, size_t size, size_t nmemb,FILE*stream)

(4)size_t fread(void *buffer, size_t size,size_t nmemb,FILE  *stream)

四、输入输出流

(1)重定向 

追加重定向的原理

重定向接口

五、struct file

六、文件继承

七、理解一切皆文件(硬件方向)

八、文件缓冲区

1、缓冲区的理解

2、缓冲区的刷新策略

3、FILE缓冲区

(1)引入

(2)本质

(3)FILE刷新方式(缓冲区到内核缓冲区)

(4)一个现象

九、内核文件区

(1)内核文件缓冲区的刷新方式

(2)接口


一、进程和文件的关系

二、背景补充

  • 文件 = 内容 + 属性  所以对文件操作本质 就分为 对内容做操作 或者对属性做操作
  • 访问一个文件,都必须先把对应的文件打开 因为根据冯诺依曼我们对文件进行操作的时候必须要要保证文件在内存中,而打开一个一个文件就是把这个文件加载到内存(本质就是把文件的属性和内容加载到内存中)
  • 如果一个文件没有被打开,他就在磁盘中,此磁盘的管理者是操作系统
  •  进程(用户通过bash,启动进程(进程通过操作系统))打开文件对文件操作本质是进程对文件的操作
  • OS内,一定同时存在大量的被打开的文件(通过数据结构管理被打开的文件)

三、打开文件接口

(1) FILE *fopen(const char* filename , const char *mode)

  • 我们可以发现以w方式打开文件,如果文件不存在,会在当前工作路径下创建一个文件,为什会在当前路径下本质是因为每一个进程在运行起来都一个cwd,默认会在cwd的后面在加上当前文件名进行创建
  • 打开文件,必须要先找到文件,要找到文件就必须要知道文件的路径+文件名,这也是为什么进程要有cwd的原因之一

方式

  • w :文件不存在创建,文件存在清空
  • w+: 文件不存在就创建,读写打开文件
  • r :   读文件
  • r+: 读写打开文件
  • a :    不清空文件,从当前文件的结尾处追加
  • a+ : 读和追加
  • 读写文件有读写位置:在我看来文件就是一个"一维数组"所以读写位置就是数组下标
  • > : 重定向就是以w的方式打开文件
  • >>:追加重定向就是以a+的方式打开文件

(2)open 系统调用

int open(const char * pathname , int flags ,mode_t mode)

   pathname:

  • 若pathname以路径的方式给出,则当需要创建该文件时,就在pathname路径下进行创建。
  • 若pathname以文件名的方式给出,则当需要创建该文件时,默认在当前路径下进行创建。(注意当前路径的含义)

flags:  

打开方式(标志位): 首先它的类型为int 有32个比特位,一个比特位就有一个标志位(本质是宏)在open函数内部就可以通过使用与运算来判断是否设置了某一选项

例:

mode:  权限

放回值: 失败-1 ,成功文件描述符

文件描述符

  • 因为进程和文件的比例关系为 1 : n 且 OS内,一定存在大量被打开的文件,操作系统对这些文件进行管理(struct file),这些对这些文件进行管理变成对struct file链表的增删查改,但是这么多文件,是被多个进程打开的,系统需要表示那个文件是由那个进程打开的由此产生了文件描述符
  • 我们知道,当一个程序运行起来时,操作系统会将该程序的代码和数据加载到内存,然后为其创建对应的task_struct、mm_struct、页表等相关的数据结构,并通过页表建立虚拟内存和物理内存之间的映射关系。
  • 而task_struct当中有一个指针,该指针指向一个名为files_struct的结构体,在该结构体当中就有一个名为fd_array的指针数组,该数组的下标就是我们所谓的文件描述符。
  • 当进程打开文件时,我们需要先将该文件从磁盘当中加载到内存,形成对应的struct file,将该struct file连入文件双链表,并将该结构体的首地址填入到fd_array数组当中下标为3的位置,使得fd_array数组中下标为3的指针指向该struct file,最后返回该文件的文件描述符给调用进程即可。

 

  • 文件描述符本质是数组下标,在OS内部,OS识别被打开的文件,OS只认fd
open和fopen的关系

为什么c语言要封装文件操作接口

系统调用太麻烦了   跨平台性  可移植性 

为什么大部分的语言,都对系统调用做封装

需要具有跨平台性(增加语言的竞争性)

 如何做到跨平台性

所有版本的都写一遍

(3)size_t fwrite(const void * ptr, size_t size, size_t nmemb,FILE*stream)

      ptr:   写入的起始地址 

      size:写入的基本单元的大小 

      nmemb:  写几个   

      stream:  向哪一个文件流中写

注意:我们不需要strlen(str)+1 将字符串的\0带上,等到读取字符串的时候加上就行了

(4)size_t fread(void *buffer, size_t size,size_t nmemb,FILE  *stream)

              buffer:   读到那   

              size:    基本单元       

             nmemb:几个           

            stream : 从哪里读

写一个cat

        

  •  补充: 今天我们向显示器写入1234就是向显示器中写入了"1" "2" "3" 所以显示器叫做字符设备 。我们向键盘输入1234,我们实际上输入的是"1" "2" "3" "4" 所以键盘叫做字符设备
  • 所以我们使用printf()函数的时候必须要使用%d  % f,它的本质是将我们输入的数据打散为字符(这就叫格式化输入)
  • scanf()同理就叫做格式化输入
  • 显示器和键盘是文本文件,而二进制文件不需要做格式化工作(可以使用fwrite)

四、输入输出流

  • stdin; stdout ; stderr;(进程在启动的时候,默认会打开三个输入输出流,就是这三个文件)
  • 标准输入 标准输出  标准错误

为什么默认打开他们

因为进程大多数都是使用CPU资源进行计算的,都需要有数据的输入,输出结果,输出错误(所以他们默认占据文件描述符的012)

 

  • 文件描述符的分配规则:给新打开的文件分配fd,从文件描述符表数组中寻找:最小的,没有被使用的下标,作为作为改文件的fd 

 

(1)重定向 

  • 输出重定向就是,将我们本应该输出到一个文件的数据重定向输出到另一个文件中
  • 例如:关闭了1 打开文件,默认会将文件描述符1给他,printf默认会向stdout里面打印,但是stdout默认封装了文件描述符1 ,所以会像log.txt里面打印,这就叫输出重定向
  • stdout指向是一个struct FILE 结构体,该结构体当中有一个文件描述符变量,stdout的文件描述符变量就是1

追加重定向的原理

输出重定向和追加重定向唯一的区别就是输出重定向是先清空文件,而追加重定向是追加式输出

重定向接口

int dup2 (int oldfd ,int newfd)

注意:如果oldfd不是有效的文件描述符,则dup2调用失败

           如果oldfd是一个有效的文件描述符,但是newfd和oldfd具有相同的值则直接放回newold

返回值:成功返回文件描述符,失败返回-1

 

五、struct file

 

  •  在调用open之前已经有了task_struct 和 struct files_struct表
  • 当我们在使用write(文件描述符,“要写入的内容”)的时候,我们通过task_struct找到文件描述符表,通过文件描述符找到对应的struct file,然后将要写入的内容写到文件内核的缓冲区,在通过文件内核刷新到磁盘中
  • 所以write根本不是写入到文件,本质是拷贝函数,把数据从用户空间拷贝到对应文件的内核缓冲区
  • 什么时候刷新到磁盘文件中,由OS决定
  • 读数据也只能从文件内核缓冲区中读,如果文件内核缓冲区没有内容,需要等待磁盘刷新到磁盘中
  • 我们进行任何文件内容的增删查改都必须把文件的内容提前预加载到该文件的文件内核缓冲区

六、文件继承

  • 所以父子printf的时候,会同时向同一个显示器文件,进行打印
  • 对于子进程来讲,他继承了父的进程,所以子进程默认打开标准输入,标准输出,标准错误
  • file(struct)有一个引用计数,表示改文件由多少struct_file指向我,当引用计数为1的时候才能真正关闭文件

七、理解一切皆文件(硬件方向)

  • 我们知道操作系统对硬件会进行管理(描述在组织)形成对应的链表,他们每一个都有对应的实现读写的方法
  •  在打开一个文件的时候,会为我们创建struct_file 包含属性集合,,文件内核缓冲区,还会有一些方法集合指向对应硬件的实现方法,在将stuct_file的地址写入到struct file_struct的表中,所以站在进程视角下,就是一切皆文件

八、文件缓冲区

1、缓冲区的理解

  • 缓冲区的本质,其实就是一段内存空间

  • 如上图张三如果像给李四送一个礼物,如果张三自己去把礼物给李四,那么会花费大量张三的时间,但是如果将礼物给菜鸟驿站,站在张三的视角下礼物已经给了出去,且节省了他的时间,缓存的数据就是礼物,礼物给菜鸟驿站就等于write
  • 缓存最大的意义:是提高使用缓存的进程效率,允许进程单位时间内做更多的工作,变相的提高了使用者的效率
  • 在菜鸟驿站的角度下来看,他不可能收到一件商品就给他寄出去,所以缓冲区允许数据积压,以一次,就可以刷新多次数据,变相的减少IO的次数)

2、缓冲区的刷新策略

三种形式:

  • 无缓冲,立即刷新   
  • 有缓冲,行刷新 (显示器中使用) 
  • 有缓冲,写满刷新(普通文件采用者这种方式)

两种情况 :

  • 进程退出,主动刷新 
  • 进程强制刷新fflush

3、FILE缓冲区

(1)引入

  • 上图的代码执行的时候先执行的是printf但是当我们运行起来的时候会发现是先暂停的3,然后屏幕上在出现的hello world,,这是因为printf的打印先开始是打印到文件缓冲区中的等到进程结束的时候才刷新出来的,这里的缓冲区是FILE缓冲区,不是内核文件缓冲区
(2)本质
  • struct FILE本质是一个结构体,包含文件描述符
  • C语言上,输入输出格式化
  • C访问文件,都是通过FILE访问的,包括stdin stdout stderr
  • FILE结构体内部为我们维护语言级别的缓冲区

 过程

  • 如上图首先通过fget(),scanf() 将键盘中输入的内容获取,在通过fput() (底层调用write)将获取的内容拷贝到struct FILE文件缓冲区中,通过刷新将内容刷新到文件内核缓冲区中,同过操作方法集刷新到磁盘中
(3)FILE刷新方式(缓冲区到内核缓冲区)
  • 无,立即刷新
  • 行,行刷新(显示器文件)
  • 满,全刷新

缓冲区在哪?

FILE内部

为什么要用语言级别缓冲区 

调用系统调用是用成本的

C语言为什么要提供语言级别的缓冲区

加速IO函数的调用效率 (加快使用C语言IO接口的效率,单位时间内执行C代码的行数,就多了)

 如何理解printf scanf的格式化过程   

1、格式化      2、格式化结果写入到FILE缓冲区中   3、检测是否需要刷新  4、 如果需要刷新调用write

例:

  • 如上的代码,我们会发现log.txt中没有内容,因为当我们关闭文件描述符1,在打开log.txt他的文件描述符默认就是为1,printf()是向stdout中进程打印,stdout 中默认封装了文件描述符1,所以printf()回向log.txt中打,但是他有文件缓冲区不会立即刷新到log.txt,默认文件关闭的时候刷新到内核缓冲区,但是这时候我们关闭了文件。 所以他会刷新到log.txt
(4)一个现象

 

  • 我们能发现上面的接口我们打印到显示器上,只会打印一次,但是我们重定向到文件中write,打印一次,其他的都会打印二次
  • 首先打印到显示器中有\n他按行刷新, 所以他每一个都只打印一个
  • 当他打印到文件中他的刷新规则变成了写满刷新,所以只有退出的时候才会刷新,write是直接写到内核中的,所以他是第一个打印的,其他的都在内核缓冲区中,当退出的时候fork()父子进程都有自己的stdout,指向同一个缓冲区,当要进行清空缓冲区的时候,就是要进行改数据,会发生写时拷贝,从而打印两次

九、内核文件区

(1)内核文件缓冲区的刷新方式

  • 一般而言:全刷新
  • 显示器:行刷新
  • 单独的执行流,根据内存的使用方式来动态刷新,即使刷新条件不满足

(2)接口

int fsync(int fd)

强制从内核 ——》外设

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

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

相关文章

SpringBoot快速上手

SpringBoot快速上手 环境准备 IDEA版本: 社区版:2021.1-2022.1.4 专业版:无要求 Maven 官方对于Maven的描述: Maven是一个项目管理工具,基于POM(Project Object Model,项目对象模型)的概念,Maven可以通过一小段描述信息来管理项目的构建,报告文档和项目管理工具软件. 人…

GitHub Actions workflow最佳实践

使用 GitHub Actions Workflow 时,遵循最佳实践可以显著提升自动化效率、安全性和可维护性。以下是经过实践验证的核心最佳实践,涵盖配置设计、性能优化、安全防护等维度,并附具体示例: 一、工作流组织与触发优化 1. 拆分工作流&a…

JAVA读取项目内的文件或图片

一、读取resources下的文件或图片;文件或图片位置:代码:InputStream fis Thread.currentThread().getContextClassLoader().getResourceAsStream("template/" xxx.jpg);二、读取项目内任意位置的文件或图片。文件或图片位置&…

Python如何将两个列表转化为一个字典

一、使用zip函数 zip函数是Python内置的一个强大工具,它可以将多个迭代器(如列表、元组等)“压缩”成一个迭代器,其中每个元素都是一个元组。使用zip函数将两个列表转换为字典是最常见的方法。 1、基本用法 keys [a, b, c] value…

Vue 3 useModel vs defineModel:选择正确的双向绑定方案

&#x1f4d6; 概述 useModel() 是 Vue 3.4 版本中引入的一个组合式 API 辅助函数&#xff0c;它是驱动 defineModel() 的底层实现。这个函数主要用于在非单文件组件中实现双向数据绑定&#xff0c;特别是在使用原始的 setup() 函数时。 ⚠️ 重要提示&#xff1a;如果使用 <…

数据库备份sql文件过大,phpAdmin无法执行Sql

数据库导出为sql文件&#xff0c;文件太大导致无法再Sql query执行&#xff0c;可使用命令行执行&#xff1a; windows系统&#xff1a; 1.切换到mysql 安装目录的bin目录下 cd C:\xampp\mysql\bin 2.执行备份sql还原mysql数据库 mysql -u root -p databasename < C://backu…

三格电子——ModbusTCP 转 Profinet 主站网关应用实例

型号&#xff1a;SG-TCP- Profinet(M)一、使用场景ModbusTCPClient 通过 ModbusTCP 控制 Profinet 接口设备&#xff0c; Profinet 接口设备接入DCS/工控机等。产品是ModbusTCP和Profinet(M)网关&#xff08;以下简称网关&#xff09;&#xff0c;使用数据映射 方式工作。 本产…

网址账号正确,密码错误返回的状态码是多少

问题网址账号正确&#xff0c;密码错误返回的状态码是多少我的回答当账号正确但密码错误时&#xff0c;服务器通常会返回401 Unauthorized状态码。这个状态码表示"未授权"&#xff0c;意味着客户端请求缺乏有效的身份验证凭据&#xff0c;或者提供的凭据无效。在实际…

JAVA核心基础篇-操作符

Java 操作符是用于操作变量和值的特殊符号&#xff0c;主要分为以下几类&#xff1a;1. 算术运算符用于执行基本的数学运算&#xff1a;&#xff1a;加法&#xff08;也可用于字符串拼接&#xff09;-&#xff1a;减法*&#xff1a;乘法/&#xff1a;除法&#xff08;整数相除取…

数据库字段类型深度解析:从关系型到 NoSQL 的全面指南

数据库字段类型深度解析&#xff1a;从关系型到 NoSQL 的全面指南 一、引言&#xff1a;数据库字段类型的重要性 在现代软件开发和数据管理中&#xff0c;数据库作为核心组件&#xff0c;其性能、可扩展性和数据完整性在很大程度上取决于字段类型的选择。作为专业的开发者和数据…

蓝牙aoa仓库管理系统功能介绍

在现代仓储物流的快节奏运作中&#xff0c;高效管理仓库人员的位置与行动轨迹&#xff0c;成为提升整体运营效率的关键。蓝牙AOA&#xff08;Angle of Arrival&#xff0c;信号到达角&#xff09;技术应运而生&#xff0c;以其独特的优势和强大的功能&#xff0c;为仓库人员定位…

【轻量级密码算法】当安全遇上资源瓶颈:轻量级加密为何成为 IoT 时代的刚需?

在智能家居的场景中&#xff0c;当你轻触智能门锁的指纹识别区域&#xff0c;期望它能快速响应并解锁时&#xff0c;你是否想过在这短短几秒内&#xff0c;门锁内部的微控制器&#xff08;MCU&#xff09;正在进行着复杂的安全验证操作&#xff1f;然而&#xff0c;对于大多数资…

嵌入式开发学习———Linux环境下网络编程学习(四)

数据库简介数据库是结构化数据的集合&#xff0c;用于高效存储、检索和管理数据。常见的数据库类型包括关系型&#xff08;如MySQL、SQLite&#xff09;和非关系型&#xff08;如MongoDB&#xff09;。关系型数据库使用表格形式存储数据&#xff0c;并通过SQL&#xff08;结构化…

在 CentOS 7 上搭建 OpenTenBase 集群:从源码到生产环境的全流程指南

目 录什么是OpenTenBaseOpenTenBase源码编译安装安装依赖创建opentenbase用户源码获取编译安装初始化数据库初始化数据库集群启动与停止服务基本使用示例开机自启动配置总结官网教程链接什么是OpenTenBase OpenTenBase 是一个提供写可靠性&#xff0c;多主节点数据同步的关系数…

LoRaWAN网络部署全流程:从方案设计到实际落地的关键要点

一、覆盖范围&#xff1a;从理论到实践 LoRaWAN的覆盖距离在理论上可达15公里&#xff0c;但实际部署受地形和环境影响极大。 城市环境中&#xff0c;密集的建筑群和多径效应常常使网关有效覆盖半径缩小至3至5公里&#xff1b;在空旷的农村或农田场景中&#xff0c;覆盖范围可提…

portswigger labs XXE漏洞利用实战

lab1 利用外部实体注入获取文件解决此 lab 需要读取到/etc/passwd<!DOCTYPE test [ <!ENTITY cmd SYSTEM "file:///etc/passwd"> ]> <productId>&cmd;</productId>lab2 利用 XXE 执行 SSRF 攻击通过构造 xxe 请求特定的 url 获取目录拼接…

深入理解 hash -r:解决 Linux 命令缓存难题的关键密钥

前言&#xff1a;在 Linux 终端的日常操作中&#xff0c;你是否遇到过这样的诡异场景&#xff1a;明明已经升级或切换了软件版本&#xff08;比如 Node.js 从旧版更新到新版 &#xff09;&#xff0c;但执行命令时&#xff0c;系统却像被“施了魔法”&#xff0c;依旧执着地调用…

onnx入门教程(二)—— PyTorch 转 ONNX 详解

在这一节里&#xff0c;我们将详细介绍 PyTorch 到 ONNX 的转换函数—— torch.onnx.export。我们希望大家能够更加灵活地使用这个模型转换接口&#xff0c;并通过了解它的实现原理来更好地应对该函数的报错&#xff08;由于模型部署的兼容性问题&#xff0c;部署复杂模型时该函…

嵌入式LINUX——————网络TCP

一、TCP连接1.TCP特点&#xff1a;&#xff08;1&#xff09;面向链接&#xff08;2&#xff09;面向字节流&#xff08;3&#xff09;安全可靠的传输协议&#xff0c;因为会先建立连接&#xff08;4&#xff09;占用资源开销大&#xff0c;效率低&#xff0c;实时性不佳&#…

alicloud 阿里云有哪些日志 审计日志

1: 阿里有哪些audit log: Audit Related Logs Below table describe the logs available in Log Service that might be applicable to the Security Operations Team. 2: 怎么来分析呢? Overview Its recommended to built a program with SLS Consumer Group which real…