一步步编写操作系统 49 加载内核2

内核文件kernel.bin是elf格式的二进制可执行文件,初始化内核就是根据elf规范将内核文件中的段(segment)展开到(复制到)内存中的相应位置。在分页模式下,程序是靠虚拟地址来运行的,无论是内核还是用户程序,它们对cpu来说都是指令或数据、没什么区别,交给cpu的指令或数据的地址一律被认为是虚拟地址。坦白说,内核文件中的地址是在编译阶段确定的,里面都是虚拟地址,程序也是靠这些虚拟地址来运行。但这些虚拟地址实际上是我们在初始化内核阶段规划好的,即想安排内核在哪片虚拟内存中,就将内核地址编译成对应的虚拟地址。而目前我们初始化的是内核,它在物理低端1MB内存中,初始化工作取决于这1MB物理内存中哪块空间可用,所以,现在还要看前面的内存分布图从中找块合适的内存空间来容纳内核映像。

其实大家早已经知道内核的入口虚拟地址是0xc0001500啦。但现在大家要假装不知道^_^,配合一下啊,咱们说一下0xc0001500是怎么来的。

物理内存中0x900处是loader.bin加载的地址,在loader.bin的开始部分是GDT,它可是必须要保留下来的,可不能覆盖,我们不打算在内核中重新定义它,以后都要指望它了。正如伟大领袖虽然仙逝了,但威望犹在,虽然loader的工作结束啦,但loader所完成的工作成果咱们还得继续发扬继续用。预计loader.bin的大小不会超过2000字节。所以咱们可选的起始物理地址是0x900+2000=0x10d0(不要把注意力放在这个奇怪的数上,偶然得出的)。内存很大,但也尽量往低了选,于是凑了个整数,选了0x1500做为内核映像的入口地址。

根据咱们的页表,低端1MB的虚拟内存与物理内存是一一对应的,所以物理地址是0x1500对应的虚拟地址是0xc0001500。这就解释了在5.3.1节中,链接命令ld中用-Ttext指定了代码段的起始虚拟地址,再把命令搬过来给大家看下:

ld kernel/main.o -Ttext 0xc0001500 -e main -o kernel/kernel.bin

好,现在咱们得说一下初始化内核的代码,见代码:

193 ;---------- 将kernel.bin中的segment拷贝到编译的地址 -----------
194 kernel_init:
195 xor eax, eax
196 xor ebx, ebx ;ebx记录程序头表地址
197 xor ecx, ecx ;cx记录程序头表中的program header数量
198 xor edx, edx ;dx 记录program header尺寸,即e_phentsize
199
200 mov dx, [KERNEL_BIN_BASE_ADDR + 42] 
; 偏移文件42字节处的属性是e_phentsize,表示program header大小
201 mov ebx, [KERNEL_BIN_BASE_ADDR + 28] 
; 偏移文件开始部分28字节的地方是e_phoff,
;表示第1 个program header在文件中的偏移量
202 ; 其实该值是0x34,不过还是谨慎一点,这里来读取实际值
203 add ebx, KERNEL_BIN_BASE_ADDR
204 mov cx, [KERNEL_BIN_BASE_ADDR + 44] 
; 偏移文件开始部分44字节的地方是e_phnum,表示有几个program header
205 .each_segment:
206 cmp byte [ebx + 0], PT_NULL ; 若p_type等于 PT_NULL,说明此program header未使用。
207 je .PTNULL
208
209 ;为函数memcpy压入参数,参数是从右往左依然压入.;函数原型类似于 memcpy(dst,src,size)
210 push dword [ebx + 16] ; program header中偏移16字节的地方是p_filesz,
;压入函数memcpy的第三个参数:size
211 mov eax, [ebx + 4] ; 距程序头偏移量为4字节的位置是p_offset
212 add eax, KERNEL_BIN_BASE_ADDR 
; 加上kernel.bin被加载到的物理地址,eax为该段的物理地址
213 push eax ; 压入函数memcpy的第二个参数:源地址
214 push dword [ebx + 8] ; 压入函数memcpy的第一个参数:目的地址;偏移程序头8字节的位置是p_vaddr,这就是目的地址
215 call mem_cpy ; 调用mem_cpy完成段复制
216 add esp,12 ; 清理栈中压入的三个参数
217 .PTNULL:
218 add ebx, edx ; edx为program header大小,即e_phentsize,;在此ebx指向下一个program header
219 loop .each_segment
220 ret
221
222 ;---------- 逐字节拷贝 mem_cpy(dst,src,size) ------------
223 ;输入:栈中三个参数(dst,src,size)
224 ;输出:无
225 ;---------------------------------------------------------
226 mem_cpy:
227 cld
228 push ebp
229 mov ebp, esp
230 push ecx ; rep指令用到了ecx,; 但ecx对于外层段的循环还有用,故先入栈备份
231 mov edi, [ebp + 8] ; dst
232 mov esi, [ebp + 12] ; src
233 mov ecx, [ebp + 16] ; size
234 rep movsb ; 逐字节拷贝
235
236 ;恢复环境
237 pop ecx
238 pop ebp
239 ret

