[自制操作系统] 第18回 实现用户进程(上)

目录
一、前景回顾
二、任务切换相关
三、实现TSS
四、运行测试

 

一、前景回顾

  在上一回我们已经实现了键盘的驱动编写和环形缓冲区的实现,现在让我们来想这么一个问题:

  一直以来我们的程序都在最高特权级0下工作,这意味着任何程序都和操作系统平起平坐,可以改动任何资源。如果不改变这种现状的话,某个不听话的程序甚至可以给操作系统致命一击,取而代之,那么后果将不堪设想。所以从本回开始,我们便要开始着手实现用户进程,让我们的操作系统看起来更安全一点。

二、任务切换相关

  下面的是我自己的一些见解。

  如果让我来设计任务切换,比较简单的一种思路便是:

  首先我们常说的任务,就是一个程序而已,程序在内存中被分为代码段和数据段。所以我们表征多个任务,那么便是多个代码段和数据段而已。至于任务的切换,可能需要费点心思在软件层面上实现多任务调度机制。

  然后现在问题出现了:

  我们知道代码段和数据段需要在全局描述符表GDT中存储,一个任务需要两个描述符来存储,而我们知道全局描述符表GDT最多也就只有2^13=8192个段描述符,那么理论上也就只能容纳4096个任务,除此之外在软件层面上实现的多任务调度机制有点类似今天的用户态多线程,效率不高且安全性有诸多问题。

  所以我们来看看硬件厂商和CPU厂商是如何解决任务切换的问题的,其中最主要的就是LDTTSS

  首先是LDT。

  LDT是局部描述符表,用来存储每个任务自己的私有实体资源,也就是代码和数据。LDT的地址被保存在一个段描述符中,那么理论上我们现在可以支持8192个任务了。对于当前运行的任务,其LDT的地址被存储在LDTR寄存器中,这样CPU就能根据这个地址从中拿到任务所需要的资源。每切换一个任务时,需要用lldt指令重新加载新任务的LDT地址到LDTR寄存器中。
        [自制操作系统] 第18回 实现用户进程(上)
  随后便是TSS。

  单核CPU要想实现多任务,唯一的方法便是多任务共享一个CPU,也就是让多个任务轮流使用CPU。前面说道,LDT是每个任务的私有资源,所以不用担心多任务时,程序的运行资源会混乱。但这还不够。

  CPU执行任务时,需要把任务所需要的数据加载到寄存器、栈和内存中,因为CPU只能直接处理这些资源中的数据,这是CPU在设计之初时工程师们决定的。于是,问题来了,任务的数据和指令是CPU的处理对象,他们被存放在内存这个低速的容器中,对于CPU来讲,内存的速度太慢了,它最喜欢寄存器。因此内存中的数据往往被加载到高速的寄存器中后再处理,等处理完毕后再将结果写入到内存中,所以,任何时候,寄存器中的内容才是任务的最新状态。当任务被换下CPU后,任务的最新状态应该被保存在某个地方,以便下次重新将此任务调度到CPU时可以恢复此任务的最新状态,这样任务才能继续执行。

  于是TSS就出现了,TSS是程序员为任务单独定义的一个结构体变量,当加载新任务时,CPU自动把当前任务(旧任务)的状态存入当前任务的TSS,然后将新任务TSS中的数据载入到对应的寄存器中。  
                               [自制操作系统] 第18回 实现用户进程(上)
  TSS和其他段也是一样的,本质上是一片存储数据的内存区域,也需要某个描述符结构来描述它,这就是TSS描述符。
        [自制操作系统] 第18回 实现用户进程(上)
  和LDT一样,CPU对TSS的处理也采用了类似的方法,提供一个名为TR的寄存器来存放当前任务的TSS位置。

  总结一下,如图所示:
                   [自制操作系统] 第18回 实现用户进程(上)
  CPU原生支持的任务切换方式是针对每一个任务都有一个LDT和一个TSS结构,这种任务切换方式,在任务切换时效率比较低,所以现代操作系统并未采纳。现代操作系统放弃了LDT,只采用了TSS,但是也没有完全采纳。我们是效仿Linux的任务切换方式的,所以拿Linux为例。

  Linux为每一个CPU创建一个TSS,在各个CPU上的所有任务共享一个TSS,各CPU的TR寄存器保存各CPU上的TSS,也就是说在用ltr指令加载TSS后,该TR寄存器永远指向同一个TSS,之后再也不会切换了。在进程切换时只需要把TSS中的SS0和esp0更新为新任务的内核栈的段地址和栈指针。

  那么任务的状态信息保存在哪里呢?

  对于Linux来讲,Linux只在TSS中初始化esp0和SS0以及IO位图。当CPU从低特权级进入高特权级时,也就是3特权级的用户态到0特权级的内核态时(Linux只有两个特权级)CPU会自动从TSS中获取到0特权级的栈指针,然后Linux手动执行一系列的push指令将任务的状态保存在0特权级的栈中。这个地方先留一下悬念,等后面实现的时候会再次提到。

