Linux0.00 代码解析(二)
Linux 0.00 的編譯、運(yùn)行、源碼下載:
http://blog.csdn.net/longintchar/article/details/78757065
Linux 0.00 Makefile 解讀:
http://blog.csdn.net/longintchar/article/details/78857966
Linux 0.00 代碼解析——boot.s:
http://blog.csdn.net/longintchar/article/details/78766916
Linux-0.00的代碼分為兩部分——boot.s和head.s.
boot.s采用as86語言編寫,是引導(dǎo)啟動(dòng)程序,先把內(nèi)核代碼加載到物理地址0x10000處,然后把內(nèi)核代碼移動(dòng)到物理地址0處,接下來設(shè)置臨時(shí)GDT表等信息,再把處理器設(shè)置成保護(hù)模式,最后跳轉(zhuǎn)到內(nèi)核代碼處(0地址)運(yùn)行。
head.s是內(nèi)核代碼,采用GNU as匯編語言編寫,實(shí)現(xiàn)了2個(gè)運(yùn)行在特權(quán)及3上的任務(wù),它們在時(shí)鐘中斷控制下相互切換運(yùn)行,一個(gè)在屏幕上打印“A”,另一個(gè)在屏幕上打印“B”。
本文要分析的是head.s。請注意,這段代碼在運(yùn)行的時(shí)候,它的起始位置在物理地址0處。
1. 設(shè)置DS,ES,SS,ESP
SCRN_SEL = 0x18 TSS0_SEL = 0x20 LDT0_SEL = 0x28 TSS1_SEL = 0X30 LDT1_SEL = 0x38SCRN_SEL等都是符號(hào)常量,代表某選擇子的值,這樣寫可讀性好。相當(dāng)于c語言中的#define SCRN_SEL 0x18.
.code32 .global startup_32 .text startup_32:movl $0x10,%eaxmov %ax,%ds.code32 是我自己加的,不然編譯會(huì)報(bào)錯(cuò)。這句偽指令告訴編譯器,下面的代碼要編譯成32位代碼。
.global 表示標(biāo)識(shí)符是外部的或者全局的。
.text 標(biāo)識(shí)正文段的開始,并切換到text段。
movl $0x10,%eax , 0x10是數(shù)據(jù)段(在boot.s文件中定義)的選擇子,此數(shù)據(jù)段的基地址為0,界限值是0x7FF(10進(jìn)制2047),粒度4KB;因?yàn)榱6仁?KB,所以段長度是(2047+1)*4KB=8MB;DPL=0,向上擴(kuò)展,可讀可寫。
mov %ax,%ds ,加載ds。
init_stack處有6個(gè)字節(jié),見
init_stack: # Will be used as user stack for task0..long init_stack.word 0x10這是一個(gè)遠(yuǎn)指針,前4個(gè)字節(jié)是偏移,后2個(gè)字節(jié)是段選擇子,這句代碼表示用偏移加載esp,用數(shù)據(jù)段選擇子0x10加載ss.
2. setup_idt
此過程用于在IDT(中斷描述符表)中安裝中斷門。代碼是
setup_idt:lea ignore_int,%edxmovl $0x00080000,%eaxmovw %dx,%ax /* selector = 0x0008 = cs */movw $0x8E00,%dx /* interrupt gate - dpl=0, present */lea idt,%edimov $256,%ecx rp_sidt:movl %eax,(%edi)movl %edx,4(%edi)addl $8,%edidec %ecxjne rp_sidtlidt lidt_opcoderet中斷門描述符
中斷門描述符如下圖:
下面是低32位(代碼中用eax存儲(chǔ)),上面是高32位(代碼中用edx存儲(chǔ))。
可以看出,中斷門定義了一個(gè)長指針(段選擇符:過程入口點(diǎn)偏移值),當(dāng)發(fā)生中斷的時(shí)候,處理器使用這個(gè)長指針把程序執(zhí)行權(quán)轉(zhuǎn)移到中斷處理過程中。
在edx和eax中組裝中斷門描述符
lea指令是取有效地址(偏移值)。lea ignore_int,%edx表示把ignore_int處的有效地址傳給edx. 注意,是取ignore_int處的偏移地址,而不是ignore_int處存儲(chǔ)的內(nèi)容。這樣,過程入口點(diǎn)偏移值31-16組裝完畢。
movl $0x00080000,%eax, 段選擇符(=0x08,索引1,內(nèi)核代碼段)組裝完畢。
movw %dx,%ax, 過程入口點(diǎn)偏移值15-0組裝完畢。
movw $0x8E00,%dx edx的低16位組裝完畢。
中斷處理過程就是ignore_int,用于在屏幕上打印一個(gè)’c’.
ignore_int:push %dspushl %eaxmovl $0x10, %eaxmov %ax, %ds #上一行和此行用內(nèi)核數(shù)據(jù)段加載dsmovl $67, %eax #打印字符'c',實(shí)際上用AL來傳參call write_char #調(diào)用過程 write_charpopl %eaxpop %dsiret注意:write_char這個(gè)過程沒有指定DS,但是確引用了DS,比如指令movl scr_loc, %ebx. 所以在調(diào)用write_char之前,一定要給DS賦合適的值。
write_char這個(gè)過程,我已經(jīng)在代碼后面添加了注釋。
write_char:push %gspushl %ebxmov $SCRN_SEL, %ebx #SCRN_SEL是顯存段的選擇子mov %bx, %gs #gs指向顯存段movl scr_loc, %ebx #scr_loc處存放的是顯示位置shl $1, %ebx #ebx*2,得到偏移,因?yàn)橐粋€(gè)字符用2個(gè)字節(jié)來描述movb %al, %gs:(%ebx) #al中是字符的ASCII碼,屬性用默認(rèn)的shr $1, %ebx #還原ebxincl %ebx #ebx自增1,算出下一個(gè)位置cmpl $2000, %ebx #比較ebx和2000jb 1f #若 ebx < 2000 則跳轉(zhuǎn)到1movl $0, %ebx #說明ebx==2000,因?yàn)槲恢弥挥?~1999,所以把ebx置為0 1: movl %ebx, scr_loc #把ebx存入scr_loc處,更新顯示位置popl %ebxpop %gsret可以看出,write_char的功能是把AL中的字符打印到屏幕上。
位置由scr_loc處存儲(chǔ)的4字節(jié)的值指定(實(shí)際上取值0~1999),打印后更新位置(計(jì)數(shù)加1)。
填寫IDT
lea idt,%edi表示把idt處的有效地址加載到edi.
idt標(biāo)號(hào)處的代碼是:
.align 8 idt: .fill 256,8,0fill偽指令的格式是
.fill repeat,size,value
表示產(chǎn)生repeat個(gè)大小為size字節(jié)的重復(fù)拷貝。size最大是8,size字節(jié)的值是value.
所以,.fill 256,8,0表示產(chǎn)生8*256字節(jié),全部用0填充。IDT最多可有256個(gè)描述符,每個(gè)描述符占8個(gè)字節(jié)。
Intel語法的間接內(nèi)存引用的格式為:
section:[base + index * scale + displacement]
而在AT&T語法中對(duì)應(yīng)的形式為:
section:displacement (base, index, scale)
其中,base和index是任意的32-bit base和index寄存器。scale可以取值1,2,4,8。如果不指定scale值,則默認(rèn)值為1。section可以指定任意的段寄存器作為段前綴,默認(rèn)的段寄存器在不同的情況下不一樣。
舉例:
| [base + index * scale + displacement] | section:displacement(base, index, scale) |
| [eax + _variable] | _variable(%eax) |
| [eax * 4 + _array] | _array(, %eax, 4) |
| [ebx + eax * 8 + _array] | _array(%ebx, %eax, 8) |
所以,movl %eax,(%edi)表示把eax的值傳送到地址edi處,即用eax填充IDT表的0~3字節(jié);movl %edx,4(%edi)表示把edx的值傳送到地址[edi+4]處,即用edx填充IDT表的4~7字節(jié);這樣,IDT表中第0個(gè)中斷門就安裝好了。同理,循環(huán)安裝,一共是256個(gè)中斷門。
加載IDTR
lidt lidt_opcode 加載IDTR寄存器,lidt_opcode處定義了6個(gè)字節(jié)。前2字節(jié)是界限值,界限值是表的總長度減去1;后4字節(jié)是IDT的線性基地址。因?yàn)楸疚募\(yùn)行時(shí)的起始地址就在物理地址0處,所以線性基地址就是idt表示的值。
lidt_opcode:.word 256*8-1 # idt contains 256 entries.long idt # This will be rewrite by code.3. setup_gdt
這個(gè)過程就一句話
setup_gdt:lgdt lgdt_opcoderet加載GDTR寄存器。
看一下 lgdt_opcode 處都有什么:
前2字節(jié)是GDT的界限值,后4字節(jié)是GDT的線性基地址。
gdt:.quad 0x0000000000000000 /* NULL descriptor */.quad 0x00c09a00000007ff /* 8Mb 0x08, base = 0x00000 */.quad 0x00c09200000007ff /* 8Mb 0x10 */.quad 0x00c0920b80000002 /* screen 0x18 - for display */.word 0x0068, tss0, 0xe900, 0x0 # TSS0 descr 0x20.word 0x0040, ldt0, 0xe200, 0x0 # LDT0 descr 0x28.word 0x0068, tss1, 0xe900, 0x0 # TSS1 descr 0x30.word 0x0040, ldt1, 0xe200, 0x0 # LDT1 descr 0x38 end_gdt:一共定義了8個(gè)段描述符。
| 0 | - | 空描述符 | - | - | - | - | - | - |
| 1 | 0x08 | 代碼段 | 0 | 0X7FF | 4KB | 1 | 0 | 內(nèi)核代碼段,非一致性,可讀 |
| 2 | 0x10 | 數(shù)據(jù)段 | 0 | 0X7FF | 4KB | 1 | 0 | 內(nèi)核數(shù)據(jù)段,向上擴(kuò)展,可寫 |
| 3 | 0x18 | 數(shù)據(jù)段 | 0XB8000 | 0X2 | 4KB | 1 | 0 | 內(nèi)核顯存段,向上擴(kuò)展,可寫 |
| 4 | 0x20 | TSS段 | tss0 | 0X68 | 1B | 1 | 3 | 任務(wù)0的TSS段,不忙 |
| 5 | 0x28 | LDT段 | ldt0 | 0X40 | 1B | 1 | 3 | 任務(wù)0的LDT描述符 |
| 6 | 0x30 | TSS段 | tss1 | 0X68 | 1B | 1 | 3 | 任務(wù)1的TSS段,不忙 |
| 7 | 0x38 | LDT段 | ldt1 | 0X40 | 1B | 1 | 3 | 任務(wù)1的LDT描述符 |
4. 重新加載段寄存器
因?yàn)镚DT的內(nèi)容改變了,所以應(yīng)該重新加載所有段寄存器。
movl $0x10,%eax # reload all the segment registersmov %ax,%ds # after changing gdt. mov %ax,%esmov %ax,%fsmov %ax,%gslss init_stack,%esp注意:因?yàn)閮?nèi)核代碼段和boot.s文件中的定義一樣,所以不用重新加載CS
5. 設(shè)置定時(shí)芯片8253
關(guān)于這個(gè)定時(shí)芯片可以參考我的博文http://blog.csdn.net/longintchar/article/details/78885556
Intel 8253芯片是可編程計(jì)數(shù)器/定時(shí)器。該芯片提供了3個(gè)獨(dú)立的16位計(jì)數(shù)器通道,每個(gè)通道可以工作在不同的工作方式下。通過向8253寫入一個(gè)控制字和一個(gè)初始計(jì)數(shù)值,就可以使它開始計(jì)數(shù)。
控制字格式如下圖:
代碼中寫入的控制字是0x36,選中通道0,先讀寫低字節(jié)再讀寫高字節(jié),工作方式3,采用二進(jìn)制計(jì)數(shù)。
通道0的端口是0x40, 先向其寫入初始計(jì)數(shù)值的低字節(jié),再寫入初始計(jì)數(shù)值的高字節(jié)。
假設(shè)N為初始計(jì)數(shù)值。在工作方式3下,方波的頻率是輸入時(shí)鐘頻率的N分之一,又因?yàn)橛?jì)數(shù)器的輸入時(shí)鐘頻率是1.193180MHz=1193180Hz,所以
1193180/N = 方波的頻率(Hz)movl $11930, %eax表示計(jì)數(shù)值N=11930,1193180/11930約等于100,
所以方波的頻率是100Hz,周期是10ms,也就是10ms產(chǎn)生一個(gè)方波上升沿,此上升沿可以產(chǎn)生中斷請求,即10ms產(chǎn)生一次中斷。
【未完待續(xù)】
與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的Linux0.00 代码解析(二)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: matlab没有找到图形用户界面,MAT
- 下一篇: matlab phog,科学网—UCF