Linux文件系统理解1

目录

    • 一、初步理解系统层面的文件
      • 1. 文件操作的本质
      • 2. 进程管理文件核心思想
    • 二、系统调用层
      • 1. 打开关闭文件函数
      • 2. 读写文件函数
    • 三、操作系统文件管理
      • 1. 文件管理机制
      • 2. 硬件管理机制
    • 四、理解重定向
      • 1. 文件描述符分配规则
      • 2. 重定向系统调用
      • 3. 重定向命令行调用
    • 五、理解缓冲区
      • 1. 缓冲区介绍
      • 2. 缓冲区刷新策略
      • 3. 有趣现象

一、初步理解系统层面的文件

1. 文件操作的本质

在C语言里文件操作时,fopen打开文件,本质是cpu执行代码到这一行,进程帮我们创建相应的内核数据结构和相关初始化,打开文件本质是进程打开文件

2. 进程管理文件核心思想

一个进程可以打开多个文件,系统中有许多进程,所以大多数情况下,OS内部,一定存在大量的被打开的文件,同时,操作系统也要进行这些文件的管理

操作系统管理文件与管理进程的方式类似,先描述在组织,管理相应的结构体(类似于进程的pcb)

文件 = 属性 + 内容

二、系统调用层

1. 打开关闭文件函数

函数原型

#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);int creat(const char *pathname, mode_t mode);#include <unistd.h>int close(int fd);//关闭一个打开的文件,参数fd为打开文件时open的返回值#include <sys/types.h>
#include <sys/stat.h>mode_t umask(mode_t mask); //动态更改当前进程创建文件时的权限掩码

参数说明

pathname为要打开文件的名称

falgs参数是用bit位来进行标志的传递,即位图,其含义为代码打开方式(只读,只写,读写,追加……),falgs常用选项如下:

O_APPEND 写的时候追加写入

O_TRUNC 写的时候清空文件

O_WRONLY 写方式打开

O_RDONLY 写方式打开

O-CREAT 打开时若没有文件,则在进程当前工作路径下创建文件

mode为新创建文件时的权限,该权限会由系统的权限掩码计算后再给设置到新文件

可以通过umask函数在进程内更改该进程创建文件时的权限掩码

  • umask计算:最终权限 = mode & ~umask
  • 示例:open("file", O_CREAT, 0666) + umask=002 → 实际权限664

返回值

返回值fd,一个整数,称为文件描述符,对应一个文件内核数据结构(在下文操作系统管理中详细说明),在后续的文件写入或者输出时,传参的fd都是文件描述符

返回值小于零打开失败

返回值非零时,打开对应文件会返回对应的值,前3个默认打开,分别是

0:标准输入 stdin 键盘

1:标准输出 stdout 显示器

2:标准错误 stderror 显示器

前3个进程启动时默认打开,一般情况下我们自己打开或者创建文件返回值从3开始依次增加

1和2都对应着显示器,为什么同时默认打开1和2

1和2对应的文件都是显示器文件,区别就是,当我们进行标准输出重定向时,只会将1号文件进行重定向,2号不会被重定向。

默认同时打开的主要原因就是我们输出信息时,有正确的信息也有错误信息,只需做一次输出重定向就可以将错误信息和正确信息分离开,标准输出重定向只会将1号文件重定向,2号文件不会改变,依旧输出在显示器上。

  #include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>                                                                                  int main(){fprintf(stdout,"hello stdout \n");fprintf(stdout,"hello stdout \n");fprintf(stdout,"hello stdout \n");fprintf(stderr,"hello stderr \n");fprintf(stderr,"hello stderr \n");fprintf(stderr,"hello stderr \n");return 0;}

运行结果

在这里插入图片描述

也可以用两次重定向(在后文重定向中说明)将正确消息和错误消息分开放在不同的文件里,方便我们的调试查看信息。
在这里插入图片描述

2. 读写文件函数

       #include <unistd.h>ssize_t write(int fd, const void *buf, size_t count);
