其实,我们等了这一刻好久好久,即使我不说,大家也有这样的认识,linux内核是用c 语言写的,咱们肯定也要用c语言。其实...说点伤感情的话,今后的工作只是大部分(99%)都要用c语言来写,还有一些要用到汇编的地方。大家也不要因此气馁心灰(其实突然不用汇编还会想它呢,这不是玩笑),我在此过程中一定会尽我所能让内容简单易接受。

我们的内核文件是kernel.bin,这个文件是由loader将其从硬盘上读出并加载到内存中的,到此,接力棒传到了最后一个选手的手里。也就是说,咱们需要事先把kernel.bin定入硬盘。好久不往虚拟硬盘上写东西了,甭说是大家,我都有点陌生了呢,不过好在操作很简单,写之前让我们先看看这块虚拟硬盘上的文件布局吧。

MBR是写在了硬盘的第0扇区,第1扇区是空着的,原因是个人喜好,其实不空着也行,不过硬盘那么大,何必搞得那么拥挤呢。因此loader是写在硬盘的第2扇区,由于loader.bin目前的大小是1342字节,占用3个扇区,所以第2~4扇区不能再用啦,从第5扇区起我们可以自由使用。但此时我的强迫症又发作啦,我这里并没有接着第5扇区写,而是选的第9扇区(要是起始为1的话算是第10个扇区)。一是为了loader万一哪天要扩展,得预留出硬盘空间,二是您可能已经预计到了,隔开点显得更放心,这纯属是出于个人喜好做出的选择。

好,既然已经确定了写入扇区的位置,我们还是要通过dd命令往磁盘上写,命令如下:

dd if= kernel.bin of=/your_path/hd60M.img bs=512 count=200 seek=9 conv=notrunc回车

seek为9,目的是跨过前9个扇区(第0~8个扇区),我们是在第9个扇区写入。

count为200,目的是一次往参数of指定的文件中写入200个扇区。

至于为什么把count设成这么大,原因是这样的:每次写完内核后,咱们要往磁盘中同步内核文件,这样才能验证内核的正确性。按理说,咱们现在的内核文件不足4扇区,count=4最合适。不过,内核发展越来越大时,每次都要根据实际内核文件大小去改写count参数,这样就难免会有忘记修改的情况。之前我就深受其苦,内核文件变大了,而count忘记调整,造成写入硬盘中的内核文件不完整,所以到后来,程序运行不受控制,以至于调试的时候都调晕啦,看着cpu中跑的指令我完全蒙圈了,根本不是自己写的。恍然大悟之后,我就干脆一步到位,因为我们将来的内核大小不会超过100KB,所以直接把count改为200块扇区。另外请大家不用担心,dd命令会自己判断写入的数据量,如果参数if指定的文件体积小于count*bs,只按实际文件大小写入。

不过,估计您也觉得参数太多了,为了方便,我通常是把下面三个命令,编译、链接、再写入硬盘一起完成,您可以将它们写成一个脚本,脚本内容如下:

gcc -c -o main.o main.c && ld main.o -Ttext 0xc0001500 -e main -o kernel.bin && dd if= kernel.bin of=/your_path/hd60M.img bs=512 count=200 seek=9 conv=notrunc

好啦,上面命令在回车之后,这样我们的内核文件就成功写进磁盘了。

菜配好啦,就等下锅啦,我们的内核是由loader加载的,所以我们还要去修改下loader.S。

loader.S需要修改两个地方:

  • λ加载内核:需要把内核文件加载到内存缓冲区。
  • λ初始化内核:需要在分页后,将加载进来的elf内核文件安置到相应的虚拟内存地址,然后跳过去执行,从此loader的工作结束。

先说第一个加载内核,这里所说的加载内核只是把内核从硬盘上拷贝到内存中,并不是运行内核代码。这项工作在开启分页前后都可以,不过为了简单,咱们把它安排在分页开启之前加载。

