【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接
【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接
目录
- 【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接
- 深入理解用户缓冲区
- 缓冲区刷新问题
- 缓冲区存在的意义
- File
- 模拟实现C语言中文件标准库
- 文件系统
- 认识磁盘
- 对目录的理解
- 软硬链接
- 软硬链接的删除
- 文件的三个时间
作者:爱写代码的刚子
时间:2023.11.5
前言:本篇博客将介绍缓冲区、磁盘的构成、分区,以及文件系统中的结构、软硬链接
深入理解用户缓冲区
观察几个现象:
正常现象一:

现象二:

现象三:

图示:

解释现象:
-
对于现象二:C语言文件操作函数中的字符串不带有’\n’,即数据还停留在C语言的缓冲区中,并未刷新和调用write()函数,将数据刷新到内核系统文件的缓冲区中,而write()函数由于是系统调用函数,能正常执行,但是由于close()函数将显示器文件关闭了,即使进程结束也不能将数据从缓冲区刷新到显示器中。(如果将close函数屏蔽,将会将C语言缓冲区中的数据刷新到显示器)
-
对于现象三:(1)在程序未进行重定向之前,默认是将数据写入显示器中,当遇到’\n’时,将数据从C语言的缓冲区中刷新到内核文件系统的缓冲区,fork()并不影响原本程序的运行。(2)在程序进行重定向之后,数据从向显示器写入变为了向文件中写入,缓冲区从行缓冲变为了全缓冲,所以此时的’\n’并不起作用,所以数据依然存储在C语言的缓冲区中,fork()函数创建子进程时子进程会将父进程的C语言缓冲区进行拷贝,当父子进程结束后将缓冲区里面的内容全部刷新,输出到显示器中。
- 显示器的文件的刷新方案是行刷新,所以在printf执行完就会在遇到’\n’的时候会立即进行刷新,用户刷新的本质就是将数据通过1+write写入到内核中。(目前我们认为,只要将数据刷新到了内核,数据就可以到硬件了)。
缓冲区刷新问题
- 无缓冲 ——直接刷新
- 行缓冲——不刷新,碰到’\n’刷新(显示器)
- 全缓冲——缓冲区满了才刷新(普通文件的写入)
- 进程退出的时候也会刷新缓冲区
缓冲区存在的意义
- 解决效率问题
- 配合格式化(printf中需要将%d等符号进行替换)
File
FILE里面含有对应打开文件等缓冲区字段和维护信息,同时FILE对象属于用户,这个缓冲区属于用户级缓冲区(语言属于用户层)
模拟实现C语言中文件标准库
- Mystdio.h文件:
#ifndef __MYSTDIO_H__
#define __MYSTDIO_H__
//open头文件
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
//
#include <unistd.h>
//
#include <string.h>
#include <stdlib.h>#define FILE_MODE 0666
#define SIZE 1024#define FLUSH_NOW 1//0001
#define FLUSH_LINE 2//0010
#define FLUSH_ALL 4//0100typedef struct IO_FILE{int fileno;int flag;char inbuffer[SIZE];int in_pos;char outbuffer[SIZE];int out_pos;//缓冲区的有效字符和无效字符分界
}_FILE;_FILE* _fopen(const char* filename,const char* flag);
int _fwrite(_FILE *fp,const char*s,int len);
void _fclose(_FILE *fp);#endif
- Mystdio.c文件:
#include "Mystdio.h"_FILE* _fopen(const char* filename,const char* flag)
{int f=0;int fd=-1;if(strcmp(flag,"w")==0){f=O_CREAT|O_WRONLY|O_TRUNC;fd= open(filename,f,FILE_MODE);}else if(strcmp(flag,"r")==0){f=O_CREAT|O_RDONLY|O_TRUNC;fd= open(filename,f,FILE_MODE);}else if(strcmp(flag,"a")==0){f=O_APPEND;fd= open(filename,f);}else{return NULL;}if(fd==-1) return NULL;//创建文件结构对象_FILE* fp=(_FILE*)malloc(sizeof(_FILE));if(fp==NULL)return NULL;fp->fileno = fd;fp->flag= FLUSH_LINE;fp->out_pos=0;return fp;
}
int _fwrite(_FILE *fp,const char*s,int len)
{memcpy(&fp->outbuffer[fp->out_pos],s,len);//这里省略了异常处理fp->out_pos+=len;if(fp->flag & FLUSH_NOW){write(fp->fileno,fp->outbuffer,fp->out_pos);fp->out_pos=0;}else if(fp->flag & FLUSH_LINE){if(fp->outbuffer[fp->out_pos-1]=='\n')//'\n'可能出现在字符中间,这时候需要对字符串做裁剪{write(fp->fileno,fp->outbuffer,fp->out_pos);fp->out_pos=0;}}else if(fp->flag & FLUSH_ALL){if(fp->out_pos==SIZE){write(fp->fileno,fp->outbuffer,fp->out_pos);//全写出去fp->out_pos=0;}}return len;//成功写入的长度
}
void _fflush(_FILE *fp)
{if(fp->out_pos>0){write(fp->fileno,fp->outbuffer,fp->out_pos);fp->out_pos=0;}
}
void _fclose(_FILE *fp)
{if(fp==NULL)return ;_fflush(fp);close(fp->fileno);free(fp);
}
Main.c文件:
#include "Mystdio.h"#define myfile "test.txt"
int main()
{_FILE* fp=_fopen(myfile,"w");if(fp==NULL){return 1;}int cnt =10;while(cnt--){const char* msage = "hello Linux\n";_fwrite(fp,msage,strlen(msage));sleep(1);}//_fflush(fp)return 0;
}
测试:
makefile文件:

while :;do cat test.txt;sleep 1;echo “----------------------”;done持续打印文件内容

一般C库函数写入文件时是全缓冲的,而写入显示器是行缓冲。 printf fwrite 库函数会自带缓冲区(进度条例子就可以说明),当发生重定向到普通文件时,数据的缓冲方式由行缓冲变成了全缓冲。而我们放在缓冲区中的数据,就不会被立即刷新,甚至fork之后 但是进程退出之后,会统一刷新,写入文件当中。 但是fork的时候,父子数据会发生写时拷贝,所以当你父进程准备刷新的时候,子进程也就有了同样的 一份数据,随即产生两份数据。write没有变化,说明没有所谓的缓冲。
printf fwrite 库函数会自带缓冲区(用户级缓冲区),而 write系统调用没有带缓冲区。(为了提升整机性能,OS也会提供相关内核级缓冲区)
文件系统
认识磁盘




- 磁头是一面一个,磁头和盘面不接触,磁头臂会进行摆动来定位柱面或磁道(磁臂运动越少,效率越高,反之越低),所以在软件设计上一定要有意识将相关数据放在一起
磁盘的逻辑结构是线性的(对磁盘理解和建模)

- 扇区的一般大小是512字节或者4kb
不仅CPU有寄存器,其他设备(外设)也存在寄存器

对磁盘进行分区

关于inode中的block数组:
inode中有struct inode结构体,里面包括了文件所有的属性,和一个blocks[15]数组,其中的下标[0, 11]直接保存的就是该文件对应的blocks编号,下标[12, 15]指向一个datablock,但是这个datablock不保存有效数据,而保存文件所适用的其它块的编号。(相当于一个二级索引)
stat +文件名查看文件的具体信息

ls -lia查看所有文件的信息(包括文件的inode)
ls -i查看当前目录下各文件的inode编号

启动块的大小是确定的,而块组的大小是由格式化的时候确定的,并且不可以更改。
文件 = 内容 + 属性,二者都是数据,都要存储。Linux采用的是将内容和属性数据分开存储的方案,内容在block中(4KB),内容是可以无限增多的。属性数据在inode中(128字节),文件的属性是稳定的。
注意一个要点,inode可能会存在用完的情况
- Linux系统中,一个文件一个inode,每一个inode。每一个inode都有自己的inode编号(inode的设置是以分区为单位的,不能跨分区)
对目录的理解
Linux下一切皆文件,目录也是一个文件,通过文件名(对应的inode编号)-> 找到自己所处的目录 -> 根据目录的inode,找到目录的data block -> 将文件名和inode编号的映射关系写入到目录的数据块中。