//向文件fd里面写入buf指向的内容,写入内容大小为count,返回值大于0表示实际写入的字节,等于0表示什么都没写,返回-1表示失败,并且设置错误码ssize_t read(int fd, void *buf, size_t count);//向buf里面读入fd文件的内容,读入内容大小为count,返回值大于0表示实际读取的字节,等于0表示读到了文件结尾,返回-1表示失败,并且设置错误码#include <sys/types.h>#include <sys/stat.h>#include <unistd.h>int stat(const char *path, struct stat *buf);  //通过路径获取文件属性//buf为输出型参数,文件属性存入buf指向的结构体,实现函数功能,成功返回0,失败返回-1,并且设置错误码int fstat(int fd, struct stat *buf);//通过文件描述符获取文件属性int lstat(const char *path, struct stat *buf); //通过路径获取文件属性

内核中的文件属性(struct stat):

struct stat {dev_t     st_dev;     // 设备IDino_t     st_ino;     // inode号mode_t    st_mode;    // 文件类型和权限nlink_t   st_nlink;   // 硬链接数uid_t     st_uid;     // 所有者UIDgid_t     st_gid;     // 组GIDoff_t     st_size;    // 文件大小(字节)// ... 时间戳等字段
};

在这里插入图片描述

三、操作系统文件管理

1. 文件管理机制

核心思想:先描述,在组织

内核级管理

每打开一个文件时,操作系统要创建相应的内核数据结构(struct file),并且会创建文件内核级的缓存(开辟的一块空间)。内核数据结构中存在指针指向该缓存,并且会用文件的属性去初始化内核数据结构,将文件的内容加载到文件缓存里。

一个进程可以打卡多个文件,操作系统需要建立进程和文件的对应关系,所以在进程的pcb(task_struct)中存在一个struct files_struct * files指针(指向文件描述符表),struct files_struct数据结构中包含struct file* fd_array[N],一个文件指针的数组,对应该进程所打开的文件的内核数据结构file,该数组下标就是打开文件时所返回的文件描述符。

操作系统提供的系统调用可以用文件描述符快速找到文件对应的内核数据结构进行操作,在进行读写时,都必须在合适的时候让OS把文件的内容读写在缓冲区,在进行刷新

在操作系统内,访问文件只认文件描述符fd

语言级管理

在C语言中,通过封装系统调用设计出来一系列的文件操作函数(fprintf,fscnaf,fopen),在配合C语言封装的文件结构体struct FILE,我们常常定义的文件指针FILE*就是这种结构的指针。

struct FILE中封装这文件描述符,语言级的缓冲区,打开方式等信息。

在这里插入图片描述

如图,int _fileno 为文件描述符,_falgs为文件打开方式, _IO_write_end为该缓冲区的结束……

C语言中所有的文件操作都是对系统调用的封装。

由于在不同的系统中系统调用是不同的,因此我们写的含有系统调的代码不具备跨平台性,但是我们用C语言库中的函数,他是具备跨平台性的,因为我们在不同的平台下有不同的C语言标准库,他们底层封装的系统调用是对应系统的系统调用。

文件打开流程

  1. 创建struct file对象
  2. 分配内核缓冲区(可延迟加载数据)
  3. 查进程的文件描述符表空闲的下标
  4. 存储file对象地址于文件描述符表中
  5. 返回fd下标

2. 硬件管理机制

在Linux系统中,一切皆文件

硬件设备都会有自己的共同的属性(名称,厂商,生产日期等),这些属性都被封装在结构体中(struct device),同时,不用的硬件也有自己独特的操作方法(驱动程序中实现),比如像显示器上输出,从键盘鼠标内读取数据等,这些方法都是驱动程序中一个个的函数。

在Linux系统中,打开或者使用某一个外设时,会像管理文件一样管理硬件,创建一个struct file(文件内核数据结构),存放着对应硬件设备使用的函数的函数指针,还有指向属性结构体(struct device)的指针和属于该硬件的缓冲区的指针,向硬件设备中读或者写数据时,先在缓冲区操作,然后将缓冲区内容刷新到设备或者内存。

不同的设备的驱动程序中,类似操作的函数参数要设计相同,因为在struct file中函数指针只有一套,但要调用不同设备的方法,参数相同才可以兼容

源码中部分函数指针:

在这里插入图片描述

由于struct file在进行文件管理时(硬件也看做文件),即有属性(struct device,文件属性),还有方法(操作底层方法指针表,或者操作文件的方法列表),因此这也是一种类的实现,并且用相同的函数名(函数指针)来操作不同类型的硬件设备或者文件,也是用C语言实现的多态技术,这种管理技术在Linux系统中也叫做vfs(virtual file system)

四、理解重定向

1. 文件描述符分配规则