话说内核加载到内存中,得有个加载地址,也就是缓冲区。其实开发经验少的同学对缓冲区这个概念总是觉得有点“只可意会不可言传”的意思。借此机会多说两句。缓冲区,buffer,意味存放物品的地点,也就是用于加工处理中暂存数据的地方。生活中的缓冲区例子有很多,比如水杯是水的缓冲区,水不是直接入口的,总有个中间载体做为中转,然后才入口。而且,水杯的作用相当于暖瓶或水房的缓存,咱们不是喝一口水就跑到水房接一口水,而是一次接一大杯,回来慢慢喝,这样就减少了去水房的次数。由此可见,缓冲区,既有存放数据的空间之意,又有提高效率的缓存之意。换在计算机世界里,缓冲区必然也是个能存储数据的介质,比如咱们这里所说的内存。

好啦,不能扯太远啦,咱们的缓冲区在设在哪里呢,这不是乱放的,得参考下目前内存中哪个地方还有可用的空间,千万不能覆盖了重要数据。也许大家首先想到的是很久之前说到的那个内存布局图,赞,答对啦,不过,大家不用往前翻看啦,一向体贴的我已经将其重点部分摘到这里啦,大家请看图

内核被加载到内存后,loader还要通过分析其elf结构将其展开到新的位置,所以说,内核在内存中是有两份拷贝,一份是elf格式的原文件kernel.bin,另一份是loader解析elf格式的kernel.bin后在内存中生成的内核映像(也就是将程序中的各种段segment复制到内存后的程序体),这个映像才是真正运行的内核。

将来内核肯定是越来越大,为了多预留出生长空间,咱们要将内核文件kernel.bin加载到地址较高的空间,而内核映像要放置到较低的地址。内核文件经过loader解析后就没用啦,这样内核映像将来往高地址处扩展时,也可以覆盖原来的内核文件kernel.bin。所以咱们的结论是,在0x7e00~0x9fbff这片区域的高地址中找一亩地给kernel.bin,这里我擅自做主啦,帮大家选的是0x70000。为什么?没有为什么,随意选的,取了个整而已,就是觉得0x70000~0x9fbff有0x2fbff=190KB字节的空间,而我们的内核不超过100KB,够用就行。

好,万事俱备啦,代码走起,请大家过目代码

147 ; ------------------------- 加载kernel ----------------------
148 mov eax, KERNEL_START_SECTOR ; kernel.bin所在的扇区号
149 mov ebx, KERNEL_BIN_BASE_ADDR; 从磁盘读出后,写入到ebx指定的地址
150 mov ecx, 200 ; 读入的扇区数
151
152 call rd_disk_m_32
153
154 ; 创建页目录及页表并初始化页内存位图
155 call setup_page

代码属于loader的一部分,它的作用是把内核文件从硬盘上加载到内存中,下面简要说一下。

第148~149行的KERNEL_START_SECTOR和KERNEL_BIN_BASE_ADDR是在boot/include/boot.inc中定义,其值分别为0x9和0x70000。

第150行的ecx为200,这是读入的扇区数,这里应该同前面用dd命令往硬盘上写入内核文件时的参数count保持一致,原因你懂的不解释。

以上的eax、ebx、ecx是函数rd_disk_m_32的三个参数,是为调用下面的函数做准备。

第152行的函数是rd_disk_m_32,用于从硬盘上读取文件。它的三个参数已经在上面赋值了。由于目前已经在32位保护模式下,所以相比之前位于mbr中的函数rd_disk_m_16,rd_disk_m_32只是版本由16位变成了32位的,函数实现原理相差无几,主要体现在里面所用的寄存器变成了32位。所以,就不细说啦,大家一看就明白啦。

接下来的第155行就是开始创建页表啦,把它放在这是为了让大家知道代码是加到了哪里,承上启下。setup_page函数实现没变,无须多说。

内核加载到缓冲区中后,现在该说要修改的第二处啦,也就是初始化内核。