三、实现TSS

  虽然我们不完全采纳TSS,但是因为TSS是硬件所要求的,所以我们必须构造一个TSS来应付硬件。在project/userprog目录下新建tss.c和tss.h文件,除此之外还需要在global.h文件中新加部分代码。

[自制操作系统] 第18回 实现用户进程(上)

 1 #include "global.h"  2 #include "thread.h"  3 #include "print.h"  4 #include "string.h"  5 #include "tss.h"  6   7 struct tss {  8     uint32_t backlink;  9     uint32_t *esp0; 10     uint32_t ss0; 11     uint32_t *esp1; 12     uint32_t ss1; 13     uint32_t *esp2; 14     uint32_t ss2; 15     uint32_t cr3; 16     uint32_t (*eip) (void); 17     uint32_t eflags; 18     uint32_t eax; 19     uint32_t ecx; 20     uint32_t edx; 21     uint32_t ebx; 22     uint32_t esp; 23     uint32_t ebp; 24     uint32_t esi; 25     uint32_t edi; 26     uint32_t es; 27     uint32_t cs; 28     uint32_t ss; 29     uint32_t ds; 30     uint32_t fs; 31     uint32_t gs; 32     uint32_t ldt; 33     uint32_t trace; 34     uint32_t io_base; 35 }; 36  37 static struct tss tss; 38  39 /*更新tss中的esp0字段的值为pthread的0级栈*/ 40 void update_tss_esp(struct task_struct *pthread) 41 { 42     tss.esp0 = (uint32_t *)((uint32_t)pthread + PG_SIZE); 43 } 44  45 /*创建gdt描述符*/ 46 static struct gdt_desc make_gdt_desc(uint32_t *desc_addr, uint32_t limit, uint8_t attr_low, uint8_t attr_high) 47 { 48     uint32_t desc_base = (uint32_t)desc_addr; 49     struct gdt_desc desc; 50     desc.limit_low_word = limit & 0x0000ffff; 51     desc.base_low_word = desc_base & 0x0000ffff; 52     desc.base_mid_byte = ((desc_base & 0x00ff0000) >> 16); 53     desc.base_high_byte = (desc_base >> 24); 54     desc.attr_low_byte = (uint8_t)attr_low; 55     desc.limit_high_attr_high = (((limit & 0x000f0000) >> 16) + (uint8_t)attr_high); 56     return desc; 57 } 58  59 /*在gdt中创建tss并重新加载gdt*/ 60 void tss_init(void) 61 { 62     put_str("tss_init start n"); 63     uint32_t tss_size = sizeof(tss); 64     memset(&tss, 0, tss_size); 65     tss.ss0 = SELECTOR_K_STACK; 66     tss.io_base = tss_size; 67     /*gdt的基地址为0x900,把tss放到第4个地址,也就是0x900+0x20的位置*/ 68     *((struct gdt_desc *)0xc0000920) = make_gdt_desc((uint32_t *)&tss, tss_size - 1, TSS_ATTR_LOW, TSS_ATTR_HIGH); 69     /*为用户进程提前作准备*/ 70     /*在gdt中添加dpl为3的数据段和代码段描述符*/ 71     *((struct gdt_desc *)0xc0000928) = make_gdt_desc((uint32_t *)0, 0xfffff, GDT_CODE_ATTR_LOW_DPL3, GDT_ATTR_HIGH); 72     *((struct gdt_desc *)0xc0000930) = make_gdt_desc((uint32_t *)0, 0xfffff, GDT_DATA_ATTR_LOW_DPL3, GDT_ATTR_HIGH); 73     //while(1); 74     /*gdt 16位的limit 32位的段基址*/ 75     uint64_t gdt_operand = ((8 * 7 - 1) | ((uint64_t)(uint32_t)0xc0000900 << 16)); 76     asm volatile ("lgdt %0" : : "m" (gdt_operand)); 77     asm volatile ("ltr %w0" : : "r" (SELECTOR_TSS)); 78  79     put_str("tss_init and ltr donen"); 80 }

