阅读分析Linux0.11 /boot/head.s

目录

  • 初始化IDT、IDTR和GDT、GDTR
  • 检查协处理器并设置CR0寄存器
  • 初始化页表和CR3寄存器,开启分页

初始化IDT、IDTR和GDT、GDTR

startup_32:movl $0x10,%eaxmov %ax,%dsmov %ax,%esmov %ax,%fsmov %ax,%gslss _stack_start,%espcall setup_idtcall setup_gdtmovl $0x10,%eax		# reload all the segment registersmov %ax,%ds		# after changing gdt. CS was alreadymov %ax,%es		# reloaded in 'setup_gdt'mov %ax,%fsmov %ax,%gslss _stack_start,%espxorl %eax,%eax
1:	incl %eax		# check that A20 really IS enabledmovl %eax,0x000000	# loop forever if it isn'tcmpl %eax,0x100000je 1b

个人阅读上面代码的知识点:

  • lss _stack_start,%esp 首先lss是远指针加载指令,不了解的查deepseek就懂了。“_stack_start”这个标号找了很久都找不到在哪儿定义的,搜索整个源码都没有。最后还是求助deepseek终于搞懂了(deepseek真自学神器)。要把“_stack_start”中下划线去掉,直接搜索“stack_start”,才知道这个标号是定义在kernel/sched.c中的结构体变量名。之所以要加下划线,是因为早期的编译器编译时会在C中变量名前面加,现在不加了 。由于对kernel代码不懂,所以没过多研究,知道是在初始化栈就行了。
  • call setup_idt 调用子程序初始化IDT表,通过指令lidt将IDT表的长度和地址加载到IDTR寄存器。 还处在内核初始化阶段,所以只是简单的将IDT表中的描述符初始化为同一个,都指向ignore_int这个中断处理程序。ignore_int子程序就是本文件中,功能就是打印一段字符。
    • lidt指令加载的IDT表的地址是线性地址,初始化阶段,此时还没开启分页模式,线性地址等于物理地址。开启分页后,IDTR中的线性地址要经过MMU查页表转化成IDT表在内存中的物理地址。
    • 需要对IDT表描述符了解,IDT表描述符总共8字节,小端法存入内存,先存低4字节,再高4字节,先低2字节,再高2字节。之所以提小端法是因为我一开始没注意,把描述符的选择子判断错了。
  • call setup_gdt 调用子程序初始化GDT表,通过指令lgdt将GDT表的长度和地址加载到GDTR寄存器。 GDT表自己构造。
    • lgdt指令加载的GDT表的地址是线性地址,只不过初始化阶段,此时还没开启分页模式,线性地址等于物理地址。开启分页后,GDTR中的线性地址要经过MMU查页表转化成GDT表在内存中的物理地址。
    • 构造GDT表时,GDT表中的描述符大小为8字节,第一个描述符为全0,后面描述符的基地址base=0X00000000,limit=最大值(0X000FFF),把内存管理就设置成了平坦模式, 现代操作系统支持这种模式,这样一个进程的 代码段、数据段、栈段就共享同一个线性地址空间了,方便虚拟内存管理
  • 保护模式下,指令执行过程中,查询IDT表、GDT表的工作是有硬件来实现的,不是软件模拟的,所以对IDT、GDT中描述符的结构也是硬件规定的。我们在构造这两个表时要按照硬件的规定来。
  • movl %eax,0x000000;cmpl %eax,0x100000 这段是负责检查A20地址线是否打开,如果没打开那么0X100000地址就等于0X000000地址,cmpl比较的结果就是相等,那就卡在这个执行死循环。
  • je 1b 这个条件跳转指令开始看了我也很蒙,还是靠deepseek,这是以前的写法“b”表示满足条件跳转到后面的标号,“f”表示满足条件跳转到前面的标号。b和f指示跳转的方向的。现在好像不用这种用法了



检查协处理器并设置CR0寄存器

movl %cr0,%eax		# check math chipandl $0x80000011,%eax	# Save PG,PE,ET
/* "orl $0x10020,%eax" here for 486 might be good */orl $2,%eax		# set MPmovl %eax,%cr0call check_x87jmp after_page_tables/** We depend on ET to be correct. This checks for 287/387.*/
check_x87:fninitfstsw %axcmpb $0,%alje 1f			/* no coprocessor: have to set bits */movl %cr0,%eaxxorl $6,%eax		/* reset MP, set EM */movl %eax,%cr0ret

