主頁 > 作業系統 > [自制作業系統] 第09回 加載內核

[自制作業系統] 第09回 加載內核

2022-06-21 19:24:08 作業系統

目錄
一、前景回顧
二、用C語言撰寫內核
三、加載內核
四、運行測驗

 

一、前景回顧

  本回開始,我們要開始撰寫內核代碼了,在此之前,先梳理一下已經完成的作業,
  
  藍色部分是目前已經完成的部分,黃色部分是本節將要實作的,

二、用C語言撰寫內核

  為什么要用C語言來撰寫內核呢,其實用匯編語言也可以實作,只是對于我們來講,看C語言代碼肯定要比匯編語言更容易理解,看起來也沒那么費勁,所以用C語言可以更加省事,

  先來看看我們內核代碼的最初形態,首先在專案路徑下新建一個project/kernel的目錄,以后我們內核相關的檔案都存放于此,在該目錄下新建一個名為main.c的檔案,在main.c中鍵入如下代碼:

1 int main(void)
2 {
3     while(1);
4     return 0;
5 }

  這就是我們的內核代碼,當然現在什么都還沒有,就算內核成功加載進去也沒有什么反應,這里我們先實作一個自己的列印函式,在main函式中呼叫這個列印函式來列印出“HELLO KERNEL”的字符,這樣就能測驗內核代碼運行是否成功,前面我們一直都是直接操作顯存段的記憶體來往螢屏上來列印字符,現在開始用C語言編程了,自然要封裝一個列印函式來列印字符,

  同樣,在專案路徑下新建另一個project/lib/kernel目錄,該目錄用來存放一些供內核使用的庫檔案,在該目錄下新建名為print.S和print.h的檔案,在此之前,我們在project/lib目錄下新建一個名為stdint.h的檔案用來定義一些資料型別,代碼如下:

 1 #ifndef __LIB_STDINT_H__
 2 #define __LIB_STDINT_H__
 3 typedef signed char int8_t;
 4 typedef signed short int int16_t;
 5 typedef signed int int32_t;
 6 typedef signed long long int int64_t;
 7 typedef unsigned char uint8_t;
 8 typedef unsigned short int uint16_t;
 9 typedef unsigned int uint32_t;
10 typedef unsigned long long int uint64_t;
11 #endif
stdint.h
  1 TI_GDT         equ  0
  2 RPL0           equ  0
  3 SELECTOR_VIDEO equ (0x0003 << 3) + TI_GDT + RPL0
  4 
  5 section .data
  6 put_int_buffer dq 0
  7 
  8 [bits 32]
  9 section .text
 10 ;-----------------------------------put_str--------------------------------------
 11 ;功能描述:put_str通過put_char來列印以0字符結尾的字串
 12 ;----------------------------------------------------------------------------------
 13 global put_str
 14 put_str:
 15         push ebx
 16         push ecx
 17         xor ecx, ecx
 18         mov ebx, [esp + 12]
 19 .goon:
 20         mov cl, [ebx]
 21         cmp cl, 0
 22         jz .str_over
 23         push ecx
 24         call put_char
 25         add esp, 4
 26         inc ebx
 27         jmp .goon
 28 .str_over:
 29         pop ecx
 30         pop ebx
 31         ret
 32         
 33 ;--------------------------put_char-------------------------
 34 ;功能描述:把堆疊中的一個字符寫入到游標所在處
 35 ;---------------------------------------------------------------
 36 global put_char
 37 put_char:
 38         pushad                                         ;備份32位暫存器環境
 39         mov ax, SELECTOR_VIDEO  ;不能直接把立即數送入段暫存器中
 40         mov gs, ax
 41 
 42         ;----------------------獲取當前游標位置---------------------------------
 43         ;先獲取高8位
 44         mov dx, 0x03d4
 45         mov al, 0x0e
 46         out dx, al
 47         mov dx, 0x03d5
 48         in al, dx
 49         mov ah, al
 50 
 51         ;再獲取低8位
 52         mov dx, 0x03d4
 53         mov al, 0x0f
 54         out dx, al
 55         mov dx, 0x03d5
 56         in al, dx
 57 
 58         ;將游標位置存入bx
 59         mov bx, ax
 60 
 61         ;在堆疊中獲取待列印的字符
 62         mov ecx, [esp + 36]  ;pushad將8個32位暫存器都壓入堆疊中,再加上主調函式4位元組的回傳地址,所以esp+36之后才是主調函式壓入的列印字符
 63         cmp cl, 0xd                 ;判斷該字符是否為CR(回車),CR的ASCII碼為0x0d
 64         jz .is_carriage_return
 65 
 66         cmp cl, 0xa                 ;判斷該字符是否為LF(換行),LF的ASCII碼為0x0a
 67         jz .is_line_feed
 68 
 69         cmp cl, 0x8                 ;判斷該字符是否為BS(空格),BS的ASCII碼為0x08
 70         jz .is_backspace
 71 
 72         jmp .put_other
 73 
 74 ;字符為BS(空格)的處理辦法
 75 .is_backspace:
 76         dec bx
 77         shl bx, 1
 78         mov byte [gs:bx], 0x20
 79         inc bx
 80         mov byte [gs:bx], 0x07
 81         shr bx, 1
 82         jmp set_cursor
 83 
 84 ;字符為CR(回車)以及LF(換行)的處理辦法
 85 .is_line_feed:
 86 .is_carriage_return:
 87         xor dx, dx
 88         mov ax, bx
 89         mov si, 80
 90         div si
 91         sub bx, dx
 92 
 93 ;CR(回車)符的處理結束
 94 .is_carriage_return_end:
 95         add bx, 80
 96         cmp bx, 2000
 97 ;LF(換行)符的處理結束
 98 .is_line_feed_end:
 99         jl set_cursor
