linux内核完全剖析—第四章 80x86保护模式及其编程—编译代码和代码详细解读

一、搭建第四章编译和调试

也就是搭建linux-0.00(简单多任务内核实例剖析)的环境和编译

下面我的是ubuntu12.04 x32编译

1.下载代码

下载http://www.oldlinux.org/Linux.old/bochs/linux-0.11-devel-050518.zip这也就是linux-0.00第四章末尾的程序

 

2.安装as86ld86

其中boot.s是用as86/ld86编译(需要我们下载)

head.s是用gnu as/ld编译(自带)

sudo apt-get install bin86 安装as86/ld86

 

3.修改源码

解压在里面,这里面已经编译好了,如果已经安装bochs2.2那么就可以直接运行了,

当然我们要自己编译那么找到boot.s和head.s, Makefile这三个文件,作如下修改

 

[1]修改head.s,修改三处

movl scr_loc, %bx改为mov lscr_loc,%ebx

movl $65, %al 改为movb $65,%al

movl $66, %al改为movb $66, %al

把所有.align 2改成.align 4, .align 3改成.align 8

在.text中添加一行 .globl startup_32

 

[2]修改boot.s

rep movw  这行书上写成了一行

代码里面是两行所以不用改了

 

 

[3]修改Makefile文件,修改三处

#AS  =gas  gas gld is old change by snownight

#LD  =gld

AS     =as

LD    =ld

 

#LDFLAGS         =-s-x -M

LDFLAGS=-m elf_i386 -Ttext 0 -e startup_32

all:    Image

 

Image: boot system

         ddbs=32 if=boot of=Image skip=1

         #ddbs=512 if=system of=Image skip=2 seek=1

         ddbs=512 if=system of=Image skip=8 seek=1   把head.s写到0xe00处

         sync

[4]编译后发现image比以前的多了0x440个字节,别的地方差别很小,不影响我们的运行

 

4.调试

新建run.bat文件内容如下

"C:\Program Files(x86)\Bochs-2.2\bochsdbg" -q -f bochsrc-0.00.bxrc

 

 

 

二、简单多任务内核实例剖析

总共有三个文件boot.s和head.s,Makefile

image文件内容分布


代码执行过程内存移动情况


内核任务在虚拟机地址空间的分配图


内存数据分布


执行结果


代码详细注释

其实就是boot.s把head.s载入到内存,然后运行,有2个任务都会调用int 0x80,此时的0x80中断已经被设置成system_interrupt函数了,此函数就打印一个字符,timer_interrupt用来切换任务,切换的办法是

 

ljmp $TSS1_SEL, $0

popl %eax

pop %ds

iret

有一个很深的疑问,根据代码可知道打印的字符应该是没有颜色的SN,但是为何出现有颜色的SN呢!!!

1.boot.s

!boot.s

!首先利用BIOS中断把内核代码(head.s的代码)加载到内存0x10000然后移动到内存0处

! 最后进入保护模式,并跳转到内存0(head.s代码)开始继续执行

BOOTSEG = 0x07c0                    !引导扇区(本程序)被BIOS加载到内存0x7c00处

SYSSEG = 0x1000                     !内核(head.s)先加载到0x10000处,然后移动到0x0处

SYSLEN = 17                              !内核占用的最大磁盘扇区数。

entry start

start:

         jmpi go,#BOOTSEG           !段间跳转至0x7c0:go处。当本程序刚运行时所有段寄存器值                

go:   mov ax,cs                                     !均为0。该跳转语句会把CS寄存器加载为0x7c0(原为0)

         mov ds,ax                                     !让DS和SS都指向0x7c0段

         mov ss,ax                                    

         mov sp,#0x400                           !设置临时栈指针。其值需大于程序末端并有一定空间即可

        

!加载内核代码到内存0x10000开始处。

load_system:

         mov dx,#0x0000                         !利用BIOS中断int 0x13功能读取head代码,从启动盘第2扇区开始读17个扇区到内存0x10000

         mov cx,#0x0002                         !DH-磁头号;DL-驱动器号;CH-10位磁道号低8位;

         mov ax,#SYSSEG                         !CL--位7、6是磁道号高2位,5-0起始扇区号(从1计)。

         mov es,ax                                     !ES:BX--读入缓冲区位置(0x1000:0x0)

         xor   bx,bx                                     !AH--读扇区功能号;AL-需读的扇区数(17)

         mov ax,#0x200+SYSLEN 

         int  0x13

         jnc    ok_load                                !若没有发生错误则跳转继续运行,否则死循环

die:  jmp  die   

 

