参考资料 Linux内核完全注释.pdf
网上相关资料
! setup程序的主要作用是利用rom bios的中断来读取机器系统参数,并将这些数据保存在0x90000 ! 开始的位置(覆盖掉了bootsect程序所在的位置),所取得的参数被内核的相关程序使用。注意在bootsect ! 中已经将该模块和system、模块加载到内存中。 ! 然后setup程序将system模块从地址0x10000-0x8fff(当时认为内核的最大值)整块移动到内存的绝对地址 ! 0x00000处。接下来加载中断描述符表寄存器idtr和全局描述符表gdtr,开启a20地址线,重新设置两个 ! 中断控制芯片,将硬件终端号重新设置,最后设置cpu的控制寄存器cr0,从而进入32位的保护模式,并 ! 且跳转到system模块最前面的head.s程序处开始继续运行。 ! ! 为了能让head.s在32位的保护模式下运行,本程序设置了中断描述符表idt和全局描述符表gdt,并在gdt ! 中设置了当前内核代码段的描述符和数据段的描述符。在下面的head.s程序中会根据内核的需要重新设置 ! 这些描述符表。 ! ! ! setup.s (C) 1991 Linus Torvalds ! ! setup.s is responsible for getting the system data from the BIOS, ! and putting them into the appropriate places in system memory. ! both setup.s and system has been loaded by the bootblock. ! ! This code asks the bios for memory/disk/other parameters, and ! puts them in a "safe" place: 0x90000-0x901FF, ie where the ! boot-block used to be. It is then up to the protected mode ! system to read them from there before the area is overwritten ! for buffer-blocks. !
! NOTE! These had better be the same as in bootsect.s!
INITSEG = 0x9000 ! we move boot here - out of the way,原来的bootsect段 SYSSEG = 0x1000 ! system loaded at 0x10000 (65536).system所在段 SETUPSEG = 0x9020 ! this is the current segment,本程序所在段
.globl begtext, begdata, begbss, endtext, enddata, endbss .text begtext: .data begdata: .bss begbss: .text
entry start start:
! ok, the read went well so we get current cursor position and save it for ! posterity. ! 整个读磁盘的过程都很顺利,现在将光标位置保存以备后用。
! 设置ds = 0x9000 mov ax,#INITSEG ! this is done in bootsect already, but... mov ds,ax
!////////////////////////////////////////////////////// ! 调用系统中断0x10读取光标位置。下面是中断前的准备和调用中断。 mov ah,#0x03 ! read cursor pos ah = 0x03 xor bh,bh ! bh = 页号 int 0x10 ! save it in known place, con_init fetches !/////////////////////////////////////////////////////// ! 将信息保存在0x90000处,控制台初始化时来读取。 mov [0],dx ! it from 0x90000.
! Get memory size (extended mem, kB) ! 得到拓展内存的大小,调用中断0x15,同时将得到信息保存到0x90002处。 mov ah,#0x88 int 0x15 mov [2],ax
! Get video-card data: ! 下面的代码用于取得的是当前的显卡的显示模式 ! 调用中断0x10,同时将信息存储。
!////////////////////////////////////////////////////// mov ah,#0x0f int 0x10 !////////////////////////////////////////////////////// ! 0x90004中存放的是当前页 ! 0x90006 显示模式 ! 0x90007 字符列数 mov [4],bx ! bh = display page mov [6],ax ! al = video mode, ah = window width !/////////////////////////////////////////////////////
! check for EGA/VGA and some config parameters ! 检查显示模式。并取得参数。其中ega和vga是显示器的两种模式。 ! 利用中断0x10来实现读取信息,并将相关信息保存。 !///////////////////////////////////////////////// mov ah,#0x12 mov bl,#0x10 int 0x10 !//////////////////////////////////////////////// mov [8],ax ! ?? mov [10],bx ! 0x9000A -- 显存大小 ! 0x9000B -- 显示状态,彩色还是单色 mov [12],cx ! 0x9000C -- 显卡的特征参数 !/////////////////////////////////////////////// ! Get hd0 data ! 获取第一个硬盘信息,赋值硬盘参数列表 mov ax,#0x0000 mov ds,ax !/////////////////////////////////////////////// ! 利用中断向量0x41的值,也即是hd0参数列表的地址。 ! ! pc机上的中断向量表 : pc机bios在初始化时会在物理内存 ! 开始的一夜内存中存放中断向量表,每个中断向量表对应的 ! 中断服务处理程序isr的地址使用4个字节来表示。但是某些 ! 的中断向量却使用其他的值,这包括中断向量0x41和ox46, ! 这两个中断向量的处理程序地址实际上就是硬盘参数表的位置。 ! ! 在CPU被加电的时候,最初的1M的内存,是由BIOS为我们安排 ! 好了的,每一字节都有特殊的用处。
lds si,[4*0x41] !//////////////////////////////////////////////// mov ax,#INITSEG mov es,ax mov di,#0x0080 mov cx,#0x10 rep movsb
! Get hd1 data ! 取得hd1的参数列表,方法同上。 mov ax,#0x0000 mov ds,ax lds si,[4*0x46] mov ax,#INITSEG mov es,ax mov di,#0x0090 mov cx,#0x10 rep movsb
! Check that there IS a hd1 :-) ! 检查是否存在第二个硬盘,如果不存在,第二个硬盘表清0.利用bios的int 0x13来实现。
mov ax,#0x01500 mov dl,#0x81 ! 0x81指的是第2个硬盘 int 0x13
jc no_disk1 cmp ah,#3 ! 是硬盘吗 ? 类型 = 3 je is_disk1 no_disk1: ! 第二个硬盘不存在,则对第二个硬盘表清0 mov ax,#INITSEG mov es,ax mov di,#0x0090 ! 0x90090 ---+--- 0x10 mov cx,#0x10 mov ax,#0x00 rep stosb is_disk1:
! now we want to move to protected mode ... cli ! no interrupts allowed !禁止中断
! first we move the system to it's rightful place ! 首先我们将system模块移动到正确的位置。下面程序代码是将system模块移动到0x0000 ! 位置,即把从0x10000-0x8ffff的内存数据512k,整体向内存低端移动了0x10000 - 64k
mov ax,#0x0000 cld ! 'direction'=0, movs moves forward do_move: mov es,ax ! destination segment ! 目的地址初始为0x000 : 0x0 add ax,#0x1000 cmp ax,#0x9000 ! 移动完毕 jz end_move mov ds,ax ! source segment ! 源地址 0x1000 : 0x0 sub di,di sub si,si mov cx,#0x8000 ! 移动64k rep movsw jmp do_move ! 现在已经将system模块加载到内存的0地址。
! then we load the segment descriptors ! lidt指令用于加载中断描述符表idt寄存器。其中加载时只是加载的描述符表的线性基地址。 ! 中断描述符表中的每一个表项指出发生中断时需要调用的代码信息。 ! ! lgdt指令用于加载中断描述符表idt。 ! ldgt指令用于加载全局描述符表gdt寄存器。 ! ! 8086处理器的保护模式和实时模式,采用实 ! 时模式的寻址,没有虚拟内存空间,首先物理 ! 内存的每个位置都是使用20位的地址来标识的。 ! 寻址是通过使用cs,ds,ss,es加上段的偏移量。 ! ! 在保护模式下,cpu通过选择子找到段描述符,来 ! 寻址。包括全局段表,局部段表,中断表。 ! 段选择子通过ldgr寄存器来找到全局段表,通过idtr ! 找到中断表。 ! end_move: ! 加载中断描述符idt mov ax,#SETUPSEG ! right, forgot this at first. didn't work :-) mov ds,ax lidt idt_48 ! load idt with 0,0,idt_48在下面定义。 lgdt gdt_48 ! load gdt with whatever appropriate, ! gdt_48在下面定义。
! that was painless, now we enable A20 ! 以上的操作很简单,现在我们开启a20地址线。 ! ! 为了兼容使用开启a20管脚。可以不必追究。 !/////////////////////////////////////////////// call empty_8042 mov al,#0xD1 ! command write out #0x64,al call empty_8042 mov al,#0xDF ! A20 on out #0x60,al call empty_8042 !///////////////////////////////////////////////
! well, that went ok, I hope. Now we have to reprogram the interrupts :-( ! we put them right after the intel-reserved hardware interrupts, at ! int 0x20-0x2F. There they won't mess up anything. Sadly IBM really ! messed this up with the original PC, and they haven't been able to ! rectify it afterwards. Thus the bios puts interrupts at 0x08-0x0f, ! which is used for the internal hardware interrupts as well. We just ! have to reprogram the 8259's, and it isn't fun. ! ! 下面的代码是给中断编程,我们将他放在处于intel保留的硬件中断后面,在 ! int 0x20 -- 0x2f,在哪里它们不会引起中断。 ! ! 下面是8259芯片的简介 : 8259芯片是一种可编程控制芯片。每片可以管理8 ! 个中断源。通过多片的级联方式,能构成最多管理64个中断向量的系统。在 ! pc/at系列的兼容机中,使用了两个8259a芯片,共可管理15级中断向量。主 ! 8259a芯片的端口基址是0x20,从芯片是0xa0. ! !///////////////////////////////////////////////////////// ! 0x11表示初始化命令开始,是icw1命令字,表示边沿触发,多片 ! 8259级联,最后要发送icw4命令字。8259a的编程就是根据应用 ! 程序需要将初始化字icw1 -- icw4和操作命令字ocw1 -- ocw3 ! 分别写入初始化命令寄存器组和操作命令寄存器组。 ! mov al,#0x11 ! initialization sequence out #0x20,al ! send it to 8259A-1 !///////////////////////////////////////////////////////// !///////////////////////////////////////////////////////// ! 使用如下的.word 0x00eb,0x00eb来祈祷延迟的作用。下面是相 ! 关解释 : 0xeb是直接跳转指令操作码,带一个字节的相对地址 ! 偏移量。0x00eb表示跳转值是0的一条指令,因此还是直接执行 ! 下一条指令。在as86中没有表示的助记符,所以linus直接使用了 ! 机器码来表示这种指令。 .word 0x00eb,0x00eb ! jmp $+2, jmp $+2 !/////////////////////////////////////////////////////////
!////////////////////////////////////////////////////////// ! 进行8259a芯片编程。一次发送icw2,3,4 out #0xA0,al ! and to 8259A-2 .word 0x00eb,0x00eb mov al,#0x20 ! start of hardware int's (0x20) out #0x21,al .word 0x00eb,0x00eb mov al,#0x28 ! start of hardware int's 2 (0x28) out #0xA1,al .word 0x00eb,0x00eb mov al,#0x04 ! 8259-1 is master out #0x21,al .word 0x00eb,0x00eb mov al,#0x02 ! 8259-2 is slave out #0xA1,al .word 0x00eb,0x00eb mov al,#0x01 ! 8086 mode for both out #0x21,al .word 0x00eb,0x00eb out #0xA1,al .word 0x00eb,0x00eb !///////////////////////////////////////////////////////// mov al,#0xFF ! mask off all interrupts for now ! 屏蔽所有的主芯片的中断请求。 out #0x21,al .word 0x00eb,0x00eb out #0xA1,al ! 屏蔽芯片所有的中断请求。
! well, that certainly wasn't fun :-(. Hopefully it works, and we don't ! need no steenking BIOS anyway (except for the initial loading :-). ! The BIOS-routine wants lots of unnecessary data, and it's less ! "interesting" anyway. This is how REAL programmers do it. ! ! Well, now's the time to actually move into protected mode. To make ! things as simple as possible, we do no register set-up or anything, ! we let the gnu-compiled 32-bit programs do that. We just jump to ! absolute address 0x00000, in 32-bit protected mode.
!//////////////////////////////////////////////// ! 加载cr0 mov ax,#0x0001 ! protected mode (PE) bit lmsw ax ! This is it! ! cpu处于保护模式。 !//////////////////////////////////////////////// jmpi 0,8 ! jmp offset 0 of segment 8 (cs) ! 跳转到cs段8,偏移量0 ! 我们已经将system模块移动到0x00000处,所以这里的偏移地址 ! 是0.这里的段值的8已经是保护模式下的段选择符了,用于选择 ! 描述符表和描述符表项以及所要求的特权级。 ! 段选择符长度为16位;0-1位表示请求的特权级;Linus只 ! 只使用了2级:0级系统级和3级用户级。第2位用于选择是全局描 ! 述符表还是局部描述符表。3-15位是描述表项的索引,指出是第 ! 几项描述符。 ! 8 -- 0000,0000,0000,1000,表示请求的特权级是0--系统级, ! 使用全局描述符表第1项,该代码指出代码的基地址是0,因此这 ! 里的跳转指令就回去执行system中的代码。 !
! This routine checks that the keyboard command queue is empty ! No timeout is used - if this hangs there is something wrong with ! the machine, and we probably couldn't proceed anyway. ! ! 下面的代码检查键盘命令队列是否为空。 ! 只有当输入缓冲区为空时才可以对其进行写的操作。 ! empty_8042: .word 0x00eb,0x00eb ! 延迟 in al,#0x64 ! 8042 status port test al,#2 ! is input buffer full? ! 输入缓冲区满 ? jnz empty_8042 ! yes - loop ret
!//////////////////////////////////////////////////////////////// ! 数据描述 ! ! (1)Linux的任务: ! ---定义GDT表 ! ---定义LDT表 ! ---初始化的时候执行LGDT指令,将GDT表的基地址装入到GDTR中 ! ---进程初始化的时候执行LLDT指令,将LLDT表的基地址装入到LDTR中 ! ! (2)CPU的任务 ! ---用GDTR寄存器保存GDT表的基地址 ! ---用LDTR寄存器保存当前进程的LDT表的基地址 ! ---需要访问内存的时候,利用LDTR(或者GDTR,多数情况下是前者)找到相应的表,再根据提供的内存地址的某些部分找到相应的表项,然后再对表项的内容继续操作,得到最终的物理地址。所有这些操作都是在一条指令的指令周期里面完成的。
! gdt -- 描述符表的主要作用是将应用程序的逻辑地址转换为线性地址。 gdt: !///////////////////////////////////////////////////// .word 0,0,0,0 ! dummy,第一个描述符,不可用, ! 主要适用于保护。 !/////////////////////////////////////////////////////
!/////////////////////////////////////////////////////// ! 系统代码段描述符。加载代码段时,使用这个偏移量。段描述符 ! 一共64位。描述如下 : ! 0 -- 15 limit字段决定段的长度 ! 16 -- 39 56 -- 63段的首字节的线性地址 ! 40 -- 43 描述段的类型和存取权限 ! 44 系统标志;如果被清0,则是系统端。 ! 45 -- 46 dpl 描述符的特权级;用于限制这个段的存取。它表示 ! 为访问这个段而要求的cpu的最小优先级。 ! 47 -- 1 ! 48 -- 51 ! 52 被linux忽略。 ! 53 -- 0 ! 54 -- d/s ! 55 -- g 力度标志 ! .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .word 0x9A00 ! code read/exec .word 0x00C0 ! granularity=4096, 386 ! ! 从高地址到低地址为 00c0 9a00 0000 07ff ! 那么断脊地址就是00 00 0000 ! 偏移量是 07ff ! 描述符为 07ff ! !///////////////////////////////////////////////////////
!/////////////////////////////////////////////////////// ! 系统数据段描述符。当加载数据段寄存器时使用的是这个偏移量。 .word 0x07FF ! 8Mb - limit=2047 (2048*4096=8Mb) .word 0x0000 ! base address=0 .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 0x800 ! gdt limit=2048, 256 GDT entries .word 512+gdt,0x9 ! gdt base = 0X9xxxx
!/////////////////////////////////////////////////////////////////
.text endtext: .data enddata: .bss endbss:
|