使用x86汇编编写一个多线程、多任务系统 (六)

ap_32.asm是两个AP在32位保护模式下执行的主要代码,其执行顺序如下:

序号

功能

1

使能中断功能:

  1. 竞争获取信号量
  2. 使能AP的IO APIC功能
  3. 设置IO APIC的ID(不同AP需要不同的ID号)
  4. 屏蔽 LINT0
  5. 打开SVR寄存器的APIC enable位(第8位),使能APIC功能。这里要注意:该位在BSP中是自动使能的,而在AP中是默认关闭的
  6. 安装0x31号和0x32号的中断处理函数,其中0x31号为AP1使用,0x32号由AP2使用。这里要注意的是:BSP每隔100ms向AP1和AP2发送IPI消息,而两个AP在接收到该IPI消息后,会在中断处理函数中做系统进程和用户进程之间的切换

2

获取AP的ID号,并按照不同的AP开始分支执行。

3

AP1或者AP2的分支都是先设置系统TSS。这里要注意:因为设置TSS会操作GDT,所以Load_Sys_Task函数是互斥执行的

4

AP1或者AP2的分支再安装用户进程的TSS,并记录该TSS在GDT中的偏移。这里需要注意的是:

  1. 设置用户进程的TSS会操作GDT,所以Load_Usr_Task函数也是互斥执行的
  2. AP1对应的用户进程的代码被放在磁盘的第100号扇区;AP2对应的用户进程的代码被放在磁盘的第200号扇区

5

AP1和AP2在各自的循环中,不断获取RTC时钟当前的秒值,并在界面上显示

对了,用户进程使用的内存页面是事先确定好的,这样做虽然灵活性差,但是方便快捷,用于做示例是完全够了。

;--------------------合成一个 PCI 地址放在 eax 寄存器--------------------------
%macro MAKE_PCI_ADDRESS 4
	mov eax, DWORD (0x80000000|(%1<<16)|(%2<<11)|(%3<<8)|(%4&0xfc))
%endmacro

    CONFIG_ADDRESS    equ 0xcf8
    CONFIG_DATA       equ 0xcfc

;-----------------------从PCI上读取一个双字的宏定义----------------------------
%macro READ_PCI_DWORD 4
	mov dx, CONFIG_ADDRESS
	MAKE_PCI_ADDRESS %1, %2, %3, %4
	out dx, eax
	mov dx, CONFIG_DATA
	in eax, dx
%endmacro

;-----------------------------常数定义-----------------------------------
    gdt_base_address       equ 0x80008000   ;常数,gdt表的起始地址
    idt_base_address       equ 0x80009000   ;常数,idt表的起始地址
    flat_mode_code         equ 0x0008       ;平坦模式的代码段选择子 
    flat_mode_data         equ 0x0010       ;平坦模式的数据段选择子

;-----------------------为用户线程1定义的常数----------------------------
    app1_page_dir_addr     equ 0x0002A000   ;常数,APP1使用的页目录硬地址 
    app1_page_tb1_addr     equ 0x0002B000   ;常数,APP1使用的页表1硬地址 
    app1_usr_code_addr     equ 0x0002C000   ;常数,APP1使用的用户代码页硬地址 
    app1_usr_stack_addr    equ 0x0002D000   ;常数,APP1使用的用户堆栈页硬地址 
    app1_sys_dir_addr      equ 0x0002E000   ;常数,APP1使用的系统堆栈页硬地址
    app1_usr_disc_start    equ 0x00000064   ;常数,APP1应用程序开始于硬盘扇区100
    app1_sys_stack_addr    equ 0x80034000   ;常数,APP1使用的系统堆栈的虚拟地址

;-----------------------为用户线程2定义的常数----------------------------
    app2_page_dir_addr     equ 0x0002F000   ;常数,APP2使用的页目录硬地址 
    app2_page_tb1_addr     equ 0x00030000   ;常数,APP2使用的页表1硬地址 
    app2_usr_code_addr     equ 0x00031000   ;常数,APP2使用的用户代码页硬地址 
    app2_usr_stack_addr    equ 0x00032000   ;常数,APP2使用的用户堆栈页硬地址 
    app2_sys_dir_addr      equ 0x00033000   ;常数,APP2使用的系统堆栈页硬地址 
    app2_usr_disc_start    equ 0x000000C8   ;常数,APP2应用程序开始于硬盘扇区200
    app2_sys_stack_addr    equ 0x80035000   ;常数,APP1使用的系统堆栈的虚拟地址

