一个简单多任务内核编译和运行

来源:互联网 发布:qq飞车淘宝刷徒弟流程 编辑:程序博客网 时间:2024/04/30 19:06

 系统的启动过程:任何一台计算机,在开机后,它要做的第一件事情就是引导(Booting),通过引导,计算机为自身搭建好运行环境,为以后OS的启动与运行做好准备。首先,我们来看看一台计算机是如何引导自身的。在机器加电后,电源供电稳定后,电源会传给8284A时钟生成器一个“Power Good”低电位信号,随后8284A会输出有效的RESET信号,使CPU复位,这时CS:IP = FFFF:0000。CPU在这里执行一条jmp far addr类指令,跳转到实际BIOS映射代码的位置,开始执行BIOS代码。

在跳转到BIOS后,首先会先关闭中断,然后开始自检(POST)工作,这个自检主要检测计算机最基本设备的运转状态。其主要包括对,CPU内部寄存器测试,BIOS芯片字节的检查,8237 DMA控制器测试,基本32K RAM检测等最基本内容。由于被检测设备在系统运行中的重要性,因此在此过程中,BIOS一旦检测到任何异常,都将判为致命性错误,系统将被停机。

通过上面的自检后,BIOS开始初始化8259可编程中断控制器,并设置BIOS的8个主要中断向量(int 10h—int 17h),然后初始化并测试CRT视频接口以及显示内存,在确认正常后,执行其内部的显示卡标准驱动程序(注意这里的驱动跟安装操作系统下的驱动是不一样的),这段代码会存放在C0000h,其主要目的是初始化显示卡。然后BIOS会打印显卡信息。

接着BIOS开始检查其他设备,其包括对8259中断控制器测试,8253定时器测试,键盘复位和卡键测试,扩展I/O测试,设置硬件中断向量,扩展RAM测试(这里的RAM测试会检测除0—32K以外的整个RAM空间),.然后BIOS会搜索其他设备的ROM,如果找到,则会执行它们。接着测试ROM-BASIC的字节检查,测试磁盘驱动器(如:FDC等),测试打印机端口和RS-232,并设置他们的地址。

然后打开NMI(不可屏蔽中断),最后就是调用Int 19h进行自举。这一阶段的自检如果发生错误,系统会判断其为一般性错误,并显示出相应的提示信息。在此过程中,BIOS会将检测收集到的数据保存在内存低1K--2K的区域,并将BIOS中断向量表,以及BIOS程序运行所需要的stack保存在内存低0K--1K的地方。

下面就是系统自举工作了,系统调用int 19h进行自举,寻找启动设备,如:软驱,硬盘,光驱等等。找到后系统读取启动设备的0号逻辑扇区(如是软盘就读取0面0道1扇区的整个内容),并将读取的内容放到内存地址为0000:7C00的地方。

当然,如果找不到启动设备,BIOS就会调用Int 18h,并给出相应的提示信息,然后进入ROM-BASIC。至此,BIOS的引导程序结束,CPU开始执行0000:7C00处的代码。在这里需要说明一下的是,BIOS的引导程序是与操作系统无关的,但随后CPU开始执行的代码就开始与操作系统存在较大的相关性了,因此对于不同的操作系统,下面这一部分可能会存在着较大的不同。不过,从目的上来讲,它们是相同的,即都是为将要运行OS的内核(Kernel)作准备。

进入这一部分的首要工作就是执行启动设备的引导程序。硬盘与软盘的对于引导程序的存放结构是不同的。硬盘有一个叫做MBR(Master Boot Record)的扇区,系统会首先执行它,以判断那个分区是启动分区,并读入该分 区的第一个扇区,并执行。并且在这个扇区中还存放着硬盘分区表(DPT),这个表的地位相当重要,因为它包含了

各个分区的诸如:分区类型,起始位置,结束位置等重要参数。下面我们来详细介绍一下MBR的结构。MBR的结构分为三部分,首先是可执行代码,占446个字节,然后是4个分区表,每个占16个字节,共64个字节,最后是签字AA55H