! 把内核代码移动到内存0开始处。共移动8KB字节(内核长度不超过8KB)。

ok_load:

         cli                        !关中断

         mov ax, #SYSSEG             !复制开始位置DS:SI=0x1000:0;目的位置ES:DI=0:0。

         mov ds, ax

         xor   ax, ax

         mov es, ax

         mov cx, #0x1000                        !8KB字节

         sub   si,si

         sub   di,di

         rep                                                  !重复其后的串操作指令。重复前先判断CX是否是0,为0就结束重复,

         movw                                             !否则CX减1,重复其后的串操作指令,movw是复制一个字节

        

!加载IDT和GDT基地址寄存器IDTR和GDTR

         mov           ax, #BOOTSEG

         mov           ds, ax

         lidt   idt_48                !加载IDTR。6字节操作数 2字节表示长度,4字节线性基地址。

         lgdt  gdt_48               !加载GDTR。6字节操作数 2字节表示长度,4字节线性基地址。

 

!设置控制寄存器CR0(即机器状态字),进入保护模式,段选择符值8对应GDT表中第2个段描述符,代码段。

         mov           ax,#0x0001      ! 在CR0中设置保护模式标志PE(位0),接下来可以进入保护模式

         lmsw         ax                        !此条指令等价于x86 masm 汇编的mov cr0,ax,

         jmpi 0,8                      !已经开启保护模式这里跳转,其实是在gdt中寻址,base=0x0,

                                                        !由于选择子是8也就是第2个描述符,至此跳到0x0处执行,进入head.s执行

                                                       

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

 

         .word        0x07FF               !8Mb - limit=2047 (2048*4096=8Mb) 第1,2字节

         .word        0x0000              !base address=0x00000

         .word        0x9A00              !code read/exec

         .word        0x00C0              !granularity=4096, 386 第7,8字节

 

         .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                ! idtlimit=0

                   .word        0,0             !idt base=0L

gdt_48: .word 0x7ff                   ! gdtlimit=2048, 256 GDT entries

                   .word        0x7c00+gdt,0  ! gdt base = 07xxx

.org 510

         .word   0xAA55

 

2.head.s

# head.s包含32位保护模式初始化设置代码,时钟中断代码、系统调用中断代码和两个任务的代码

# 在初始化完成之后程序移动到任务0开始执行,并在时钟中断控制下进行任务0和1之间的切换

SCRN_SEL         =0x18

TSS0_SEL =0x20

LDT0_SEL          =0x28

TSS1_SEL =0X30

LDT1_SEL          =0x38

 

.text

.globl startup_32

startup_32:

#首先加载数据段寄存器DS,堆栈段寄存器SS和堆栈指针ESP.所有段的线性基地址都是0.

         movl$0x10,%eax                       #0x10=1000 0b是GDT中第三项数据段选择子

         mov%ax,%ds

         lssinit_stack,%esp                    #设置系统堆栈init_stack->ss:esp;

 

#在新的位置重新设置IDT和GDT表

#执行setup_idt后idtr=0x198,limit=0x7ff

#把256个中断门全部填入默认

#0x00000198 <bogus+       0>:   0x14    0x01    0x08   0x00    0x00    0x8e   0x00    0x00

         callsetup_idt                     #设置IDT,先把256个中断门都填默认处理过程的描述符

         callsetup_gdt                    #设置GDT

         movl$0x10,%eax                       #在改变GDT之后重新加载所有段寄存器

         mov%ax,%ds

         mov%ax,%es

         mov%ax,%fs

         mov%ax,%gs

         lssinit_stack,%esp

 

# 设置8253定时芯片。把计数器通道0设置成每隔10毫秒向中断控制器发送一个中断请求信号。

#8254芯片的时钟输入频率为1.193180MHZ,英雌初始计数值LATCH=1193180/100,约为11931,也就是我们设置成

#这样,这货就会每隔10秒钟在通道0输出端out发出一个方波上升沿。也就是发出一个时钟中断请求IRQ0信号

         movb$0x36, %al                        #控制字:设置通道0工作在方式3(方波发生器方式)计算初值采用二进制

         movl$0x43, %edx             #8253芯片控制字寄存器写端口

         outb%al, %dx

         movl$11930, %eax       #初始计数值设置为LATCH

         movl$0x40, %edx             #通道0的端口

         outb%al, %dx                    #分别两次把初始计算值写入通道0

         movb%ah, %al                           

         outb%al, %dx

 