;-----------------------系统调用接口定义的常数----------------------------
    hpet_global_tick       equ 0x80036008   ; 高精度时钟timer0保存ticks的地址
    ap_lock_addr           equ 0x00006000   ; AP启动信号量的地址

;-----------------------创建用户进程的结构定义----------------------------
struc usr_task_confg  
    .page_dir:         resd  1              ; 页目录对应页面的硬件地址
    .page_table_0:     resd  1              ; 第0号页表对应页面的硬件地址
    .usr_code_page:    resd  1              ; 用户进程code页对应页面的硬件地址
    .usr_stack_page:   resd  1              ; 用户进程堆栈页对应页面的硬件地址
    .sys_stack_0:      resd  1              ; 用户进程中断时系统堆栈页对应的硬件地址
    .ldt_sel:          resw  1              ; ldt在gdt中的选择子(GDT中的偏移)
    .tss_sel:          resw  1              ; 进程在gdt中的选择子(GDT中的偏移)
    .disc_start:       resd  1              ; 进程代码的起始扇区号
    .ldt_addr:         resd  1              ; 进程对应LDT段地址
    .tss_addr:         resd  1              ; 进程对应TSS段地址
endstruc

;-----------------------创建系统进程的结构定义----------------------------
struc sys_task_confg  
    .tss_sel:          resw  1              ; 进程在gdt中的选择子(GDT中的偏移)
    .word_resrv:       resw  1              ; 保留位,用于4字节对齐
    .tss_addr:         resd  1              ; 进程对应TSS段地址
endstruc

;----------------------------LDT段设置参数-------------------------------
    ; 常数,ldt表中代码段的高双字 base-0x0;TYPY-8 只执行;P-1;DPL-3;DT-1;G-1;D-1;limit-0xf;base-0;
    ldt_code_high_dd         equ 0x00cff800      	 
    ; 常数,ldt表中代码段的低双字 limit-0xffff;base-0x0;
    ldt_code_low_dd          equ 0x0000ffff      
    ; 常数,ldt表中数据段的高双字 base-0x0;TYPY-2 读/写;P-1;DPL-3;DT-1;G-1;D-1;limit-0xf;base-0;
    ldt_data_high_dd         equ 0x00cff200      
    ; 常数,ldt表中数据段的低双字 limit-0xffff;base-0x0;     
    ldt_data_low_dd          equ 0x0000ffff


;------------------------------AP代码开始------------------------------------
SECTION  ap_32  vstart=0x80200000
    [bits 32]

    ; 安装各类中断
    call init_interrupt_function                    ; 调用中断初始化函数

    sti                                             ; 开启中断功能   

    ; 给BSP发送启动成功的消息
    mov DWORD [0xFEE00310], 0x00000000              ; APIC ID = 00h, Fixed交付模式,vector为0x30
    mov DWORD [0xFEE00300], 0x00004030

    call Get_APIC_ID
    cmp al, 0x01
    jnz _handle_ap_2

    ; 初始化AP1的系统进程和用户进程配置参数
    call init_ap1_configs

    ; 创建AP1的系统进程,并写入TR寄存器
    mov eax, _SYS_Task_Confg_1
    call Load_Sys_Task    
    
    ; 创建AP1的用户进程
    mov eax, _Usr_Task_Confg_1
    call Load_Usr_Task 
    
    ; 打印AP1启动的信息
    mov eax, ap1_up_str
    mov ebx, 320
    call disp_string
    
    ; 打印Usr1显示的信息
    mov eax, usr1_up_str
    mov ebx, 360
    call disp_string

    jmp _ap_sleep

_handle_ap_2:                                       ; 以下是AP2的运行代码
    ; 初始化AP2的系统进程和用户进程配置参数
    call init_ap2_configs    
    
    ; 创建AP2的系统进程,并写入TR寄存器
    mov eax, _SYS_Task_Confg_2
    call Load_Sys_Task

    ; 创建AP2的用户进程
    mov eax, _Usr_Task_Confg_2
    call Load_Usr_Task 

    ; 打印AP2启动的信息
    mov eax, ap2_up_str
    mov ebx, 480
    call disp_string

    ; 打印Usr2显示的信息
    mov eax, usr2_up_str
    mov ebx, 520
    call disp_string