tss.c

[自制操作系统] 第18回 实现用户进程(上)

1 #ifndef  __USERPROG_TSS_H 2 #define  __USERPROG_TSS_H 3 #include "stdint.h" 4  5 void tss_init(void); 6 static struct gdt_desc make_gdt_desc(uint32_t *desc_addr, uint32_t limit, uint8_t attr_low, uint8_t attr_high); 7 void update_tss_esp(struct task_struct *pthread); 8 #endif

tss.h

[自制操作系统] 第18回 实现用户进程(上)

 1 ...  2   3 /******************** TSS描述符属性**********************/  4 #define     TSS_DESC_D 0  5 #define  TSS_ATTR_HIGH ((DESC_G_4K << 7) + (TSS_DESC_D << 6) + (DESC_L << 5) + (DESC_AVL << 4) + 0x0)  6 #define  TSS_ATTR_LOW  ((DESC_P << 7) + (DESC_DPL_0 << 5) + (DESC_S_SYS << 4) + DESC_TYPE_TSS)  7   8 #define  SELECTOR_TSS  ((4 << 3) + (TI_GDT << 2) + RPL0)  9  10 ...

global.h

  注释写的比较清楚,我们挑重点来讲。注意这个函数:

1 /*更新tss中的esp0字段的值为pthread的0级栈*/ 2 void update_tss_esp(struct task_struct *pthread) 3 { 4     tss.esp0 = (uint32_t *)((uint32_t)pthread + PG_SIZE); 5 }

  这个函数的作用就是用来更新TSS中的esp0。我们前面在实现线程的时候,线程的PCB上有一块名为中断栈的区域一直没有被使用,现在就被用上了。忘记的话点这里其实它就是这里所说的0级栈,用户进程从3特权级进入0特权级时,CPU会自动从TSS中获取到0特权级的栈指针,也就是0级栈。

  最后还需要修改一下mbr.S和loader.S文件,为什么呢?原来在loader.S文件中,我们在开头通过jmp loader_start跳转到后面执行loader部分, 在这行代码后面实现了GDT表的建立,而jmp loader_start这行代码是需要占据3个字节的内容,这样就导致GDT表位于内存0x903地址处,不利于后面的对齐,所以我们为了让GDT表位于0x900处,需要移除jmp loader_start这行代码,但是我们知道这行代码是mbr跳转执行到的,为了让mbr直接跳转到loader部分,我们需要修改mbr.S中的最后跳转语句,修改为jmp LOADER_BASE_ADDR + 0x206 这个0x206怎么来的呢,GDT表总共有64个描述符,再加上gdt指针占用6个字节,总共便是64*8+6=518个字节,也就是0x206。这里就不多啰嗦了,直接将修改好的mbr.S和loader.S附上。

