linux0.11内核源码修仙传第十六章——获取硬盘信息及根目录挂载

🚀 前言

    书接第十四章:linux0.11内核源码修仙传第十四章——进程调度之fork函数,在这一节博客中已经通过fork进程创建了一个新的进程1,并且可以被调度,接下来接着主线继续走下去。希望各位给个三连,拜托啦,这对我真的很重要!!!

目录

  • 🚀 前言
  • 🏆硬盘基本信息的赋值
  • 🏆硬盘分区表的设置
    • 📃硬盘分区表(Disk Partition Table,DPT)
    • 📃代码实现
  • 🏆加载根文件系统
    • 📃mount_root 整体解读
    • 📃内存中用于文件系统的数据结构
      • 文件信息初始化
      • 超级块初始化
      • inode信息读取
      • 记录位图信息
      • 记录inode位图信息
  • 🎯总结
  • 📖参考资料

🏆硬盘基本信息的赋值

    好久没回顾 main 函数了,来回顾一下:

void main(void)
{···mem_init(main_memory_start,memory_end);trap_init();blk_dev_init();chr_dev_init();tty_init();time_init();sched_init();buffer_init(buffer_memory_end);hd_init();floppy_init();sti();move_to_user_mode();if (!fork()) {		/* we count on this going ok */init();}for(;;) pause();
}

    这里面前面的一堆初始化已经看完了,fork函数也运行了,成功创建了进程1。进程1会返回0,。现在压力来到了init函数:

void init(void)
{int pid,i;setup((void *) &drive_info);(void) open("/dev/tty0",O_RDWR,0);(void) dup(0);(void) dup(0);printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,NR_BUFFERS*BLOCK_SIZE);printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);if (!(pid=fork())) {close(0);if (open("/etc/rc",O_RDONLY,0))_exit(1);execve("/bin/sh",argv_rc,envp_rc);_exit(2);}if (pid>0)while (pid != wait(&i))/* nothing */;while (1) {if ((pid=fork())<0) {printf("Fork failed in init\r\n");continue;}if (!pid) {close(0);close(1);close(2);setsid();(void) open("/dev/tty0",O_RDWR,0);(void) dup(0);(void) dup(0);_exit(execve("/bin/sh",argv,envp));}while (1)if (pid == wait(&i))break;printf("\n\rchild %d died with code %04x\n\r",pid,i);sync();}_exit(0);	/* NOTE! _exit, not exit() */
}

    ok,fine,里面内容很多,没关系,一点点来,这一篇博文来看第一行 setup 函数的内容。

struct drive_info { char dummy[32]; } drive_info;void init(void)
{setup((void *) &drive_info);···
}

    先来看看传入的参数:drive_info 。这个变量是来自内存 0x90080 的数据,设置是在main函数的最开始。详情可以看博客:linux0.11内核源码修仙传第二章——setup.s,内存里的存放位置如下所示:

在这里插入图片描述

    接下来来看setup函数:

static inline _syscall1(int,setup,void *,BIOS)

    好的,这又是一个系统调用,返回类型是int,有一个参数是 void * 类型的BIOS。有关于系统调用可以参考这篇博客:linux0.11内核源码修仙传第十四章——进程调度之fork函数。其实直白一点,可以直接去系统调用的sys_call_table里面找对应的函数,这里讲结论,它会直接调用 sys_setup 函数:

int sys_setup(void * BIOS)
{···hd_info[0].cyl = *(unsigned short *) BIOS;			// 总柱面数hd_info[0].head = *(unsigned char *) (2+BIOS);		// 磁头数hd_info[0].wpcom = *(unsigned short *) (5+BIOS);	// 写入补偿hd_info[0].ctl = *(unsigned char *) (8+BIOS);		// 控制字节hd_info[0].lzone = *(unsigned short *) (12+BIOS);	// 逻辑区域起始柱面hd_info[0].sect = *(unsigned char *) (14+BIOS);		// 每磁道扇区数BIOS += 16;	···
}

    上面是这个函数的第一部分,对硬盘基本信息的赋值。BIOS是来自内存 0x90080 处的数据,包括柱面数、磁头数、扇区数等信息。不了解这些信息的可以看参考资料[3]。其实这里本来是个循环的,循环赋值所有硬盘信息进hd_info这个结构体数组,但是这里只考虑一个盘的情况,因此去掉了循环。最后BIOS加了16可以看上面内存的图,硬盘1和硬盘2参数之间隔了16字节。这里是准备到下一个硬盘了,但是这里没有了,所以不作考虑。

    最终结果如下所示:

在这里插入图片描述

🏆硬盘分区表的设置

