操作系统 4.5-文件使用磁盘的实现

通过文件进行磁盘操作入口

// 在fs/read_write.c中
int sys_write(int fd, const char* buf, int count)
{struct file *file = current->filp[fd];struct m_inode *inode = file->inode;if (S_ISREG(inode->i_mode))return file_write(inode, file, buf, count);
}

  1. 进程控制块(PCB)

    • current 是指向当前进程控制块(PCB)的指针。PCB 包含了进程的所有信息,包括打开的文件列表。

  2. 文件指针数组(filp)

    • current->filp[fd] 访问当前进程的文件指针数组,获取与文件描述符 fd 关联的 file 结构。

  3. 文件结构(file)

    • struct file 包含了打开文件的所有信息,包括指向 inode 的指针。

  4. inode 结构

    • struct m_inode 是文件的元数据结构,包含了文件的属性(如权限、大小、数据块位置等)。

    • inode->i_mode 包含了文件的类型和权限信息。

  5. 文件写入操作

    • file_write(inode, file, buf, count) 是实际执行文件写入操作的函数,它将 buf 中的 count 字节数据写入到由 inodefile 描述的文件中。

  6. 文件类型检查

    • S_ISREG(inode->i_mode) 检查 inode 表示的是否是一个普通文件。如果是,才执行写入操作。

  7. 数据流和控制流

    • 图中展示了从 write(fd) 调用开始,通过 PCB 和文件表,最终访问到 inode 和数据盘块的过程。

File_write

工作流程分析

  1. 确定要写入的字符段

    • 首先,需要确定要写入文件的字符段。这通常由文件的读写指针决定,该指针指示了文件中的当前位置。通过修改这个指针(例如使用 fseek),可以改变写入的起始位置,然后加上要写入的字符数量(count)来确定结束位置。

  2. 找到要写的盘块号

    • 确定了要写入的字符段后,下一步是找到这些字符对应的磁盘块号。这是通过文件的 inode 结构来实现的,inode 包含了文件的元数据,包括文件数据块的位置信息。

  3. 形成写入请求并执行写入

    • 使用找到的盘块号和要写入的数据(buf),形成一个写入请求(request),然后将这个请求放入磁盘调度算法(如电梯算法)中进行处理,以优化磁盘I/O操作。

函数参数解释:

  • inode

    • 这是一个指向文件的 inode 结构的指针。inode 包含了文件的元数据,如文件类型、权限、所有者、文件大小以及指向文件数据块的指针等。在写入操作中,inode 用于查找文件数据块的位置。

  • file

    • 这是一个指向文件结构的指针。文件结构包含了打开文件的所有信息,包括文件状态标志、文件的读写指针等。在写入操作中,file 结构用于获取文件的当前位置和状态。

  • buf

    • 这是一个指向要写入文件的数据缓冲区的指针。buf 包含了实际要写入文件的数据。

  • count

    • 这是一个整数,表示要写入文件的字节数。countbuf 参数一起,指定了要写入文件的数据量。

实现

以下是带有详细注解的 file_write 函数代码,解释了每一部分的功能和作用:

// file_write函数用于将数据从用户缓冲区写入到文件对应的磁盘块中
int file_write(struct m_inode *inode, struct file *filp, char *buf, int count)
{off_t pos; // 定义一个变量pos,用于记录写入文件的起始位置
​// 根据文件的读写标志(O_APPEND)和当前文件位置(filp->f_pos)确定写入的起始位置if (filp->f_flags & O_APPEND)pos = inode->i_size; // 如果设置了追加标志,则从文件末尾开始写入elsepos = filp->f_pos; // 否则,从文件当前位置开始写入
​// 当还有字节需要写入时,继续循环while (i < count) {block = create_block(inode, pos / BLOCK_SIZE); // 创建或分配一个新的磁盘块bh = bread(inode->i_dev, block); // 读取指定的磁盘块到内存中,并将块地址存储在bh中
​int c = pos % BLOCK_SIZE; // 计算当前块内的偏移char *pc = c + bh->b_data; // 计算内存中块数据区域的起始地址bh->b_dirt = 1; // 标记块为脏块,表示该块已被修改,需要写回磁盘c = BLOCK_SIZE - c; // 计算剩余需要写入的字节数pos += c; // 更新文件位置
​// 将用户缓冲区buf中的数据逐字节写入到内存中的块数据区域bh->b_datawhile (c-- > 0)*(pc++) = get_fs_byte(buf++);
​brelse(bh); // 释放缓冲区,将其返回到缓冲区管理中}
​filp->f_pos = pos; // 更新文件的当前位置
}
  1. 确定写入位置

    • 通过检查文件的读写标志和当前文件位置,确定写入操作的起始位置。

  2. 循环写入数据

    • 使用 while 循环,根据要写入的字节数 count,逐块写入数据。

  3. 创建和读取磁盘块

    • create_block 函数用于创建或分配一个新的磁盘块。

    • bread 函数用于读取指定的磁盘块到内存中,并将块地址存储在 bh(缓冲头)中。

  4. 计算块内偏移和剩余字节数

    • 计算当前块内的偏移 c 和剩余需要写入的字节数。

  5. 写入数据

    • 将用户缓冲区 buf 中的数据逐字节写入到内存中的块数据区域 bh->b_data

  6. 标记块为脏块

    • bh->b_dirt 设置为 1,表示该块已被修改,需要写回磁盘。

  7. 释放缓冲区

    • 使用 brelse 函数释放缓冲区,将其返回到缓冲区管理中。

  8. 更新文件位置

    • 更新文件的当前位置 filp->f_pos,以便后续操作可以从正确的位置开始。

通过这些步骤,file_write 函数实现了将用户数据写入文件的完整过程,包括确定写入位置、创建和读取磁盘块、写入数据、标记块为脏块以及更新文件位置等。

Create_block

图中展示了 create_block 函数和 _bmap 函数的代码,这些函数用于在文件系统中创建和映射磁盘块。以下是提取的代码和注解:

提取的代码:

// create_block函数用于根据inode和块号分配或创建一个新的磁盘块
int create_block(struct m_inode *inode, int block)
{while (i < count) {block = create_block(inode, pos / BLOCK_SIZE);bh = bread(inode->i_dev, block);}
​int _bmap(m_inode *inode, int block, int create){if (block < 7) {if (create && !inode->i_zone[block]) {inode->i_zone[block] = new_block(inode->i_dev);inode->i_ctime = CURRENT_TIME;inode->i_dirt = 1;}return inode->i_zone[block];}block -= 7;if (block < 512) {bh = bread(inode->i_dev, inode->i_zone[7]);return (bh->b_data)[block];}}
}

代码注解:

  1. create_block函数

    • 这个函数用于根据给定的 inode 和块号 block 分配或创建一个新的磁盘块。

    • 它通过调用 _bmap 函数来实现块的映射和创建。

  2. _bmap函数

    • 这是一个辅助函数,用于处理块号的映射。

    • 它首先检查块号是否小于7,这7个块号直接存储在 inodei_zone 数组中。

    • 如果块号小于7,并且需要创建新块(create 为真且当前块号未分配),则调用 new_block 函数分配新块,更新 inodei_ctimei_dirt 标志。

    • 如果块号大于等于7且小于512,表示这是一个一重间接块,需要读取间接块表并找到对应的块号。

  3. new_block函数

    • 这个函数用于分配一个新的磁盘块。具体实现未在图中展示,但通常涉及查找空闲块、分配块号等操作。

  4. bread函数

    • 这个函数用于读取指定的磁盘块到内存中。它将块地址存储在 bh(缓冲头)中,以便后续操作。

总结:

这段代码实现了文件系统中磁盘块的分配和映射机制。通过 create_block_bmap 函数,系统可以根据文件的 inode 和块号动态分配和映射磁盘块。这种机制支持文件的动态增长和高效的磁盘空间管理。

通过间接块表(如一重间接和二重间接块表),文件系统还可以支持非常大的文件,即使单个文件的数据量超过了直接块表所能表示的范围。

m_inode

图中展示了与设备文件的 inode 结构相关的代码,以及如何通过 sys_open 函数打开设备文件。以下是提取的代码和注解:

提取的代码:

// m_inode结构体定义,用于表示设备文件的inode
struct m_inode {unsigned short i_mode; // 文件的类型和属性unsigned short i_zone[9]; // 指向文件内容数据块struct task_struct *i_wait; // 多个进程共享的打开这个inode,有的进程等待...unsigned short i_count; // unsigned char i_lock; // unsigned char i_dirt; // 
};
​
// sys_open函数用于打开文件或设备
int sys_open(const char* filename, int flag)
{if (S_ISCHR(inode->i_mode)) { // 检查是否为字符设备文件if (MAJOR(inode->i_zone[0]) == 4) { // 检查设备文件类型current->tty = MINOR(inode->i_zone[0]); // 设置当前进程的tty}}
}
​
// 宏定义,用于从设备号中提取主设备号和次设备号
#define MAJOR(a) ((((unsigned)(a)) >> 8)) // 取高字节
#define MINOR(a) ((a) & 0xff) // 取低字节

代码注解:

  1. m_inode结构体

    • i_mode:表示文件的类型和属性,例如普通文件、目录、字符设备等。

    • i_zone:一个数组,用于存储指向文件内容数据块的指针或设备号等信息。

    • i_wait:指向等待该inode的进程的task_struct结构体。

    • i_count:表示当前有多少个进程打开了该inode。

    • i_lock:用于控制对inode的访问,防止并发访问导致的问题。

    • i_dirt:表示inode是否被修改,需要写回磁盘。

  2. sys_open函数

    • 该函数用于打开文件或设备。

    • S_ISCHR(inode->i_mode):检查文件是否为字符设备文件。

    • MAJOR(inode->i_zone[0])MINOR(inode->i_zone[0]):从设备号中提取主设备号和次设备号。

    • current->tty:设置当前进程的tty(终端设备)。

  3. 宏定义

    • MAJOR(a):从设备号中提取主设备号(高字节)。

    • MINOR(a):从设备号中提取次设备号(低字节)。

总结:

这段代码展示了如何使用 m_inode 结构体来表示设备文件的inode,并演示了如何通过 sys_open 函数打开设备文件。

文件视图的两条路

读写磁盘的过程:

  1. 用户发起文件操作

    • 用户通过 writeread 系统调用操作文件。

    • 传递参数:文件描述符(FD)、内存缓冲区(buff)、操作字节数(count)。

  2. 系统调用转换

    • write 调用内核中的 sys_writeread 调用 sys_read

    • 需要获取文件的 inode(索引节点)信息。

  3. 计算目标磁盘块

    • 通过文件结构 file 获取文件指针 f_pos,确定字符流中的位置。

    • 根据 inode 及索引结构,计算出对应的磁盘块号(block)。

    • 采用直接索引、一级索引、二级索引等方式解析出数据块。

  4. 磁盘操作

    • 读操作:调用 bread() 读取磁盘数据到缓存,再复制到用户缓冲区。

    • 写操作:调用 bwrite() 将用户缓冲区数据写入磁盘缓存,并同步到磁盘。

  5. 磁盘调度与完成

    • 磁盘请求加入电梯调度队列。

    • 设备驱动完成磁盘读写,系统中断通知完成。

    • 若是写操作,需更新 f_pos 以维护文件流的连续性。


输出到显示器的过程:

  1. 用户发起输出请求

    • 例如 printf()write(1, buff, count),其中 1 代表标准输出(stdout)。

  2. 系统调用转换

    • 进入内核,调用 sys_write 处理。

    • 识别文件描述符 1 代表标准输出(显示器)。

  3. 数据传输到终端设备

    • 由于 stdout 不是磁盘文件,而是字符设备,直接走 tty 终端驱动。

    • 终端驱动 tty_write 处理输出,将数据放入 tty 缓冲区。

  4. 驱动程序与硬件交互

    • tty 设备驱动调用 console_driver,执行 putc() 逐字符输出到屏幕。

    • 可能涉及 VGAframebufferUART 串口等设备。

  5. 显示完成

    • 终端驱动完成数据输出,可能触发屏幕刷新或回显。

    • 若是行缓冲模式,遇到 \n 或缓冲区满才实际输出。

对比总结

  • 读写磁盘涉及块设备(block device),数据按块存取,可能需要磁盘调度与缓冲管理。

  • 输出到显示器涉及字符设备(char device),数据按字符流传输,即时性更强。

  • 两者都通过系统调用进入内核,但处理方式不同,磁盘用 bread/bwrite,显示器用 tty_write

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

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

相关文章

libreoffice-help-common` 的版本(`24.8.5`)与官方源要求的版本(`24.2.7`)不一致

出现此错误的原因主要是软件包依赖冲突&#xff0c;具体分析如下&#xff1a; ### 主要原因 1. **软件源版本不匹配&#xff08;国内和官方服务器版本有差距&#xff09; 系统中可能启用了第三方软件源&#xff08;如 PPA 或 backports 源&#xff09;&#xff0c;导致 lib…

使用Geotools中的原始方法来操作PostGIS空间数据库

目录 前言 一、原生PostGIS连接介绍 1、连接参数说明 2、创建DataStore 二、工程实战 1、Maven Pom.xml定义 2、空间数据库表 3、读取空间表的数据 三、总结 前言 在当今数字化与信息化飞速发展的时代&#xff0c;空间数据的处理与分析已成为众多领域不可或缺的一环。从…

讯飞语音合成(流式版)语音专业版高质量的分析

一、引言 在现代的 Web 应用开发中&#xff0c;语音合成技术为用户提供了更加便捷和人性化的交互体验。讯飞语音合成&#xff08;流式版&#xff09;以其高效、稳定的性能&#xff0c;成为了众多开发者的首选。本文将详细介绍在 Home.vue 文件中实现讯飞语音合成&#xff08;流…

走进未来的交互世界:下一代HMI设计趋势解析

在科技日新月异的今天&#xff0c;人机交互界面&#xff08;HMI&#xff09;设计正以前所未有的速度发展&#xff0c;不断引领着未来的交互世界。从简单的按钮和图标&#xff0c;到如今的智能助手和虚拟现实&#xff0c;HMI设计不仅改变了我们的生活方式&#xff0c;还深刻影响…

洛谷题单3-P1217 [USACO1.5] 回文质数 Prime Palindromes-python-流程图重构

题目描述 因为 151 151 151 既是一个质数又是一个回文数&#xff08;从左到右和从右到左是看一样的&#xff09;&#xff0c;所以 151 151 151 是回文质数。 写一个程序来找出范围 [ a , b ] ( 5 ≤ a < b ≤ 100 , 000 , 000 ) [a,b] (5 \le a < b \le 100,000,000…

学习笔记,DbContext context 对象是保存了所有用户对象吗

DbContext 并不会将所有用户对象保存在内存中&#xff1a; DbContext 是 Entity Framework Core (EF Core) 的数据库上下文&#xff0c;它是一个数据库访问的抽象层它实际上是与数据库的一个连接会话&#xff0c;而不是数据的内存缓存当您通过 _context.Users 查询数据时&…

本地命令行启动服务并连接MySQL8

启动服务命令 net start mysql8 关闭服务命令 net stop mysql8 本地连接MySQL数据库mysql -u [用户名] -p[密码] 这里&#xff0c;我遇到了个问题 —— 启动、关闭服务时&#xff0c;显示 “发生系统错误 5。拒绝访问。 ” 解法1&#xff1a;在 Windows 上以管理员身份打开…

数据蒸馏:Dataset Distillation by Matching Training Trajectories 论文翻译和理解

一、TL&#xff1b;DR 数据集蒸馏的任务是合成一个较小的数据集&#xff0c;使得在该合成数据集上训练的模型能够达到在完整数据集上训练的模型相同的测试准确率&#xff0c;号称优于coreset的选择方法本文中&#xff0c;对于给定的网络&#xff0c;我们在蒸馏数据上对其进行几…

【spring cloud Netflix】Ribbon组件

1.基本概念 SpringCloud Ribbon是基于Netflix Ribbon 实现的一套客户端负载均衡的工具。简单的说&#xff0c;Ribbon 是 Netflix 发布的开源项目&#xff0c;主要功能是提供客户端的软件负载均衡算法&#xff0c;将 Netflix 的中间层服务连接在一 起。Ribbon 的客户端组件提供…

P1036 [NOIP 2002 普及组] 选数(DFS)

题目描述 已知 n 个整数 x1​,x2​,⋯,xn​&#xff0c;以及 1 个整数 k&#xff08;k<n&#xff09;。从 n 个整数中任选 k 个整数相加&#xff0c;可分别得到一系列的和。例如当 n4&#xff0c;k3&#xff0c;4 个整数分别为 3,7,12,19 时&#xff0c;可得全部的组合与它…

在响应式网页的开发中使用固定布局、流式布局、弹性布局哪种更好

一、首先看下固定布局与流体布局的区别 &#xff08;一&#xff09;固定布局 固定布局的网页有一个固定宽度的容器&#xff0c;内部组件宽度可以是固定像素值或百分比。其容器元素不会移动&#xff0c;无论访客屏幕分辨率如何&#xff0c;看到的网页宽度都相同。现代网页设计…

二分查找与二叉树中序遍历——面试算法

目录 二分查找与分治 循环方式 递归方式 元素中有重复的二分查找 基于二分查找的拓展问题 山脉数组的顶峰索引——局部有序 旋转数字中的最小数字 找缺失数字 优化平方根 中序与搜索树 二叉搜索树中搜索特定值 验证二叉搜索树 有序数组转化为二叉搜索树 寻找两个…

字符串——面试考察高频算法题

目录 转换成小写字母 字符串转化为整数 反转相关的问题 反转字符串 k个一组反转 仅仅反转字母 反转字符串里的单词 验证回文串 判断是否互为字符重排 最长公共前缀 字符串压缩问题 转换成小写字母 给你一个字符串 s &#xff0c;将该字符串中的大写字母转换成相同的…

现代复古电影海报品牌徽标设计衬线英文字体安装包 Thick – Retro Vintage Cinematic Font

Thick 是一种大胆的复古字体&#xff0c;专为有影响力的标题和怀旧的视觉效果而设计。其厚实的字体、复古魅力和电影风格使其成为电影海报、产品标签、活动品牌和编辑设计的理想选择。无论您是在引导电影的黄金时代&#xff0c;还是在现代布局中注入复古活力&#xff0c;Thick …

[C++面试] new、delete相关面试点

一、入门 1、说说new与malloc的基本用途 int* p1 (int*)malloc(sizeof(int)); // C风格 int* p2 new int(10); // C风格&#xff0c;初始化为10 new 是 C 中的运算符&#xff0c;用于在堆上动态分配内存并调用对象的构造函数&#xff0c;会自动计算所需内存…

Unity URP管线与HDRP管线对比

1. 渲染架构与底层技术 URP 渲染路径&#xff1a; 前向渲染&#xff08;Forward&#xff09;&#xff1a;默认单Pass前向&#xff0c;支持少量实时光源&#xff08;通常4-8个逐物体&#xff09;。 延迟渲染&#xff08;Deferred&#xff09;&#xff1a;可选但功能简化&#…

JDK8卸载与安装教程(超详细)

JDK8卸载与安装教程&#xff08;超详细&#xff09; 最近学习一个项目&#xff0c;需要使用更高级的JDK&#xff0c;这里记录一下卸载旧版本与安装新版本JDK的过程。 JDK8卸载 以windows10操作系统为例&#xff0c;使用快捷键winR输入cmd&#xff0c;打开控制台窗口&#xf…

python爬虫:DrissionPage实战教程

如果本文章看不懂可以看看上一篇文章&#xff0c;加强自己的基础&#xff1a;爬虫自动化工具&#xff1a;DrissionPage-CSDN博客 案例解析&#xff1a; 前提&#xff1a;我们以ChromiumPage为主&#xff0c;写代码工具使用Pycharm&#xff08;python环境3.9-3.10&#xff09; …

07-01-自考数据结构(20331)- 排序-内部排序知识点

内部排序算法是数据结构核心内容,主要包括插入类(直接插入、希尔)、交换类(冒泡、快速)、选择类(简单选择、堆)、归并和基数五大类排序方法。 知识拓扑 知识点介绍 直接插入排序 定义:将每个待排序元素插入到已排序序列的适当位置 算法步骤: 从第二个元素开始遍历…

Go语言-初学者日记(八):构建、部署与 Docker 化

&#x1f9f1; 一、go build&#xff1a;最基础的构建方式 Go 的构建工具链是出了名的轻量、简洁&#xff0c;直接用 go build 就能把项目编译成二进制文件。 ✅ 构建当前项目 go build -o myapp-o myapp 指定输出文件名默认会构建当前目录下的 main.go 或 package main &a…