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