100 
101 .put_other:
102         shl bx, 1
103         mov [gs:bx], cl
104         inc bx
105         mov byte [gs:bx], 0x07
106         shr bx, 1
107         inc bx
108         cmp bx, 2000
109         jl set_cursor
110 
111 .roll_screen:
112         cld
113         mov ecx, 960
114         mov esi, 0xc00b80a0
115         mov edi, 0xc00b8000
116         rep movsd
117         
118         mov ebx, 3840
119         mov ecx, 80
120 
121 .cls:
122         mov word [gs:ebx], 0x0720
123         add ebx, 2
124         loop .cls
125         mov bx, 1920
126 global set_cursor
127 set_cursor:
128         mov dx, 0x03d4
129         mov al, 0x0e
130         out dx, al
131         mov dx, 0x03d5
132         mov al, bh
133         out dx, al
134 
135         mov dx, 0x03d4
136         mov al, 0x0f
137         out dx, al
138         mov dx, 0x03d5
139         mov al, bl
140         out dx, al
141 .put_char_done:
142         popad
143         ret
144 ;-----------------------------------put_int--------------------------------------
145 ;功能描述:將小端位元組序的數字變成對應的ASCII后,倒置
146 ;輸入:堆疊中引數為待列印的數字
147 ;輸出:在螢屏中列印十六進制數字,并不會列印前綴0x
148 ;如列印十進制15時,只會列印f,而不是0xf
149 ;----------------------------------------------------------------------------------
150 global put_int
151 put_int:
152         pushad
153         mov ebp, esp
154         mov eax, [ebp + 36]
155         mov edx, eax
156         mov edi, 7
157         mov ecx, 8
158         mov ebx, put_int_buffer
159 
160 ;將32位數字按照16進制的形式從低位到高位逐個處理,共處理8個16進制數字
161 .16based_4bits:                   ; 每4位二進制是16進制數字的1位,遍歷每一位16進制數字
162         and edx, 0x0000000F               ; 決議16進制數字的每一位,and與操作后,edx只有低4位有效
163         cmp edx, 9                   ; 數字0~9和a~f需要分別處理成對應的字符
164         jg .is_A2F 
165         add edx, '0'                   ; ascii碼是8位大小,add求和操作后,edx低8位有效,
166         jmp .store
167 .is_A2F:
168         sub edx, 10                   ; A~F 減去10 所得到的差,再加上字符A的ascii碼,便是A~F對應的ascii碼
169         add edx, 'A'
170 
171 ;將每一位數字轉換成對應的字符后,按照類似“大端”的順序存盤到緩沖區put_int_buffer
172 ;高位字符放在低地址,低位字符要放在高地址,這樣和大端位元組序類似,只不過咱們這里是字符序.
173 .store:
174 ; 此時dl中應該是數字對應的字符的ascii碼
175         mov [ebx+edi], dl               
176         dec edi
177         shr eax, 4
178         mov edx, eax 
179         loop .16based_4bits
180 
181 ;現在put_int_buffer中已全是字符,列印之前,
182 ;把高位連續的字符去掉,比如把字符000123變成123
183 .ready_to_print:
184         inc edi                   ; 此時edi退減為-1(0xffffffff),加1使其為0
185 .skip_prefix_0:  
186         cmp edi,8                   ; 若已經比較第9個字符了,表示待列印的字串為全0 
187         je .full0 
188 ;找出連續的0字符, edi做為非0的最高位字符的偏移
189 .go_on_skip:   
190         mov cl, [put_int_buffer+edi]
191         inc edi
192         cmp cl, '0' 
193         je .skip_prefix_0               ; 繼續判斷下一位字符是否為字符0(不是數字0)
194         dec edi                   ;edi在上面的inc操作中指向了下一個字符,若當前字符不為'0',要恢復edi指向當前字符               
195         jmp .put_each_num
196 
197 .full0:
198         mov cl,'0'                   ; 輸入的數字為全0時,則只列印0
199 .put_each_num:
200         push ecx                   ; 此時cl中為可列印的字符
201         call put_char
202         add esp, 4
203         inc edi                   ; 使edi指向下一個字符
204         mov cl, [put_int_buffer+edi]           ; 獲取下一個字符到cl暫存器
205         cmp edi,8
206         jl .put_each_num
207         popad
208         ret
print.S
1 #ifndef  __LIB_KERNEL_PRINT_H
2 #define  __LIB_KERNEL_PRINT_H
3 #include "stdint.h"
4 void put_char(uint8_t char_asci);
5 void put_str(char *message);
6 void put_int(uint32_t num);
7 #endif
print.h

  最后輸入如下命令來編譯print.S:

nasm -f elf -o ./project/lib/kernel/print.o ./project/lib/kernel/print.S

  完善了列印函式后,我們現在可以在main函式中實作列印功能了,修改main.c檔案:

1 #include "print.h"
2 int main(void)
3 {
4     put_str("HELLO KERNEL\n");
5     while(1);
6     return 0;
7 }

三、加載內核

  前面我們已經將內核代碼實作完成了,接下來按道理應該和前面一樣,將main.c檔案編譯加載到硬碟中,隨后通過loader來讀取加載該檔案,最終跳轉運行,的確也是如此,不過略有不同,請聽我慢慢講來,

  現在我們是main.c檔案,不同于匯編代碼,我們接下來要使用gcc工具將main.c檔案編譯成main.o檔案:

gcc -m32 -I project/lib/kernel/ -c -fno-builtin project/kernel/main.c -o project/kernel/main.o

  它只是一個目標檔案,也稱為重定位檔案,重定位檔案指的是檔案里面所用的符號還沒有安排地址,這些符號的地址將來是要與其他目標檔案“組成”一個可執行檔案時再重定位(編排地址),這里的符號就是指的所呼叫的函式或使用的變數,看我們的main.c檔案中,在main函式中呼叫了print.h中宣告的put_str函式,所以將來main.o檔案需要和print.o檔案一起組成可執行檔案,

  如何“組成”呢?這里的“組成”其實就是指的C語言程式變成可執行檔案下的四步驟(預處理、編譯、匯編和鏈接)中的鏈接,Linux下使用的是ld命令來鏈接,我們是在Linux平臺下的,所以自然使用ld命令:

ld -m elf_i386 -Ttext 0xc0001500 -e main -o project/kernel/kernel.bin project/kernel/main.o project/lib/kernel/print.o

  最終生成可執行檔案kernel.bin,它就是我們需要加載到硬碟里的那個檔案,

  到這里都和前面步驟一致,只是后面loader并不是單純的將kernel.bin檔案拷貝到記憶體某處再跳轉執行,這是因為我們生成的kernel.bin檔案的格式為elf,elf格式的檔案,在檔案最開始有一個名為elf格式頭的部分,該部分詳細包含了整個檔案的資訊,具體內容過多我這里不再展開講,感興趣的朋友可以參考原書《作業系統真象還原》p213~222,或者百度,所以說如果我們只是單純地跳轉到該檔案的加載處,那么必定會出現問題,因為該檔案的開始部分并不是可供CPU執行的程式,我們跳轉的地址應該是該檔案的程式部分,這個地址在我們前面鏈接時已經指定為0xc0001500,因為我們前面已經開啟了分頁機制,所以實際上這個地址對應的是物理地址的0x1500處,

  接下來再修改loader.S檔案,增加拷貝內核部分代碼以及拷貝函式代碼,為了便于閱讀,我將新代碼附在了之前的loader.S檔案下,除此之外,boot.inc也有新增的內容,

  1 %include "boot.inc"
  2 section loader vstart=LOADER_BASE_ADDR
  3 LOADER_STACK_TOP equ LOADER_BASE_ADDR
  4 jmp loader_start
  5 
  6 ;構建gdt及其內部描述符
  7 GDT_BASE:        dd 0x00000000
  8                 dd 0x00000000
  9 CODE_DESC:       dd 0x0000FFFF
 10                 dd DESC_CODE_HIGH4
 11 DATA_STACK_DESC: dd 0x0000FFFF
 12                 dd DESC_DATA_HIGH4
 13 VIDEO_DESC:      dd 0x80000007
 14                 dd DESC_VIDEO_HIGH4
 15 
 16 GDT_SIZE  equ $-GDT_BASE
 17 GDT_LIMIT equ GDT_SIZE-1
 18 times 60 dq 0  ;此處預留60個描述符的空位
 19 
 20 SELECTOR_CODE  equ (0x0001<<3) + TI_GDT + RPL0
 21 SELECTOR_DATA  equ (0x0002<<3) + TI_GDT + RPL0
 22 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0
 23 
 24 ;以下是gdt指標,前2個位元組是gdt界限,后4個位元組是gdt的起始地址
 25 gdt_ptr   dw GDT_LIMIT 
 26         dd GDT_BASE
 27 
 28 ;---------------------進入保護模式------------
 29 loader_start:
 30     ;一、打開A20地址線
 31     in al, 0x92
 32     or al, 0000_0010B
 33     out 0x92, al
 34     
 35     ;二、加載GDT
 36     lgdt [gdt_ptr]
 37 
 38     ;三、cr0第0位(pe)置1
 39     mov eax, cr0
 40     or eax, 0x00000001
 41     mov cr0, eax
 42     
 43     jmp dword SELECTOR_CODE:p_mode_start ;重繪流水線
 44 
 45     [bits 32]
 46     p_mode_start:
 47             mov ax, SELECTOR_DATA
 48             mov ds, ax
 49             mov es, ax
 50             mov ss, ax
 51             mov esp, LOADER_STACK_TOP
 52             mov ax, SELECTOR_VIDEO
 53             mov gs, ax
 54             
 55             mov byte [gs:160], 'p'
 56 
 57 ;------------------開啟分頁機制-----------------
 58     ;一、創建頁目錄表并初始化頁記憶體位圖
 59     call setup_page
 60 
 61     ;將描述符表地址及偏移量寫入記憶體gdt_ptr,一會兒用新地址重新加載
 62     sgdt [gdt_ptr]
 63     ;將gdt描述符中視頻段描述符中的段基址+0xc0000000
 64     mov ebx, [gdt_ptr + 2]
 65     or dword [ebx + 0x18 + 4], 0xc0000000
 66             
 67     ;將gdt的基址加上0xc0000000使其成為內核所在的高地址
 68     add dword [gdt_ptr + 2], 0xc0000000
 69 
 70     add esp, 0xc0000000  ;將堆疊指標同樣映射到內核地址
 71             
 72     ;二、將頁目錄表地址賦值給cr3
 73     mov eax, PAGE_DIR_TABLE_POS
 74     mov cr3, eax
 75             
 76     ;三、打開cr0的pg位
 77     mov eax, cr0
 78     or eax, 0x80000000
 79     mov cr0, eax
 80             
 81     ;在開啟分頁后,用gdt新的地址重新加載
 82     lgdt [gdt_ptr]
 83     mov byte [gs:160], 'H'
 84     mov byte [gs:162], 'E'
 85     mov byte [gs:164], 'L'
 86     mov byte [gs:166], 'L'
 87     mov byte [gs:168], 'O'
 88     mov byte [gs:170], ' '
 89     mov byte [gs:172], 'P'
 90     mov byte [gs:174], 'A'
 91     mov byte [gs:176], 'G'
 92     mov byte [gs:178], 'E'
 93 
 94 ;---------------------------------------------
 95 
 96 ;--------------------拷貝內核檔案并進入kernel--------------------------
 97     mov eax, KERNEL_START_SECTOR              ;kernel.bin所在的扇區號 0x09
 98     mov ebx, KERNEL_BIN_BASE_ADDR             ;從磁盤讀出后,寫入到ebx指定的地址0x70000
 99     mov ecx, 200                              ;讀入的扇區數