📃硬盘分区表(Disk Partition Table,DPT)

    硬盘分区表用于定义硬盘的分区信息。分区表存储在硬盘的某个特定区域,通常是硬盘的第一个扇区,称为主引导记录(Master Boot Record, MBR),现代的分区表格式则主要是GPT(GUID Partition Table)。其作用主要是告诉操作系统硬盘上有多少个分区,每个分区的大小和位置

📃代码实现

    在linux0.11中的做法如下所示:

int sys_setup(void * BIOS)
{···hd[0].start_sect = 0;hd[0].nr_sects = hd_info[i].head*hd_info[i].sect*hd_info[i].cyl;struct buffer_head *bh = bread(0x300 + drive*5,0);struct partition *p = 0x1BE + (void *)bh->b_data;for (int i=1;i<5;i++,p++) {hd[i].start_sect = p->start_sect;hd[i].nr_sects = p->nr_sects;}brelse(bh);···
}

    其实仔细看就能看出来,只是给一个新的结构体数组 hd 做赋值,而且这个结构体里面就两个成员:start_sectnr_sects ,也就是开始扇区和总扇区数来记录。循环里面一共4次,加上最开始初始化的,因此一共是五个分区,最后结果如下所示:

在这里插入图片描述

    这些信息从哪里获取呢,就是在硬盘的第一个扇区的 0x1BE 偏移处,这里存储着该硬盘的分区信息,只要把这个地方的数据拿到就 OK 了。

    所以 bread 就是干这事的,从硬盘读取数据:

struct buffer_head *bh = bread(0x300 + drive*5,0);

    第一个参数 0x300 是第一块硬盘的主设备号,就表示要读取的块设备是硬盘一。第二个参数 0 表示读取第一个块,一个块为 1024 字节大小,也就是连续读取硬盘开始处 0 ~ 1024 字节的数据。拿到这部分数据后,再取 0x1BE 偏移处,就得到了分区信息:

struct partition *p = 0x1BE + (void *)bh->b_data;

    从硬盘的视角来看分区。0号块本来是一个超级块,可以参考这篇博客:linux0.11内核源码修仙传第十五章——文件系统,现在在里面多放一个分区信息。下面是示意图:

在这里插入图片描述

    至于如何从硬盘中读取指定位置(块)的数据,也就是 bread 函数的内部实现,这部分略微复杂,先埋个坑日后再细聊。

🏆加载根文件系统

    最后setup函数还有一部分:

int sys_setup(void * BIOS)
{···rd_load();		// 不用管mount_root();···
}

    其中 rd_load 是当有 ramdisk 时,也就是虚拟内存盘,才会执行。虚拟内存盘是通过软件将一部分内存(RAM)模拟为硬盘来使用的一种技术,此处当不存在,因此这行代码无用。

    mount_root是加载根文件系统,有了它之后,操作系统才能从一个根开始找到所有存储在硬盘中的文件,所以它是文件系统的基石,很重要:

void mount_root(void)
{int i,free;struct super_block * p;struct m_inode * mi;if (32 != sizeof (struct d_inode))panic("bad i-node size");for(i=0;i<NR_FILE;i++)file_table[i].f_count=0;if (MAJOR(ROOT_DEV) == 2) {printk("Insert root floppy and press ENTER");wait_for_keypress();}for(p = &super_block[0] ; p < &super_block[NR_SUPER] ; p++) {p->s_dev = 0;p->s_lock = 0;p->s_wait = NULL;}if (!(p=read_super(ROOT_DEV)))panic("Unable to mount root");if (!(mi=iget(ROOT_DEV,ROOT_INO)))panic("Unable to read root i-node");mi->i_count += 3 ;	/* NOTE! it is logically used 4 times, not 1 */p->s_isup = p->s_imount = mi;current->pwd = mi;current->root = mi;free=0;i=p->s_nzones;while (-- i >= 0)if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))free++;printk("%d/%d free blocks\n\r",free,p->s_nzones);free=0;i=p->s_ninodes+1;while (-- i >= 0)if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))free++;printk("%d/%d free inodes\n\r",free,p->s_ninodes);
}

📃mount_root 整体解读

    从整体上来说,mount_root 这个函数就是要把硬盘中的数据以文件系统的格式进行解读,加载到内存中设计好的数据结构,这样操作系统就可以通过内存中的数据,以文件系统的方式访问硬盘中的一个个文件。首先回顾一下硬盘中的文件系统格式,区别于之前我们的博客里介绍的文件系统,哪个是ext2的格式,但是linux-0.11是MINIX文件系统,但是都是大同小异的,这里贴出来这个文件系统的格式:

在这里插入图片描述

    简单看看这个文件系统:

引导块:启动区,当然不一定所有的硬盘都有启动区,但我们还是得预留出这个位置,以保持格式的统一。
超级块:用于描述整个文件系统的整体信息,我们看它的字段就知道了,有后面的 inode 数量,块数量,第一个块在哪里等信息。
inode位图:inode的使用情况。
块位图:块的使用情况。
i 结点:inode存放每个文件或目录的元信息和索引信息。
块:存放文件数据。

    可是硬盘中凭什么就有了这些信息呢?这就是个鸡生蛋蛋生鸡的问题了。你可以先写一个操作系统,然后给一个硬盘做某种文件系统类型的格式化,这样你就得到一个有文件系统的硬盘了,有了这个硬盘,你的操作系统就可以成功启动了。

📃内存中用于文件系统的数据结构

文件信息初始化

    下面来逐步看,就只看第一个循环目前:

void mount_root(void)
{for(i=0;i<64;i++)file_table[i].f_count=0;···
}

    这个循环就干了一件事,把 64 个 file_table 里的 f_count 清零。来看看具体这个 file_table,其表示进程所使用的文件,进程每使用一个文件都需要记录在这里面,包括文件类型,inode索引信息,引用次数f_count,现在没有被引用,所以先将其都置为0。来看看代码里的具体实现:

struct file file_table[NR_FILE];struct file {unsigned short f_mode;unsigned short f_flags;unsigned short f_count;struct m_inode * f_inode;off_t f_pos;
};

    来看一个file_table的使用案例。比如现在有如下的命令:echo "hello" > 0 。这个命令表示将字符串“hello”写入到0号文件描述符。这个0号文件就是file_table[0]对应的文件。这个文件在硬盘哪里呢?注意到其中有个f_inode成员,通过这个即可找到indoe信息,inode里面包含了一个文件所需要的全部信息,包括文件大小,文件类型,文件所在硬盘号等。此事已在前面有所记载。

超级块初始化

    接着看这个函数后面的内容:

void mount_root(void)
{···for(p = &super_block[0] ; p < &super_block[8] ; p++) {p->s_dev = 0;p->s_lock = 0;p->s_wait = NULL;}···
}

    这又是一个初始化的操作。super_block 就是我们之前讲的超级块,其作用也和之前的超级块一样,里面存的这个把设备的信息,通过这个超级块就可以掌握整个设备的文件系统全局了。

s_dev :超级块对应的设备号,置为 0 表示未使用。
s_lock :超级块锁,置为 0(未锁定)
s_wait :等待队列指针,置为 NULL

    来看接下来的操作:

void mount_root(void)
{···if (!(p=read_super(0)))panic("Unable to mount root");···
}

    接下来就是读取硬盘的超级块信息到内存中,read_super 函数就是读取硬盘中的超级块。

inode信息读取

    接下来读取根目录的inode信息。

int ROOT_DEV = 0;
#define ROOT_INO 1void mount_root(void)
{···if (!(mi=iget(ROOT_DEV, ROOT_INO)))panic("Unable to read root i-node");mi->i_count += 3 ;	// 逻辑上使用4次,初始为1···
}

    iget函数会获取根inode,同时会增加inode引用次数,表示inode被使用。

    接下来就是将 inode 设置当前进程(新建的进程1)的根目录和工作目录:

void mount_root(void)
{···p->s_isup = p->s_imount = mi;current->pwd = mi;		// 当前工作目录(m_inode指针)current->root = mi;		// 根目录(m_inode指针)···
}

记录位图信息

    超级块下面是块位图:

void mount_root(void)
{···free=0;i=p->s_nzones;		// 文件系统的总磁盘块数while (-- i >= 0)if (!set_bit(i&8191,p->s_zmap[i>>13]->b_data))free++;···
}

    首先来看看结构体p的内容:s_zmap 是位图数组,每个元素指向一个页,b_data是页的内存地址。用于管理块的占用状态。i>>13 是因为一页是 2^13 个块,超出了就是其他页,就是其他索引了。

    其次来搞清楚set_bit函数的含义:

#define set_bit(bitnr,addr) ({ \
register int __res __asm__("ax"); \
__asm__("bt %2,%3;setb %%al":"=a" (__res):"a" (0),"r" (bitnr),"m" (*(addr))); \
__res; })

    这个不是设置位!!!是检查位的值。addr 的第 bitnr 位为 1,返回 1;否则返回 0。

    8191换算成二进制是这个:1 1111 1111 1111,共13个1,这是取出低13位的值。为什么是13呢?那是因为在早期文件系统,如 MINIX。每个位图页管理8192(2^13)个块。i&8191 表示位图页内偏移。合起来就是检查每个块对应的位是否为0,0就是空闲,就递增free变量。通过 i >> 13i & 8191 分页定位位图中的位。

    free变量的含义就是统计文件系统中未被占用的磁盘块(空闲块)数量。