对于可执行程序,我们只对其中的段(segment)感兴趣,它们才是程序运行的实质指令和数据的所在地,所以我们要找出程序中所有的段。

函数kernel_init的作用是将kernel.bin中的段(segment)拷贝到各段自己被编译的虚拟地址处,将这些段单独提取到内存中,这就是平时所说的内存中的程序映像。kernel_init的原理是分析程序中的每个段(segment),如果段类型不是PT_NULL(空程序类型),就将该段拷贝到编译的地址中。

现在内核已经被加载到KERNEL_BIN_BASE_ADDR地址处,该处是文件头elf_header。在我们的程序中,遍历段的方式是指向第一个程序头后,每次增加一个段头的大小,即e_phentsize。该属性位于偏移程序开头42字节处。为了以后遍历段时方便,避免了频繁的访问内存,在第200行,我们用寄存器dx来存储段头大小,这样,每遍历一个段头时,就直接从dx中获取段头大小,这将在第218行体现。

为了找到程序中所有的段,必须要获取程序头表。在文件开头偏移28字节处是属性e_phoff,该属性表示程序头表在文件中的偏移量,程序头表是程序头program header的数组,所以e_phoff也就是第1 个program header在文件中的偏移量。第201行,在内存e_phoff处取值,将得到的程序头表偏移量存入寄存器ebx。

我们需要的是程序头表的物理地址,由于此时的ebx还是程序头表文件内的偏移量,所以要将其加上内核的加载地址,这样才是程序头表的物理地址。所以在第203行为ebx加上了内核文件的加载地址KERNEL_BIN_BASE_ADDR。最终ebx寄存器做为程序头表的基址,用它来遍历每一个段,此时ebx指向程序中的第1 个program header。

我们已经知道,段是由程序头(program header)来描述的,一个程序头代表一个段。在知道了第一个程序头的地址后,为了遍历所有的程序头,还需要知道程序中程序头的数量,也就是段的数量,这是由elf_header中的属性e_phnum决定,它在elf_header中偏移为44。我们通常用cx寄存器来做循环计数器,所以在第204行,汇编语句“mov cx, [KERNEL_BIN_BASE_ADDR + 44]”将段的数量赋值给寄存器cx。

现在程序头表地址在寄存器ebx中,而且又知道了程序头表中段的数量,所以现在可以遍历每一个段的信息啦,其工作在代码第205~220行中完成。