#在IDT表的第8项和128项(0x80)分别设置定时中断门描述符(timer_interrupt)和系统调用陷阱门描述符(system_interrupt)

         #下面执行完后

         #<bochs:56>x /8bx 0x198+0x8*8

         #0x000001d8<bogus+       0>:    0x2c   0x01    0x08    0x00   0x00    0x8e    0x00   0x00

         #0x000x00 x8e 0x00 0x00 0x08 0x01 0x2c

         #

        

         movl$0x00080000, %eax                           #中断程序属于内核,即eax高字是内核代码段选择符0x0008

         movw$timer_interrupt, %ax           #设置定时中断们描述符。取定时中断处理程序地址 ,timer_interrupt在内存的地址是0x12c

         movw$0x8E00, %dx                                    #中断门类型是14(屏蔽中断),特权级0或硬件使用

         movl$0x08, %ecx                       #开机时BIOS设置的时钟中断向量号8.这里直接使用它

         #此时eax=0x8012C,dx=0x8e00,ecx=0x8

         #下面就是lea esi,dword ptr ds:[ecx*8+0x198]

         leaidt(,%ecx,8), %esi                         #把IDT描述符0x08地址放入ESI中,然后设置该描述符

         movl%eax,(%esi)

         movl%edx,4(%esi)

        

         #下面执行完后

         #<bochs:63>x /8bx 0x198+0x8*0x80

         #0x00000598<bogus+       0>:    0x68   0x01    0x08    0x00   0x00    0xef    0x00   0x00

        

        

         movw$system_interrupt, %ax                 #设置系统调用陷阱门描述符,取系统调用处理程序地址

         movw$0xef00, %dx                                     #陷阱门类型是15,特权3的程序可以执行

         movl$0x80, %ecx                                #系统调用向量号是0x80

         #leaesi, dword ptr ds:[ecx*8+0x198]

         leaidt(,%ecx,8), %esi                         #把IDT描述符项0x80地址放入ESI中,然后设置该描述符

         movl%eax,(%esi)

         movl%edx,4(%esi)

 

#好了,现在我们为移动到任务0(任务A)中执行操作堆栈内容,在堆栈中人工建立中断返回时场景。

         pushfl                                                               #复位标志寄存器EFLAGS中的嵌套任务标志

         andl$0xffffbfff, (%esp)

         popfl

         movl$TSS0_SEL, %eax                       #把任务0的TSS段选择符加载到任务寄存器TR,(TSS0_SEL=0x20=10000 0b指向GDT第五项)

         ltr%ax

         movl$LDT0_SEL, %eax                      #把任务0的LDT段选择符加载到局部描述符表寄存器LDTR(LDT0_SEL=0x28=101 0 00b,指向GDT第六项)

         lldt%ax                                                 #TR和LDTR只需人工加载一次,以后CPU会自动处理

        

         #0008:00000092(unk. ctxt): mov dword ptr ds:0x17f, 0x0

         movl$0, current                                  #把任务号0保存在current变量中

         sti                                                                      #现在开启中断,并在栈中营造中断返回时的场景

         pushl$0x17                                                     #把任务0当前局部空间数据段(堆栈段)选择符入栈

         pushl$init_stack                                  #把堆栈指针入栈(也可以直接把ESP入栈)

         pushfl                                                               #把标志寄存器值入栈

         pushl$0x0f                                                     #把当前局部空间代码选择符入栈

         pushl$task0                                          #把代码指针入栈

#IP<-SS:[SP],SP<-SP+2;所以就开始执行task0。

#0008:000000ac (unk. ctxt): iretd                     ; cf

#执行此条指令前

#<bochs:52> info cpu

#eax:0x00000028, ebx:0x00000000,ecx:0x00000080, edx:0x0000ef00

#ebp:0x00000000, esp:0x00000bc4,esi:0x00000598, edi:0x00000998

#eip:0x000000ac, eflags:0x00000246,inhibit_mask:0

#执行后

#<bochs:54> info cpu

#eax:0x00000028, ebx:0x00000000,ecx:0x00000080, edx:0x0000ef00

#ebp:0x00000000, esp:0x00000bd8,esi:0x00000598, edi:0x00000998

#eip:0x000010e0, eflags:0x00000246,inhibit_mask:0

         iret                                                           #执行中断返回指令,从而切换特权级3的任务0中执行。

 

/****************************************/

#一下是设置GDT和IDT中描述符项的子程序

setup_gdt:                                                      #使用6字节操作数lgdt_opcode设置GDT表位置和长度

         lgdtlgdt_opcode              

         ret

#暂时设置IDT表中所有256中断门描述符都是同一个默认值,均使用默认的中断处理过程

#ignore_int.设置的具体方法是:首先在eax和edx寄存器对中分别设置好默认中断门描述符的0-3