_ap_sleep:
    call Get_APIC_ID
    cmp al, 0x01
    jnz _do_ap2_work
_do_ap1_work:
    nop
    mov al, 0x80
    cli                                             ; 关中断
    out 0x70, al
    in al, 0x71
    sti                                             ; 开中断
    and eax, 0x0ff
    mov ebx, 342
    call disp_word
    nop

    jmp _ap_sleep

_do_ap2_work:
    nop
    mov al, 0x80
    cli                                             ; 关中断
    out 0x70, al
    in al, 0x71
    sti                                             ; 开中断
    and eax, 0x0ff
    mov ebx, 502
    call disp_word
    nop

    jmp _ap_sleep

;-----------------------------打印单字的函数-----------------------------------
disp_word:                                          ; 打印单字的接口
                                                    ; 输入:eax -- 打印的数值放在eax中
                                                    ;       ebx -- 打印位置(就是对于0x800b8000地址的偏移)
                                                    ; 输出:无
    pusha

    mov ebp, eax                                    ; 保存被打印数值
    mov esi, 0x800b8000
    add esi, ebx                                    ; 计算打印位置
    mov ecx, 16

_disp_next:    
    mov eax, ebp
    mov edx, disp_chars
    sub ecx, 4
    shr eax, cl                                     ; 位移指令的第二个参数只能是CL或者具体的数字
    and eax, 0x0f                                   ; 取最右边的4位
    add edx, eax
    mov al, [edx]
    mov [esi], al
    inc esi
    mov byte [esi], 0x0e                            ; 显示亮黄色字体
    inc esi
    cmp cl, 0
    ja _disp_next

    popa

    ret

;-----------------------------打印字符串的函数---------------------------------
disp_string:                                        ; 打印字符串的函数
                                                    ; 输入:eax -- 打印字符串的地址
                                                    ;       ebx -- 打印位置(就是对于0x800b8000地址的偏移)
                                                    ; 输出:无
    pusha

    mov esi, 0x800b8000
    add esi, ebx                                    ; 计算打印位置
    
_disp_string_loop:
    mov cl, [eax]                                   ; 取字符串中的字符
    cmp cl, 0                                       ; 如果字符为0,结束打印
    jz _disp_string_finished
    mov [esi], cl                                   ; 打印取出的字符
    inc esi
    mov byte [esi], 0x71                            ; 蓝色字体
    inc esi
    inc eax
    jmp _disp_string_loop

_disp_string_finished:
    popa
    ret

;------------------------在GDT中安装用户进程LDT段------------------------------
Set_LDT_sel:                                        ; 安装LDT段
                                                    ; 输入参数: eax-—LDT段基地址
                                                    ;           ecx—-cx为在gdt中的选择子
                                                    ; 输出参数:无
	
    push edx                                        ; 保存edx  

    sgdt [pgdt]                                     ; 获取gdt表长度和基址
    mov edx, dword [pgdt+2]                         ; 段基址
    add edx, ecx                                    ; 安装地址        
    mov word [edx], 15                              ; LDT段长度的低16位
    mov word [edx+2], ax                            ; LDT段基址的低16位
    shr eax, 16                                     ; LDT段基址右移16位
    mov byte [edx+4], al                            ; LDT段基址的中8位
    mov byte [edx+7], ah                            ; LDT段基址的高8位
    mov byte [edx+5], 1_11_0_0010B                  ; P-1,DPL=3,TYPE-2(LDT段)
    mov byte [edx+6], 0_1_0_0_0000B                 ; G-0,X-1,limit-0
    mov word [pgdt], 255                            ; gdt表长度定死为255字节
    lgdt [pgdt]                                     ; 重新安装gdt表到系统
    pop edx                                         ; 弹出edx

    ret

