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


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

     

    一、前景回顾

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

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

    二、任务切换相关

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

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

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

      然后现在问题出现了:

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

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

      首先是LDT。

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

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

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

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

      总结一下,如图所示:
                       
      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文件中新加部分代码。

     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 done\n");
    80 }
    tss.c
    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
     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附上。

      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
      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成功初始化。

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

  • 相关阅读:
    酒糟废水处理设备有哪些
    python的Django项目中常见命令以及常错点(Linux环境下)
    【C#】WPF获取屏幕分辨率
    智能合约安全分析,针对 ERC777 任意调用合约 Hook 攻击
    【青书学堂】 2023年第二学期 JavaScript 基础编程(高起专) 作业
    JDK线程池的总结
    30天学习之-自动化测试
    黑客新工具,可窃取Gmail、雅虎、Outlook等电子邮件
    springboot+高校教室排课系统 毕业设计-附源码221556
    【每日一题】统计范围内的元音字符串数
  • 原文地址:https://www.cnblogs.com/Lizhixing/p/15978058.html