打开文件时,查文件描述符表从0开始分配,还没有被使用的最小的下标将被分配

重定向的本质是在内核中改变文件描述符表特定下标的内容,与上层无关

2. 重定向系统调用

       #include <unistd.h>int dup(int oldfd);int dup2(int oldfd, int newfd);//将文件描述符表中oldfd下标对应的内容拷贝到newfd对应下标的位置

举例

  #include<stdio.h>#include<sys/types.h>#include<sys/stat.h>#include<fcntl.h>#include<unistd.h>                                                                                  int main(){const char* filename = "file.txt";//打开文件,不存在文件时新建,以写入方式打开,每次打开时清空文件int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 0666);//重定向dup2(fd, 1);//默认向stdout中写入printf("hello Linux\n");//指定向stdout中写入fprintf(stdout, "hello Linux\n");return 0;}

运行结果:

在这里插入图片描述

可以看到,本应当向标准输出stdout中输出的内容输出到文件file.txt中了。

因为我们使用了重定向将该进程中文件描述符表里的fd(值为3,因为0,1,2默认被使用,从3开始分配)下标的内容拷贝到1下标位置,因此1号下标本来存放的标准输出文件对象指针被我们修改为“file.txt”文件的文件对象指针。

在底层的文件描述符表中,1号下标处为我们重定向的file.txt文件对象指针,但是上层stdout(C语言提供的FILE*指针,里面封装这文件描述符等信息)里面的文件描述符等其他信息都没有改变,在调用printf,fprintf时都向stdout中打印,stdout中封装着_fileno = 1,操作系统就会打印到文件描述符表里将下标为1处的文件指针所指的文件对象的缓冲区中,即向我们重定向的文件file.txt中打印。

3. 重定向命令行调用

简单调用

在指定被重定向的文件时,默认为标准输出重定向,即文件描述符为1的文件

可执行程序 > filename ,表示标准输出重定向到filename,不存在该文件时就新建

可执行程序 >> filename, 和> 大致相同,唯一区别就是>的重定向默认清空文件,>>是追加的打印,不会在打开文件时清空,

< filename 表示标准输入重定向

举例:

echo hello Linux > log.txt

该命令可以将本来向显示器打印的 hello Linux 打印到log.txt中

复杂调用

指令或可执行程序 1>filename1 2>filename2 ……,表示将文件描述符为1的文件重定向到filename1, 文件描述符为2的文件重定向到filename2

举例:

在这里插入图片描述

指令或可执行程序 1>filename 2>&1,将1号重定向到filename,在&1(1号下标的内容,即filename的地址)放入下标为2,即1,2同时指向filename
在这里插入图片描述

五、理解缓冲区

1. 缓冲区介绍

缓冲区就是一段内存空间,可以给上层提供高效的IO体验,间接提高整体的效率,

缓冲区有用户级缓冲区(语言提供,维护的)和内核级缓冲区(操作系统提供,维护的),缓冲区的优点有解耦(用户只需将数据交到缓冲区,缓冲区会自己刷新到下一个目标位置,一般不需要我们在进行操作,设计),提高效率。每一个打开的文件都有自己的缓冲区,语言级的缓冲区在struct FILE中(C语言中的文件指针FILE*),内核级的缓冲区在文件对象(struct file)中。

2. 缓冲区刷新策略

1.立即刷新,fflush(stdout),fsync(fd),这类函数调用可以立即刷新缓冲区,可以认为是无缓冲

2.行刷新,显示器通常采用行刷新

3.全缓冲,缓冲区写满,才刷新,普通文件通常采用全缓冲

4.进程退出,系统会自动刷新

3. 有趣现象

有下面2段代码

代码1:

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

执行结果
在这里插入图片描述

代码2:

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

执行结果

在这里插入图片描述

现象:

重定向后先输出的是write,后输出printf和fprintf,

当创建子进程时,printf和fprintf被输出两遍

原因:

我们在命令行进行了重定向执行程序时,会使得原本向stdout输出的内容输出到file.txt文件中,这会改变刷新策略,显示器是按行刷新,普通文件是写满缓冲区或者进程退出在刷新,因此当"hello printf\n"和"hello fprintf\n"被写入用户缓冲区时,不会直接刷新,而调用write时,该调用是系统调用,无用户缓冲区,直接写入内核缓冲区,内核缓冲区直接进行刷新,因此第一行是"hello write",后面程序退出时在将用户缓冲区的内容刷新,先刷新到内核缓冲区,在刷新到文件,