;---------------------------在GDT中安装TSS段-----------------------------------
Set_TSS_sel:                                        ; 安装TSS段
                                                    ; 输入参数: eax-—TSS段基地址
                                                    ;           ecx—-cx为在gdt中的选择子
                                                    ; 输出参数: 无
	
    push edx                                        ; 保存edx

    sgdt [pgdt]                                     ; 获取gdt表长度和基址
    mov edx, dword [pgdt+2]                         ; 段基址
    add edx, ecx                                    ; 安装地址        
    mov word [edx], 103                             ; TSS段长度的低16位
    mov word [edx+2], ax                            ; TSS段基址的低16位
    shr eax, 16                                     ; TSS段基址右移16位
    mov byte [edx+4], al                            ; TSS段基址的中8位
    mov byte [edx+7], ah                            ; TSS段基址的高8位
    mov byte [edx+5], 1_00_0_1001B                  ; P-1,TYPE-9(可用386TSS段)
    mov byte [edx+6], 0_1_0_0_0000B                 ; G-0,X-1,limit-0
    mov word [pgdt], 255                            ; gdt表长度定死为255字节
    lgdt [pgdt]                                     ; 重新安装gdt表到系统

    pop edx                                         ; 弹出edx

    ret

;----------------------------获取本AP对应的APIC ID-----------------------------
Get_APIC_ID:                                        ; 获取本线程对应的APIC ID
                                                    ; 输入参数:无
                                                    ; 输出参数:eax -- al返回本线程的APIC ID
    push ebx
    mov eax, 1
    cpuid
    mov eax, ebx
    shr eax, 24
    pop ebx
    
    ret

;------------------------------创建系统进程------------------------------------
Load_Sys_Task:                                      ; 创建系统进程(权限0)
                                                    ; 输入:eax -- 系统进程配置参数结构地址
                                                    ; 返回:NULL	 
    pushad
    
    ; 检测信号量,避免多AP搞乱GDT
_lock_for_ap2:
    lock bts dword [ap_lock_addr], 0                ; 将信号量第0位放入CF,并设置信号量第0位为1
    jc _lock_for_ap2 

    mov ebp, eax
	 
    ;内核任务的TSS段填写,清空缓冲区,使段内所有保留位置0 
    mov eax, [ebp + sys_task_confg.tss_addr]
    mov ecx, 104
_init_tss:
    mov byte [eax], 0x0
    inc eax
    loop _init_tss
	 
    mov eax, [ebp + sys_task_confg.tss_addr]
    mov ecx, cr3
    mov dword [eax + 0x1c], ecx                     ; 此处的CR3的写入必须要有,因为Set_TSS_sel不会主动加入页目录!!!
	 
    ;在gdt中写入tss段描述符
    mov eax, [ebp + sys_task_confg.tss_addr]
    xor ecx, ecx
    mov cx, [ebp + sys_task_confg.tss_sel]
    call Set_TSS_sel                                ; 安装TSS,段首地址在eax中
    ltr cx                                          ; 将内核任务写入系统 

    ; 信号量复位
    mov dword [ap_lock_addr], 0 

    popad
    ret

;----------------------------安装用户进程的接口--------------------------------
Load_Usr_Task:                                      ; 安装用户进程的接口
                                                    ; 输入:eax -- 用户进程配置参数结构地址
                                                    ; 返回:NULL

    push ebp
    mov ebp, eax

    ; 检测信号量,避免多AP搞乱GDT
_lock_for_ap3:
    lock bts dword [ap_lock_addr], 0                ; 将信号量第0位放入CF,并设置信号量第0位为1
    jc _lock_for_ap3
    
    ; 拷贝系统页目录到用户进程的页目录
    mov ecx, 1024
    mov ebx, [ebp + usr_task_confg.page_dir]
    xor edx, edx
_ap_dir_cpy:
    mov eax, [0xfffff000 + edx]
    mov dword [ebx + edx], eax
    add edx, 4
    loop _ap_dir_cpy
    
    ; 用户页目录第一项指向页表
    mov edx, [ebp + usr_task_confg.page_dir]
    mov eax, [ebp + usr_task_confg.page_table_0]
    or eax, 0x07
    mov dword [edx], eax
    ; 用户页表1第一项指向用户app页(对应线性地址:0x0-0x1000)
    mov edx, [ebp + usr_task_confg.page_table_0]
    mov eax, [ebp + usr_task_confg.usr_code_page]
    or eax, 0x07
    mov dword [edx], eax
    ; 用户页表1第二项指向权限3堆栈(对应线性地址:0x1000-0x2000)
    mov edx, [ebp + usr_task_confg.page_table_0]
    mov eax, [ebp + usr_task_confg.usr_stack_page]
    or eax, 0x07
    mov dword [edx+4], eax    
    ; 将用户进程数据从逻辑扇区拷贝到到用户app页中
    mov eax, [ebp + usr_task_confg.disc_start]      ; AP用户代码的逻辑扇区号 
    mov ebx, [ebp + usr_task_confg.usr_code_page]   ; 将用户代码拷贝进用户代码页
    mov ecx, 2                                      ; 拷贝2个扇区,1K