个人阅读上面代码的知识点:

  • 了解CR0寄存器,CR0寄存器中,PE位开启保护模式、PG位开启分页机制、WP位写保护位。还有其它涉及协处理器的位,我一知半解就不写了。上面这段代码主要就是检查协处理器,然后进行设置。 指令call check_x87就是调用子程序检查是否存在287/387协处理器。
  • fninit fstsw这两个是协处理器指令,自己查deepseek能看懂,不赘述了。check_x87这个子程序功能就是检查协处理器的。



初始化页表和CR3寄存器,开启分页

after_page_tables:pushl $0		# These are the parameters to main :-)pushl $0pushl $0pushl $L6		# return address for main, if it decides to.pushl $mainjmp setup_paging
L6:jmp L6			# main should never return here, but# just in case, we know what happens.
-----------------------------------------------------------------------------------------
setup_paging:movl $1024*5,%ecx		/* 5 pages - pg_dir+4 page tables */xorl %eax,%eaxxorl %edi,%edi			/* pg_dir is at 0x000 */cld;rep;stoslmovl $pg0+7,pg_dir		/* set present bit/user r/w */movl $pg1+7,pg_dir+4		/*  --------- " " --------- */movl $pg2+7,pg_dir+8		/*  --------- " " --------- */movl $pg3+7,pg_dir+12		/*  --------- " " --------- */movl $pg3+4092,%edimovl $0xfff007,%eax		/*  16Mb - 4096 + 7 (r/w user,p) */std
1:	stosl			/* fill pages backwards - more efficient :-) */subl $0x1000,%eaxjge 1bxorl %eax,%eax		/* pg_dir is at 0x0000 */movl %eax,%cr3		/* cr3 - page directory start */movl %cr0,%eaxorl $0x80000000,%eaxmovl %eax,%cr0		/* set paging (PG) bit */ret			/* this also flushes prefetch-queue */

个人阅读上面代码的知识点:

  • jmp setup_paging 跳转到初始化页表的子程序。该指令前面的push指令是在压栈,pushl $main 目的是从setup_paging子程序返回时,ret指令能返回到main程序。 我还没看过main.c的代码,不赘述了。功能就是结束head.s的初始化任务。开启main.c
    • 强化我自己的一个知识点,call和jmp都是跳转指令,区别就是call跳转前要将返回地址压栈,jmp无需压栈直接跳转无法返回。开始我还疑问为什么不用call指令,然后返回到main.c,仔细一想call指令是将下一条指令的地址压栈,只能返回到本源文件call的下一条指令。而我们需要的是结束head.s,返回到另一个源文件main.c。所以通过pushl $main压入返回地址,不用call调用,而用jmp直接跳转到setup_paging初始化页表子程序
  • setup_paging子程序初始化页表、CR3寄存器,置CR0寄存器的PG为1开启分页。了解页表和页表项,即使有些汇编指令不熟,查一下就是看懂这段了。
    • stosl指令,我忽略了它执行时会自动增加EDI。stos、movs、cmps、scas都是字符串操作指令,可以顺便都了解一下。伟大的deepseek很好用的。
    • CR3寄存器是用来存放顶级页表的物理地址的。MMU将线性地址转换位物理地址时需要通过CR3寄存器找到页表进行映射。CR3寄存器的改变,会引起TLB表的改变。CR3改变代表页表的切换,TLB相当于部分页表项的缓存,自然也要切换



