1.簡介
head.s 程式在被編譯生成目標檔案后會與內核其他程式一起被鏈接成 system 模塊,它位于 system 模塊的最開始部分,system模塊將被放置在磁盤上setup模塊之后的扇區,從磁盤上第6個扇區開始放置,
注:這段程式處于絕對地址0x00000處,
程式進入保護模式,程式采用AT&T語法格式,
Linux AT&T匯編語法簡介:
添加鏈接描述
作用:head.s程式:設定中斷描述符表項(啞中斷);檢查A20;測驗是否有協處理器;初始化記憶體頁目錄表;跳轉到main.c執行內核初始化
head完成后完成了記憶體頁目錄和頁表的設定,并重新設定了內核實際使用的中斷描述符表idt和全域描述符表GDT,還為軟盤驅動程式開辟了1KB位元組的緩沖區,
32位下尋址
將實模式下的段暫存器當作保護模式下的段描述符的指標使用,此時段暫存器中存放的是一個描述符在描述符表中的偏移地址暫存器,而當前描述符表的基地址則保存在描述符表暫存器中,
head程式結束后記憶體映像

.align
.align 2
完整格式 :.align val1,val2,val3
val1 是需要對齊的值
val2 填充位元組指定的值
val3 指明最大用于填充或跳過的直接數
.align是匯編語言指示符,其含義是邊界對齊調整,”2”表示把隨后的代碼或資料的偏移位置調整到地址值最后 2 位元位為零的位置,即按 4(=2^2)位元組方式對齊記憶體地址,不過現在 GNU as 直接寫出對齊的值而非 2 的冪次,使用該指示符的目的是為了提高 32 位 CPU 訪問記憶體中代碼或資料的效率,
.ORG
ORG偽指令用來表示起始的偏移地址,緊接著ORG的數值就是偏移地址的起始值,ORG偽操作常用來指定資料的存盤地址,有時也用來指定代碼段的起始地址
fill
fill偽指令的格式是 .fill repeat,size,value
表示產生 repeat 個大小為 size 位元組的重復拷貝,size 最大是 8,size 位元組的值是 value.
按位異或 xor
1. 使某些特定的位翻轉
例如要使 EAX 的 b1 位和 b2 位翻轉:
EAX = EAX ^ 00000110
2.不使用臨時變數就可以實作兩個值的交換
例如 a=11110000,b=00001111,要交換a、b的值
a = a^b; //a=11111111
b = b^a; //b=11110000
a = a^b; //a=00001111
3.在匯編語言中經常用于將變數置零
xor eax,eax
4.快速判斷兩個值是否相等
?例如判斷兩個整數a、b是否相等,可通過下列陳述句實作:
?return ((a ^ b) == 0);
LSS指令
格式:LSS r32,m16:32 #用記憶體中的長指標加載 SS:r32
m16:32表示一個記憶體運算元,這個運算元是一個長指標,由2部分組成:16位的段選擇
子和32位的偏移,
2.原始碼分析
1.啟動32位程式
startup_32:
movl $0x10,%eax ;0x10 GDT中的偏移值(一個描述符表項的選擇符)
;請求特級權0(位0-1 =0),GDT(位2=0),選擇表中第2項(位3-15=2)
; 正好指向資料段描述符項
mov %ax,%ds ; 設定ds,es,fs,gs為setup中構造的資料段的選擇符 =0x10
mov %ax,%es ;并將堆疊放置在stack_start(指標)指向的user_stack陣列區
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp ;_stack_start-->ss:esp,設定系統堆疊
;移動到任務0執行(init/main.c),該堆疊就被用做任務0和任務1共同使用的用戶堆疊
call setup_idt ; 呼叫設定中斷描述符表子程式
call setup_gdt ; 呼叫設定全域描述符表子程式
movl $0x10,%eax # reload all the segment registers
mov %ax,%ds # after changing gdt. CS was already
mov %ax,%es # reloaded in 'setup_gdt'
mov %ax,%fs # 因為修改了gdt,所以重新加載這些暫存器
mov %ax,%gs
2.加載各個段暫存器,重新設定中斷描述符表,共256項,并使各個表項均指向一個只報錯誤的啞中斷程式,重新設定全域描述符表,
1)IDT:
;設定中斷描述符表(IDT)字程式setup_idt
; 將中斷描述符表設定具有256個項,并都指向ignore_int中斷門,然后加載中斷描述符表暫存器
; 中斷描述符表中的項為8個位元組,稱為門描述符
setup_idt:
lea ignore_int,%edx # 將ignore_int有效地址值賦值給edx
movl $0x00080000,%eax # 將選擇符0x0008賦值給eax的高16位
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea _idt,%edi # _idt 是中斷描述符表的地址
mov $256,%ecx
rp_sidt:
movl %eax,(%edi) # eax -> [edi] 將啞中斷門描述符存入表中
movl %edx,4(%edi) # edx -> [edi+4]
addl $8,%edi # edi + 8 -> edi
dec %ecx
jne rp_sidt
lidt idt_descr #加載中斷描述符表暫存器
ret
2)GDT
# 設定全域描述符表項
setup_gdt:
lgdt gdt_descr # 加載全域描述符暫存器
ret
3.對比物理地址0與1M開始處的內容是否相同,如果相同那么沒有開啟A20地址線,進入死回圈
;測驗A20地址線是否開啟
;方法:向記憶體地址0處寫入任意一個數值,看記憶體地址是否是這個數值
;如果是就一直比較下去,產生死回圈,
;表示A20線沒有選通,內核就不能使用1MB以后的記憶體空間
xorl %eax,%eax
1: incl %eax # check that A20 really IS enabled
# 1-->標號,表示活動位置計數的當前值,并可以作為指令的運算元
movl %eax,0x000000 # loop forever if it isn't
cmpl %eax,0x100000
je 1b # Nb:參考先前最近的標號 'b':backwards 向后
# Nf:參考下一個標號 'f':forwards 向前
# 這里‘1b’表示:向后跳轉到標號1去
# 如果是5f,則是向前跳轉到標號5去
4.測驗PC機是否含有資料協處理器芯片,并在控制暫存器CR0中設定相應的標志位
; 檢測486數學協處理器芯片是否存在
; 方法:修改控制暫存器CR0,然后執行一條協處理器指令,若出錯,則不存咋
; 需要設定協處理器仿真位(EM)位2,并復位協處理器存在標志(MP)位2
movl %cr0,%eax # check math chip
andl $0x80000011,%eax # Save PG,PE,ET
/* "orl $0x10020,%eax" here for 486 might be good */
orl $2,%eax # set MP 并復位協處理器存在標志(MP)
movl %eax,%cr0
call check_x87
jmp after_page_tables
5.設定管理記憶體的分頁處理機制,將頁目錄表放在絕對物理地址0開始處緊隨其后放置共可尋址16MB記憶體的4個頁表,并分別設定它們的表項
6.最后利用回傳指令將預先放置在堆疊中的/init/main.c程式的入口地址彈出,運行main()程式
# 下面幾個入堆疊操作為了跳轉到init/main.c中main()函式做準備
# 前面三個入堆疊0值分別表示envp,argv指標和argc的值
after_page_tables:
pushl $0 # These are the parameters to main :-) main函式引數 envp
pushl $0 # argv指標
pushl $0 # argc
pushl $L6 # return address for main, if it decides to. 模擬呼叫main時首先將回傳地址入堆疊的操作
pushl $_main # _main--->main
jmp setup_paging
# main函式到不了這里 ,到標號L6這里,是一個死回圈
L6:
jmp L6 # main should never return here, but
# just in case, we know what happens.
3.head完整原始碼
/*
* linux/boot/head.s
*
* (C) 1991 Linus Torvalds
*/
/*
* head.s contains the 32-bit startup code.
*
* NOTE!!! Startup happens at absolute address 0x00000000, which is also where
* the page directory will exist. The startup code will be overwritten by
* the page directory.
*/
; head程式含有32啟動程式代碼
; 32啟動程式代碼是從絕對地址0x00000處開始
; 頁目錄也在該記憶體,以后啟動代碼將會被覆寫
.text
.globl _idt,_gdt,_pg_dir,_tmp_floppy_area
_pg_dir: ; 頁目錄將會放在在這里
startup_32:
movl $0x10,%eax ;0x10 GDT中的偏移值(一個描述符表項的選擇符)
;請求特級權0(位0-1 =0),GDT(位2=0),選擇表中第2項(位3-15=2)
; 正好指向資料段描述符項
mov %ax,%ds ; 設定ds,es,fs,gs為setup中構造的資料段的選擇符 =0x10
mov %ax,%es ;并將堆疊放置在stack_start(指標)指向的user_stack陣列區
mov %ax,%fs
mov %ax,%gs
lss _stack_start,%esp ;_stack_start-->ss:esp,設定系統堆疊
;移動到任務0執行(init/main.c),該堆疊就被用做任務0和任務1共同使用的用戶堆疊
call setup_idt ; 呼叫設定中斷描述符表子程式
call setup_gdt ; 呼叫設定全域描述符表子程式
movl $0x10,%eax # reload all the segment registers
mov %ax,%ds # after changing gdt. CS was already
mov %ax,%es # reloaded in 'setup_gdt'
mov %ax,%fs # 因為修改了gdt,所以重新加載這些暫存器
mov %ax,%gs
lss _stack_start,%esp
;測驗A20地址線是否開啟
;方法:向記憶體地址0處寫入任意一個數值,看記憶體地址是否是這個數值
;如果是就一直比較下去,產生死回圈,
;表示A20線沒有選通,內核就不能使用1MB以后的記憶體空間
xorl %eax,%eax
1: incl %eax # check that A20 really IS enabled
# 1-->標號,表示活動位置計數的當前值,并可以作為指令的運算元
movl %eax,0x000000 # loop forever if it isn't
cmpl %eax,0x100000
je 1b # Nb:參考先前最近的標號 'b':backwards 向后
# Nf:參考下一個標號 'f':forwards 向前
# 這里‘1b’表示:向后跳轉到標號1去
# 如果是5f,則是向前跳轉到標號5去
/*
* NOTE! 486 should set bit 16, to check for write-protect in supervisor
* mode. Then it would be unnecessary with the "verify_area()"-calls.
* 486 users probably want to set the NE (#5) bit also, so as to use
* int 16 for math errors.
*/
; 檢測486數學協處理器芯片是否存在
; 方法:修改控制暫存器CR0,然后執行一條協處理器指令,若出錯,則不存咋
; 需要設定協處理器仿真位(EM)位2,并復位協處理器存在標志(MP)位2
movl %cr0,%eax # check math chip
andl $0x80000011,%eax # Save PG,PE,ET
/* "orl $0x10020,%eax" here for 486 might be good */
orl $2,%eax # set MP 并復位協處理器存在標志(MP)
movl %eax,%cr0
call check_x87
jmp after_page_tables
/*
* We depend on ET to be correct. This checks for 287/387.
*/
; fninit和fstsw是協處理器的指令
check_x87:
fninit # 向協處理器發出初始化指令
fstsw %ax # 將協處理器狀態字復制給ax
cmpb $0,%al # 初始化后狀態字應該位0,否則協處理器不存在
je 1f /* no coprocessor: have to set bits */ #跳轉到標號位1處(前面)
movl %cr0,%eax # 如果存在則跳到標號位1處,否則改寫cr0
xorl $6,%eax /* reset MP, set EM */ ;設定協處理器仿真位(EM)
movl %eax,%cr0
ret
# .align 是匯編語言指示符,用于存盤邊界對齊調整
# 2表示把隨后的代碼和資料的偏移位置調整到地址值最后2位元位為0的位置(2*2)
# 即按四位元組方式對齊記憶體地址
# 使用該指示符目的是為了提高32位cpu訪問記憶體中代碼或資料的速度和效率
.align 2
# 287協處理碼,將80287設定為保護模式
1: .byte 0xDB,0xE4 /* fsetpm for 287, ignored by 387 */
ret
/*
* setup_idt
*
* sets up a idt with 256 entries pointing to
* ignore_int, interrupt gates. It then loads
* idt. Everything that wants to install itself
* in the idt-table may do so themselves. Interrupts
* are enabled elsewhere, when we can be relatively
* sure everything is ok. This routine will be over-
* written by the page tables.
*/
;設定中斷描述符表(IDT)字程式setup_idt
; 將中斷描述符表設定具有256個項,并都指向ignore_int中斷門,然后加載中斷描述符表暫存器
; 中斷描述符表中的項為8個位元組,稱為門描述符
setup_idt:
lea ignore_int,%edx # 將ignore_int有效地址值賦值給edx
movl $0x00080000,%eax # 將選擇符0x0008賦值給eax的高16位
movw %dx,%ax /* selector = 0x0008 = cs */
movw $0x8E00,%dx /* interrupt gate - dpl=0, present */
lea _idt,%edi # _idt 是中斷描述符表的地址
mov $256,%ecx
rp_sidt:
movl %eax,(%edi) # eax -> [edi] 將啞中斷門描述符存入表中
movl %edx,4(%edi) # edx -> [edi+4]
addl $8,%edi # edi + 8 -> edi
dec %ecx
jne rp_sidt
lidt idt_descr #加載中斷描述符表暫存器
ret
/*
* setup_gdt
*
* This routines sets up a new gdt and loads it.
* Only two entries are currently built, the same
* ones that were built in init.s. The routine
* is VERY complicated at two whole lines, so this
* rather long comment is certainly needed :-).
* This routine will beoverwritten by the page tables.
*/
# 設定全域描述符表項
setup_gdt:
lgdt gdt_descr # 加載全域描述符暫存器
ret
/*
* I put the kernel page tables right after the page directory,
* using 4 of them to span 16 Mb of physical memory. People with
* more than 16MB will have to expand this.
*/
# 將內核的記憶體頁表直接放在頁目錄后,使用4個表來尋址16MB的物理地址
.org 0x1000 # 從偏移地址0x1000處開始是第一個頁表(偏移0開始將存放頁表目錄)
pg0:
.org 0x2000
pg1:
.org 0x3000
pg2:
.org 0x4000
pg3:
.org 0x5000 # 定義下面的記憶體資料從偏移地址0x5000開始
/*
* tmp_floppy_area is used by the floppy-driver when DMA cannot
* reach to a buffer-block. It needs to be aligned, so that it isn't
* on a 64kB border.
*/
# 當DMA(直接存盤器訪問)不能訪問緩沖塊時,則_tmp_floppy_area記憶體塊就可以供軟盤驅動程式使用
# 需要保證地址對齊
_tmp_floppy_area:
.fill 1024,1,0 # 保留1024項,每一項一個位元組,填充數值為0
# 下面幾個入堆疊操作為了跳轉到init/main.c中main()函式做準備
# 前面三個入堆疊0值分別表示envp,argv指標和argc的值
after_page_tables:
pushl $0 # These are the parameters to main :-) main函式引數 envp
pushl $0 # argv指標
pushl $0 # argc
pushl $L6 # return address for main, if it decides to. 模擬呼叫main時首先將回傳地址入堆疊的操作
pushl $_main # _main--->main
jmp setup_paging
# main函式到不了這里 ,到標號L6這里,是一個死回圈
L6:
jmp L6 # main should never return here, but
# just in case, we know what happens.
/* This is the default interrupt "handler" :-) */
# 下面是默認的中斷'向量句柄''
int_msg:
.asciz "Unknown interrupt\n\r" # 定義‘未知的指定’
.align 2 # 按4位元組方式對齊記憶體地址
# 中斷處理程序
ignore_int:
pushl %eax
pushl %ecx
pushl %edx
push %ds # 這里請注意ds,es,fs,gs等雖然是16位的暫存器
push %es # 但仍然會以32位的形式入堆疊,即需要占用4個位元組的堆疊空間
push %fs # 以上用于保存暫存器
movl $0x10,%eax
mov %ax,%ds
mov %ax,%es
mov %ax,%fs
pushl $int_msg
call _printk # 該函式在 kernel/printk.c 中
popl %eax # 清理引數
pop %fs
pop %es
pop %ds
popl %edx
popl %ecx
popl %eax
iret # 中斷回傳(把中斷呼叫是壓入堆疊的CPU標志暫存器值也彈出)
/*
* Setup_paging
*
* This routine sets up paging by setting the page bit
* in cr0. The page tables are set up, identity-mapping
* the first 16MB. The pager assumes that no illegal
* addresses are produced (ie >4Mb on a 4Mb machine).
*
* NOTE! Although all physical memory should be identity
* mapped by this routine, only the kernel page functions
* use the >1Mb addresses directly. All "normal" functions
* use just the lower 1Mb, or the local data space, which
* will be mapped to some other place - mm keeps track of
* that.
*
* For those with more memory than 16 Mb - tough luck. I've
* not got it, why should you :-) The source is here. Change
* it. (Seriously - it shouldn't be too difficult. Mostly
* change some constants etc. I left it at 16Mb, as my machine
* even cannot be extended past that (ok, but it was cheap :-)
* I've tried to show which constants to change by having
* some kind of marker at them (search for "16Mb"), but I
* won't guarantee that's all :-( )
*/
# 下面這段子程式通過控制CR0的標志位(PG位31)來啟動對記憶體的分頁處理功能,并設定各個表項的內容
.align 2 # 按4位元組方式對齊記憶體地址邊界
setup_paging:
movl $1024*5,%ecx /* 5 pages - pg_dir+4 page tables */#對(1頁目錄+4頁頁表)清零
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */ #目錄頁從0x00開始
cld;rep;stosl
# 設定頁目錄表中的項,內核中有4個頁表需要設定4項
movl $pg0+7,_pg_dir /* set present bit/user r/w */# pg0+7:0x000010007,頁目錄表中的第一項
movl $pg1+7,_pg_dir+4 /* --------- " " --------- */
movl $pg2+7,_pg_dir+8 /* --------- " " --------- */
movl $pg3+7,_pg_dir+12 /* --------- " " --------- */
# 填寫4個頁表中所有內容:4(頁表)*1024(項)=4096(項)
movl $pg3+4092,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
1: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
# 設定頁目錄表基址暫存器CR3的值(保存的頁目錄表的物理地址),指向頁目錄表,
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
# 設定啟用分頁處理,(cr0的標志PG,位31)
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-queue */
# 在改變分頁處理標志后要求使用轉移指令重繪預取指令佇列,這里用的是回傳指令ret
# 回傳指令的作用將main程式壓入堆疊中的地址彈出,并轉到init/main.c去運行
.align 2
.word 0
# 加載中斷描述符表暫存器idtr的lidt指令
idt_descr:
.word 256*8-1 # idt contains 256 entries
.long _idt
.align 2
.word 0
# 加載全域描述符表暫存器gdtr的lgdt指令
gdt_descr:
.word 256*8-1 # so does gdt (not that that's any
.long _gdt # magic number, but it works for me :^)
.align 3
_idt: .fill 256,8,0 # idt is uninitialized
# 全域表,前4項是空項,代碼段描述符,資料段描述符,系統呼叫段描述符
_gdt: .quad 0x0000000000000000 /* NULL descriptor */
.quad 0x00c09a0000000fff /* 16Mb */
.quad 0x00c0920000000fff /* 16Mb */
.quad 0x0000000000000000 /* TEMPORARY - don't use */
.fill 252,8,0 /* space for LDT's and TSS's etc */
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/301863.html
標籤:其他
上一篇:面試題:Linux常考命令