_ap_cpy_disk:
    call read_hard_disk_0
    inc eax
    loop _ap_cpy_disk  
    
    ; 初始化用户进程的LDT段
    mov eax, [ebp + usr_task_confg.ldt_addr]
    mov dword [eax], ldt_code_low_dd
    mov dword [eax + 4], ldt_code_high_dd
    mov dword [eax + 8], ldt_data_low_dd
    mov dword [eax + 0x0c], ldt_data_high_dd    

    ;将用户ldt段加入gdt
    xor ecx, ecx
    mov cx, [ebp + usr_task_confg.ldt_sel]          ; 记录AP用户进程的LDT段选择子
    call Set_LDT_sel

    ;初始化用户进程TSS页,段内所有保留位都清空
    mov eax, [ebp + usr_task_confg.tss_addr]
    push eax
    mov ecx, 26
    xor edx, edx
_ap_init_usr_tss:
    mov dword [eax + edx], 0x0
    add edx, 4
    loop _ap_init_usr_tss
	
    ;逐项填写TSS段
    mov edx, [ebp + usr_task_confg.sys_stack_0]     ; 设置用户进程使用的R0权限下的堆栈页
    add edx, 4092                                   ; esp0指针指向页的最后
    pop eax                                         ; 恢复指向tss段的首地址
    mov dword [eax+0x4], edx                        ; esp0
    mov word [eax+0x8], flat_mode_data              ; ss0
    mov ebx, [ebp + usr_task_confg.page_dir]        ; cr3,页目录硬地址
    mov dword [eax+0x1c], ebx
    mov dword [eax+0x20], 12                        ; eip,这里需要按照进程结构写入执行代码在code中的偏移量
    pushfd
    pop ebx
    mov dword [eax+0x24], ebx                       ; eflag
    mov dword [eax+0x38], 0x2000-4                  ; esp
    mov dword [eax+0x4c], 0x7                       ; cs(list-0,TI-1,RPL-3)
    mov dword [eax+0x50], 0x0f                      ; ss(list-1,TI-1,RPL-3)
    mov dword [eax+0x54], 0x0f                      ; ds(list-1,TI-1,RPL-3)
    mov cx, [ebp + usr_task_confg.ldt_sel]
    mov word [eax+0x60], cx                         ; ldt选择子
    mov word [eax+0x66], 103                        ; TSS段长度

    ;将用户tss写入gdt
    mov eax, [ebp + usr_task_confg.tss_addr]
    xor ecx, ecx
    mov cx, [ebp + usr_task_confg.tss_sel]
    call Set_TSS_sel
  
    ; 信号量复位
    mov dword [ap_lock_addr], 0 

    pop ebp

    ret

;-------------------初始化AP1的系统进程和用户进程的配置参数--------------------
init_ap1_configs:                                   ; 初始化AP1系统进程和用户进程的配置参数
                                                    ; 输入:无
                                                    ; 返回:NULL
    pusha

    ; 初始化系统进程配置参数
    mov eax, _SYS_Task_Confg_1
    mov dword [eax + sys_task_confg.tss_addr], sys1_tss
    mov dword [eax + sys_task_confg.tss_sel], 96

    ; 初始化用户进程配置参数
    mov eax, _Usr_Task_Confg_1
    mov dword [eax + usr_task_confg.page_dir], app1_page_dir_addr
    mov dword [eax + usr_task_confg.page_table_0], app1_page_tb1_addr
    mov dword [eax + usr_task_confg.usr_code_page], app1_usr_code_addr    
    mov dword [eax + usr_task_confg.usr_stack_page], app1_usr_stack_addr  
    mov dword [eax + usr_task_confg.sys_stack_0], app1_sys_stack_addr
    mov dword [eax + usr_task_confg.ldt_sel], 64
    mov dword [eax + usr_task_confg.tss_sel], 72
    mov dword [eax + usr_task_confg.disc_start], app1_usr_disc_start
    mov dword [eax + usr_task_confg.ldt_addr], app1_ldt
    mov dword [eax + usr_task_confg.tss_addr], app1_tss

    popa
    ret