记录inode位图信息

    最后就是inode位图:

void mount_root(void)
{···free=0;i=p->s_ninodes+1;while (-- i >= 0)if (!set_bit(i&8191,p->s_imap[i>>13]->b_data))free++;···
}

    做法和目标同上面的块位图,只是i的值不一样。

🎯总结

    这篇博客就主要讲了setup函数的内容,获硬盘信息以及加载根文件。


📖参考资料

[1] linux源码趣读
[2] 一个64位操作系统的设计与实现
[3] 硬盘基本知识(磁道、扇区、柱面、磁头数、簇、MBR、DBR)
[4] 分区表(Partition Table)是计算机硬盘驱动器上一个重要的数据结构

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

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

相关文章

mobile自动化测试-appium webdriverio

WebdriverIO是一款支持mobile app和mobile web自动化测试框架&#xff0c;与appium集成&#xff0c;完成对mobile应用测试。支持ios 和android两种平台&#xff0c;且功能丰富&#xff0c;是mobile app自动化测试首选框架。且官方还提供了mobile 应用测试example代码&#xff0…

Kubernetes排错(十):常见网络故障排查

通用排查思路 Kubernetes 集群内不同服务之间的网络通信出现异常&#xff0c;表现为请求超时、连接失败或响应缓慢&#xff0c;导致服务间依赖关系中断&#xff0c;依赖服务的功能不可用或性能下降&#xff0c;甚至可能波及整个微服务架构&#xff0c;引发连锁反应&#xff0c…

PyTorch 张量与自动微分操作

笔记 1 张量索引操作 import torch ​ # 下标从左到右从0开始(0->第一个值), 从右到左从-1开始 # data[行下标, 列下标] # data[0轴下标, 1轴下标, 2轴下标] ​ def dm01():# 创建张量torch.manual_seed(0)data torch.randint(low0, high10, size(4, 5))print(data->,…

接口的基础定义与属性约束

在 TypeScript 中&#xff0c;接口&#xff08;Interface&#xff09;是一个非常强大且常用的特性。接口定义了对象的结构&#xff0c;包括对象的属性和方法&#xff0c;可以为对象提供类型检查和约束。通过接口&#xff0c;我们可以清晰地描述一个对象应该具备哪些属性和方法。…

高效全能PDF工具,支持OCR识别

软件介绍 PDF XChange Editor是一款功能强大的PDF编辑工具&#xff0c;支持多种操作功能&#xff0c;不仅可编辑PDF内容与图片&#xff0c;还具备OCR识别表单信息的能力&#xff0c;满足多种场景下的需求。 软件特点 这款PDF编辑器完全免费&#xff0c;用户下载后直接…

OpenCV 中用于背景分割的一个类cv::bgsegm::BackgroundSubtractorGMG

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 cv::bgsegm::BackgroundSubtractorGMG 是 OpenCV 中用于背景分割的一个类&#xff0c;它实现了基于贝叶斯推理的背景建模算法&#xff08;Bayesi…

MongoDB知识框架

简介&#xff1a;MongoDB 是一个基于分布式文件存储的数据库&#xff0c;属于 NoSQL 数据库产品&#xff0c;以下是其知识框架总结&#xff1a; 一、数据模型 文档&#xff1a;MongoDB 中的数据以 BSON&#xff08;二进制形式的 JSON&#xff09;格式存储在集合中&#xff0c;…

WEBSTORM前端 —— 第2章:CSS —— 第8节:网页制作2(小兔鲜儿)

目录 1.项目目录 2.SEO 三大标签 3.Favicon 图标 4.版心 5.快捷导航(shortcut) 6.头部(header) 7.底部(footer) 8.banner 9.banner – 圆点 10.新鲜好物(goods) 11.热门品牌(brand) 12.生鲜(fresh) 13.最新专题(topic) 1.项目目录 【xtx-pc】 ima…

1、RocketMQ 核心架构拆解

1. 为什么要使用消息队列&#xff1f; 消息队列&#xff08;MQ&#xff09;是分布式系统中不可或缺的中间件&#xff0c;主要解决系统间的解耦、异步和削峰填谷问题。 解耦&#xff1a;生产者和消费者通过消息队列通信&#xff0c;彼此无需直接依赖&#xff0c;极大提升系统灵…

