Linux-0.00 代码解析(三)
6. 安裝中斷門和陷阱門
# setup timer & system call interrupt descriptors.movl $0x00080000, %eax movw $timer_interrupt, %axmovw $0x8E00, %dxmovl $0x08, %ecx # The PC default timer int.lea idt(,%ecx,8), %esimovl %eax,(%esi) movl %edx,4(%esi)movw $system_interrupt, %axmovw $0xef00, %dxmovl $0x80, %ecxlea idt(,%ecx,8), %esimovl %eax,(%esi) movl %edx,4(%esi)中斷門的格式是:
代碼中edx是高32位,eax是低32位
movl $0x00080000, %eax 代碼段的選擇子就緒
movw $timer_interrupt, %ax 偏移15..0就緒
movw $0x8E00, %dx edx的低16位就緒
我的疑問是:edx的高16位呢?
movl $0x08, %ecx
從表格中可以看出BIOS把8253的中斷號設置為8.
lea idt(,%ecx,8), %esi
esi = idt + ecx * 8,計算出第8個中斷門的位置,乘以8是因為一個中斷門描述符占8字節。
此中斷門用于切換任務,每隔10ms切換一次。
中斷門timer_interrupt的代碼是:
timer_interrupt:push %dspushl %eaxmovl $0x10, %eax # 0x10是內核數據段的選擇子mov %ax, %dsmovb $0x20, %aloutb %al, $0x20 # 向8259發送中斷結束(EOI)命令,端口是0x20, 命令字是0x20,不用深究movl $1, %eax # eax=1cmpl %eax, currentje 1f #相等跳轉到1處movl %eax, current # current = 1ljmp $TSS1_SEL, $0 #切換到任務1jmp 2f 1: movl $0, current #切換到任務0ljmp $TSS0_SEL, $0 2: popl %eaxpop %dsiretljmp $TSS1_SEL, $0
當處理器執行這條指令時,首先用指令中給出的段選擇子訪問GDT或LDT(這里是GDT),分析它的描述符類型,這里發現是TSS描述符,于是執行任務切換,指令中的偏移量(這里是0)被忽略。
下面是安裝陷阱門。
陷阱門的格式是:
movw $system_interrupt, %ax eax的低16位就緒,高16位在上面已經設置成了代碼段的選擇子
movw $0xef00, %dx DPL=3 的陷阱門
疑問:edx的高16位呢?
movl $0x80, %ecx 系統調用向量號是0x80,這個0x80應該是作者指定的
這個陷阱門其實是一個系統調用,用AL傳參,把AL代表的字符打印到屏幕上。其內部調用了內核過程write_char
system_interrupt: # 0x80系統調用,把AL中的字符打印到屏幕上push %dspushl %edxpushl %ecxpushl %ebxpushl %eaxmovl $0x10, %edxmov %dx, %ds #以上兩句是否可以不要??call write_charpopl %eaxpopl %ebxpopl %ecxpopl %edxpop %dsiret7. 開始執行任務0
# Move to user mode (task 0)pushflandl $0xffffbfff, (%esp)popflmovl $TSS0_SEL, %eaxltr %axmovl $LDT0_SEL, %eaxlldt %ax movl $0, currentstipushl $0x17pushl $init_stackpushflpushl $0x0fpushl $task0iret上面這段代碼要想說清楚,就說來話長了。
pushfl #把EFLAGS入棧andl $0xffffbfff, (%esp) #設NT=0popfl #加載EFLAGS上面三行是為了使EFLAGS的NT位=0;為什么要這樣做呢?因為后面要用iret指令返回,當返回的時候,如果NT=1,表示返回到前一個任務,而這種情況不是我們想要的。
movl $TSS0_SEL, %eax #TSS0_SEL是任務0的TSS選擇子ltr %ax以上兩行是把任務0的TSS選擇子裝入任務寄存器TR;
LTR指令的格式是
操作數是16位的通用寄存器或者是指向16位單元的內存地址。TSS選擇子是16位的,所以沒有用eax,而用ax; LLDT指令用法類似。
movl $LDT0_SEL, %eaxlldt %ax #把任務0的LDT段選擇子裝入LDTR(局部描述符表寄存器)movl $0, current #表示當前運行的是任務0sti #開中斷7.1 中斷處理過程
咱們先復習一下異常或中斷的處理過程。
當目標代碼段描述符的DPL(可以用門描述符中的段選擇子,從GDT或LDT中找到)在數值上<=CPL(當前特權級)時,才允許將控制轉移到中斷或異常處理程序。
如果 DPL < CPL,將發生棧切換。棧切換的過程:
(1)根據DPL,從當前任務的TSS中取得對應的SS和ESP,作為中斷或異常處理過程使用的SS和ESP(新棧)。
(2)處理器把舊棧的選擇子和棧指針壓入新棧。
(3)把EFLAGS、CS、EIP壓入新棧。
(4)對于有錯誤碼的異常,還要把錯誤代碼壓入新棧(我們模擬返回的時候沒有錯誤碼)。
有了上面的鋪墊,我們可以繼續看代碼了。
pushl $0x17 #把任務0的局部空間數據段(也是棧段)選擇子入棧pushl $init_stack #把任務0的ESP入棧pushfl #把EFLAGS入棧pushl $0x0f #把任務0的代碼段選擇子入棧pushl $task0 #把任務0的EIP入棧iret #環境已經設置完畢,這里模擬從中斷返回,返回后開始執行任務0,其特權級為37.2 中斷返回過程
先不管后面的操作數是怎么來的,總之壓棧該壓什么,順序是什么我們理解了。接下來看看IRET指令。這個指令的含義,我首先看了Intel指令集手冊,感覺說得很復雜,某些地方還有歧義。我不甘心,又搜了很多網上的資料,沒有一個令我滿意。最后,我打算用AMD的解釋,因為很簡明:
IRET, Less Privilege. If an IRET changes privilege levels, the return program must be at a lower privilege than the interrupt handler. The IRET in this case causes a stack switch to occur:
說到這里,文件head.s的主體就完了。
8. 任務0的LDT
ldt0: .quad 0x0000000000000000.quad 0x00c0fa00000003ff # 0x0f.quad 0x00c0f200000003ff # 0x17任務0的局部描述符表分析如下:
| 0 | 空描述符 | - | - | - | - | - | - | - |
| 1 | 代碼段 | 0 | 0X3FF | 4KB | 1 | 3 | 代碼段,非一致性,可讀 | 0x0F |
| 2 | 數據段 | 0 | 0X3FF | 4KB | 1 | 3 | 數據段,向上擴展,可寫 | 0x17 |
為了少耗費點腦細胞,我寫了一個C語言的小程序,專門用來分析各種類型的段描述符,拿走不謝。
http://blog.csdn.net/longintchar/article/details/78881396
運行截圖如下:
9. 任務0的TSS
tss0: .long 0 /* back link */.long krn_stk0, 0x10 /* esp0, ss0*/.long 0, 0, 0, 0, 0 /* esp1, ss1, esp2, ss2, cr3 */.long 0, 0, 0, 0, 0 /* eip, eflags, eax, ecx, edx */.long 0, 0, 0, 0, 0 /* ebx esp, ebp, esi, edi */.long 0, 0, 0, 0, 0, 0 /* es, cs, ss, ds, fs, gs */.long LDT0_SEL, 0x8000000 /* ldt, trace bitmap */.fill 128,4,0 krn_stk0:以上填0的字段就不分析了,我們重點看看非0字段。
krn_stk0是ESP0,就在代碼的最后一行。
0x10是內核數據段的選擇子。
LDT0_SEL是任務0的LDT的選擇子,數值上 = 0x28,在GDT中有定義。
復習一下,GDT中的描述符。
| 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描述符 |
0x8000000是I/O許可位串。如果這個值大于或者等于TSS的段界限(在TSS描述符中),則表明沒有I/O許可位串。因為TSS的段界限是0x68,所以表示沒有許可位串。
10. 任務1的TSS
tss1: .long 0 /* back link */.long krn_stk1, 0x10 /* esp0, ss0 */.long 0, 0, 0, 0, 0 /* esp1, ss1, esp2, ss2, cr3 */.long task1, 0x200 /* eip, eflags */.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */.long usr_stk1, 0, 0, 0 /* esp, ebp, esi, edi */.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */.long LDT1_SEL, 0x8000000 /* ldt, trace bitmap */.fill 128,4,0 krn_stk1:和任務0相比,任務1的TSS有幾點不同。
(1). eip和eflags不是0,而是task1和0x200,task1是任務1代碼的入口點,當任務0首次被時鐘中斷打斷時,將會切換到任務1,這時CPU把tss1作為任務1的EIP初始值。同理,0x200作為任務1的eflags初始值。為什么eflags初始值是0x200呢?根據下圖,可以知道eflags中IF為1,表示允許中斷。必須要允許中斷,因為任務切換靠中斷實現。
【System Flags in the EFLAGS Register】
(2). 任務0的esp=0,ss=0,任務1的esp=usr_stk1,ss=0x17。這是因為TSS中的esp和ss對應的特權級別是3,任務0的esp、ss的初始值通過iret指令從棧中獲取,而任務1的要從TSS中獲取。
(3). 任務0的es,cs,ds,fs,gs都為0,任務1的es=ds=fs=gs=0x17,cs=0x0f,道理同上,任務0的cs初始值從棧中獲得,任務1的所有段寄存器的初始值都從TSS中獲得。
下圖是32位任務狀態段(TSS)的格式,貼出來方便復習。
11. 任務0的代碼段
task0:movl $0x17, %eax #0x17是任務0的數據段的選擇子movw %ax, %ds #因為任務0沒有用到局部數據段,所以這兩句可以不要movb $65, %al # print 'A' int $0x80 # 系統調用movl $0xfff, %ecx 1: loop 1b # 為了延時jmp task0 # 死循環任務1的代碼段類似,不再贅述。
用了3篇博文,基本把代碼說完了。下篇文章打算修改幾個地方并做實驗。
【未完待續】
總結
以上是生活随笔為你收集整理的Linux-0.00 代码解析(三)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: python异常值处理箱型图_如何利用p
- 下一篇: Linux-0.00 代码解析(四)