Linux bootloader 编写方法

news/2024/6/29 12:01:34 标签: linux, linux内核, dos, command, 编译器, 嵌入式

1.概述

linux运行在保护模式下,但是当机器启动复位的时候却处于实模式下。所以写bootloader做的工作也是在实模式之下的。

linux的内核有多种格式,老式的zImage和新型的bzImage。它们之间最大的差别是对于内核体积大小的限制。由于zImage内核需要放在实模式1MB的内存之内,所以其体积受到了限制。目前采用的内核格式大多为bzImage,这种格式没有1MB内存限制。本文以下部分主要以bzImage为例进行分析。

2.bzImage格式内核的结构

bzImage内核从前向后分为3个部分,前512字节被称为bootsect,这就是软盘引导linux时用到的bootloader,如果不从软盘引导,这部分就没有用,其中存储了一些编译时生成的内核启动选项的默认值。从512个字节开始的512*n个字节称为setup部分,这是linux内核的实模式部分,这部分在实模式下运行,主要功能是为保护模式的linux内核启动准备环境。这个部分最后会切换进入保护模式,跳转到保护模式的内核执行。最后的部分就是保护模式的内核,也就是真正意义上的linux内核。其中n的大小可以从bootsect后半部得到,详细地址可以参阅linux boot protocol。

3.引导过程概述

第一步,打开冰箱门;第二步把大象放到冰箱里……不要笑,过程就是这么简单。首先需要把linux内核的setup部分拷贝到9020H:0开始的地址,然后把保护模式内核拷贝到1MB开始的地址,然后根据Linux Boot Protocol 2.03的内容设定参数区的内容,基地址就是9000H:0,最后使用一条ljmp $0x9020,$0跳转到setup段,剩下的事情就是linux自己的了^_^,果然简单吧!

4.THE LINUX/I386 BOOT PROTOCOL

这个就是我们引导linux所使用的协议,它的位置在:Documetation/i386/boot.txt中。里面详细的写了引导linux所需要知道的一切知识,对于其它体系结构的CPU,也一定存在着类似的东东,仿照本文的方法就可以了。

5.细节一:基本引导参数

当然我们不指定任何参数linux内核也可以启动,但是这样有可能启动进入一个我们不支持的framebuffer模式,导致没有任何屏幕显示;也可能mount了错误的根分区失败,导致No Init Found的kernel panic。所以我们必须要指定一些东西。

如果你像我一样是一个懒人,那么可以直接把bootsect拷到9000H:0的位置,使用软盘引导时它会把自己复制到这个地方的,这里面有些默认的设置,详情请见boot.txt。

首先是root的位置,这里bootsect_pos指向的是9000H:0的地址。

bootsect_pos[0x1fc] = root_minor;
bootsect_pos[0x1fd] = root_major;

其中root_minor和root_major分别是root的主设备号和次设备号。

当前显示模式:

bootsect_pos[0x1fa] = 0xff;
bootsect_pos[0x1fb] = 0xff;

这两个数值相当于引导参数vga=0xHHH的值,两个0xff代表文本模式。

bootsect_pos[0x210] = 0xff;

这是在设定你的bootloader的类型,其实只要不是0就行,因为0代表的loader太旧无法引导新的内核,setup发现这个后就会停下来。按照规范你应该写成0xff,这表示未知的boot loader,如果你的bootloader已经得到了一个官方分配的type id,那就写上自己的数值。

6.细节二:如何加载内核

如果你现在的环境是一无所有,那么必须使用bios中断或者ATA指令去读硬盘了,不过如果你手中如果有基本的DOS系统,那么就可以使用DOS的程序了。为了能够操作整个4GB的地址空间,我使用了WATCOM C写了个小程序读内核,不过你可以仿照bootsect里面的做法,在实模式中读一部分,然后进入到保护模式拷贝到1MB以上,然后再从实模式读一部分……需要注意1:9000H:0也是DOS占用的地址空间,所以读完内核后就不要返回DOS了,否则会有问题;

注意2:一定保证是纯DOS,不要加载HIMEM或者EMM386这样的东西,它们会使上面的引导过程失败。loadlin倒是可以来者通吃几乎所有的DOS,不过它的作者也是这方面的大牛,对DOS下的内存管理非常的熟悉。我们现在研究这些古老的东西很难找资料了,况且我们是在写bootloader,不是DOS killer^_^。