100 
101     call rd_disk_m_32
102 
103     ;由于一直處在32位下,原則上不需要強制重繪,但是以防萬一還是加上
104     ;跳轉到kernel處
105     jmp SELECTOR_CODE:enter_kernel
106     
107     enter_kernel:
108         call kernel_init
109         mov esp, 0xc009f000               ;更新堆疊底指標
110         jmp KERNEL_ENTRY_POINT            ;內核地址0xc0001500
111         ;jmp $
112         ;---------------------將kernel.bin中的segment拷貝到指定的地址
113         kernel_init:
114             xor eax, eax
115             xor ebx, ebx   ;ebx記錄程式頭表地址
116             xor ecx, ecx    ;cx記錄程式頭表中的program header數量
117             xor edx, edx    ;dx記錄program header 尺寸,即e_phentsize
118 
119             ;偏移檔案42位元組處的屬性是e_phentsize, 表示program header大小
120             mov dx, [KERNEL_BIN_BASE_ADDR + 42]
121             
122             ;偏移檔案28位元組處的屬性是e_phoff
123             mov ebx, [KERNEL_BIN_BASE_ADDR + 28]
124 
125             add ebx, KERNEL_BIN_BASE_ADDR
126             mov cx, [KERNEL_BIN_BASE_ADDR + 44]
127     
128             .each_segment: 
129                     cmp byte [ebx + 0], PT_NULL
130                     je .PTNULL
131 
132             ;為函式memcpy壓入引數,引數是從右往左壓入
133             push dword [ebx + 16]
134             mov eax, [ebx + 4]
135             add eax, KERNEL_BIN_BASE_ADDR
136             push eax
137             push dword [ebx + 8]
138             call mem_cpy
139             add esp, 12
140 
141             .PTNULL:
142                     add ebx, edx
143                     loop .each_segment
144             ret
145 
146             ;-----------逐位元組拷貝mem_cpy(dst, src, size)
147             mem_cpy:
148                     cld
149                     push ebp
150                     mov ebp, esp
151                     push ecx
152                     mov edi, [ebp + 8]
153                     mov esi, [ebp + 12]
154                     mov ecx, [ebp + 16]
155                     rep movsb
156 
157                     pop ecx
158                     pop ebp
159                     ret 
160 ;---------------------------------------------------    
161 
162 ;--------------函式宣告------------------------
163     ;setup_page:(功能)設定分頁------------
164     setup_page:
165         ;先把頁目錄占用的空間逐位元組清0
166         mov ecx, 4096
167         mov esi, 0
168         .clear_page_dir:
169                 mov byte [PAGE_DIR_TABLE_POS + esi], 0
170                 inc esi
171         loop .clear_page_dir
172         
173         ;開始創建頁目錄項
174         .create_pde:
175                 mov eax, PAGE_DIR_TABLE_POS
176                 add eax, 0x1000             ;此時eax為第一個頁表的位置
177                 mov ebx, eax
178         
179         ;下面將頁目錄項0和0xc00都存為第一個頁表的地址,每個頁表表示4MB記憶體
180         ;頁目錄表的屬性RW和P位為1,US為1,表示用戶屬性,所有特權級別都可以訪問
181         or eax, PG_US_U | PG_RW_W | PG_P
182         
183         ;在頁目錄表中的第1個目錄項中寫入第一個頁表的地址(0x101000)和屬性
184         mov [PAGE_DIR_TABLE_POS + 0x0], eax
185 
186         mov [PAGE_DIR_TABLE_POS + 0xc00], eax
187 
188         ;使最后一個目錄項指向頁目錄表自己的地址
189         sub eax, 0x1000
190         mov [PAGE_DIR_TABLE_POS + 4092], eax
191 
192         ;下面創建頁表項(PTE)
193         mov ecx, 256     ;1M低端記憶體/每頁大小4K=256
194         mov esi, 0
195         mov edx, PG_US_U | PG_RW_W | PG_P
196         .create_pte:     ;創建page table entry
197                 mov [ebx + esi*4], edx
198                 add edx, 4096
199                 inc esi
200         loop .create_pte
201         
202         ;創建內核其他頁表的PDE
203         mov eax, PAGE_DIR_TABLE_POS
204         add eax, 0x2000           ;此時eax為第二個頁表的位置
205         or eax, PG_US_U | PG_RW_W | PG_P
206         mov ebx, PAGE_DIR_TABLE_POS
207         mov ecx, 254              ;范圍為第769~1022的所有目錄項數量
208         mov esi, 769 
209         .create_kernel_pde:
210                 mov [ebx + esi*4], eax
211                 inc esi
212                 add eax, 0x1000
213         loop .create_kernel_pde
214         ret
215         
216     ;rd_disk_m_32:(功能)讀取硬碟n個扇區------------
217     rd_disk_m_32:
218         mov esi,eax               ;備份eax,eax中存放了扇區號
219         mov di,cx                 ;備份cx,cx中存放待讀入的扇區數
220 
221         ;讀寫硬碟:
222         ;第一步:設定要讀取的扇區數
223         mov dx,0x1f2
224         mov al,cl
225         out dx,al
226         
227         mov eax,esi
228 
229         ;第二步:將lba地址存入到0x1f3 ~ 0x1f6
230                 ;lba地址7-0位寫入埠0x1f3
231         mov dx,0x1f3
232         out dx,al
233         
234         ;lba地址15-8位寫入埠0x1f4
235         mov cl,8
236         shr eax,cl
237         mov dx,0x1f4
238         out dx,al
239         
240         ;lba地址23-16位寫入埠0x1f5
241         shr eax,cl
242         mov dx,0x1f5
243         out dx,al
244                 
245         shr eax,cl
246         and al,0x0f
247         or al,0xe0
248         mov dx,0x1f6
249         out dx,al
250 
251     ;第三步:向0x1f7埠寫入讀命令,0x20
252         mov dx,0x1f7
253         mov al,0x20
254         out dx,al
255 
256     ;第四步:檢測硬碟狀態
257         .not_ready:
258                 nop
259                 in al,dx
260                 and al,0x88
261                 cmp al,0x08
262                 jnz .not_ready
263 
264     ;第五步:從0x1f0埠讀資料
265         mov ax,di
266         mov dx,256
267         mul dx
268         mov cx,ax
269     ;di為要讀取的扇區數,一個扇區共有512位元組,每次讀入一個字,總共需要
270     ;di*512/2次,所以di*256
271         mov dx,0x1f0
272         .go_on_read:
273                 in ax,dx
274                 mov [ebx],ax
275                 add ebx,2
276                 loop .go_on_read
277                 ret
278 ;----------------------------------------------
loader.S
 1 ;--------------------loader和kernel ---------------
 2 LOADER_BASE_ADDR    equ 0x900
 3 LOADER_START_SECTOR equ 0x2
 4 PAGE_DIR_TABLE_POS  equ 0x100000
 5 KERNEL_START_SECTOR equ 0x9
 6 KERNEL_BIN_BASE_ADDR equ 0x70000 
 7 KERNEL_ENTRY_POINT equ 0xc0001500
 8 PT_NULL equ 0
 9 ;-------------------gdt描述符屬性------------------