#字节和4-7字节的内容,然后利用该寄存器对循环往IDT表中填充默认中断描述符内容

setup_idt:

         leaignore_int,%edx        

         movl$0x00080000,%eax                   #选择符位0x0008

         movw%dx,%ax                  /* selector =0x0008 = cs */

         movw$0x8E00,%dx         #类型E=1110b,即中断门类型,特权级为0

         leaidt,%edi

         mov$256,%ecx                 #循环设置所有256个门描述符项

rp_sidt:

         movl%eax,(%edi)

         movl%edx,4(%edi)

         addl$8,%edi

         dec%ecx

         jnerp_sidt

         lidtlidt_opcode                  #最后用6字节操作数加载IDTR寄存器

         ret

 

# -----------------------------------

#显示字符串自存钱,取当前光标位置并把al中的字符显示在屏幕上,整屏可以显示80X25个字符

#在80X25彩色字符模式下,显示器显示25行,每行80个字符

#每个字符占2个字节,低字节是字符的ASCII码,高字节是设置所示字符的属性

#从高位到低位 闪烁,背景红,背景绿,背景蓝,高亮,前景红,前景绿,前景蓝

 

write_char:                        

         push%gs                                      #首先保存要用到的寄存器,eax由调用者负责保存,所以无需pushl%eax

         pushl%ebx

         #0008:000000e8(unk. ctxt): mov ebx, 0x18

         mov$SCRN_SEL, %ebx                       #然后让gs指向显存内存段(0xb8000),,0x18指向GDT的第4个个描述符

         mov%bx, %gs

         movlscr_loc, %ebx                  #再从变量src_loc中取目前字符串显示位置值,scr_loc初始为0

         shl$1, %ebx                                 #每2个字节表示一个字符,一个是属性一个是字符的ascii,这里属性是灰色,值是al

         #movbyte ptr gs:[ebx], al

         movb%al, %gs:(%ebx)              #从0xb8000开始0xb8000+ebx一直写,

         shr$1, %ebx                                #把字符放到显示内存后把位置值除2加1,此时位置值对

         incl%ebx                                       #应下一个显示位置,如果该值大于2000,则复位成0

         cmpl$2000, %ebx                      #一个屏幕写完

         jb1f

         movl$0, %ebx

1:      movl%ebx, scr_loc                    #最后把这个位置值保存起来

         popl%ebx                                             

         pop%gs

         ret                                                            #返回

 

/***********************************************/

#下面是3个中断处理程序,默认中断,定时中断和系统调用中断

 

.align 4

ignore_int:                                   #默认中断处理程序,若系统产生了其它中断,则会在屏幕上显示一个字符C

         push%ds

         pushl%eax

         movl$0x10, %eax             #首先让ds指向内核数据段,因为中断程序属于内核

         mov%ax, %ds

         movl$67, %eax            /* print 'C' */#在AL中存放字符C的代码,调用显示程序显示在屏幕上

         callwrite_char                   #call指令是自动把eax入栈

         popl%eax

         pop%ds

         iret

 

/* Timer interrupt handler */

.align 4

timer_interrupt:                                  #定时中断处理程序,其中主要执行任务切换操作

         push%ds

         pushl%eax

         movl$0x10, %eax

         mov%ax, %ds                             #首先DS指向内核数据段

         movb$0x20, %al                                 

         outb%al, $0x20                                   #然后立刻允许其他硬盘中断,即向8259A发送EOI命令

         movl$1, %eax                            

         cmpl%eax, current                    #接着判断当前任务,若是任务1则去执行任务0,或反之

         je1f

         movl%eax, current

         ljmp$TSS1_SEL, $0

         jmp2f

1:      movl$0, current

         ljmp$TSS0_SEL, $0

2:      popl%eax

         pop%ds

         iret

 

/* system call handler */

#系统调用中断int 0x80处理程序,该例子只有一个显示字符功能

.align 4

system_interrupt:

         push%ds

         pushl%edx

         pushl%ecx

         pushl%ebx

         pushl%eax

         movl$0x10, %edx

         mov%dx, %ds           #让DS指向内核数据段

         #[0x00000174]0008:00000174 (unk. ctxt): call .+0xe5

         callwrite_char                   #调用write_char,显示al中的字符

         popl%eax

         popl%ebx

         popl%ecx

         popl%edx

         pop%ds

         iret

 

/*********************************************/

current:.long 0

scr_loc:.long 0

 

.align 4

lidt_opcode:

         .word256*8-1          # idt contains 256entries

         .longidt             # This will be rewrite bycode.