;-------------------初始化AP2的系统进程和用户进程的配置参数--------------------
init_ap2_configs:                                   ; 初始化AP2系统进程和用户进程的配置参数
                                                    ; 输入:无
                                                    ; 返回:NULL
    pusha

    ; 初始化系统进程配置参数
    mov eax, _SYS_Task_Confg_2
    mov dword [eax + sys_task_confg.tss_addr], sys2_tss
    mov dword [eax + sys_task_confg.tss_sel], 104

    ; 初始化用户进程配置参数
    mov eax, _Usr_Task_Confg_2
    mov dword [eax + usr_task_confg.page_dir], app2_page_dir_addr
    mov dword [eax + usr_task_confg.page_table_0], app2_page_tb1_addr
    mov dword [eax + usr_task_confg.usr_code_page], app2_usr_code_addr    
    mov dword [eax + usr_task_confg.usr_stack_page], app2_usr_stack_addr  
    mov dword [eax + usr_task_confg.sys_stack_0], app2_sys_stack_addr
    mov dword [eax + usr_task_confg.ldt_sel], 80
    mov dword [eax + usr_task_confg.tss_sel], 88
    mov dword [eax + usr_task_confg.disc_start], app2_usr_disc_start
    mov dword [eax + usr_task_confg.ldt_addr], app2_ldt
    mov dword [eax + usr_task_confg.tss_addr], app2_tss

    popa
    ret


;---------------------------初始化并启动系统中断-------------------------------
init_interrupt_function:                            ; 初始化系统中断
                                                    ; 输入:NULL
                                                    ; 返回:NULL

    ; 检测信号量,避免多AP搞乱GDT
_lock_for_ap1:
    lock bts dword [ap_lock_addr], 0                ; 将信号量第0位放入CF,并设置信号量第0位为1
    jc _lock_for_ap1 

    ; 初始化IO APIC寄存器,使能IO APIC功能
    READ_PCI_DWORD 0, 31, 0, 0x0f0
    and eax, 0xFFFFC000                             ; 读取RCBA寄存器的地址
    mov esi, [eax + 0x31fe]                         ; RCBA寄存器地址偏移0x31fe为OIC寄存器地址
    bts esi, 8                                      ; IO APIC使能
    and esi, 0xFFFFFF00                             ; IO AIC寄存器的基地址设置为0
    mov [eax + 0x31fe], esi                         ; 写入OIC寄存器

    ; 设置IO APIC的ID。这个ID每个线程都必须不同,不然系统不知道把中断发送给哪个线程
    mov dword [0xFEC00000], 0x0
    mov dword [0xFEC00010], 0x0E000000              ; ID=1的AP,IO APIC的ID设置为0x0E
    call Get_APIC_ID
    cmp al, 0x01
    jz _IO_APIC_ID_END
    mov dword [0xFEC00000], 0x0
    mov dword [0xFEC00010], 0x0C000000              ; ID=2的AP,IO APIC的ID设置为0x0C
    
_IO_APIC_ID_END:
    ; 屏蔽 LINT0
    bts DWORD [0xFEE00000 + 0x350], 16

    ; 打开SVR寄存器的APIC enable位(第8位)。该位在BSP中主动使能,AP中默认关闭
    mov eax, 0x10F
    mov [0xFEE000F0], eax

    ; 设置中断向量表并使能中断
    mov word [pidt],256*8-1                         ; IDT的界限
    mov dword [pidt+2], idt_base_address            ; IDT表的地址设为0x80009000
    lidt [pidt] 

    mov eax, do_int31h_handler                      ; 安装0x31号中断向量
    mov ebx, 0x31
    call install_interrupt_handle

    mov eax, do_int32h_handler                      ; 安装0x32号中断向量
    mov ebx, 0x32
    call install_interrupt_handle

    ; 信号量复位
    mov dword [ap_lock_addr], 0 

    ret