图片转存中…(img-MnhIhwH7-1753197796549)]

现象:

重定向后先输出的是write,后输出printf和fprintf,

当创建子进程时,printf和fprintf被输出两遍

原因:

我们在命令行进行了重定向执行程序时,会使得原本向stdout输出的内容输出到file.txt文件中,这会改变刷新策略,显示器是按行刷新,普通文件是写满缓冲区或者进程退出在刷新,因此当"hello printf\n"和"hello fprintf\n"被写入用户缓冲区时,不会直接刷新,而调用write时,该调用是系统调用,无用户缓冲区,直接写入内核缓冲区,内核缓冲区直接进行刷新,因此第一行是"hello write",后面程序退出时在将用户缓冲区的内容刷新,先刷新到内核缓冲区,在刷新到文件,

创建子进程后,用户缓冲区没有写满还没有被刷新,内核缓冲区已刷新,父子进程各有自己的用户缓冲区,父子进程各自刷新一次,所以出现了printf和fprintf打印2次

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

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

相关文章

科技向善,银发向暖:智慧养老与经济共筑适老未来

人口老龄化是当今中国社会面临的重大课题&#xff0c;也是推动社会变革与经济转型的重要引擎。随着数字技术的飞速发展&#xff0c;“智慧养老”正以科技向善的温度&#xff0c;为老年群体构建更舒适、更安全、更有尊严的晚年生活&#xff0c;同时为银发经济注入蓬勃活力&#…

numpy库 降维,矩阵创建与元素的选取,修改

目录 1.降维函数ravel()和flatten ravel(): flatten(): 2.矩阵存储与内存结构 3.修改矩阵形状的方法 4.特殊矩阵创建 全零矩阵: 如np.zeros(5) 创建含5个零的一维数组&#xff0c;输出中零后的点&#xff08;如 0.&#xff09;表示浮点数类型。 全一矩阵&#xff1a;如n…

SpringCloud seata全局事务

项目https://github.com/apache/incubator-seata docker拉取启动server $ docker run --name seata-server -p 8091:8091 apache/seata-server:2.1.0 seata注册到nacos <dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-…

OpenLayers 快速入门(八)事件系统

看过的知识不等于学会。唯有用心总结、系统记录&#xff0c;并通过温故知新反复实践&#xff0c;才能真正掌握一二 作为一名摸爬滚打三年的前端开发&#xff0c;开源社区给了我饭碗&#xff0c;我也将所学的知识体系回馈给大家&#xff0c;助你少走弯路&#xff01; OpenLayers…

【Linux | 网络】应用层(HTTPS)

目录一、HTTPS的概念二、准备概念2.1 什么是加密和解密2.2 为什么要加密2.3 常见的加密方式2.3.1 对称加密2.3.1 非对称加密2.4 数据摘要&&数据指纹三、HTTPS理解过程3.1 只使用对称加密3.2 只使用非对称加密3.3 双方都使用非对称加密3.4 对称加密 非对称加密3.5 中间…

GRE协议

一、实验拓扑二、实验配置1、静态路由实现GRERT1配置&#xff1a;RT1(config)# int fa1/0RT1(config-if)# ip add 192.168.20.1 255.255.255.0RT1(config-if)# no shutdownRT1(config)# int fa0/0RT1(config-if)# ip add 172.1.1.2 255.255.255.0RT1(config-if)# no shutdownRT…

JDialong弹窗