lgdt_opcode:

         .word(end_gdt-gdt)-1     # so does gdt

         .longgdt           # This will be rewrite bycode.

 

         .align8

idt:   .fill256,8,0                # idt isuninitialized

 

gdt: 

         .quad0x0000000000000000   /*第1个NULLdescriptor */

         .quad0x00c09a00000007ff     /*第2个 8Mb 0x08,base = 0x00000 */

         .quad0x00c09200000007ff     /*第3个 8Mb 0x10 */

         .quad0x00c0920b80000002   /*第4个 screen 0x18- for display */

 

         .word0x0068, tss0, 0xe900, 0x0    #第5个是TSS0段的描述符0x20

         .word0x0040, ldt0, 0xe200, 0x0     #第6个是LDT0段的描述符0x28

         .word0x0068, tss1, 0xe900, 0x0    #第7个 TSS1 descr0x30

         .word0x0040, ldt1, 0xe200, 0x0     #第8个 LDT1 descr0x38

end_gdt:

         .fill128,4,0                                            #初始化内核堆栈空间

init_stack:                         #刚进入保护模式时用于加载SS:ESP堆栈指针值

         .longinit_stack                                     #堆栈段偏移位置

         .word0x10                                                      #堆栈段同内核数据段

 

/*************************************/

#下面是任务0的LDT表段中的局部描述符

.align 8

ldt0:

         .quad0x0000000000000000   #第一个 NULL

         .quad0x00c0fa00000003ff      #第2个局部代码段描述符,对应选择符是0x0f

         .quad0x00c0f200000003ff      #第3个局部数据段描述符,对应选择符是0x17

        

#下面是任务0的TSS段的内容,注意其中标号等字段在任务切换时不会改变

tss0:        

         .long0                       /* back link */

         .longkrn_stk0, 0x10                  /* esp0,ss0 */

         .long0, 0, 0, 0, 0               /* esp1, ss1,esp2, ss2, cr3 */

         .long0, 0, 0, 0, 0               /* eip,eflags, eax, ecx, edx */

         .long0, 0, 0, 0, 0               /* ebx esp,ebp, esi, edi */

         .long0, 0, 0, 0, 0, 0                   /* es,cs, ss, ds, fs, gs */

         .longLDT0_SEL, 0x8000000    /* ldt, tracebitmap */

 

         .fill128,4,0

krn_stk0:

#       .long0

 

/************************************/

#下面是任务1的LDT表段内容和TSS段内容

.align 8

ldt1:

         .quad0x0000000000000000   #第一个  null

         .quad0x00c0fa00000003ff      # 0x0f, base =0x00000

         .quad0x00c0f200000003ff      # 0x17

 

tss1:        

         .long0                       /* back link */

         .longkrn_stk1, 0x10                  /* esp0,ss0 */

         .long0, 0, 0, 0, 0               /* esp1, ss1,esp2, ss2, cr3 */

         .longtask1, 0x200            /* eip, eflags */

         .long0, 0, 0, 0           /* eax, ecx, edx, ebx*/

         .longusr_stk1, 0, 0, 0               /* esp,ebp, esi, edi */

         .long0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */

         .longLDT1_SEL, 0x8000000    /* ldt, tracebitmap */

 

         .fill128,4,0                #任务1的内核栈空间,其用户栈直接使用初始化栈空间

krn_stk1:

 

/************************************/

#下面是任务0和任务1的程序,循环显示A和B

task0:                                                                                 

         movl$0x17, %eax             # [0x000010e5]000f:000010e5 (unk. ctxt): mov ds, ax

         movw%ax, %ds                          #首先让DS指向任务的局部数据段

         movb$0x53,%al            /* print 'S' */ #要显示的字符C放入al,等下用系统调用显示

         int$0x80                             #执行系统调用显示字符,也就是执行函数system_interrupt

         movl$0xfff, %ecx              #循环执行,起延时作用      

1:      loop1b

#000f:000010f2 (unk. ctxt): jmp .+0x10e0

         jmptask0                 

 

task1:

         movl$0x17, %eax

         movw%ax, %ds

         movb$0x4E, %al              /* print 'N' */

         int$0x80                             #执行系统调用显示字符

         movl$0xfff, %ecx

1:      loop1b

         jmptask1

 

         .fill128,4,0               #这是任务1的用户栈空间

usr_stk1:

 

参考文章

http://blog.chinaunix.net/uid-23917107-id-3191402.htmlubuntu-11下编译Linux0.00内核(基于赵博士提供的源码包:linux-0.00-041217和linux-0.00-050613

 

 


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值