- 问题1:为什么同一个目录下不能存在同名文件?
因为一个文件只存在一个inode,文件名是作为key值去找inode的,如果存在同名文件就会破坏对应的映射关系。
- 问题2:为什么没有w权限就不能创建文件?
即便是能创建文件,也不能将该文件与inode的映射关系写到数据块中。
- 问题3:为什么没有r权限就不能查看文件?
无法拿到该目录文件中文件与inode之间的映射关系
- 问题4:为什么没有x权限就不能进入目录?
不让用户更改环境变量中的目录信息
查找任何一个文件时,我们必须从当前目录递归到根目录,然后从根目录信息中查找对应子目录的inode信息(效率会低)
所以Linux中会存在提升效率的方法,将常用的路径信息进行缓存(dentry缓存,里面的结构算法较复杂)
软硬链接
建立软链接



软链接的inode不同
软连接又叫符号链接,软连接文件相当于源文件来说是一个独立的文件,该文件有自己的inode号,但是该文件只包含了源文件的路径名,所以软连接文件的大小要比源文件小得多。软连接就类似于Windows操作系统当中的快捷方式。软链接保存的是对应文件的所在路径
ln -s 对应的路径 软链接的名字添加快捷方式
建立硬链接



硬链接的inode相同,同时文件的引用计数变为了2
- 硬连接数本质就是该文件inode属性中的计数器count,标识有几个文件名和我的inode建立了映射关系。简言之,就是有几个文件名指向我的inode(文件本身)硬链接就是让多个不在或者同在一个目录下的文件名,同时能够修改同一个文件,其中一个修改后,所有与其有硬链接的文件都一起修改了。
为什么文件被创建出来,默认的硬连接数是1?
- 如果硬链接数是0,那么就应该是被关闭的文件了,所以至少应该从1开始。此外,普通文件的文件名,本身就和自己的inode具有映射关系,且只有1个,所以文件的默认硬连接数是1。
创建一个新目录时引用计数默认为2


- 我们也可以根据系统的硬连接数,不进入文件,从而估算出文件的目录数(一个目录下相邻的子目录数 = 该目录的硬连接数 - 2)。因此,硬链接的一个作用就是进行路径切换。
软硬链接的删除
unlink(unlink也可以删除普通文件,与rm没什么区别)

文件的三个时间