public class DialogDemo extends JFrame {public DialogDemo(){this.setVisible(true);this.setSize(700,500);this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);//JFrame 放东西&#xff0c;容器Container contentPane this.getContentPane();//绝对布局conte…

tlias智能学习辅助系统--违纪处理(实战)

目录 1.StudentController.java 2.interface StudentService 3.StudentServiceImpl.java 4.interface StudentMapper 1.StudentController.java // 违纪处理PutMapping("/violation/{id}/{score}")Operation(summary "违纪处理")public Result violat…

传统RNN模型笔记:输入数据长度变化的结构解析

一、案例背景 本案例通过PyTorch的nn.RNN构建单隐藏层RNN模型&#xff0c;重点展示RNN对变长序列数据的处理能力&#xff08;序列长度从1变为20&#xff09;&#xff0c;帮助理解RNN的输入输出逻辑。 二、核心代码与结构拆解 def dm_rnn_for_sequencelen():# 1. 定义RNN模型rnn…

OpenLayers 快速入门(四)View 对象

看过的知识不等于学会。唯有用心总结、系统记录&#xff0c;并通过温故知新反复实践&#xff0c;才能真正掌握一二 作为一名摸爬滚打三年的前端开发&#xff0c;开源社区给了我饭碗&#xff0c;我也将所学的知识体系回馈给大家&#xff0c;助你少走弯路&#xff01; OpenLayers…

测试左移方法论

测试左移&#xff08;Shift-Left Testing&#xff09;​是一种软件测试方法论&#xff0c;核心思想是将测试活动从传统的开发后期&#xff08;如系统测试、验收测试阶段&#xff09;提前到软件生命周期的更早期阶段&#xff08;如需求分析、设计、编码阶段&#xff09;&#xf…

OpenCV(01)基本图像操作、绘制,读取视频

图像基础 import cv2 as cv#读取图像 cv.imread(path,读取方式)默认读为彩色图像 #cv.imread(path) cat cv.imread(E:\hqyj\code\opencv\images\\face.png)#显示图像 cv.imshow(window,img) cv.imshow(myimg,cat)print(cat) print(cat.shape) #(h,w,c) 元组(1,1) print(cat…

biji 1

1.应用层&#xff1a;为应用程序提供网络服务。2.表示层&#xff1a;定义数据的格式&#xff0c;对数据进行压缩、解压缩、加密、解密、编码、解码。3.会话层&#xff1a;对通信双方间的会话进行建立、维护、拆除-----session id---区分同一应用程序的不同进程4.传输层&#x…

mongodb的备份和还原(精简)

1 官网下载对应版本msi2 运行msi mongodb-database-tools-windows-x86_64-100.12.2.msi3 将安装地址加到环境变量 C:\Program Files\MongoDB\Tools\100\bin4 查看version mongodump --version mongorestore --version5 运行 备份命令 mongodump --host 127.0.0.1 --db dbname--…

Mac安装Typescript报错

目录 Mac上安装Typescript报错: 原因分析 1. 默认 npm 全局安装目录的权限问题 2. Node.js 的安装方式 如何解决?(无需每次用 `sudo`) 方法 1:修改 npm 全局目录的权限(推荐) 方法 2:配置 npm 使用用户级目录 方法 3:使用 `nvm` 管理 Node.js(最推荐) 为什么建议避免…

spring-cloud概述

单体架构 把业务的所有功能实现都打包在一个war包或者jar包&#xff0c;这种方式就成为单体架构。 比如Spring课程中的博客系统,前端后端数据库实现&#xff0c;都在一个项目中&#xff0c;这种架构就称为单体架构. 举个例子&#xff1a; 比如在电商系统中&#xff0c;我们…

android ROOM kotlin官方文档完全学习

android ROOM kotlin官方文档完全学习2.6 使用 Room 将数据保存到本地数据库 | Android Developers (google.cn) 一、简介 1.1 引入 dependencies {def room_version "2.6.1"implementation "androidx.room:room-runtime:$room_version"//如下三选一a…

DOM编程全解析:操作、事件与存储实战指南

引言&#xff1a;DOM——JavaScript与网页交互的桥梁 DOM&#xff08;文档对象模型&#xff09; 是JavaScript操作HTML/XML文档的接口&#xff0c;它将网页文档抽象为一个树形结构&#xff0c;允许开发者通过API动态修改文档的内容、结构和样式。无论是实现动态交互&#xff0…

Ansible命令

Ansible命令 ansible 常用命令 /usr/bin/ansible   #Ansibe AD-Hoc 临时命令执行工具&#xff0c;常用于临时命令的执行 /usr/bin/ansible-doc    #Ansible 模块功能查看工具 /usr/bin/ansible-galaxy   #下载/上传优秀代码或Roles模块 的官网平台&#xff0c;基于网…

SY6974芯片添加enable充电控制节点

1. 需求描述项目背景&#xff1a;基于 Qualcomm MDM9x07 平台的 4G MIFI 产品&#xff0c;使用 Silergy 公司的 SY6974 充电 IC需求内容&#xff1a; 在环境 /sys/class/power_supply/sy6794/enable 下增加一个 sysfs 节点&#xff0c;用于控制是否允许充电&#xff1a;cat /sy…