;------------------------线程1消息接收中断处理接口----------------------------
do_int31h_handler:	                            ; AP1的消息接收处理函数
                                                    ; 输入:无
                                                    ; 返回:NULL

    mov dword [0xFEE000B0], 0

    inc dword [ap1_switch]
    mov eax, [ap1_switch]
    and eax, 0x1
    cmp eax, 0x1
    jnz _ap1_do_sys
    ; 跳转到用户进程1
    jmp far [app1_task_rsvr]
    jmp _out1
_ap1_do_sys:    
    ; 跳转到系统进程1
    jmp far [sys1_task_rsvr]

_out1:
    iret

;------------------------线程2消息接收中断处理接口----------------------------
do_int32h_handler:                                  ; AP2的消息接收处理函数
                                                    ; 输入:无
                                                    ; 返回:NULL	
    
    mov dword [0xFEE000B0], 0

    inc dword [ap2_switch]
    mov eax, [ap2_switch]
    and eax, 0x1
    cmp eax, 0x1
    jnz _ap2_do_sys
    ; 跳转到用户进程2
    jmp far [app2_task_rsvr]
    jmp _out2
_ap2_do_sys:    
    ; 跳转到系统进程2
    jmp far [sys2_task_rsvr]
_out2:
    iret

;--------------------------在IDT中安装中断处理接口-----------------------------
install_interrupt_handle:                           ; 安装中断处理接口
                                                    ; 输入:eax = 中断处理程序入口地址
                                                    ;       ebx = 中断向量号
                                                    ; 返回:NULL

    pusha
    sidt [pidt]                                     ; 获取IDT中断描述符表信息
    
    xor edx, edx
    mov edx, flat_mode_code << 16                   ; 中断程序所述段的选择子
    mov dx, ax                                      ; 中断程序入口地址低16位
    shl ebx, 3                                      ; 中断向量号左移3位(*8)
    mov ecx, [pidt + 2]                             ; 中断描述符表的线性地址
    mov [ecx + ebx], edx                            ; 中断描述符低32位
    
    xor edx, edx
    and eax, 0xffff0000
    mov edx, eax                                    ; 中断程序入口地址高16位
    mov dx, 0x8e00                                  ; 32位中断门,0特权级, P=1, PDL=0
    mov [ecx + ebx + 4], edx                        ; 中断描述符高32位

    lidt [pidt]                                     ; 重新写入IDT中断描述符表
    popa

    ret

;-----------------------------从硬盘读取一个扇区-------------------------------
read_hard_disk_0:                                   ; 从硬盘读取一个逻辑扇区(平坦模型) 
                                                    ; EAX=逻辑扇区号
                                                    ; EBX=目标缓冲区线性地址
                                                    ; 返回:EBX=EBX+512
    cli
 
    push eax 
    push ecx
    push edx
  
    push eax
	 
    mov dx,0x1f2
    mov al,1
    out dx,al                                       ;读取的扇区数

    inc dx                                          ;0x1f3
    pop eax
    out dx,al                                       ;LBA地址7~0

    inc dx                                          ;0x1f4
    mov cl,8
    shr eax,cl
    out dx,al                                       ;LBA地址15~8

    inc dx                                          ;0x1f5
    shr eax,cl
    out dx,al                                       ;LBA地址23~16

    inc dx                                          ;0x1f6
    shr eax,cl
    or al,0xe0                                      ;第一硬盘  LBA地址27~24
    out dx,al

    inc dx                                          ;0x1f7
    mov al,0x20                                     ;读命令
    out dx,al

.waits:
    in al,dx
    and al,0x88
    cmp al,0x08
    jnz .waits                                      ;不忙,且硬盘已准备好数据传输 

    mov ecx,256                                     ;总共要读取的字数
    mov dx,0x1f0
.readw:
    in ax,dx
    mov [ebx],ax
    add ebx,2
    loop .readw

    pop edx
    pop ecx
    pop eax
  
    sti
  
    ret