7.引导时的高级功能

1)initrd

initrd是启动时的一个小虚拟盘,一般用它来实现模块化的内核。引导initrd的方法主要有两个要点: 
第一,把initrd读入内存,我们可以仿照大多数boot loader的方法把它放在内存的最高端; 
第二,设定initrd的起始位置和长度

bootsect_pos[0x218]开始的4个字节放的是起始物理地址,bootsect_pos[0x21c]开始的4个字节放的是initrd的长度。

2)command_line支持

command_line你可以给内核传一些参数,自己定制内核的行为。我是这样做的,首先把command_line放在9900H:0的地址里,然后把9900H:0的物理地址存放在bootsect_pos[0x228]开始的4个字节里面。注意一定是物理地址,所以你应该放99000H这个数,然后内核就会识别你的command_line了。

8.结束语

写本文的目的主要是为了用最少的语言和最短的时间说明bootloader的原理,真正的权威资料还是要看linux内核源码和boot.txt文件。我曾经写过一个例子loaderx,使用WATCOM C和TASM,WATCOM C是一个可以在DOS下生成能访问4GB物理地址程序的C编译器,里面也有详细的注释和文档说明。可以从下面的地址下载: loaderx.tar.gz


参考资料

  • THE LINUX/I386 BOOT PROTOCOL 2.03

关于作者

范晓炬,联想(北京)有限公司软件设计中心嵌入式研发处开发工程师,研究兴趣为 Linux 内核,网络安全,XWindow 系统,Linux 桌面应用,人工智能系统。你可以通过 xiaoju_f@263.net联系他。


http://www.niftyadmin.cn/n/822983.html

相关文章

基于C语言的面向对象编程

嵌入式软件开发中,虽然很多的开发工具已经支持C的开发,但是因为有时考虑运行效率和编程习惯,还是有很多人喜欢用C来开发嵌入式软件。Miro Samek说:“我在开发现场发现,很多嵌入式软件开发者(绝大多数是电子工程师)都有…

谈谈我与 Intellij IDEA 的故事

说到这款 IDEA,很早就想写一篇关于它的文章了,可不知从何处写起,刚好一位师弟问起我来,正好写一篇入门文章,本文的涉及的快捷键主要针对于 Windows系统「因大部分读者都在使用此系统」。相识IntelliJ IDEA 于 2001 年 …

Redis 深度历险:核心原理与应用实践

Redis 是如今互联网技术架构中,使用最广泛的缓存。支持复杂的数据结构,支持持久化,支持主从集群,支持高可用,支持较大的value存储...同时, Redis 也是中高级后端工程师技术面试中,面试官最喜欢问…

Android进程守护,让APP在系统内存中常驻(一)

其实我们开发者并不想让自己做的应用变成流氓软件,但是没办法, 你的老板需要,你要不想让你的应用常驻,那咱就常驻不了了。。。所以说,言归正传。。。 第一篇准备使用系统的服务保活。如果想看提高app的进程等级来实现应…

Mongodb 创建索引

db.getCollection(ct_project).ensureIndex({pro_code:1}) 创建索引 db.getCollection(ct_project).ensureIndex({pro_code:1,unique:true}) 创建唯一索引 db.getCollection(ct_project).getIndexes(); 查询索引 db.getCollection(ct_project).dropIndex({"username"…

这场程序设计大赛有点硬核,它究竟怎么玩的?

我是因为竞赛才走向程序生涯。大学虚度光阴几年,就连玩都感觉玩腻了,当时再过一年就毕业了,心中满是愧疚和无奈,想着我的大学竟然没有干成任何一件让我满意的事情,如果就这样毕业了,太多遗憾和没意思。在数…

Android进程守护,让APP在系统内存中常驻(二)

昨天晚上写了用系统服务等方法来实现应用保活。今天写一下用提高app的进程等级来实现应用保活。想看直接调用系统方法保活应用的可以点击Android进程守护,让APP在系统内存中常驻(一)进行跳转。 一:第一种实现思路,创建…

个人冲刺09

一、任务:将两个页面与其他页面连接起来。 二、预计时间:1天。 三、自问自答三个问题: (1)昨天做了什么? 做2个activity,一个是用户信息管理的(比如说修改密码等)&#x…