[自制操作系统] 第18回 实现用户进程(上)

  1 %include "boot.inc"   2 section MBR vstart=0x7c00   3     mov ax, cs   4     mov ds, ax   5     mov es, ax   6     mov ss, ax   7     mov fs, ax   8     mov sp, 0x7c00   9     mov ax, 0xb800  10     mov gs, ax  11       12 ;利用int 0x10 的0x06号功能实现清屏  13     mov ax, 0x600  14     mov bx, 0x700  15     mov cx, 0  16     mov dx, 0x184f  17   18     int 0x10  19       20     mov ah, 3  21     mov bh, 0  22   23     int 0x10  24 ;输出字符串“HELLO MBR” A表示绿色背景闪烁,4表示前景色为红色  25     mov byte [gs:0x00],'H'  26     mov byte [gs:0x01],0xA4  27       28     mov byte [gs:0x02],'E'  29     mov byte [gs:0x03],0xA4  30   31     mov byte [gs:0x04],'L'  32     mov byte [gs:0x05],0xA4  33           34     mov byte [gs:0x06],'L'  35     mov byte [gs:0x07],0xA4  36           37     mov byte [gs:0x08],'O'  38     mov byte [gs:0x09],0xA4  39   40     mov byte [gs:0x0A],' '  41     mov byte [gs:0x0B],0xA4  42   43     mov byte [gs:0x0C],'M'  44     mov byte [gs:0x0D],0xA4  45           46     mov byte [gs:0x0E],'B'  47     mov byte [gs:0x0F],0xA4  48           49     mov byte [gs:0x10],'R'  50     mov byte [gs:0x11],0xA4  51   52     mov eax, LOADER_START_SECTOR ;起始扇区lba的地址  53     mov bx, LOADER_BASE_ADDR     ;loader将要被写入的内存地址  54     mov cx, 4                    ;待读入的扇区数  55     call rd_disk_m_16            ;调用函数,将loader写入到内存中  56       57     jmp LOADER_BASE_ADDR + 0x206  58   59 ;---------------------------------------  60 ;功能:读取硬盘n个扇区  61         rd_disk_m_16:    62                 mov esi, eax               ;备份eax,eax中存放了扇区号,这里为0x2  63                 mov di, cx                 ;备份cx,cx中存放待读入的扇区数  64   65         ;读写硬盘:  66         ;第一步:设置要读取的扇区数  67                 mov dx, 0x1f2  68                 mov al, cl  69                 out dx, al  70                   71                 mov eax, esi  72   73         ;第二步:将lba地址存入到0x1f3 ~ 0x1f6  74                 ;lba地址7-0位写入端口0x1f3  75                 mov dx, 0x1f3  76                 out dx, al  77                   78                 ;lba地址15-8位写入端口0x1f4  79                 mov cl, 8  80                 shr eax, cl  81                 mov dx, 0x1f4  82                 out dx, al  83                   84                 ;lba地址23-16位写入端口0x1f5  85                 shr eax, cl  86                 mov dx, 0x1f5  87                 out dx, al  88                           89                 shr eax, cl  90                 and al, 0x0f  91                 or al, 0xe0  92                 mov dx, 0x1f6  93                 out dx, al  94   95         ;第三步:向0x1f7端口写入读命令,0x20  96                 mov dx, 0x1f7  97                 mov al, 0x20  98                 out dx, al  99  100         ;第四步:检测硬盘状态 101         .not_ready: 102                 nop 103                 in al, dx 104                 and al, 0x88 105                 cmp al, 0x08 106                 jnz .not_ready 107  108         ;第五步:从0x1f0端口读数据 109                 mov ax, di 110                 mov dx, 256 111                 mul dx 112                 mov cx, ax 113         ;di为要读取的扇区数,一个扇区共有512字节,每次读入一个字,总共需要 114         ;di*512/2次,所以di*256 115                 mov dx, 0x1f0 116         .go_on_read: 117                 in ax, dx 118                 mov [bx], ax 119                 add bx,2 120                 loop .go_on_read 121                 ret 122 ;--------------------------------------- 123  124     times 510-($-$$) db 0 125     db 0x55, 0xaa