参照赵炯老师《Linux内核完全剖析》中的 一个简单多任务内核实现源码,来分析一下boot.s(源码下载:oldlinux.org/Linux.old/bochs/linux-0.00-050613.zip)

!    boot.s
!
! It then loads the system at 0x10000, using BIOS interrupts. Thereafter
! it disables all interrupts, changes to protected mode, and calls the

BOOTSEG = 0x07c0
SYSSEG  = 0x1000            ! system loaded at 0x10000 (65536).
SYSLEN  = 17                ! sectors occupied.

entry start
start:
    jmpi    go,#BOOTSEG //跳转到段BOOTSEG,段内偏移为go的地方执行代码,此时为实模式,所以BOOTSEG为段地址,jmpi后面跟的是偏移和段选择符。为什么要跳转到这里,上面说过系统调用int 19h进行自举,寻找启动设备,如:软驱,硬盘,光驱等等。找到后系统读取启动设备的0号逻辑扇区(如是软盘就读取0面0道1扇区的整个内容),并将读取的内容放到内存地址为0000:7C00的地方。

通过调试也可以查看到内容被读取到内存地址为0000:7C00的地方


    go:      mov    ax,cs
    mov    ds,ax
    mov    ss,ax
    mov    sp,#0x400        ! arbitrary value >>512

! ok, we've written the message, now

 INT 13H,AH=02H 读扇区说明:

软盘其容量的计算如下:
80(磁道)x18(扇区)x512 bytes(扇区的大小)x2(双面) = 1440 x1024 bytes = 1440 KB = 1.44MB
3.5英寸软盘片,其上、下两面各被划分为80个磁道,每个磁道被划分为18个扇区,每个扇区的存储容量固定为512字节。

调用此功能将从磁盘上把一个或更多的扇区内容读进存贮器。因为这是一个低级功能,在一个操作中读取的全部扇区必须在同一条磁道上(磁头号和磁道号相同)。BIOS不能自动地从一条磁道末尾切换到另一条磁道开始,因此用户必须把跨多条磁道的读操作分为若干条单磁道读操作。
入口参数:
AH=02H 指明调用读扇区功能。
AL 置要读的扇区数目
,不允许使用读磁道末端以外的数值,也不允许
使该寄存器为0。
DL 需要进行读操作的驱动器号。
DH 所读磁盘的磁头号。
CH 磁道号的低8位数。
CL 低5位放入所读起始扇区号,位7-6表示磁道号的高2位。
ES:BX 读出数据的缓冲区地址。
返回参数:
如果CF=1,AX中存放出错状态。读出后的数据在ES:BX区域依次排列。


load_system:
    mov    dx,#0x0000 //驱动器号:DH=00H(软盘);磁头号:DL=00H
    mov    cx,#0x0002 //CH=00H0磁道CL02H,从第2扇区开始读。
    mov    ax,#SYSSEG//ES:BX 读入到此缓冲区地址处(0x1000:0x0000)。
    mov    es,ax
    xor      bx,bx
    mov    ax,#0x200+SYSLEN //AH=02H,读扇区;AL=17,需要读到第17个扇区。
    int       0x13

    jnc    ok_load //CF=0时跳转。CF=0表明读扇区操作成功。
die:       jmp    die //如果读取失败进入死循环 


通过调试可以看到上图,未调用int     0x13之前地址处0x1000:0x0000没有内容的,调用之后,才有了内容。通过UltraEdit查看内核磁盘映像文件第2扇区(0x200h)内容对应得上的内容


! now we want to move to protected mode ...
ok_load:
    cli            ! no interrupts allowed !
    mov    ax, #SYSSEG
    mov    ds, ax
    xor      ax, ax
    mov    es, ax
    mov    cx, #0x2000
    sub     si,si
    sub     di,di
    rep
    movw  //ES:DI<-DS:SI(从DS段的SI偏移处移动CX个字到ES段的DI处)
    mov    ax, #BOOTSEG
    mov    ds, ax
    lidt      idt_48        ! load idt with 0,0
    lgdt     gdt_48        ! load gdt with whatever appropriate