.org 0x1000
pg0:.org 0x2000
pg1:.org 0x3000
pg2:.org 0x4000
pg3:.org 0x5000
刚看见这段代码时,感觉整齐有规律,就是不知道作用是什么。即使知道了.org这个伪指令的功能,也不知道写这段代码的意义。直到最后看到初始化页表的子程序setup_paging。补充点因为这段代码学到的零碎知识点。
(1).org(Origin)是汇编语言中的一条伪指令(Directive),用于指定程序或数据在内存中的起始地址。 在需要直接控制内存布局的场景(如裸机开发、嵌入式开发)中,.org 是一个简单有效的工具,但在高层编程中通常由链接器管理地址分配
(2) 这段代码其实就是在划分页表的页。它们之间的地址差是0X1000(4KB),从0地址开始到0X5000共20KB,分成5个页,每页4KB,第一个页是页目录表,其余4个页都是普通页表。setup_paging子程序的开始将5个页20KB用0填充,接着构造4个普通物理页的页表项填充到0地址的页目录表,最后构造一个普通物理页的页表项填充4个普通物理页。
(3)你还可以观察到初始化IDT、IDTR和GDT、GDTR的代码,还有检查协处理器并设置CR0寄存器的代码都写在这段代码之前,因为那些初始化、检查的代码执行完就没用了,它们所在的内存空间可以被页表覆盖; 结束head.s跳转到main.c的代码、初始化页表的子程序setup_paging、IDT表GDT表的位置,包括中断处理程序ignore_int的代码位置都在“.org 0X5000”之后,就是防止误操作,初始化页表时覆盖比较重要的代码、表空间
(4) 内存中IDT表、GDT表,各有1个,所有应用程序和操作系统共用;页表有多个,每个应程序都有自己的页表,任务切换时,通过修改CR3寄存器切换页表,同时TLB表也要刷新。

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

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

相关文章

33、单元测试实战练习题

以下是三个练习题的具体实现方案,包含完整代码示例和详细说明: 练习题1:TDD实现博客评论功能 步骤1:编写失败测试 # tests/test_blog.py import unittest from blog import BlogPost, Comment, InvalidCommentErrorclass TestBl…

16-算法打卡-哈希表-两个数组的交集-leetcode(349)-第十六天

1 题目地址 349. 两个数组的交集 - 力扣(LeetCode)349. 两个数组的交集 - 给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。 示例 1:输入:nu…

SciPy库详解

SciPy 是一个用于数学、科学和工程计算的 Python 库,它建立在 NumPy 之上,提供了许多高效的算法和工具,用于解决各种科学计算问题。 CONTENT 1. 数值积分功能代码 2. 优化问题求解功能代码3. 线性代数运算功能代码 4. 信号处理功能代码 5. 插…

杰弗里·辛顿:深度学习教父

名人说:路漫漫其修远兮,吾将上下而求索。—— 屈原《离骚》 创作者:Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder😊) 杰弗里辛顿:当坚持遇见突破,AI迎来新纪元 一、人物简介 杰弗…

BladeX单点登录与若依框架集成实现

1. 概述 本文档详细介绍了将BladeX认证系统与若依(RuoYi)框架集成的完整实现过程。集成采用OAuth2.0授权码流程,使用户能够通过BladeX账号直接登录若依系统,实现无缝单点登录体验。 2. 系统架构 2.1 总体架构 #mermaid-svg-YxdmBwBtzGqZHMme {font-fa…

初识Redis · set和zset

目录 前言: set 基本命令 交集并集差集 内部编码和应用场景 zset 基本命令 交集并集差集 内部编码和应用场景 应用场景(AI生成) 排行榜系统 应用背景 设计思路 热榜系统 应用背景 设计思路 热度计算方式 总结对比表 前言&a…

playwright 教程高级篇:掌握网页自动化与验证码处理等关键技术详解

Playwright 教程高级篇:掌握网页自动化与验证码处理等关键技术详解 本教程将带您一步步学习如何使用 Playwright——一个强大的浏览器自动化工具,来完成网页任务,例如提交链接并处理旋转验证码。我们将按照典型的自动化流程顺序,从启动浏览器到关闭浏览器,详细讲解每个步骤…

数据结构(完)

