[转载] 内核代号101 — 动手写自己的内核

原文: http://www.geekfan.net/8663/

有点意思的小文章, 揭开内核神秘的面纱.

本文由 极客范 - 何伟寰 翻译自 Arjun Sreedharan。欢迎加入极客翻译小组,同我们一道翻译与分享。转载请参见文章末尾处的要求。

Hi, 大家好。

在这篇文章中,我们将从零开始,动手编写一个可以用GRUB来引导的简单x86内核,该内核会在屏幕上打印一条信息后挂起。

一个人写一个内核是一件简单的事情

X86机器是怎样启动的?

在我们思考怎样写一个内核之前,让我们先看一下x86机器从启动到把控制权交给内核的过程是怎样的:

x86 CPU在机器启动之后就会从地址 [0xFFFFFFF0]处开始执行,这个地址就是在32位寻址空间中的最后16个字节处,这里存放了一条跳转指令,会跳转到内存中BIOS代码起始处。

接着,cpu就开始开始执行BIOS代码块了,BIOS首先会在我们配置好的启动设备序列中,通过检查一个特定的魔数,找到第一个可以引导的设备。

一旦BIOS找到一个可以引导的设备后,它就会把该设备第一个扇区的代码复制到物理内存的[0x7c00]的位置,然后跳转到这个地址开始执行这一段代码,我们习惯把这一段代码叫作bootloader。

Bootloader会将内核代码加载到物理内存[0x100000]的位置,[0x100000]这个地址是所有x86机器宏内核代码的起始地址。

我们需要哪一些工具?

* 一个x86构架的计算机

* Linux

NASM汇编器

GCC

LD(GNU 连接器)

GRUB

源码

源代码可以在我的Github系在 Github repository - mkernel

用汇编代码来编写内核入口

我们喜欢用c来做所有的事情,但是我们无可避免地需要用到一点儿汇编,我们将会写一小段x86的汇编代码来作为内核入口,这一段汇编代码会在调用我们的c代码后停止整个程序流程。

我们怎样确认汇编代码会作为内核的起始点呢?

我们将用一个连接器脚本将这些目标文件链接成我们最终的内核程序(稍后解释更多),在连接器脚本里,我们指定了这段二进制代码会被加载到内存 [0x100000]处。这个地址就是我之前说过的,内核所希望的起始地址。

汇编代码如下:

第一条指令 bit32 不是x86汇编指令,它是一条NASM 指令,指定nasm汇编器产生32位的程序,这条语句并不是必不可少的,但加上它是一个好的编程习惯。

第二行是text段(代码段)的开始,在这里存放着我们的代码块。

global是另外一个NASM指令,用将一个符号设置为全局符号。这样做连接器才会知道符号start在哪儿开始,start是我们程序的入口地址。

kmain是我们定义在kernel.c文件中的函数,extern关键字声明了该函数定义在别的文件中。

到这里,我们的函数start调用kmian函数之后就会使用hlt指令将CPU挂起,中断会cpu从hlt 指令中唤醒,我们要在挂起之前用cli指令来关闭系统的中断响应,cli指令是清除中断(clear-interrupts)的缩写。

用C实现的内核

kernle.asm中,我们调用了kmain()函数,所以我们的c代码将会在kmain()中开始运行:

我们的内核首先会清空整个屏幕,然后打印出字符串。

首先,我们用一个vidptr指针,指向地址[0xb8000] , 这个地址是保护模式下显存的起始地址。屏幕的文本内容对应着的内存空间中一个内存段,即屏幕的输出输出映射到了内存中地址[0xb8000]的地方,整个屏幕共支持25行,每行80个ASCII字符。

在文本内存中每一个字符由16bits(2个字节)表示,这不像我们以前使用8bits来定义。其中第一个字节是该字符的ASCII码,第二个字节是属性字节, 它描述了字符的表现形式,包括了字符颜色等属性。

为了在黑色的背景下打印绿色字符’s‘,我们将字符’s‘放在显存中的第一个字节,接着将[0x02]放在第二个字节中, 其中 0表示黑色背景,2表示绿色前景。

下面是不同颜色的定义:

在我们的内核中,我们将字符颜色设置为灰色,将背景颜色设定为黑色,因此我们的属性字节的值是[0x07].

在第一个while循环中,程序将属性值为[0x07]的空格字符(‘  ’)写到整个屏幕中(共25行,每行80个字符),这样就会将整个屏幕清空了。

在第二个while循环中,我们将null结尾的字符串 “my first kernel” ,从显存的起始处开始写入。

这样字符串就打印在屏幕上了

链接部分

我们用NASM,GCC分别将kernale.asm,kernel.c编译成目标文件,接着将这些目标文件链接成一个可引导的内核程序。