10 ;使用平坦模型,所以需要將段大小設定為4GB
11 DESC_G_4K equ 100000000000000000000000b     ;表示段大小為4G
12 DESC_D_32 equ 10000000000000000000000b      ;表示運算元與有效地址均為32位
13 DESC_L    equ 0000000000000000000000b       ;表示32位代碼段
14 DESC_AVL  equ 000000000000000000000b        ;忽略
15 DESC_LIMIT_CODE2  equ  11110000000000000000b   ;代碼段的段界限的第2部分
16 DESC_LIMIT_DATA2  equ  DESC_LIMIT_CODE2            ;相同的值  資料段與代碼段段界限相同
17 DESC_LIMIT_VIDEO2 equ    00000000000000000000b      ;第16-19位 顯存區描述符VIDEO2 書上后面的0少打了一位 這里的全是0為高位 低位即可表示段基址
18 DESC_P      equ  1000000000000000b      ;p判斷段是否在記憶體中,1表示在記憶體中
19 DESC_DPL_0  equ  000000000000000b
20 DESC_DPL_1  equ  010000000000000b
21 DESC_DPL_2  equ  100000000000000b
22 DESC_DPL_3  equ  110000000000000b
23 DESC_S_CODE equ  1000000000000b  ;S等于1表示非系統段,0表示系統段
24 DESC_S_DATA equ  DESC_S_CODE
25 DESC_S_sys  equ  0000000000000b
26 DESC_TYPE_CODE  equ  100000000000b ;x=1,c=0,r=0,a=0 代碼段是可執行的,非一致性,不可讀,已訪問位a清0
27 DESC_TYPE_DATA  equ  001000000000b ;x=0,e=0,w=1,a=0 資料段是不可執行的,向上拓展,可寫,已訪問位a清0
28 
29 DESC_CODE_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_CODE2 + DESC_P + DESC_DPL_0 + DESC_S_CODE + DESC_TYPE_CODE + 0x00 ;代碼段的高四個位元組內容
30 DESC_DATA_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_DATA2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x00 ;資料段的高四個位元組內容
31 
32 DESC_VIDEO_HIGH4 equ (0x00 << 24) + DESC_G_4K + DESC_D_32 + DESC_L + DESC_AVL + DESC_LIMIT_VIDEO2 + DESC_P + DESC_DPL_0 + DESC_S_DATA + DESC_TYPE_DATA + 0x0B
33 
34 
35 ;------------選擇子屬性------------
36 RPL0 equ 00b
37 RPL1 equ 01b
38 RPL2 equ 10b
39 RPL3 equ 11b
40 TI_GDT equ 000b
41 TI_LDT equ 100b
42 
43 ;---------------頁表相關屬性----------------
44 PG_P    equ  1b
45 PG_RW_R equ  00b
46 PG_RW_W equ  10b
47 PG_US_S equ  000b
48 PG_US_U equ  100b
boot.inc

  來看代碼,首先呼叫函式rd_disk_m_32將kernel.bin檔案從硬碟拷貝到地址KERNEL_BIN_BASE_ADDR,也就是0x70000處,

  enter_kernel是進入內核的函式,首先呼叫kernel_init函式,在該函式中其實就是對前面拷貝到地址0x70000處的kernel.bin檔案進行決議,將其中的程式部分拷貝到地址0xc0001500處,隨后再跳轉過去,

  這里講解一下為什么是地址0xc0001500處,物理記憶體中的0x900是loader.bin的加載地址,在該地址開始部分是GDT,GDT以后會被一直使用不能被覆寫,這里預計loader.bin的大小不會超過2000位元組,前面我們有說到內核是要放在loader的上面的,因為內核會不斷增大,所以我們可選的物理地址是0x900+2000=0x10d0,湊個整數就選了0x1500作為內核的入口地址,不必好奇為什么是這個地址,只是憑感覺就這么設計了,因為我們的記憶體相對來說比較寬松,沒必要那么緊湊,

  進入內核后,我們修改了堆疊頂指標,不再是以前的0x900,查看記憶體布局,我們可以知道在地址0x7E00~0x9FBFF之間,還有約630KB的空間未被使用,因此我們選用地址0x9F000作為堆疊頂,考慮到以后內核的拓展,預計也就只有70KB,我們內核從0x1500開始,堆疊向下發展,我們的內核是不會和堆疊發生沖突的,

四、運行測驗

  首先將前面生成的可執行檔案kernel.bin,也就是我們最終的內核檔案使用dd命令將其寫入到硬碟中去,這里記得還要重新編譯loader.S以及加載loader.bin檔案,因為loader.S也被修改了,

dd if=./project/kernel/kernel.bin of=./hd60M.img bs=512 count=200 seek=9 conv=notrunc

  這里我們count的引數為200,意為向硬碟一次性寫入兩百個扇區,當然我們的內核檔案現在還沒有這么大,Seek=9表示跳過前面9個扇區,從第10個扇區開始存放(以LBA法計算),啟動boch,最終得到如下畫面:
  
  說明我們的內核檔案成功寫入并且加載成功了,雖然只是一小步,但是卻是我們整個作業系統學習的一大步,到了這里我們整個作業系統的基本框架算是搭建完畢了,接下來就是不斷地進行完善內核檔案即可,

  欲知后事如何,請看下回分解,

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/494050.html

標籤:嵌入式

上一篇:VMware 虛擬機圖文安裝和配置 Ubuntu Server 22.04

下一篇:shell學習

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more