树 二叉树 构建二叉树 int value;Node left;Node right;public Node(int val) {valueval;} 节点的添加 Node rootnull;public void insert(int num) {Node nodenew Node(num);if(rootnull) {rootnode;return;}Node index root;while(true) {//插入的节点值小if(index.value&g…

FastAPI与SQLAlchemy数据库集成与CRUD操作

title: FastAPI与SQLAlchemy数据库集成与CRUD操作 date: 2025/04/16 09:50:57 updated: 2025/04/16 09:50:57 author: cmdragon excerpt: FastAPI与SQLAlchemy集成基础包括环境准备、数据库连接配置和模型定义。CRUD操作通过数据访问层封装和路由层实现,确保线程安全和事务…

一个基于Django的写字楼管理系统实现方案

一个基于Django的写字楼管理系统实现方案 用户现在需要我用Django来编写一个写字楼管理系统的Web版本,要求包括增删改查写字楼的HTML页面,视频管理功能,本地化部署,以及人员权限管理,包含完整的代码结构和功能实现&am…

mongodb在window10中创建副本集的方法,以及node.js连接副本集的方法

创建Mongodb的副本集最好是新建一个文件夹,如D:/data,不要在mongodb安装文件夹里面创建副本集,虽然这样也可以,但是容易造成误操作或路径混乱;在新建文件夹里与现有 MongoDB 数据隔离,避免误操作影响原有数…

Maven 多仓库与镜像配置全攻略:从原理到企业级实践

Maven 多仓库与镜像配置全攻略:从原理到企业级实践 一、核心概念:Repository 与 Mirror 的本质差异 在 Maven 依赖管理体系中,repository与mirror是构建可靠依赖解析链的两大核心组件,其核心区别如下: 1. Repositor…

STM32 四足机器人常见问题汇总

文章不介绍具体参数,有需求可去网上搜索。 特别声明:不论年龄,不看学历。既然你对这个领域的东西感兴趣,就应该不断培养自己提出问题、思考问题、探索答案的能力。 提出问题:提出问题时,应说明是哪款产品&a…

MySQL 中 `${}` 和 `#{}` 占位符详解及面试高频考点

文章目录 一、概述二、#{} 和 ${} 的核心区别1. 底层机制代码示例 2. 核心区别总结 三、为什么表名只能用 ${}?1. 预编译机制的限制2. 动态表名的实现 四、安全性注意事项1. ${} 的风险场景2. 安全实践 五、面试高频考点1. 基础原理类问题**问题 1**:**问…

C语言编译预处理2

#include <XXXX.h>和#include <XXXX.c> #include "XXXX.h" 是 C 语言中一条预处理指令 #include <XXXX.h>&#xff1a;这种形式用于包含系统标准库的头文件。预处理器会在系统默认的头文件搜索路径中查找XXXX.h 文件。例如在 Linux 系统中&#…

Elasticvue-轻量级Elasticsearch可视化管理工具

Elasticvue一个免费且开源的 Elasticsearch 在线可视化客户端&#xff0c;用于管理 Elasticsearch 集群中的数据&#xff0c;完全支持 Elasticsearch 版本 8.x 和 7.x. 功能特色&#xff1a; 集群概览索引和别名管理分片管理搜索和编辑文档REST 查询快照和存储库管理支持国际…

Git提交规范及最佳实践

Git 提交规范通常是为了提高代码提交的可读性、可维护性和自动化效率&#xff08;如生成 ChangeLog&#xff09;。以下是常见的 Conventional Commits 规范&#xff0c;结合社区最佳实践总结而成&#xff1a; 1. 提交格式 每次提交的 commit message 应包含三部分&#xff1a;…

Ubuntu中snap

通过Snap可以安装众多的软件包。需要注意的是&#xff0c;snap是一种全新的软件包管理方式&#xff0c;它类似一个容器拥有一个应用程序所有的文件和库&#xff0c;各个应用程序之间完全独立。所以使用snap包的好处就是它解决了应用程序之间的依赖问题&#xff0c;使应用程序之…

android studio 运行java main报错

运行某个带main函数的java文件报错 Could not create task :app:Test.main(). > SourceSet with name main not found. 解决办法&#xff1a;在工程的.idea/gradle.xml 文件下添加&#xff1a; <option name"delegatedBuild" value"false" /&g…

openssh离线一键升级脚本分享(含安装包)

查看当前的版本 [rootmyoracle ~]#ssh -V相关安装包下载地址 openssh下载地址&#xff1a;http://ftp.openbsd.org/pub/OpenBSD/OpenSSH/portable/openssl下载地址&#xff1a;https://www.openssl.org/source/zlib下载地址&#xff1a;http://www.zlib.net/今天演示从7.4升级…