我们指定ld连接器按照我们脚本规定来进行链接。

脚本指定了输出格式为 32位的ELF文件格式. ELF(Executable and Linkable Format)是x86构架的类Unix系统标准的二进制格式。

ENTRY 接收一个参数。它指定了可执行文件的入口符号。

SECTIONS  对我们来讲是最重要的。在这里,我们定义即将生成的可执行文件的布局。我们可以定义各个段链接融合的方式以及放置的位置。

SECTIONS 后的花括号中,符号 (.) 表示的是一个位置计数器。它通常会被初始化为[0x0],作为SECTIONS 块的起始地址 ,它的值是可以被修改的。 之前我说过,内核代码需要在地址[0x100000]处,所以我们将它修改为[0x100000]。

接着看下一行的 .text : { *(.text) }

星号( * )是一个通配符,表示所有的文件名。*(.text)表示将所有输入文件的 .text 

因此,按照这个设定,连接器将所有目标文件的text段融合到最终可执行文件的text 段中,即在位置计数器所标识的地址处 ([0x100000])。

在连接器将处理好输出的text段后,地址计数器的值会变为[0x100000]+text段的长度。

类似的,data段和bss段也会相应得融合后放置到地址计数器所标识的位置。

Grub和多重引导

现在我们已经准备好所有制作内核所需的文件了,但我们还有一步工作,我们还需要用grub Bootloader来启动我们的内核。

在按照Mutileboot 规范来编译我们的内核后,它就可以被GRUB引导了。

按照Mutileboot 的规范说明,内核必须在起始的8KB中包含这一个多引导项头(Multiboot header)。

而且,这个多引导项头里面必须有3个4字节对齐的块。

一个魔术块:包含了魔数[0x1BADB002],是多引导项头结构的定义值。

一个标志块:我们不关心这个块的内容,我们简单设定为0。

一个校检块:校检块,魔术块和标志块的数值的总和必须是0。

因此,我们的内核代码如下:

dd 指令定义了个4字节的双字。

生成内核

我们现在开始将kernel.asm和kernel.c编译成目标文件,接着将它们根据我们的连接器脚本的设定链接到一起:

启动NASM汇编器将kernel.asm编译成ELF-32位格式的目标文件。

-c选项告知GCC编译器在将源文件编译成目标文件后,不要对它们进行链接。

启动链接器,根据我们的链接脚本生成一个名为kernel的可执行的文件。

配置grub, 启动内核

GRUB 需要以kernel-<version>的形式来命名内核程序,所以,我将它重名为kernel-701.

接着将它放在/boot目录下,这一步需要你需要拥有超级用户权限才能够进行操作。

在你的GRUB配置文件grub.cfg中加上一个引导入口,如下:

如果存在一个隐藏菜单的指令,记得要把它移除掉。

重启电脑,你就能够看到你的内核也在启动选择项列表中了。

选择启动它之后,结果如下:

成功显示出来了。

”这是你的内核“

”不,是你的内核“。

PS:

*  建议你在虚拟机中进行你所有内核hacking。

*  在一些新的发行版中,使用了grub2作为默认的bootloader,你需要向下面这样来配置你的配置文件。

(感谢 Rubén Laguna提供了grub2的配置)

*  如果你想用qemu模拟器代替GRUB来启动你的内核程序的话,你可以怎么做:

时间: 2024-10-18 13:40:56

[转载] 内核代号101 — 动手写自己的内核的相关文章

《自己动手写Docker》书摘之一: Linux Namespace

Linux Namespace 介绍 我们经常听到说Docker 是一个使用了Linux Namespace 和 Cgroups 的虚拟化工具,但是什么是Linux Namespace 它在Docker内是怎么被使用的,说到这里很多人就会迷茫,下面我们就先介绍一下Linux Namespace 以及它们是如何在容器里面使用的. 概念 Linux Namespace 是kernel 的一个功能,它可以隔离一系列系统的资源,比如PID(Process ID),User ID, Network等等.一

【原创】连“霍金”都想学习的“人工智能”---【自己动手写神经网络】小白入门连载开始了(1)

欢迎关注[自己动手写神经网络]的博客连载!!! 第1章 神经网络简介 神经网络这个词,相信大家都不陌生.就在你打开本书,并试图了解神经网络时,你已经在使用一个世界上最复杂的神经网络——你的大脑,一个由大约1000亿个神经元(每个单元拥有约1万个连接)构成的复杂系统.但人的大脑太过复杂,以至于科学家们到目前为止仍然无法准确解释大脑的工作原理和方式.但有幸的是,生物神经网络的最最基本的元素已经能够被识别,而这就构成了本书想为你介绍的人工神经网络(Artificial Neural Network).