在第206行,程序先判断下段的类型是不是PT_NULL,PT_NULL是在boot/include/boot.inc中定义的宏,其值为0,该意义表示空段类型。(PT_NULL也可以在linux系统的/usr/include/elf.h中找到其定义:#define PT_NULL 0)

在207行,如果发现该段是空段类型的话,就跨过该段不处理,跳到.PTNULL处,也就是第217行。

指定下一个段是通过在程序头表地址处加上一个段的大小e_phentsize来实现的,e_phentsize的值咱们已经将其存储在dx寄存器啦,所以在第218行,直接将ebx,也就是当前program header地址,加上edx,ebx便指向了下一个段的program header。edx的高16位为0,所以这里用add ebx, edx没有问题。

第209~216行,程序中的段通过mem_cpy函数复制到段自身的虚拟地址处。在这里,我们涉及到了函数调用约定的知识,不过为了叙述的更清楚,在这里我不想简单地说,在下一章中我们专门拿出一节来说这事儿。在此我还是本着够用的原则,把用到的部分给您说明白。

我们在此实现的函数是mem_cpy,不是c标准库中的memcpy函数,将来我们会在内核中实现memcpy。memcpy原型是void *memcpy(void *dest, const void *src, size_t n),功能是将src指向的地址空间处的连续n个字节拷贝到dest指向的地址空间。我们的学习它的用法,在汇编语言中用mem_cpy函数实现了它,此函数的原型相当于mem_cpy(void* dst, void* src, int size)。所以我们也要提供三个参数才能使用它。这三个参数都在程序头program header中,所以它们都可以基于ebx再增加适当的偏移量来得到。program header结构,很容易理解210~214行的代码。

第215行是调用 mem_cpy,这涉及到为该函数传入参数的问题。在汇编语言中传递参数的方法太多了,原因是汇编语言太灵活了,不怎么受约束,咱们可以访问到的资源太多了。所以,主调函数可以把参数放在寄存器中,也可以放在栈中,而栈就是内存,所以只要大家高兴,也可以把参数直接放到某块内存中,类似共享内存的方式来传递参数。主调函数以上面任意一种方式传递参数,被调函数都可以轻松地拿到参数。

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

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

相关文章

【牛客 - NC93】设计LRU缓存结构(模拟)

设计LRU缓存结构_牛客题霸_牛客网 描述 设计LRU(最近最少使用)缓存结构,该结构在构造时确定大小,假设大小为 k ,并有如下两个功能 1. set(key, value):将记录(key, value)插入该结构 2. get(key):返回key对应的val…

【转】理解SQL Server的安全对象和权限

理解安全对象(Securable) 安全对象,是SQL Server 数据库引擎授权系统控制对其进行访问的资源。通俗点说,就是在SQL Server权限体系下控制的对象,因为所有的对象(从服务器,到表,到视图触发器等)都在SQL Server的权限体系…

【LeetCode 986】 区间列表的交集(区间交集)

给定两个由一些 闭区间 组成的列表,firstList 和 secondList ,其中 firstList[i] [starti, endi] 而 secondList[j] [startj, endj] 。每个区间列表都是成对 不相交 的,并且 已经排序 。 返回这 两个区间列表的交集 。 形式上&#xff0c…

【转】SQL Server服务器名称与默认实例名不一致的修复方法

服务器级的urn筛选器无效:筛选器必须为空,或服务器属性必须等于实际的服务器名称 这个问题是出在本地连接还是远程连接上,这个问题可能是由于修改过服务器名称导致的。你可以尝试在本地的服务器和SQL Server上运行以下指令,看看服…

【LeetCode 295】. 数据流的中位数

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。 例如, [2,3,4] 的中位数是 3 [2,3] 的中位数是 (2 3) / 2 2.5 设计一个支持以下两种操作的数据结构: void addNum(int num) - 从数据流中添加一个整数到…

【转】如何从SharePoint Content DB中查询List数据***

SharePoint用来维护基础数据非常方便,只需要建立自定义列表,然后使用InfoPath自定义一下维护界面,就可以实现在线的增删改查,开发效率很高。如果维护的数据需要进行审批,还可以加入工作流功能。使用SharePoint Designe…

【搬石头排序】

据说是2020浪潮笔试 时间限制:C / C 语言 1000 MS;其他语言 3000 MS 内存限制:C / C 语言 131072 KB;其他语言 655360 KB 题目描述: 沙滩按照线型摆放着n个大小不一的球形石头,已知第i个石头的半径为ri&a…

【转】SharePoint Content Database简介

SharePoint作为微软主打的企业Portal平台,功能强大,使用简单,非常的方便。对于很多关系数据,我们可以使用自定义列表来维护,如果是非关系数据,可以使用文档库来维护。另外还可以在上面进行版本维护&#xf…

【LeetCode 2】两数相加(链表)

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 …

软件测试几个概念 --dev sit uat

DEV环境:DEV顾名思义就是develop,即代码开发的环境。 SIT环境:System Integration Test系统集成测试,开发人员自己测试流程是否走通。 UAT环境:User Acceptance Test用户验收测试,由专门的测试人员验证&…

【LeetCode 629】K个逆序对数组

给出两个整数 n 和 k&#xff0c;找出所有包含从 1 到 n 的数字&#xff0c;且恰好拥有 k 个逆序对的不同的数组的个数。 逆序对的定义如下&#xff1a;对于数组的第i个和第 j个元素&#xff0c;如果满i < j且 a[i] > a[j]&#xff0c;则其为一个逆序对&#xff1b;否则…

VSTS TFS 强制删除签出锁定项 解除 锁定

项目组一哥们走的时候以独占方式迁出了文件&#xff0c;现在其他人都无法修改&#xff0c;管理员似乎也无法将文件解除。经过摸索&#xff0c;找到了一种暴力的方法——直接改数据库。虽然暴力&#xff0c;却能实实在在地解决这个问题。 步骤&#xff1a; 1、连接到TFS数据库…

【NC30】缺失的第一个正整数

描述 给定一个无重复元素的整数数组nums&#xff0c;请你找出其中没有出现的最小的正整数 进阶&#xff1a; 空间复杂度 O(1)&#xff0c;时间复杂度 O(n) 数据范围: -2^31<nums[i]<2^31-1 0<len(nums)<5*10^5 示例1 输入&#xff1a; [1,0,2] 复制返回值…

【NC140 排序】手写快速排序

描述 给定一个长度为 n 的数组&#xff0c;请你编写一个函数&#xff0c;返回该数组按升序排序后的结果。 数据范围&#xff1a; 0 \le n \le 1\times10^30≤n≤1103&#xff0c;数组中每个元素都满足 0 \le val \le 10^90≤val≤109 要求&#xff1a;时间复杂度 O(n^2)O(n2)…

【LCS系列】最长公共子序列和最长公共子串

最长公共子序列&#xff1a; 如果要回溯出整个字符串的答案的话&#xff0c;可以直接看dp[i][len2-1]列&#xff0c;或者dp[len1-1][i]这一行&#xff0c;变化的时候&#xff0c;则代表要选这个字符&#xff0c;然后连起来就可以了。&#xff08;即构造字符串的过程是On的&…

sharepoint文档库文档版本信息操作

SPListItem spDoc oWeb.Lists["共享文档"].GetItemById(DocumentID); SPFileVersionCollection versionColl spDoc.File.Versions; foreach (SPFileVersion version in versionColl) {     //将UTC时间格式转换为本地时间 DateTime versionTime Conv…

【NC14 按之字形顺序打印二叉树】

描述 给定一个二叉树&#xff0c;返回该二叉树的之字形层序遍历&#xff0c;&#xff08;第一层从左向右&#xff0c;下一层从右向左&#xff0c;一直这样交替&#xff09; 数据范围&#xff1a;0 \le n \le 15000≤n≤1500,树上每个节点的val满足 |val| < 100∣val∣<1…

【NC51 合并k个已排序的链表】K路归并

描述 合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。 数据范围&#xff1a;节点总数 0 \le n \le 50000≤n≤5000&#xff0c;每个节点的val满足 |val| < 1000∣val∣<1000 要求&#xff1a;时间复杂度 O(nlogn)O(nlogn) 示例1 输入&#xff1a; [{1…

SharePoint 2010文档库批量下载文档的实现

在SharePoint 2010文档库中&#xff0c;结合单选框&#xff0c;在Ribbon中提供了批量处理文档的功能&#xff0c;比如&#xff0c;批量删除、批量签出、批量签入等&#xff0c;但是&#xff0c;很遗憾&#xff0c;没有提供批量下载&#xff0c;如图: 若选中多个文档后&#xff…

【NC54 三数之和】(待整理)

描述 给出一个有n个元素的数组S&#xff0c;S中是否有元素a,b,c满足abc0&#xff1f;找出数组S中所有满足条件的三元组。 数据范围&#xff1a;0 \le n \le 10000≤n≤1000&#xff0c;数组中各个元素值满足 |val | \le 100∣val∣≤100 空间复杂度&#xff1a;O(n^2)O(n2)&a…