;----------------------------- 全局变量定义 ----------------------------------
align 4    
    disp_chars db "0123456789ABCDEF"                ; 用于打印字符
    ap1_up_str db "AP 1 is UP", 0x0, 0x0            ; AP1启动信息
    ap2_up_str db "AP 2 is UP", 0x0, 0x0            ; AP2启动信息
    usr1_up_str db "Usr 1 is Running:", 0x0, 0x0, 0x0  ; Usr1启动信息
    usr2_up_str db "Usr 2 is Running:", 0x0, 0x0, 0x0  ; Usr2启动信息

;-------------------------------------------------------------------------------
align 4
    sys1_tss times 104 db 0                         ; APP1对应的系统进程使用的TSS段
    sys2_tss times 104 db 0                         ; APP2对应的系统进程使用的TSS段
    app1_tss times 104 db 0                         ; APP1使用的用户进程TSS段
    app2_tss times 104 db 0                         ; APP2使用的用户进程TSS段
    app1_ldt times 16 db 0                          ; APP1使用的LDT段
    app2_ldt times 16 db 0                          ; APP2使用的LDT段
    app1_task_rsvr dd 0                             ; APP1的保留字,用于进程跳转
    app1_tss_sel dw 72                              ; APP1的TSS段在GDT中的选择子
    app2_task_rsvr dd 0                             ; APP2的保留字,用于进程跳转
    app2_tss_sel dw 88                              ; APP2的TSS段在GDT中的选择子
    app1_ldt_sel dw 64                              ; APP1的LDT段在GDT中的选择子
    app2_ldt_sel dw 80                              ; APP2的LDT段在GDT中的选择子
    sys1_task_rsvr dd 0
    sys1_tss_sel dw 96                              ; AP1系统进程的TSS段在GDT中的偏移
    sys2_task_rsvr dd 0
    sys2_tss_sel dw 104                             ; AP2系统进程的TSS段在GDT中的偏移

;-------------------------------------------------------------------------------
align 4
;这个结构用于配置AP1的用户进程
_Usr_Task_Confg_1:
istruc usr_task_confg  
    at usr_task_confg.page_dir,        dd    0
    at usr_task_confg.page_table_0,    dd    0
    at usr_task_confg.usr_code_page,   dd    0
    at usr_task_confg.usr_stack_page,  dd    0
    at usr_task_confg.sys_stack_0,     dd    0
    at usr_task_confg.ldt_sel,         dw    0
    at usr_task_confg.tss_sel,         dw    0
    at usr_task_confg.disc_start,      dd    0
    at usr_task_confg.ldt_addr,        dd    0
    at usr_task_confg.tss_addr,        dd    0
iend

;这个结构用于配置AP2的用户进程
_Usr_Task_Confg_2:
istruc usr_task_confg  
    at usr_task_confg.page_dir,        dd    0
    at usr_task_confg.page_table_0,    dd    0
    at usr_task_confg.usr_code_page,   dd    0
    at usr_task_confg.usr_stack_page,  dd    0
    at usr_task_confg.sys_stack_0,     dd    0
    at usr_task_confg.ldt_sel,         dw    0
    at usr_task_confg.tss_sel,         dw    0
    at usr_task_confg.disc_start,      dd    0
    at usr_task_confg.ldt_addr,        dd    0
    at usr_task_confg.tss_addr,        dd    0
iend

;-------------------------------------------------------------------------------
align 4
;这个结构用于配置AP1的系统进程
_SYS_Task_Confg_1:
istruc sys_task_confg  
    at sys_task_confg.tss_sel,         dw    0
    at sys_task_confg.word_resrv,      dw    0
    at sys_task_confg.tss_addr,        dd    0
iend

;这个结构用于配置AP2的系统进程
_SYS_Task_Confg_2:
istruc sys_task_confg  
    at sys_task_confg.tss_sel,         dw    0
    at sys_task_confg.word_resrv,      dw    0
    at sys_task_confg.tss_addr,        dd    0
iend

;-------------------------------------------------------------------------------
align 4
    ap1_switch  dd  0
    ap2_switch  dd  0 
	  
;-------------------------------------------------------------------------------                             
align 4
    pidt  dw 0                                     ; IDT limit 值
          dd idt_base_address                      ; IDT base 值(其实对应的就是硬件0x9000页面)
    pgdt  dw 0
          dd gdt_base_address                       ;GDT的物理/线性地址:8K处开始 

下面,我们最后再讲一下两个用户进程的功能。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值