一步步编写操作系统 48 加载内核1相关推荐

  1. 一步步编写操作系统 51 加载内核4

    咱们的内容都是连栽的,如果您没看过我之前的文章,本节您是看不懂的. 接上节. 介绍完内核初始化的函数kernel_init后,本节代码部分还差一点点没说啦,下面看代码: -略 179 ;在开启分页后, ...

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

    内核文件kernel.bin是elf格式的二进制可执行文件,初始化内核就是根据elf规范将内核文件中的段(segment)展开到(复制到)内存中的相应位置.在分页模式下,程序是靠虚拟地址来运行的,无论 ...

  3. 一步步编写操作系统 50 加载内核3

    接上节,在这里,我们把参数放到了栈中保存,大家注意到了,参数入栈的顺序是先从最右边的开始,最后压入的参数最左边的,其实这是某种约定,要不,为什么不先把中间的参数src入栈呢.既然主调函数按照从右到左的 ...

  4. 一步步编写操作系统 48 二进制程序的加载方式

    接上节,程序头可以自定义,只要我们按照自己定义的格式去解析就行.也许我光这么一说,很多同学还是不能彻底明白如何自定义文件头,因为大多数同学都是用高级语言来写程序,即使用了偏底层的c语言,不同平台的c编 ...

  5. 一步步编写操作系统 24 编写内核加载器

    这一节的内容并不长,因为在进入保护模式之前,我们能做的不多,loader是要经过实模式到保护模式的过渡,并最终在保护模式下加载内核.本节只实现一个简单的loader,本loader只在实模式下工作,等 ...

  6. 一步步编写操作系统 45 用c语言编写内核2

    在linux下用于链接的程序是ld,链接有一个好处,可以指定最终生成的可执行文件的起始虚拟地址.它是用-Ttext参数来指定的,所以咱们可以执行以下命令完成链接: ld kernel/main.o - ...

  7. 操作系统真象还原实验记录之实验七:加载内核

    操作系统真象还原实验记录之实验七:加载内核 对应书P207 1.相关基础知识总结 1.1 elf格式 1.1.1 c程序如何转化成elf格式 写好main.c的源程序 //main.c int mai ...

  8. 一步步编写操作系统 46 用c语言编写内核3

    再把上节代码贴出来, 1 //int main(void) { 2 int _start(void) { 3 while(1); 4 return 0; 5 } 有没有同学想过,这里写一个_start ...

  9. 一步步编写操作系统 44 用c语言编写内核1

    先来个简单的,欢迎我们神秘嘉宾--main.c.这是我们第一个c语言代码. 1 int main(void) { 2 while(1); 3 return 0; 4 } 它没法再简单啦,简单的程序似乎 ...

最新文章

  1. 010-012列表:一个打了激素的数组
  2. 使用ViewBag传送数据从控制器至视图
  3. java生成apk工具,生成并运行apk流程
  4. day10_cookiesession学习笔记
  5. .NET5.0 Preview 8 开箱教程
  6. ASP正则表达式方面小笔记
  7. 这家公司疑被查封、员工被带走,股东李笑来称“做天使投资人真不容易”
  8. 华为发布企业服务云化转型战略 未来5年投资5亿美金建设服务能力(2017年09月06日)
  9. 一个盒子相对于另一盒子垂直居中的方法
  10. 广西民族博物馆真好玩
  11. 判定表与判定树的画法_判定树和判定表
  12. 测绘的真正出路在于什么?
  13. 17.敏捷项目管理流程实例 - 整体流程框架
  14. WPS中如何删除中文保留英文
  15. Oracle LiveLabs实验:Application Continuity Fundamentals
  16. 修改jar 注入_ORA00600[16703]安装介质注入型勒索病毒恢复案例
  17. [工业互联-1]:工业互联全局概述
  18. 数字化助力生产制造管理:专项生产管理系统
  19. 假期,推荐豆瓣评分 9.0 以上的 100 部必看电影
  20. Python 竟然不是最赚钱的编程语言?!

热门文章

  1. CSS-posiziton
  2. orcale 基本查询(1)
  3. birt报表与现有系统的集成
  4. Servlet JSP系列文章总结
  5. Membership 数据字典
  6. arry-718 Maximum Length of Repeated Subarray
  7. elementui图片上传php,vue+element-ui+富文本————图片上传
  8. mysql参数化查询为什么可以实现_为什么参数化SQL查询可以防止SQL注入?
  9. java中的sql语句书写_Java中sql语句之通配符*
  10. python数组元素复制_python的numpy数组 的复制问题?