Linux Kernel 0.12 启动简介,调试记录(Ubuntu1804, Bochs, gdb)

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

环境说明
  • Ubuntu 18.04
  • gcc version 7.5.0 (Ubuntu 7.5.0-3ubuntu1~18.04)
  • Bochs 2.6
  • As86 version: 0.16.17

前言


  自从我近段时间开始温习一些基础知识以来,其中觉得以前学的很浅的就是OS原理。为啥这样说呢?因为就是浅,知道一些琐碎的知识。以前我自负的认为OS就是硬件的抽象,然后把这些硬件资源合理的分配给用户使用就完了,因为我觉得合理的整合这些硬件资源是非常‘简单’的。

  由于我本身对底层是非常着迷的。带着觉得OS很简单的想法,想着去看看LinuxKernel的源码。在以前,我对LinuxKernel的认知很肤浅,就知道一些驱动移植的事情。如果硬要说一件我在LinuxKernel中玩的很深的事情,那就是自己理解并实现了一个类似Anonymous Shared Memory的Linux驱动,详见以下两篇文章。

  • 《Android匿名共享内存(Anonymous Shared Memory) — 瞎折腾记录 (驱动程序篇)》 https://blog.csdn.net/u011728480/article/details/88420467
  • 《linux kernel 中进程间描述符的传递方法及原理》 https://blog.csdn.net/u011728480/article/details/88553602

  带着这样的想法其实已经很久了,由于现在的LinuxKernel太大了,对新手不友好。我就想着去找一个老一点的版本内核看看。结果去网上一找,就发现了前人已经做了许多许多了,比如这个之前就有了解的《linux 0.11内核完全注释》,还比如其他许许多多前人种的‘树’,看到了许多,最终我决定跟着国内现在比较好和新的资料从‘远古’开始学习它。它就是《Linux内核完全注释(PDF) v5.0 by 赵炯.pdf》。它是基于LinuxKernel0.12 讲述的,它是我在ubuntu1804上编译通过LinuxKernel0.12的主要参考和学习资料,同时也是我在Bochs上运行成功的主要参考和学习资料。

  好的多说无益,直接看运行效果。

result_img

  说来也惭愧,利用断断续续的时间,我花了约2月,把LinuxKernel0.12在Ubuntu1804上编译通过,并在1804上通过Bochs运行成功。而且要命的事情是我其实只加了一些打印调试函数,和根据实际的调试情况修改了一些代码,却花了那么久的时间,搞得我很不自信了QAQ。

  我修改好的源码已经开源,立即想要源码的请直接去文末两个rep clone即可。

  本文主要还是简单介绍LinuxKernel从上电到进入sh的中间的简要流程。这些流程网上已经有很多了,可能我会挑选一些我觉得比较重要的来说。

  本文适用于:

  • 会编译和使用bochs的人。不会可以去网上找找,很多这方面的资料。
  • 对Intel AT&T 汇编有点了解的人。
  • 会GDB调试的人。
  • 知道C语言常识的人。
  • 对LinuxKernel感兴趣的人。




搭环境


  工欲善其事必先利其器。本文主要是在Ubuntu1804上编译生成LinuxKernel,然后用Bochs运行我们的内核。



Ubuntu18.04环境安装

我们应该首先安装make,gcc,gcc-multilib,bin86。

  • sudo apt install build-essential cmake make gcc-multilib g+±multilib module-assistant bin86

然后进入源码目录。

  • cd my_src
  • make disk

更多的详情信息查看开源的rep。



编译两个bochs版本备用

  我们首先就得把Linux0.12的运行环境搭建起来,方便我们调试。我们使用的是Bochs2.6 和 GDB远程调试。并编译出两个bochs版本,一个是带本身调试功能(命名为:bochs),一个是和gdb联调(命名为:bochsdbg)。bochs 主要是调试在init/main()函数之前的内容以及查看更多的x86寄存器。 bochsdbg主要是调试进入init/main()函数之后到sh成功执行的事情。

  • 通过 ./configure --enable-debugger 生成bochs。
  • 通过 ./configure --enable-gdb-stub 生成bochsdbg。


运行我们编译的内核

  通过本文介绍生成的文件是Linux内核镜像,稍微懂点行的人都知道还差一个RootFS。这个文件系统我们在网上下载的例如: http://oldlinux.org/Linux.old/bochs/linux-0.12-080324.zip 。本文生成的Linux内核镜像使用的是rootimage-0.12-hd这个文件系统。

  我建议这里自己配置两个.bxrc文件,一个对应bochs,一个对应bochsdbg远程调试。这样在遇到问题的时候我们可以很方便的调试。





LinuxKernel启动简介


  本节简述LinuxKernel的启动流程。根据我近段时间的学习来看,这里包含了许多的历史性的东西,大家不要去细究为啥是这样,很多都是为了兼容。

  此外在整个学习期间,由于涉及到许多的x86 硬件体系知识,除了参考上文我说的文档以外,还必须参考以下Intel官方文档:

  • Intel® 64 and IA-32 architectures software developer’s manual combined volumes 2A, 2B, 2C, and 2D:Instruction set reference, A-Z
  • Intel® 64 and IA-32 architectures software developer’s manual combined volumes 3A,
    3B, 3C, and 3D: System programming guide
  • 《Linux内核完全注释(PDF) v5.0 by 赵炯.pdf》 第4章,全篇精华。