! absolute address 0x00000, in 32-bit protected mode.
    mov     ax,#0x0001    ! protected mode (PE) bit
    lmsw    ax        ! This is it! //设置CR0保护模式标志PE(0).这个指令只能影响最低的4 bit,即PE,MP,EM和TS,对其它的没有影响。
    jmpi     0,8        ! jmp offset 0 of segment 8 (cs) //因为此时已经进入到保护模式,所以这里的8是段选择符,8=1000bTI0表示GDT表,索引为1,表示GDT表中的第2项(索引为0表示第1项),即下面GDT表中的第二项——代码段。

gdt:    .word    0,0,0,0        ! dummy

    .word    0x07FF        ! 8Mb - limit=2047 (2048*4096=8Mb)
    .word    0x0000        ! base address=0x00000
    .word    0x9A00        ! code read/exec
    .word    0x00C0        ! granularity=4096, 386

    .word    0x07FF        ! 8Mb - limit=2047 (2048*4096=8Mb)
    .word    0x0000        ! base address=0x00000
    .word    0x9200        ! data read/write
    .word    0x00C0        ! granularity=4096, 386

idt_48: .word    0        ! idt limit=0
    .word    0,0        ! idt base=0L
gdt_48: .word    0x7ff        ! gdt limit=2048, 256 GDT entries
    .word    0x7c00+gdt,0    ! gdt base = 07xxx
.org 510
    .word   0xAA55 //最后是签字AA55H

开始编译下载代码 编译环境


一开始编译时候很多问题,主要代码版本老太旧了,很多东西都已经更新换旧了。

1 make: as86: Command not finded,as86汇编器未安装,我安装bin86 amd64,就OK了。bin86下载

2 make: gas: Command not found,gas、gld 的名称已经过时,现在GNU assembler的名称是 as,

修改主Makefile 文件,将AS =gas 修改为 AS =as,将LD =gld 修改为 LD =ld

3 Error: alignment not a powerof 2 ,修改boot/head.s 文件,将.align 2 应该改为 .align 4,.align 3 应该改为.align 8

这是由于在64位机器上编译的原因,需要告诉编译器,我们要编译32位的code,在所有Makefile的AS后面添加 --32

5 5.1这个链接问题的原因就是链接器在将一堆目标文件组合到一起的时候,不知道那个应该放在开头,也就是生成的可执行文件的第一条指令应该是哪个目标文件的第一条。5.2连接时候也要选择32位(ld的参数选项中增加-m elf_i386),根据内核知识可知,内核最先运行的是head程序,所以打开head.s在.text段中增加.globl startup_32。然后在ld的参数选项中增加-m elf_i386 -Ttext 0 -e startup_32。

最后成功编译通过


6当我使用Bochs-2.6.8在模拟环境下运行时候,一直停留在如下这个界面。


调试时候BOOT.S引导是没有问题,那问题出现在执行HEAD.S代码上面了,通过调试查看,调用了INT 0x13之后,HEAD.S没有拷贝到0x10000


查看Makefile 文件,我们知道最后生成Image文件时候,是通过dd bs=512 if=system of=Image skip=2 seek=1,书上原文:as/ld 编译连接出的head文件开始部分包括1024字节的a.out格式头部信息,因此在生成内核Image文件时我们利用dd命令分别去掉两者头部信息并把它们合成内核映像Image文件。而我通过UltraEdit查看内核映像Image文件,发现HEAD.S代码被放在(0x0e00 = 3584)/512 = 7.放在第7个扇区,而不是第2个扇区。


所以命令修改成:dd bs=512 if=system of=Image skip=8 seek=1,就OK了。


使用Bochs-2.6.8在模拟环境下运行一切正常。



整个程序下载

0 0
原创粉丝点击