[Linux网络_71] NAT技术 | 正反代理 | 网络协议总结 | 五种IO模型

目录 1.NAT技术 NAPT 2.NAT和代理服务器 3.网线通信各层协议总结 补充说明 4.五种 IO 模型 1.什么是IO&#xff1f;什么是高效的IO&#xff1f; 2.有那些IO的方式&#xff1f;这么多的方式&#xff0c;有那些是高效的&#xff1f; 异步 IO &#x1f3a3; 关键缺陷类比…

Unity基础学习(八)时间相关内容Time

众所周知&#xff0c;每一个游戏都会有自己的时间。这个时间可以是内部&#xff0c;从游戏开始的时间&#xff0c;也可以是外部真实的物理时间&#xff0c;时间相关内容 主要用于游戏中 参与位移计时 时间暂停等。那么我们今天就来看看Unity中和时间相关的内容。 Unity时间功能…

Java游戏服务器开发流水账(1)游戏服务器的架构浅析

新项目立项停滞&#xff0c;头大。近期读老项目代码看到Java&#xff0c;笔记记录一下。 为什么要做服务器的架构 游戏服务器架构设计具有多方面的重要意义&#xff0c;它直接关系到游戏的性能、可扩展性、稳定性以及用户体验等关键因素 确保游戏的流畅运行 优化数据处理&a…

计算机视觉与深度学习 | 基于Transformer的低照度图像增强技术

基于Transformer的低照度图像增强技术通过结合Transformer的全局建模能力和传统图像增强理论(如Retinex),在保留颜色信息、抑制噪声和平衡亮度方面展现出显著优势。以下是其核心原理、关键公式及典型代码实现: 一、原理分析 1. 全局依赖建模与局部特征融合 Transformer的核…

Linux 文件目录管理常用命令

pwd 显示当前绝对路径 cd 切换目录 指令备注cd -回退cd …返回上一层cd ~切换到用户主目录 ls 列出目录的内容 指令备注ls -a显示当前目录中的所有文件和目录&#xff0c;包括隐藏文件ls -l以长格式显示当前目录中的文件和目录ls -hl以人类可读的方式显示当前目录中的文…

【Linux 系统调试】性能分析工具perf使用与调试方法

目录 一、perf基本概念 1‌. 事件类型‌ 2‌. 低开销高精度 3‌. 工具定位‌ 二、安装与基础配置 1. 安装方法 2. 启用符号调试 三、perf工作原理 1. 数据采集机制 2. 硬件事件转化流程 四、perf应用场景 1. 系统瓶颈定位 2. 锁竞争优化 3. 缓存优化 五、perf高级…

嵌入式中屏幕的通信方式

LCD屏通信方式详解 LCD屏&#xff08;液晶显示屏&#xff09;的通信方式直接影响其数据传输效率、显示刷新速度及硬件设计复杂度。根据应用场景和需求&#xff0c;LCD屏的通信方式主要分为以下三类&#xff0c;每种方式在协议类型、数据速率、硬件成本及适用场景上存在显著差异…

【el-admin】el-admin关联数据字典

数据字典使用 一、新增数据字典1、新增【图书状态】和【图书类型】数据字典2、编辑字典值 二、代码生成配置1、表单设置2、关联字典3、验证关联数据字典 三、查询操作1、模糊查询2、按类别查询&#xff08;下拉框&#xff09; 四、数据校验 一、新增数据字典 1、新增【图书状态…

【Spring】Spring MVC笔记

文章目录 一、SpringMVC简介1、什么是MVC2、什么是SpringMVC3、SpringMVC的特点 二、HelloWorld1、开发环境2、创建maven工程a>添加web模块b>打包方式&#xff1a;warc>引入依赖 3、配置web.xmla>默认配置方式b>扩展配置方式 4、创建请求控制器5、创建springMVC…

如何在大型项目中解决 VsCode 语言服务器崩溃的问题

在大型C/C项目中&#xff0c;VS Code的语言服务器&#xff08;如C/C扩展&#xff09;可能因内存不足或配置不当频繁崩溃。本文结合系统资源分析与实战技巧&#xff0c;提供一套完整的解决方案。 一、问题根源诊断 1.1 内存瓶颈分析 通过top命令查看系统资源使用情况&#xff…

LeetCode百题刷002摩尔投票法

遇到的问题都有解决的方案&#xff0c;希望我的博客可以为你提供一些帮助 图片源自leetcode 题目&#xff1a;169. 多数元素 - 力扣&#xff08;LeetCode&#xff09; 一、排序法 题目要求需要找到多数值&#xff08;元素个数>n/2&#xff09;并返回这个值。一般会想到先…