亚洲精品亚洲人成在线观看麻豆,在线欧美视频一区,亚洲国产精品一区二区动图,色综合久久丁香婷婷

              當(dāng)前位置:首頁(yè) > IT技術(shù) > 其他 > 正文

              操作系統(tǒng)實(shí)現(xiàn)-loader
              2022-05-11 11:02:47

              博客網(wǎng)址:www.shicoder.top
              微信:18223081347
              歡迎加群聊天 :452380935

              大家好呀,終于我們到了操作系統(tǒng)的loader部分了,loader也是操作系統(tǒng)中最重要的一個(gè)部分,承接上面的boot,啟下下面的kernel,那我們就開(kāi)始吧!!!

              內(nèi)存檢測(cè)

              在loader中,最重要的一點(diǎn)就是檢測(cè)內(nèi)存,檢測(cè)一些系統(tǒng)參數(shù),到時(shí)候給kernel使用,那么下面我們就介紹下loader中如何檢測(cè)內(nèi)存。還是一樣,我們先看下檢測(cè)內(nèi)存的代碼

              detect_memory:
                  ; 置為0
                  xor ebx, ebx
              
                  ; es:di賦值
                  mov ax, 0
                  mov es, ax
                  mov edi, ards_buffer
              
                  mov edx, 0x534d4150 ;固定簽名
              
              .next:
                  mov eax, 0xe820
                  mov ecx, 20
                  ; 執(zhí)行系統(tǒng)調(diào)用
                  int 0x15
              
                  ; 檢測(cè)cf標(biāo)志位
                  jc error
                  ; 將緩存指針指向下一個(gè)結(jié)構(gòu)體
                  add di, cx
              
                  ; 將結(jié)構(gòu)體數(shù)量+1
                  inc word [ards_count]
                  ; 檢測(cè)ebx是否為0
                  cmp ebx, 0
                  jnz .next
              
                  mov si, detecting
                  call print
              

              注意,我們這里獲取內(nèi)存的方式是采用BIOS中int 0x15中子功能0xE820。我們先給出int 0x15下3個(gè)子功能的具體描述

              • EAX=0xE820 :遍歷主機(jī)上全部?jī)?nèi)存
              • AX=0xE801:分別檢測(cè)第15MB和16MB-4GB的內(nèi)存
              • AH=0x88:最多檢測(cè)出64MB內(nèi)存

              內(nèi)存的相關(guān)值共同組成一個(gè)結(jié)構(gòu)體:ARDS(地址范圍描述符),共20字節(jié)如下

              • BaseAddrLow(4字節(jié)):基地址的低32位
              • BaseAddrHigh(4字節(jié)):基地址的高32位
              • LengthLow(4字節(jié)):內(nèi)存長(zhǎng)度的低32位,以字節(jié)為單位
              • LengthHigh(4字節(jié)):內(nèi)存長(zhǎng)度的高32位,以字節(jié)為單位
              • Type(4字節(jié)):本段內(nèi)存的類(lèi)型

              返回值如下

              • CF位:若CF位為0表示調(diào)用未出錯(cuò)
              • EAX:0x534d4150
              • ED:DI:ARDS的地址
              • ECX:寫(xiě)入到ARDS的字節(jié)數(shù),一般為20字節(jié)
              • EBX:下一個(gè)ARDS的地址,當(dāng)CF=0,且EBX=0,表示結(jié)束

              通過(guò)上述代碼,就可以將ARDS的個(gè)數(shù)存在ards_count中,將每一個(gè)ARDS的值放在ards_buffer中。

              準(zhǔn)備進(jìn)入保護(hù)模式

              進(jìn)入保護(hù)模式需要三個(gè)步驟

              • 打開(kāi)A20
              • 加載GDT
              • 將cr0的pe位置1

              全局描述符表

              在實(shí)模式下,訪(fǎng)問(wèn)一個(gè)地址的方式為

              段地址 << 4 + 偏移地址

              但是在進(jìn)入保護(hù)模式后,地址線(xiàn)是足夠的,共32條,所以并不需要上面的方式,其尋址方式為

              段選擇子(16位):段內(nèi)偏移(32位)

              我們來(lái)說(shuō)下段選擇子。段選擇子有16位,3-15位為描述符索引(13位可表示8192個(gè)),第2位為T(mén)I位,TI=0,表示從全局描述符表中取,TI=1,表示從局部描述符表中取。第0-1位為特權(quán)級(jí)RPL(熟悉的特權(quán)級(jí)0-3級(jí),用2位描述),代碼如下

              typedef struct selector
              {
                  unsigned char RPL : 2; // Request PL 
                  unsigned char TI : 1; // 0  全局描述符 1 局部描述符 LDT Local 
                  unsigned short index : 13; // 全局描述符表索引
              } __attribute__((packed)) selector;
              

              上面出現(xiàn)了一個(gè)全局描述符表的東西(GDT),全局描述符表中每一項(xiàng)都是一個(gè)全局描述符,每個(gè)全局描述符都指向內(nèi)存中的一個(gè)位置,下面的圖展示了其關(guān)系

              image-20220427214535140

              因此如何描述這一段內(nèi)存,就變得尤為重要,全局描述符的結(jié)構(gòu)如下

              typedef struct descriptor /* 共 8 個(gè)字節(jié) */
              {
                  unsigned short limit_low;      // 段界限 0 ~ 15 位
                  unsigned int base_low : 24;    // 基地址 0 ~ 23 位 16M
                  unsigned char type : 4;        // 段類(lèi)型
                  unsigned char segment : 1;     // 1 表示代碼段或數(shù)據(jù)段,0 表示系統(tǒng)段
                  unsigned char DPL : 2;         // Descriptor Privilege Level 描述符特權(quán)等級(jí) 0 ~ 3
                  unsigned char present : 1;     // 存在位,1 在內(nèi)存中,0 在磁盤(pán)上
                  unsigned char limit_high : 4;  // 段界限 16 ~ 19;
                  unsigned char available : 1;   // 該安排的都安排了,送給操作系統(tǒng)吧
                  unsigned char long_mode : 1;   // 64 位擴(kuò)展標(biāo)志
                  unsigned char big : 1;         // 32 位 還是 16 位;
                  unsigned char granularity : 1; // 粒度 4KB 或 1B
                  unsigned char base_high;       // 基地址 24 ~ 31 位
              } __attribute__((packed)) descriptor;
              

              image-20220427214816129

              則全局描述符表就有8192項(xiàng),每一項(xiàng)都是指示一片內(nèi)存的全局描述符,且表的第0項(xiàng)是NULL。有一個(gè)特殊寄存器GDT register指向它,只要讀取這個(gè)寄存器的值,就可以找到這個(gè)表,然后通過(guò)段選擇子就可以知道是哪一個(gè)下標(biāo)。GDT register有48位,結(jié)構(gòu)如下,0-15位共16位標(biāo)識(shí)GDT界限,共65536字節(jié),每個(gè)全局描述符8字節(jié),所以一共65536/8=8192個(gè)

              image-20220427215152160

              下面給出相關(guān)代碼

              memory_base equ 0 ; 內(nèi)存開(kāi)始的位置
              ; 32位下,內(nèi)存為4G,然后選用的粒度為4KB
              memory_limit equ ((1024 * 1024 * 1024 * 4) / (1024 * 4) - 1) ; 內(nèi)存界限 4G / 4k -1
              
              ; 準(zhǔn)備進(jìn)入保護(hù)模式
              prepare_protected_mode:
              
                  cli; 關(guān)閉中斷
              	...
                  ; 加載GDT
                  lgdt [gdt_ptr]
              	...
              
              
              gdt_ptr:
                  dw (gdt_end-gdt_base)-1
                  dd gdt_base
              gdt_base:
                  ; dd 4個(gè)字節(jié),全局描述符表中第一個(gè)8字節(jié)為null描述符
                  dd 0,0 ;null描述符
              gdt_code:
                  dw memory_limit & 0xffff ; 段界限 0 ~ 15 位
                  dw memory_base & 0xffff ; 基地址 0 ~ 15 位
                  db (memory_base >> 16) & 0xff ; 基地址 16 ~ 23 位
                  ; 存在位,1 在內(nèi)存中
                  ; 特權(quán)等級(jí) 00
                  ; 1 表示代碼段或數(shù)據(jù)段
                  ; 段類(lèi)型 | X | C/E | R/W | A | 1 0 1 0 代碼段-非依從-可讀-沒(méi)有訪(fǎng)問(wèn)
                  db 0b_1_00_1_1_0_1_0
                  ; 1 粒度 4KB
                  ; 1 32 位
                  ; 0 非64 位擴(kuò)展標(biāo)志
                  ; 0 available 隨意
                  ; 段界限 16 ~ 19
                  db 0b_1_1_0_0_0000 | (memory_limit >> 16) & 0xf
                  ; 基地址 24 ~ 31 位
                  db (memory_base >> 24) & 0xff
              
              gdt_data:
                  dw memory_limit & 0xffff ; 段界限 0 ~ 15 位
                  dw memory_base & 0xffff ; 基地址 0 ~ 15 位
                  db (memory_base >> 16) & 0xff ; 基地址 16 ~ 23 位
                  ; 存在位,1 在內(nèi)存中
                  ; 特權(quán)等級(jí) 00
                  ; 1 表示代碼段或數(shù)據(jù)段
                  ; 段類(lèi)型 | X | C/E | R/W | A | 0 0 1 0 數(shù)據(jù)段-向上-可寫(xiě)-沒(méi)有訪(fǎng)問(wèn)
                  db 0b_1_00_1_0_0_1_0
                  ; 1 粒度 4KB
                  ; 1 32 位
                  ; 0 非64 位擴(kuò)展標(biāo)志
                  ; 0 available 隨意
                  ; 段界限 16 ~ 19
                  db 0b_1_1_0_0_0000 | (memory_limit >> 16) & 0xf
                  ; 基地址 24 ~ 31 位
                  db (memory_base >> 24) & 0xff
              gdt_end:
              

              我們重點(diǎn)來(lái)看重點(diǎn)部分,其他我們后續(xù)來(lái)說(shuō)

              我們前面說(shuō)過(guò),在進(jìn)入保護(hù)模式前,我們要加載GDT,以便在保護(hù)模式后,其他地方要用到,所以使用如下命令

              lgdt [gdt_ptr]; 加載GDT 將gdt_ptr所指向的區(qū)域加載到GDT register中
              sgdt [gdt_ptr]; 保存 gdt 將GDT register中的內(nèi)容保存到gdt_ptr所指向的區(qū)域
              

              然后我們構(gòu)建代碼段和數(shù)據(jù)段的段選擇子,通過(guò)選擇子的結(jié)構(gòu)進(jìn)行構(gòu)建

              ; 構(gòu)建代碼段和數(shù)據(jù)段的段選擇子
              ; 1 << 3 => 0001 根據(jù)段選擇子的結(jié)構(gòu),第0-1位為 RPL ,第2位為T(mén)I ,后面為index
              code_selector equ (1 << 3)
              data_selector equ (2 << 3)
              

              A20線(xiàn)

              其實(shí)就是為了在保護(hù)模式下可以使用更大的尋址線(xiàn),因此打開(kāi)A20線(xiàn),方式很簡(jiǎn)單,就是將端口0x92的第1位置1就可以,代碼如下:

              ; 打開(kāi)A20線(xiàn)
              in al, 0x92
              or al, 0b10 ; 第1位置1
              out 0x92, al
              

              CR0寄存器

              我們需要將CR0寄存器的第0位(PE位)Protection Enable打開(kāi),方式如下

              mov eax, cr0
              or eax, 1 ; 第0位置1
              mov cr0, eax
              

              刷新流水線(xiàn)

              我們可以看到一條很奇怪的jmp指令

              ; 用跳轉(zhuǎn)來(lái)刷新緩存,啟用保護(hù)模式
              jmp dword code_selector:protect_mode
              
              ; 提醒編譯器,到了32位的保護(hù)模式
              [bits 32]
              protect_mode:
              

              因?yàn)槲覀冎涝谔D(zhuǎn)前是實(shí)模式,可能是16位,但是跳轉(zhuǎn)到保護(hù)模式后,需要在32位下進(jìn)行,那么CPU指令卻不知道,仍然可能用16位的方式去解析32位指令,就會(huì)出錯(cuò),因此采用1個(gè)jmp模式進(jìn)行

              進(jìn)入保護(hù)模式

              經(jīng)過(guò)前面的步驟,我們終于來(lái)到了保護(hù)模式protect_mode。這個(gè)版本的操作系統(tǒng)我們?cè)O(shè)置的保護(hù)模式很簡(jiǎn)單,代碼如下:

              [bits 32]
              protect_mode:
              
                  mov ax, data_selector
                  ; 初始化段寄存器
                  mov ds, ax
                  mov es, ax
                  mov fs, ax
                  mov gs, ax
                  mov ss, ax
              
                  ; 在0x7e00-0x9fbff可用區(qū)域間隨便找一個(gè)位置
                  mov esp, 0x10000 ;修改棧頂
              
                  ; 因?yàn)閟ystem.bin(kernel文件夾里面的程序編譯的)是從第10個(gè)扇區(qū)開(kāi)始寫(xiě)入,寫(xiě)了200個(gè)扇區(qū)
                  mov edi, 0x10000;讀取的目標(biāo)內(nèi)存
                  mov ecx, 10 ;起始扇區(qū)
                  mov bl, 200 ;扇區(qū)數(shù)量
                  call read_disk
                  ; 內(nèi)核代碼被放在0x10000處,所以跳轉(zhuǎn)到這里執(zhí)行內(nèi)核代碼
                  jmp dword code_selector: 0x10000
              

              我們?cè)诰幾g的時(shí)候,先將system.bin寫(xiě)入到磁盤(pán)的第10個(gè)扇區(qū),命令如下

              dd if=system.bin of=master.img bs=512 count=200 seek=10 conv=notrunc  
              

              終于我們可以編寫(xiě)c語(yǔ)言了,前面寫(xiě)匯編實(shí)在難受,哈哈哈。

              內(nèi)核的主程序在main.c中,先簡(jiǎn)單實(shí)現(xiàn)下把,后續(xù)再補(bǔ)充

              void kernel_init(){
                  char *video = (char*)0xb8000; // 文本顯示器的內(nèi)存位置
                  for (int i = 0; i < sizeof(message); i++)
                  {
                      // 第一個(gè)位是字符,第二個(gè)位是該字符的特性,比如是閃爍還是不閃爍等,所以每個(gè)字符要在內(nèi)存位置占2個(gè)位
                      video[i*2] = message[i];
                  }
              }
              

              注意下0xb8000,在這個(gè)系列的第2章中,有如下代碼

              ; 0xb8000 文本顯示器的內(nèi)存區(qū)域
              mov ax, 0xb800
              mov ds, ax
              mov byte [0], 'H'
              

              0xb8000已經(jīng)超過(guò)16位了,所以在實(shí)模式下,需要使用( 16 位段基址 << 4 ) + 16 位偏移地址方式,而在保護(hù)模式下有32位,所以可以直接訪(fǎng)問(wèn)

              本文摘自 :https://www.cnblogs.com/

              開(kāi)通會(huì)員,享受整站包年服務(wù)立即開(kāi)通 >