boot/bootsect.S 阶段

  当我们的计算机上电以后,IntelCPU进入实模式,并且PC指向了0xfff0整个地址,如下图。什么意思呢?就是开机的时候执行的第一句指令放在0xffff0这个地方,通常这里有一个很重要的东西叫做BIOS。我们可以看到下图,cs=0xf000,base=0xffff0000,在实模式下面,cs:pc 就是真实的指向地址0xffff0。到了这里不知道大家发现没有,这里还差一个东西,那就是bios本来是放在rom里面的,怎么被指向了内存地址0xffff0的地方呢?是谁在之前自动搬运的吗?经过查询后发现,大部分人说开机的时候,对特殊地址的访问会被仲裁器件指向BIOS-ROM器件。仲裁器还可以把地址翻译并指向我们熟悉的MEM和IO。所以这里我理解对0xffff0的访问就是对BIOS-ROM器件的直接访问和执行。

poweron_img

  BIOS主要是做自检,并且在物理地址0x0开始初始化BIOS的中断向量,同时通过BIOS访问存储设备的中断,将可启动设备的第一个扇区512字节给搬运到绝对地址0x7c00(31k)处。然后跳转到0x7c00继续执行,这里被搬运的512字节就是bootsect.S生成的指令。这一段没啥营养,都是一些约定好的,到了CPU执行到绝对地址0x7c00的时候,才是真正的我们能控制的地方。其实这里也能够看到,我们的bootsect.S生成的指令最大只能够512字节,超过了就会出问题。下图为我们的0x7c00处的开始几句指令和bootsect.S的几句指令,同时也能够看到BIOS初始化和自检打印的一些内容:

7c00_img
  在上图的图中,我打印了0x7c00开始的一部分反汇编代码。可以看到和下面的bootsect.S的代码是一致的。
entry start
start:
! start at 0x07c0:0
! add by sky
	mov ax,#BOOTSEG
	mov es,ax
	mov	bp,#msg2	  ! sky-notes: src-str is es:bp
	mov	si,#15        ! sky-notes: src-str-len is cx
	call pirnt_str
! add by sky

	mov	ax,#BOOTSEG
	mov	ds,ax
	mov	ax,#INITSEG
	mov	es,ax
	mov	cx,#256
	sub	si,si
	sub	di,di
	rep
	movw
	jmpi	go,INITSEG

  从0x7c00开始,就是我们自己的可以编程的领域了,也开始有了一些我自己特有的内容。主要是各种方法实现的print语句。这种调试方法简直不要太好。

  下面简要说明一下bootsect.S的功能:

  • 首先用rep movw把自己从0x7c00搬运到0x90000,并跳转cs=0x9000, pc=go 标号的地址。继续执行剩下的内容。
  • 通过读取0x1E号中断向量位置的软驱参数(由BIOS初始化时候通过BIOS中断读取的)到内存,然后修改其中的最大扇区数,并重新写回到0x1E中断向量位置绝对地址0x78去。最后重置软驱,使其加载最新的参数。
  • 使用BIOS INT 0x13的2号功能,将第一个软盘第2,3,4,5扇区读取到0x90200开始的位置。这里读取的就是setup.S的指令内容,最大共2k(4*512)。0x90000-0x90200存放的是bootsect.S, 0x90200-0x90A00 为setup.S。
  • 使用BIOS INT 0x13的8号功能,读取磁盘参数:每磁道扇区数。并保存到变量sectors中。
  • 使用BIOS INT 0x13的2号功能,使用刚刚的参数,读取system模块到0x10000,我们的bootsect.S放在0x90000,所以我们system模块最大只能够占用0x10000~0x8ffff。这里的system模块就是除了bootsect和setup模块之外的所有内核代码。
  • 判断bootsect模块第508,509字节是否为0,来判断我们是否指定根文件系统的设备号。我们的内核定义为0x0301,代表第一个磁盘第一个分区为我们的根文件系统。
  • 然后通过jmpi 0:9020跳转到cs=0x9020,pc=0的地方去执行setup.S的代码。

  在我的bootsect模块,我定义了一个打印字符串的函数,主要是通过使用BIOS INT 0x10的0x13号功能实现。主要还是为了调试,注意,这里不能够随意添加代码,因为生成的代码超过512byte后,链接器会报错。只能够少量的添加我们的调试代码。

  至此,我们就执行完了bootsect模块。本模块的主要内容还是加载setup和system到指定位置。bootsect执行的一些调试日志如下图(在0x90200下断点):

bootsect_log_img
注意:图中话框的部分就是我们上文贴出的call pirnt_str打印的。

boot/setup.S 阶段

  首先我们还是来看一下0x90200的位置是否是setup.S,换句话来说是否加载好了setup模块。

90200_img
  这里和bootsect一样,我也弄了一个prtstr函数,这个prtstr和bootsect里面的是一样的,原理也是一致的。

  刚刚我们提到,setup是从0x90200开始存放的。那么0x90000~0x901ff中的bootsect已经无用了,于是我们setup中,用这里的内存存放一些参数。下面简要说明一下setup.S的功能:

  • 用BIOS INT 0x15功能号0x88取系统所含扩展内存大小并保存在内存0x90002~0x90003处。共两个字节。
  • 用BIOS INT 0x10功能号0x12读取显卡参数,0x9000A 显存大小,0x9000B 显卡类型(单色/彩色),0x9000C显卡特性参数。
  • 用BIOS中断读取屏幕的行列存放到0x9000E 0x9000F
  • 用BIOS INT 0x10功能号0x03读取当前光标位置存放到0x90000 0x90001
  • 用BIOS INT 0x10功能号0x0f读取当前显示页,显示模式,字符列数。 0x90004~0x90005 存放当前显示页。 0x90006
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值