mbr.S

[自制操作系统] 第18回 实现用户进程(上)

  1 %include "boot.inc"   2 section loader vstart=LOADER_BASE_ADDR   3 LOADER_STACK_TOP equ LOADER_BASE_ADDR   4 ;构建gdt及其内部描述符   5 GDT_BASE:        dd 0x00000000   6                  dd 0x00000000   7 CODE_DESC:       dd 0x0000FFFF   8                  dd DESC_CODE_HIGH4   9 DATA_STACK_DESC: dd 0x0000FFFF  10                  dd DESC_DATA_HIGH4  11 VIDEO_DESC:      dd 0x80000007  12                  dd DESC_VIDEO_HIGH4  13   14 GDT_SIZE  equ $-GDT_BASE  15 GDT_LIMIT equ GDT_SIZE-1  16 times 60 dq 0  ;此处预留60个描述符的空位  17   18 SELECTOR_CODE  equ (0x0001<<3) + TI_GDT + RPL0  19 SELECTOR_DATA  equ (0x0002<<3) + TI_GDT + RPL0  20 SELECTOR_VIDEO equ (0x0003<<3) + TI_GDT + RPL0  21   22 ;以下是gdt指针,前2个字节是gdt界限,后4个字节是gdt的起始地址  23 gdt_ptr   dw GDT_LIMIT   24           dd GDT_BASE  25   26 ;---------------------进入保护模式------------  27 loader_start:  28     ;一、打开A20地址线  29     in al, 0x92  30     or al, 0000_0010B  31     out 0x92, al  32       33     ;二、加载GDT  34     lgdt [gdt_ptr]  35   36     ;三、cr0第0位(pe)置1  37     mov eax, cr0  38     or eax, 0x00000001  39     mov cr0, eax  40       41     jmp dword SELECTOR_CODE:p_mode_start ;刷新流水线  42   43     [bits 32]  44     p_mode_start:  45             mov ax, SELECTOR_DATA  46             mov ds, ax  47             mov es, ax  48             mov ss, ax  49             mov esp, LOADER_STACK_TOP  50             mov ax, SELECTOR_VIDEO  51             mov gs, ax  52               53             mov byte [gs:160], 'p'  54 ;---------------------------------------             55   56 ;------------------开启分页机制-----------------  57     ;一、创建页目录表并初始化页内存位图  58     call setup_page  59   60     ;将描述符表地址及偏移量写入内存gdt_ptr,一会儿用新地址重新加载  61     sgdt [gdt_ptr]  62     ;将gdt描述符中视频段描述符中的段基址+0xc0000000  63     mov ebx, [gdt_ptr + 2]  64     or dword [ebx + 0x18 + 4], 0xc0000000  65               66     ;将gdt的基址加上0xc0000000使其成为内核所在的高地址  67     add dword [gdt_ptr + 2], 0xc0000000  68   69     add esp, 0xc0000000  ;将栈指针同样映射到内核地址  70               71     ;二、将页目录表地址赋值给cr3  72     mov eax, PAGE_DIR_TABLE_POS  73     mov cr3, eax  74               75     ;三、打开cr0的pg位  76     mov eax, cr0  77     or eax, 0x80000000  78     mov cr0, eax  79               80     ;在开启分页后,用gdt新的地址重新加载  81     lgdt [gdt_ptr]  82     mov byte [gs:160], 'H'  83     mov byte [gs:162], 'E'  84     mov byte [gs:164], 'L'  85     mov byte [gs:166], 'L'  86     mov byte [gs:168], 'O'  87     mov byte [gs:170], ' '  88     mov byte [gs:172], 'P'  89     mov byte [gs:174], 'A'  90     mov byte [gs:176], 'G'  91     mov byte [gs:178], 'E'  92   93 ;---------------------------------------------  94   95 ;--------------------拷贝内核文件并进入kernel--------------------------  96     mov eax, KERNEL_START_SECTOR              ;kernel.bin所在的扇区号 0x09  97     mov ebx, KERNEL_BIN_BASE_ADDR             ;从磁盘读出后,写入到ebx指定的地址0x70000  98     mov ecx, 200                              ;读入的扇区数  99  100     call rd_disk_m_32 101  102     ;由于一直处在32位下,原则上不需要强制刷新,但是以防万一还是加上 103     ;跳转到kernel处 104     jmp SELECTOR_CODE:enter_kernel 105      106     enter_kernel: 107         call kernel_init 108         mov esp, 0xc009f000               ;更新栈底指针 109         jmp KERNEL_ENTRY_POINT            ;内核地址0xc0001500 110         ;jmp $ 111         ;---------------------将kernel.bin中的segment拷贝到指定的地址 112         kernel_init: 113             xor eax, eax 114             xor ebx, ebx   ;ebx记录程序头表地址 115             xor ecx, ecx    ;cx记录程序头表中的program header数量 116             xor edx, edx    ;dx记录program header 尺寸,即e_phentsize 117  118             ;偏移文件42字节处的属性是e_phentsize, 表示program header大小 119             mov dx, [KERNEL_BIN_BASE_ADDR + 42] 120              121             ;偏移文件28字节处的属性是e_phoff 122             mov ebx, [KERNEL_BIN_BASE_ADDR + 28] 123  124             add ebx, KERNEL_BIN_BASE_ADDR 125             mov cx, [KERNEL_BIN_BASE_ADDR + 44] 126      127             .each_segment:  128                     cmp byte [ebx + 0], PT_NULL 129                     je .PTNULL 130  131             ;为函数memcpy压入参数,参数是从右往左压入 132             push dword [ebx + 16] 133             mov eax, [ebx + 4] 134             add eax, KERNEL_BIN_BASE_ADDR 135             push eax 136             push dword [ebx + 8] 137             call mem_cpy 138             add esp, 12 139  140             .PTNULL: 141                     add ebx, edx 142                     loop .each_segment 143             ret 144  145             ;-----------逐字节拷贝mem_cpy(dst, src, size) 146             mem_cpy: 147                     cld 148                     push ebp 149                     mov ebp, esp 150                     push ecx 151                     mov edi, [ebp + 8] 152                     mov esi, [ebp + 12] 153                     mov ecx, [ebp + 16] 154                     rep movsb 155  156                     pop ecx 157                     pop ebp 158                     ret  159 ;---------------------------------------------------      160  161  162  163  164 ;--------------函数声明------------------------ 165     ;setup_page:(功能)设置分页------------ 166     setup_page: 167         ;先把页目录占用的空间逐字节清0 168         mov ecx, 4096 169         mov esi, 0 170         .clear_page_dir: 171                 mov byte [PAGE_DIR_TABLE_POS + esi], 0 172                 inc esi 173         loop .clear_page_dir 174          175         ;开始创建页目录项 176         .create_pde: 177                 mov eax, PAGE_DIR_TABLE_POS 178                 add eax, 0x1000             ;此时eax为第一个页表的位置 179                 mov ebx, eax 180          181         ;下面将页目录项0和0xc00都存为第一个页表的地址,每个页表表示4MB内存 182         ;页目录表的属性RW和P位为1,US为1,表示用户属性,所有特权级别都可以访问 183         or eax, PG_US_U | PG_RW_W | PG_P 184          185         ;在页目录表中的第1个目录项中写入第一个页表的地址(0x101000)和属性 186         mov [PAGE_DIR_TABLE_POS + 0x0], eax 187  188         mov [PAGE_DIR_TABLE_POS + 0xc00], eax 189  190         ;使最后一个目录项指向页目录表自己的地址 191         sub eax, 0x1000 192         mov [PAGE_DIR_TABLE_POS + 4092], eax 193  194         ;下面创建页表项(PTE) 195         mov ecx, 256     ;1M低端内存/每页大小4K=256 196         mov esi, 0 197         mov edx, PG_US_U | PG_RW_W | PG_P 198         .create_pte:     ;创建page table entry 199                 mov [ebx + esi*4], edx 200                 add edx, 4096 201                 inc esi 202         loop .create_pte 203          204         ;创建内核其他页表的PDE 205         mov eax, PAGE_DIR_TABLE_POS 206         add eax, 0x2000           ;此时eax为第二个页表的位置 207         or eax, PG_US_U | PG_RW_W | PG_P 208         mov ebx, PAGE_DIR_TABLE_POS 209         mov ecx, 254              ;范围为第769~1022的所有目录项数量 210         mov esi, 769  211         .create_kernel_pde: 212                 mov [ebx + esi*4], eax 213                 inc esi 214                 add eax, 0x1000 215         loop .create_kernel_pde 216         ret 217  218  219     ;rd_disk_m_32:(功能)读取硬盘n个扇区------------ 220     rd_disk_m_32: 221         mov esi,eax               ;备份eax,eax中存放了扇区号 222         mov di,cx                 ;备份cx,cx中存放待读入的扇区数 223  224         ;读写硬盘: 225         ;第一步:设置要读取的扇区数 226         mov dx,0x1f2 227         mov al,cl 228         out dx,al 229          230         mov eax,esi 231  232         ;第二步:将lba地址存入到0x1f3 ~ 0x1f6 233                 ;lba地址7-0位写入端口0x1f3 234         mov dx,0x1f3 235         out dx,al 236          237         ;lba地址15-8位写入端口0x1f4 238         mov cl,8 239         shr eax,cl 240         mov dx,0x1f4 241         out dx,al 242          243         ;lba地址23-16位写入端口0x1f5 244         shr eax,cl 245         mov dx,0x1f5 246         out dx,al 247                  248         shr eax,cl 249         and al,0x0f 250         or al,0xe0 251         mov dx,0x1f6 252         out dx,al 253  254     ;第三步:向0x1f7端口写入读命令,0x20 255         mov dx,0x1f7 256         mov al,0x20 257         out dx,al 258  259     ;第四步:检测硬盘状态 260         .not_ready: 261                 nop 262                 in al,dx 263                 and al,0x88 264                 cmp al,0x08 265                 jnz .not_ready 266  267     ;第五步:从0x1f0端口读数据 268         mov ax,di 269         mov dx,256 270         mul dx 271         mov cx,ax 272     ;di为要读取的扇区数,一个扇区共有512字节,每次读入一个字,总共需要 273     ;di*512/2次,所以di*256 274         mov dx,0x1f0 275         .go_on_read: 276                 in ax,dx 277                 mov [ebx],ax 278                 add ebx,2 279                 loop .go_on_read 280                 ret 281 ;----------------------------------------------

loader.S

四、运行测试

  运行测试后,tss成功初始化。

  [自制操作系统] 第18回 实现用户进程(上)
  在bochs控制台输入info gdt可以看到GDT表的内容,可以看到现在有7个描述符,在GDT中第4个描述符是刚安装好的TSS段描述符,其显示为32-Bit TSS(Busy),说明TSS的B位被CPU置1了,TSS已经生效了。
  [自制操作系统] 第18回 实现用户进程(上)
  本回到此结束,预知后事如何,请看下回分解。

发表评论

相关文章