自己动手写工具:百度图片批量下载器

开篇:在某些场景下,我们想要对百度图片搜出来的东东进行保存,但是一个一个得下载保存不仅耗时而且费劲,有木有一种方法能够简化我们的工作量呢,让我们在离线模式下也能爽爽地浏览大量的美图呢?于是,我们想到了使用网络抓取去帮我们去下载图片,并且保存到我们设定的文件夹中,现在我们就来看看如何来设计开发一个这样的图片批量下载器. 一.关于网络抓取与爬虫 网络蜘蛛的主要作用是从Internet上不停地下载网络资源.它的基本实现思想就是通过一个或多个入口网址来获取更多的URL,然后通过对这些URL所指向的网络资

【自己动手写神经网络】小白入门连载(二)--机器人时代必须得有人工神经(不是神经病)

[自己动手写神经网络]小白入门连载(一) 在上一章中,我们已经介绍了神经网络的基本概念.思想,并提及了有关人工神经元模型的部分内容.在本章中,将对人工神经元模型做更多的介绍. 图2.1 多输入生物神经元示意图 在上一章中提到了一个简单的神经元模型,并且该模型只有一个输入p.这意味着只能有一个额外的神经元与之相连接,这显然是不够的.因此,一个实用的神经元必须是可以接受多个输入的,如图2.1所示,神经元拥有3个输入p1.p2和p3.其中,w和b是根据网络情况不断进行调整的,而传入函数s和传输函数f是

javascript动手写日历组件(2)——优化UI和添加交互(by vczero)

一.优化UI 继上一篇,http://www.cnblogs.com/vczero/p/js_ui_1.html.开始优化UI,主要优化的部分有: (1)增加星期行.(2)字体设置.(3)日期垂直居中.(4)将单元格->底部线条.(5)修改文本的颜色对比.(6)将内部调用的函数加前缀_,如_addHeader()._addWeekday(). 修改的后基本效果如下图: 整个代码做了小修小改: 1 var Calendar = function(div){ 2 this.div = documen

自己动手写处理器之第一阶段(2)——MIPS指令集架构的演变

将陆续上传本人写的新书<自己动手写处理器>(尚未出版),今天是第三篇,我尽量每周四篇 MIPS指令集架构自上世纪80年代出现后,一直在进行着更新换代,从最初的MIPS I到MIPS V,发展到可支持扩展模块的MIPS32.MIPS64系列,再到集成代码压缩技术的microMIPS32.microMIPS64.每个MIPS ISA都是其前一个的超集,没有任何遗漏,只有增加新的功能.       1.MIPS Ⅰ 提供加载/存储.计算.跳转.分支.协处理及其它特殊指令.该指令集架构用于最初的MIP

【自己动手写神经网络】小白入门连载(三)--神经元的感知

[真实原创,转载务必注明出处] 上一个连载中我们已经了解了神经元模型和其工作方式.单个神经元就可以构成一个最简单的神经网络--感知机.在单层神经元感知机中,网络接收若干过输入,并通过输入函数.传输函数给出一个网络的输出.这个网络已经可以解决苹果和香蕉的分类问题.在本系列中,将具体介绍其内部原理. 首先,我们确定感知机的输入.在此,我们引入形状和颜色两个变量,苹果的形状为圆形记为1,颜色为红色记为1:香蕉的形状为弯形记为-1,颜色为黄色记为-1.则有输入p如表3.1所示. 表3.1 常用传输函数列

连“霍金”都想学习的“人工智能”---【自己动手写神经网络】小白入门连载开始了(1)

欢迎关注[自己动手写神经网络]的博客连载!!! 第1章 神经网络简介 神经网络这个词,相信大家都不陌生.就在你打开本书,并试图了解神经网络时,你已经在使用一个世界上最复杂的神经网络--你的大脑,一个由大约1000亿个神经元(每个单元拥有约1万个连接)构成的复杂系统.但人的大脑太过复杂,以至于科学家们到目前为止仍然无法准确解释大脑的工作原理和方式.但有幸的是,生物神经网络的最最基本的元素已经能够被识别,而这就构成了本书想为你介绍的人工神经网络(Artificial Neural Network).

javascript动手写日历组件(3)——内存和性能优化(by vczero)

一.列表 javascript动手写日历组件的文章列表,主要是通过原生的JavaScript写的一个简约的日历组件: (1)javascript动手写日历组件(1)——构建日历逻辑:http://www.cnblogs.com/vczero/p/js_ui_1.html (2)javascript动手写日历组件(2)——优化UI和添加交互:http://www.cnblogs.com/vczero/p/js_ui_2.html (3)javascript动手写日历组件(2)——内存和性能优化:h