这其中包含了文件的三个时间信息:
- Access: 文件最后被访问的时间。
- Modify: 文件内容最后的修改时间。
- Change: 文件属性最后的修改时间。
当我们修改文件内容时,文件的大小一般会随之改变,所以Modify的改变会带动Change一起改变,但对该文件属性一般不会影响文件内容,所以一般情况下Change的改变不会带动Modify的改变。此外,我们可以使用touch命令把这三个时间都更新到最新状态。(当一文件存在时使用touch命令,此时touch命令的作用变为更新文件信息)。
相关文章:
【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接
【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接 目录 【Linux基础IO篇】用户缓冲区、文件系统、以及软硬链接深入理解用户缓冲区缓冲区刷新问题缓冲区存在的意义 File模拟实现C语言中文件标准库 文件系统认识磁盘对目录的理解 软硬链接软硬链接的删除文件的三个时间 作者…...
电脑软件:推荐一款电脑多屏幕管理工具DisplayFusion
下载https://download.csdn.net/download/mo3408/88514558 一、软件简介 DisplayFusion是一款多屏幕管理工具,它可以让用户更轻松地管理连接到同一台计算机上的多个显示器。 二、软件功能 2.1 多个任务栏 通过在每个显示器上显示任务栏,让您的窗口管理更…...
免费好用的网页采集工具软件推荐
在众多各具特色的采集器软件中,真正好用的采集器软件有哪些? 自己一个个去查找和尝试无疑会耗费大量的时间和精力。 因此,在深入体验大多数采集器后,给大家推荐几款优秀且好用的免费网页采集器软件。 本文将对这几款采集器进行…...
6.ELK之Elasticsearch嵌套(Nested)类型
0、前言 在Elasticsearch实际应用中经常会遇到嵌套文档的情况,而且会有“对象数组彼此独立地进行索引和查询的诉求”。在ES中这种嵌套文档称为父子文档,父子文档“彼此独立地进行查询”至少有以下两种方式: 1)父子文档。在ES的5.…...
RefConv: 重参数化的重新聚焦卷积(论文翻译)
文章目录 摘要1、简介2、相关研究2.1、用于更好性能的架构设计2.2、结构重参数化2.3、权重重参数化方法 3、重参数化的重聚焦卷积3.1、深度RefConv3.2、普通的RefConv3.3、重聚焦学习 4、实验4.1、在ImageNet上的性能评估4.2、与其他重参数化方法的比较4.3、目标检测和语义分割…...
指令重排序
指令重排序是现代处理器在执行指令时的一种优化技术,其目的是为了提高处理器执行指令的效率。这种优化手段会对指令进行重新排序,以提高并行度和性能。 为何会发生指令重排序: 处理器性能优化: 为了更好地利用现代处理器的流水线、…...
【Head First 设计模式】-- 观察者模式
背景 客户有一个WeatherData对象,负责追踪温度、湿度和气压等数据。现在客户给我们提了个需求,让我们利用WeatherData对象取得数据,并更新三个布告板:目前状况、气象统计和天气预报。 WeatherData对象提供了4个接口: …...
JavaWeb篇_01——JavaEE简介【面试常问】
JavaEE简介 什么是JavaEE JavaEE(Java Enterprise Edition),Java企业版,是一个用于企业级web开发平台,它是一组Specification。最早由Sun公司定制并发布,后由Oracle负责维护。在JavaEE平台规范了在开发企业级web应用…...
QtC++与QRadioButton详解
介绍 QRadioButton 是 Qt 中的一个重要部件,用于创建单选按钮,它有以下几个主要作用和特点: 单选功能: QRadioButton 用于创建单选按钮,用户可以从一组互斥的选项中选择一个。这在用户界面设计中常用于需要用户从多个…...
移远EC600U-CN开发板 day01
1.官方文档快速上手,安装驱动,下载QPYcom QuecPython 快速入门 - QuecPython (quectel.com)https://python.quectel.com/doc/Getting_started/zh/index.html 注意: (1)打开开发板步骤 成功打开之后就可以连接开发板…...
【C/C++】什么是POD(Plain Old Data)类型
2023年11月6日,周一下午 目录 POD类型的定义标量类型POD类型的特点POD类型的例子整数类型:C 风格的结构体:数组:C 风格的字符串:std::array:使用 memcpy 对 POD 类型进行复制把POD类型存储到文件中,并从文…...
注册虾皮买家号需要哪些资料?
注册虾皮买家号其实是很简单的,使用相应国家的手机号及对应的环境就可以注册了的,如果想要账号更方便使用,也可以绑定邮箱进行认证。 而如果想要使用shopee买家通系统进行自动化的注册,那么对于资料就有一定的要求了。 1、手机号…...
小腿筋膜炎怎么治疗最有效
小腿筋膜炎症状主要有疼痛、肌肉紧张、活动受限等。 1.疼痛:小腿筋膜炎主要会导致炎症性疼痛,没有固定的压痛点,通常以踝关节、膝关节活动时疼痛为主。疼痛呈持续性,或者反复发作,尤其是在晨起或者天气变化、劳累、受…...
After Effects 2024 v24.0.2(AE2024)
After Effects 2024是视频特效和动态图形设计软件。以下是After Effects 2024的主要功能和特点: 支持创建各种令人惊叹的视觉效果,例如粒子系统、合成特效、绿屏抠像等。支持动画制作,包括关键帧动画、形状动画、运动跟踪等工具,…...
自己实现一个自动检测网卡状态,并设置ip地址
阅读本文前,请先学习下面几篇文章 《搞懂进程组、会话、控制终端关系,才能明白守护进程干嘛的?》 《简简单单教你如何用C语言列举当前所有网口!》 《Linux下C语言操作网卡的几个代码实例!特别实用》 《安卓如何设置…...
【Linux】进程程序替换
文章目录 替换原理站在进程的角度站在程序的角度初体验及理解原理 替换函数函数解释命名理解exec系列函数与main函数之间的关系在一个程序中调用我们自己写的程序 替换原理 创建子进程的目的是什么? ->想让子进程执行父进程代码的一部分 执行父进程对应的磁盘代码…...
项目构建工具maven的基本配置+idea 中配置 maven
👑 博主简介:知名开发工程师 👣 出没地点:北京 💊 2023年目标:成为一个大佬 ——————————————————————————————————————————— 版权声明:本文为原创文…...
【解密ChatGPT】:从过去到未来,揭示其发展与变革
🎊专栏【ChatGPT】 🌺每日一句:天行健,君子以自强不息,地势坤,君子以厚德载物 ⭐欢迎并且感谢大家指出我的问题 文章目录 一、ChatGPT的发展历程 二、ChatGPT的技术原理 三、ChatGPT的应用场景 四、ChatGPT的未来趋势 五、总结 引言:随着…...
系统架构设计】计算机公共基础知识: 5 数学与经济管理
一 运筹方法 1 线性规划 线性规划问题的数学模型通常由线性目标函数、线性约束条件、变量非负条件组成,特点如下: (1)线性规划的可行解域是由一组线性约束条件形成的。 (2)如果存在两个最优解,则连接这两点的线段内所有的点都是最优解,而线段两端延长线上可能会超出…...
Visual Studio 2019光标变成灰色方块问题
文章目录 Visual Studio 2019光标变成灰色方块问题问题描述解决方案 Visual Studio 2019光标变成灰色方块问题 问题描述 单击和双击都无法选中单词,总是选择整行或者是当前光标处的前几个字符一起选中,没有规则,貌似选择单词复制࿰…...
华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...
.Net框架,除了EF还有很多很多......
文章目录 1. 引言2. Dapper2.1 概述与设计原理2.2 核心功能与代码示例基本查询多映射查询存储过程调用 2.3 性能优化原理2.4 适用场景 3. NHibernate3.1 概述与架构设计3.2 映射配置示例Fluent映射XML映射 3.3 查询示例HQL查询Criteria APILINQ提供程序 3.4 高级特性3.5 适用场…...
【Linux】C语言执行shell指令
在C语言中执行Shell指令 在C语言中,有几种方法可以执行Shell指令: 1. 使用system()函数 这是最简单的方法,包含在stdlib.h头文件中: #include <stdlib.h>int main() {system("ls -l"); // 执行ls -l命令retu…...
华为OD机试-食堂供餐-二分法
import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...
【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...
佰力博科技与您探讨热释电测量的几种方法
热释电的测量主要涉及热释电系数的测定,这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中,积分电荷法最为常用,其原理是通过测量在电容器上积累的热释电电荷,从而确定热释电系数…...
Netty从入门到进阶(二)
二、Netty入门 1. 概述 1.1 Netty是什么 Netty is an asynchronous event-driven network application framework for rapid development of maintainable high performance protocol servers & clients. Netty是一个异步的、基于事件驱动的网络应用框架,用于…...
SQL Server 触发器调用存储过程实现发送 HTTP 请求
文章目录 需求分析解决第 1 步:前置条件,启用 OLE 自动化方式 1:使用 SQL 实现启用 OLE 自动化方式 2:Sql Server 2005启动OLE自动化方式 3:Sql Server 2008启动OLE自动化第 2 步:创建存储过程第 3 步:创建触发器扩展 - 如何调试?第 1 步:登录 SQL Server 2008第 2 步…...
微服务通信安全:深入解析mTLS的原理与实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、引言:微服务时代的通信安全挑战 随着云原生和微服务架构的普及,服务间的通信安全成为系统设计的核心议题。传统的单体架构中&…...
JS红宝书笔记 - 3.3 变量
要定义变量,可以使用var操作符,后跟变量名 ES实现变量初始化,因此可以同时定义变量并设置它的值 使用var操作符定义的变量会成为包含它的函数的局部变量。 在函数内定义变量时省略var操作符,可以创建一个全局变量 如果需要定义…...
