X86系统内存分页机制--加载不同的页表并读取同一个虚拟地址中内容 或者 调用同一个虚拟地址的函数

本文介绍了一种通过设置不同的页表来实现同一虚拟地址指向不同物理地址的方法,并演示了如何将函数映射到特定的物理地址,在不同的页表间切换以调用相同的虚拟地址对应的函数。

在这里插入图片描述
在这里插入图片描述
实验1 :加载不同的页表并读取同一个虚拟地址中内容

%include "inc.asm"

;两个页目录 子页表 起始地址  对应两个不同的任务
;简单的分页构建方法,所有的子页表顺序的连续排列
;注意: 因为我们设计的也表示连续的内存排布,所以单个任务的页表总大小就是 :1024(页表个数)*4096(单个子页表大小) 
	;所以 设计多个页表的时候 需要注意 每个页表的基地址之间的差值 要大于页表总大小

;页目录0起始地址
PageDirBase0    equ    0x200000
;子页表0起始地址
PageTblBase0    equ    0x201000
;页目录1起始地址
PageDirBase1    equ    0x700000
;子页表1起始地址
PageTblBase1    equ    0x701000

;用处:在不同的页表中 将目标虚地址X 分别映射到 Y Z 两个目标物理地址
;目标虚地址X  4K字节对齐
ObjectAddrX     equ    0x401000
;目标物理地址Y 低12位0 4K的整数倍 说明是某个物理页面的起始地址
TargetAddrY     equ    0xD01000
;目标物理地址Z 低12位0 4K的整数倍 说明是某个物理页面的起始地址
TargetAddrZ     equ    0xE01000

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]
; GDT definition
;                                       段基址,           段界限,       段属性
GDT_ENTRY         :     Descriptor        0,                0,           0
CODE32_DESC       :     Descriptor        0,        Code32SegLen - 1,    DA_C + DA_32
VIDEO_DESC        :     Descriptor     0xB8000,         0x07FFF,         DA_DRWA + DA_32
DATA32_DESC       :     Descriptor        0,        Data32SegLen - 1,    DA_DRW + DA_32
STACK32_DESC      :     Descriptor        0,         TopOfStack32,       DA_DRW + DA_32
;页目录段0 段描述符  最大4096 可读可写,32位保护模式下的段
PAGE_DIR_DESC0    :     Descriptor    PageDirBase0,       4095,          DA_DRW + DA_32
;子页表段0 段描述符  最大1024(0--1023)个子页表 可读可写,属性表明 段界限的单位是页(DA_LIMIT_4K) 4K字节,32位保护模式下的段
;默认单位是字节 这里显式的指明了单位是 4K字节
PAGE_TBL_DESC0    :     Descriptor    PageTblBase0,       1023,          DA_DRW + DA_LIMIT_4K + DA_32
;页目录段1 段描述符
PAGE_DIR_DESC1    :     Descriptor    PageDirBase1,       4095,          DA_DRW + DA_32
;子页表段1 段描述
PAGE_TBL_DESC1    :     Descriptor    PageTblBase1,       1023,          DA_DRW + DA_LIMIT_4K + DA_32
;平坦内存模型 段描述符 段基地址是0  段界限4G(DA_LIMIT_4K表示单位是4K)  可读可写
FLAT_MODE_RW_DESC :     Descriptor        0,             0xFFFFF,        DA_DRW + DA_LIMIT_4K + DA_32
; GDT end

GdtLen    equ   $ - GDT_ENTRY

GdtPtr:
          dw   GdtLen - 1
          dd   0
          
          
; GDT Selector

Code32Selector      equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector       equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector      equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector     equ (0x0004 << 3) + SA_TIG + SA_RPL0
;页目录段0 选择子
PageDirSelector0    equ (0x0005 << 3) + SA_TIG + SA_RPL0
;;子页表段0 选择子
PageTblSelector0    equ (0x0006 << 3) + SA_TIG + SA_RPL0
;页目录段1 选择子
PageDirSelector1    equ (0x0007 << 3) + SA_TIG + SA_RPL0
;子页表段1 选择子
PageTblSelector1    equ (0x0008 << 3) + SA_TIG + SA_RPL0
;平坦内存模型选择子
FlatModeRWSelector  equ (0x0009 << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]

TopOfStack16    equ 0x7c00

[section .dat]
[bits 32]
DATA32_SEGMENT:
    DTOS               db  "D.T.OS!", 0
    DTOS_LEN           equ $ - DTOS
    DTOS_OFFSET        equ DTOS - $$
    HELLO_WORLD        db  "Hello World!", 0
    HELLO_WORLD_LEN    equ $ - HELLO_WORLD
    HELLO_WORLD_OFFSET equ HELLO_WORLD - $$

Data32SegLen equ $ - DATA32_SEGMENT

[section .s16]
[bits 16]
ENTRY_SEGMENT:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, TopOfStack16
    
    ; initialize GDT for 32 bits code segment
    mov esi, CODE32_SEGMENT
    mov edi, CODE32_DESC
    
    call InitDescItem
    
    mov esi, DATA32_SEGMENT
    mov edi, DATA32_DESC
    
    call InitDescItem
    
    mov esi, STACK32_SEGMENT
    mov edi, STACK32_DESC
    
    call InitDescItem
    
    ; initialize GDT pointer struct
    mov eax, 0
    mov ax, ds
    shl eax, 4
    add eax, GDT_ENTRY
    mov dword [GdtPtr + 2], eax

    ; 1. load GDT
    lgdt [GdtPtr]
    
    ; 2. close interrupt
    cli 
    
    ; 3. open A20
    in al, 0x92
    or al, 00000010b
    out 0x92, al
    
    ; 4. enter protect mode
    mov eax, cr0
    or eax, 0x01
    mov cr0, eax
    
    ; 5. jump to 32 bits code
    jmp dword Code32Selector : 0


; esi    --> code segment label
; edi    --> descriptor label
InitDescItem:
    push eax

    mov eax, 0
    mov ax, cs
    shl eax, 4
    add eax, esi
    mov word [edi + 2], ax
    shr eax, 16
    mov byte [edi + 4], al
    mov byte [edi + 7], ah
    
    pop eax
    
    ret

    
[section .s32]
[bits 32]
CODE32_SEGMENT:
    mov ax, VideoSelector
    mov gs, ax
    
    mov ax, Stack32Selector
    mov ss, ax
    
    mov eax, TopOfStack32
    mov esp, eax
    
    mov ax, Data32Selector
    mov ds, ax
    
	;加载平坦内存模型选择子 使用平坦内存模型
    mov ax, FlatModeRWSelector
    mov es, ax
	
	;将 DTOS_OFFSET位置处字符串 ”D.T.OS!“ 拷贝到指定物理内存TargetAddrY中 长度为DTOS_LEN
    mov esi, DTOS_OFFSET ;source
    mov edi, TargetAddrY ;destination
    mov ecx, DTOS_LEN
    ;拷贝
    call MemCpy32
    
	;将 HELLO_WORLD_OFFSET位置处字符串 Hello World!" 拷贝到指定物理内存TargetAddrY中 长度为 HELLO_WORLD_LEN
    mov esi, HELLO_WORLD_OFFSET  ;source
    mov edi, TargetAddrZ ;destination
    mov ecx, HELLO_WORLD_LEN 
    ;拷贝
    call MemCpy32
    
	;初始化 页表0 内存布局
    mov eax, PageDirSelector0 ;页目录段0 选择子
    mov ebx, PageTblSelector0 ;子页表段0 选择子
    mov ecx, PageTblBase0 ;子页表0起始地址
    call InitPageTable
    
	;初始化 页表1 内存布局
    mov eax, PageDirSelector1
    mov ebx, PageTblSelector1
    mov ecx, PageTblBase1
    
    call InitPageTable
    
	;在页表0中将虚地址映射到目标物理地址
    mov eax, ObjectAddrX   ; 0x401000 目标虚地址
    mov ebx, TargetAddrY   ; 0xD01000 对应物理地址
    mov ecx, PageDirBase0  ; 在0号页目录PageDirBase0中进行映射
    call MapAddress
    
    ;在页表1中将虚地址映射到目标物理地址
    mov eax, ObjectAddrX   ; 0x401000
    mov ebx, TargetAddrZ   ; 0xE01000
    mov ecx, PageDirBase1
    call MapAddress
    
	;加载页表 PageDirBase0
    mov eax, PageDirBase0
    call SwitchPageTable
    
	;通过平坦内存模型实现指哪打哪儿,此时的地址是虚地址 因为已经加载了页表
	;打印
    mov ax, FlatModeRWSelector
    mov ds, ax
    mov ebp, ObjectAddrX ;此时的地址是虚地址 因为已经加载了页表
    mov bx, 0x0C
    mov dh, 12
    mov dl, 33
    ; ds:ebp    --> string address
	; bx        --> attribute
	; dx        --> dh : row, dl : col
    call PrintString
    
    mov eax, PageDirBase1
    
    call SwitchPageTable
    
    mov ax, FlatModeRWSelector
    mov ds, ax
    mov ebp, ObjectAddrX
    mov bx, 0x0C
    mov dh, 13
    mov dl, 31
    
    call PrintString
    
    jmp $

;功能:在页表中将虚地址映射到目标物理地址(需要工作在平坦内存模型下)
	;直接将物理地址 存储到 虚拟地址所对应的子页表的偏移位置处。此时映射就是直接的物理地址 不再是物理页面的页号
; es  --> flat mode
; eax --> virtual address 虚地址
; ebx --> target  address 目标物理地址
; ecx --> page directory base 页目录,即改写该页目录中某个虚地址所映射到的目标物理地址
MapAddress:
    push edi
    push esi 
    push eax    ; [esp + 8]
    push ebx    ; [esp + 4]
    push ecx    ; [esp] 当前栈顶
    
    ; 1. 取虚地址高 10 位, 计算子页表在页目录中的位置
    mov eax, [esp + 8] ;获取虚地址
    shr eax, 22 ;右移22位 取高10位
    and eax, 1111111111b
    shl eax, 2 ; *4  = 子页表在页目录中的位置
    
    ; 2. 取虚地址中间 10 位, 计算物理地址在子页表中的位置
    mov ebx, [esp + 8] ;获取虚地址
    shr ebx, 12 ;右移21位 取中间10位
    and ebx, 1111111111b
    shl ebx, 2 ; *4 = 物理地址在子页表中的位置
    
    ; 3. 取子页表起始地址
    mov esi, [esp] ;获取页目录起始地址 栈顶
    add esi, eax  ;获取子页表偏移地址(平坦内存模型) = *(页目录地址+子页表偏移地址)
    mov edi, [es:esi] ;获取子页表起始地址(平坦内存模型)
    and edi, 0xFFFFF000 ;低12位清零
    
    ; 4. 将目标地址写入子页表的对应位置
    add edi, ebx ;目标物理地址所在地址 = 子页表中地址 + 物理地址存储偏移位置
    mov ecx, [esp + 4] ;获取 目标物理地址
    and ecx, 0xFFFFF000 ;低12位清零 4k字节对齐
    or  ecx, PG_P | PG_USU | PG_RWW ;目标物理地址加属性
    mov [es:edi], ecx ;将 目标物理地址 存入子页表对应的位置中
    
    pop ecx
    pop ebx
    pop eax
    pop esi
    pop edi
    
    ret

; es    --> flat mode selector
; ds:si --> source
; es:di --> destination
; cx    --> length
MemCpy32:
    push esi
    push edi
    push ecx
    push ax
    
    cmp esi, edi
    
    ja btoe
    
    add esi, ecx
    add edi, ecx
    dec esi
    dec edi
    
    jmp etob
    
btoe:
    cmp ecx, 0
    jz done
    mov al, [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec ecx
    jmp btoe
    
etob: 
    cmp ecx, 0
    jz done
    mov al, [ds:esi]
    mov byte [es:edi], al
    dec esi
    dec edi
    dec ecx
    jmp etob

done:   
    pop ax
    pop ecx
    pop edi
    pop esi
    ret

;初始化页表    es:平坦内存模型选择子 使用平坦内存模型
; eax --> page dir base selector  页目录选择子
; ebx --> page table base selector  子页表选择子
; ecx --> page table base 子页表起始地址
InitPageTable:
    push es
    push eax  ; [esp + 12]
    push ebx  ; [esp + 8]
    push ecx  ; [esp + 4]
    push edi  ; [esp] 栈顶
    
	;设置目录项
	;页目录基地址
    mov es, ax ;获取页目录基地址
    mov ecx, 1024    ;  1K sub page tables 有1024个子页表 循环1024次
    mov edi, 0
    mov eax, [esp + 4] ;获取子页表起始地址
	;子页表基地址 + 属性:对应页已经在内存中 是用户级别的页 可读可写
    or  eax, PG_P | PG_USU | PG_RWW
    
    cld
    
stdir:
	;将 eax中的值存储到[es:edi]指向的内存单元中,edi+4(页目录中下一项子页表)
    stosd
	;填充页目录下一项 子页表地址
    add eax, 4096
    loop stdir
	
    ;设置子页表
	;子页表基地址
    mov ax, [esp + 8] ;获取 子页表选择子
    mov es, ax
    mov ecx, 1024 * 1024   ; 1M pages, 1024个子页表*每个子页表有1024个物理页面条目
    mov edi, 0
	;第一个物理页面地址 接上面累加结果,属性:对应页已经在内存中 是用户级别的页 可读可写
    mov eax, PG_P | PG_USU | PG_RWW
    
    cld
    
sttbl:
	;将 eax中的值存储到[es:edi]指向的内存单元中,edi+4(页目录中下一项子页表)
    stosd
	;填充子页表中记录的下一项 物理页面地址信息
    add eax, 4096
    loop sttbl
    
    pop edi
    pop ecx
    pop ebx
    pop eax
    pop es
    
    ret   

; eax --> page directory base
SwitchPageTable:
    push eax
    
    mov eax, cr0
    and eax, 0x7FFFFFFF
    mov cr0, eax
    
    mov eax, [esp]
    mov cr3, eax
    mov eax, cr0
    or  eax, 0x80000000
    mov cr0, eax
    
    pop eax
    
    ret


; ds:ebp    --> string address
; bx        --> attribute
; dx        --> dh : row, dl : col
PrintString:
    push ebp
    push eax
    push edi
    push cx
    push dx
    
print:
    mov cl, [ds:ebp]
    cmp cl, 0
    je end
    mov eax, 80
    mul dh
    add al, dl
    shl eax, 1
    mov edi, eax
    mov ah, bl
    mov al, cl
    mov [gs:edi], ax
    inc ebp
    inc dl
    jmp print

end:
    pop dx
    pop cx
    pop edi
    pop eax
    pop ebp
    
    ret
    
Code32SegLen    equ    $ - CODE32_SEGMENT

[section .gs]
[bits 32]
STACK32_SEGMENT:
    times 1024 * 4 db 0
    
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32  equ Stack32SegLen - 1

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
回顾:

段内跳转 call jmp 近跳转,参数是相对地址(如 跳转到距离当前位置10个字节的位置执行)
其中 call跳转指令 就是 函数调用时的跳转,他最大的特点是 跳转过去执行之后,还能返回回来,而jmp 是无条件跳转,跳转过去之后 不会返回,有去无回

段间跳转 call far, jmp far 远跳转,参数是选择子和偏移地址(即目标段基地址和段内偏移地址)

	;通过选择子调用相应的函数
	;说明 这里的0是语法需要 表示是段间跳转 0本身无意义,但是不能删除
	;如果删除后 就变成了 call FuncCGAddSelector,成了段内跳转,意义本身发生变化,所以必须留着
    ;call FuncCGAddSelector : 0
    ;call FuncCGSubSelector : 0
	
	;函数指针的本质就是函数入口地址,所以也可以这样调用函数:
	;原理就是直接使用函数地址(段基址+段内偏移地址)
	;call FunctionSelector : CG_Add
        ;call FunctionSelector : CG_Sub

...

;返回指令 retf (ret far  远调用 远返回)
 	retf

实验:加载不同的页表并调用同一个虚拟地址的函数

%include "inc.asm"

;两个页目录 子页表 起始地址  对应两个不同的任务
;简单的分页构建方法,所有的子页表顺序的连续排列
;注意: 因为我们设计的也表示连续的内存排布,所以单个任务的页表总大小就是 :1024(页表个数)*4096(单个子页表大小) 
	;所以 设计多个页表的时候 需要注意 每个页表的基地址之间的差值 要大于页表总大小
;页目录0起始地址
PageDirBase0    equ    0x200000
;子页表0起始地址
PageTblBase0    equ    0x201000
;页目录1起始地址
PageDirBase1    equ    0x700000
;子页表1起始地址
PageTblBase1    equ    0x701000

;用处:在不同的页表中 将目标虚地址X 分别映射到 Y Z 两个目标物理地址
;目标虚地址X  4K字节对齐
ObjectAddrX     equ    0x401000
;目标物理地址Y 低12位0 4K的整数倍 说明是某个物理页面的起始地址
TargetAddrY     equ    0xD01000
;目标物理地址Z 低12位0 4K的整数倍 说明是某个物理页面的起始地址
TargetAddrZ     equ    0xE01000

org 0x9000

jmp ENTRY_SEGMENT

[section .gdt]
; GDT definition
;                                       段基址,           段界限,       段属性
GDT_ENTRY         :     Descriptor        0,                0,           0
CODE32_DESC       :     Descriptor        0,        Code32SegLen - 1,    DA_C + DA_32
VIDEO_DESC        :     Descriptor     0xB8000,         0x07FFF,         DA_DRWA + DA_32
DATA32_DESC       :     Descriptor        0,        Data32SegLen - 1,    DA_DRW + DA_32
STACK32_DESC      :     Descriptor        0,         TopOfStack32,       DA_DRW + DA_32
;页目录段0 段描述符  最大4096 可读可写,32位保护模式下的段
PAGE_DIR_DESC0    :     Descriptor    PageDirBase0,       4095,          DA_DRW + DA_32
;子页表段0 段描述符  最大1024(0--1023)个子页表 可读可写,属性表明 段界限的单位是页(DA_LIMIT_4K) 4K字节,32位保护模式下的段
;默认单位是字节 这里显式的指明了单位是 4K字节
PAGE_TBL_DESC0    :     Descriptor    PageTblBase0,       1023,          DA_DRW + DA_LIMIT_4K + DA_32
;页目录段1 段描述符
PAGE_DIR_DESC1    :     Descriptor    PageDirBase1,       4095,          DA_DRW + DA_32
;子页表段1 段描述
PAGE_TBL_DESC1    :     Descriptor    PageTblBase1,       1023,          DA_DRW + DA_LIMIT_4K + DA_32
;代码段(这里属性是数据段 事实上代码本质就是一段数据)
FUNC32_DESC:			Descriptor        0,        Func32SegLen - 1,    DA_DR + DA_32
;平坦内存模型 段描述符 段基地址是0 用来读写数据 段界限4G(DA_LIMIT_4K表示单位是4K)  可读可写
FLAT_MODE_RW_DESC :     Descriptor        0,             0xFFFFF,        DA_DRW + DA_LIMIT_4K + DA_32
;平坦内存模型 段描述符 段基地址是0 用来执行数据
FLAT_MODE_C_DESC  :     Descriptor        0,             0xFFFFF,        DA_C + DA_LIMIT_4K + DA_32
; GDT end

GdtLen    equ   $ - GDT_ENTRY

GdtPtr:
          dw   GdtLen - 1
          dd   0
          
          
; GDT Selector

Code32Selector      equ (0x0001 << 3) + SA_TIG + SA_RPL0
VideoSelector       equ (0x0002 << 3) + SA_TIG + SA_RPL0
Data32Selector      equ (0x0003 << 3) + SA_TIG + SA_RPL0
Stack32Selector     equ (0x0004 << 3) + SA_TIG + SA_RPL0
;页目录段0 选择子
PageDirSelector0    equ (0x0005 << 3) + SA_TIG + SA_RPL0
;;子页表段0 选择子
PageTblSelector0    equ (0x0006 << 3) + SA_TIG + SA_RPL0
;页目录段1 选择子
PageDirSelector1    equ (0x0007 << 3) + SA_TIG + SA_RPL0
;子页表段1 选择子
PageTblSelector1    equ (0x0008 << 3) + SA_TIG + SA_RPL0
;代码段(就是数据段)选择子
Func32Selector      equ (0x0009 << 3) + SA_TIG + SA_RPL0
;平坦内存模型选择子 用于读取数据
FlatModeRWSelector  equ (0x000A << 3) + SA_TIG + SA_RPL0
;平坦内存模型选择子 用于执行数据
FlatModeCSelector   equ (0x000B << 3) + SA_TIG + SA_RPL0
; end of [section .gdt]

TopOfStack16    equ 0x7c00

[section .dat]
[bits 32]
DATA32_SEGMENT:
    DTOS               db  "D.T.OS!", 0
    DTOS_LEN           equ $ - DTOS
    DTOS_OFFSET        equ DTOS - $$
    HELLO_WORLD        db  "Hello World!", 0
    HELLO_WORLD_LEN    equ $ - HELLO_WORLD
    HELLO_WORLD_OFFSET equ HELLO_WORLD - $$

Data32SegLen equ $ - DATA32_SEGMENT

;定义保护模式下 32位数据段
[section .func]
[bits 32]
FUNC32_SEGMENT:

;函数1 求平方
; cx --> n
; return:
;     eax --> n * n
Sqr:
    mov eax, 0
    
    mov ax, cx
    mul cx
    ;返回指令 retf (ret far  远调用 远返回)
    retf
    
SqrLen  equ  $ - Sqr
SqrFunc equ  Sqr - $$

;函数2 累加函数
; cx --> n
; return:
;     eax --> 1 + 2 + 3 + ... + n
Acc:
    push cx
    
    mov eax, 0
    
accloop:
    add ax, cx
    loop accloop
    
    pop cx
    
    retf
    
AccLen  equ  $ - Acc
AccFunc equ  Acc - $$

Func32SegLen  equ  $ - FUNC32_SEGMENT

[section .s16]
[bits 16]
ENTRY_SEGMENT:
    mov ax, cs
    mov ds, ax
    mov es, ax
    mov ss, ax
    mov sp, TopOfStack16
    
    ; initialize GDT for 32 bits code segment
    mov esi, CODE32_SEGMENT
    mov edi, CODE32_DESC
    
    call InitDescItem
    
    mov esi, DATA32_SEGMENT
    mov edi, DATA32_DESC
    
    call InitDescItem
    
    mov esi, STACK32_SEGMENT
    mov edi, STACK32_DESC
    
    call InitDescItem
    
	;初始化 代码段(本质是数据段)段描述符
    mov esi, FUNC32_SEGMENT
    mov edi, FUNC32_DESC
    
    call InitDescItem
    
    ; initialize GDT pointer struct
    mov eax, 0
    mov ax, ds
    shl eax, 4
    add eax, GDT_ENTRY
    mov dword [GdtPtr + 2], eax

    ; 1. load GDT
    lgdt [GdtPtr]
    
    ; 2. close interrupt
    cli 
    
    ; 3. open A20
    in al, 0x92
    or al, 00000010b
    out 0x92, al
    
    ; 4. enter protect mode
    mov eax, cr0
    or eax, 0x01
    mov cr0, eax
    
    ; 5. jump to 32 bits code
    jmp dword Code32Selector : 0


; esi    --> code segment label
; edi    --> descriptor label
InitDescItem:
    push eax

    mov eax, 0
    mov ax, cs
    shl eax, 4
    add eax, esi
    mov word [edi + 2], ax
    shr eax, 16
    mov byte [edi + 4], al
    mov byte [edi + 7], ah
    
    pop eax
    
    ret

    
[section .s32]
[bits 32]
CODE32_SEGMENT:
    mov ax, VideoSelector
    mov gs, ax
    
    mov ax, Stack32Selector
    mov ss, ax
    
    mov eax, TopOfStack32
    mov esp, eax
    ;注意本实验 的数据段是 FUNC32_SEGMENT
    mov ax, Func32Selector
    mov ds, ax
    
    mov ax, FlatModeRWSelector
    mov es, ax
    
	;将 求平方函数 SqrFunc()首地址 拷贝到目标物理内存TargetAddrY
    mov esi, SqrFunc
    mov edi, TargetAddrY
    mov ecx, SqrLen
    call MemCpy32
    
	;将 求累加函数 AccFunc()首地址 拷贝到目标物理内存TargetAddrZ
    mov esi, AccFunc
    mov edi, TargetAddrZ
    mov ecx, AccLen
    call MemCpy32
    
	;初始化 页表0 内存布局
    mov eax, PageDirSelector0 ;页目录段0 选择子
    mov ebx, PageTblSelector0 ;子页表段0 选择子
    mov ecx, PageTblBase0 ;子页表0起始地址
    
    call InitPageTable
    
	;初始化 页表1 内存布局
    mov eax, PageDirSelector1
    mov ebx, PageTblSelector1
    mov ecx, PageTblBase1
    
    call InitPageTable
    
	;在页表0中将虚地址映射到目标物理地址
    mov eax, ObjectAddrX   ; 0x401000 目标虚地址
    mov ebx, TargetAddrY   ; 0xD01000 对应物理地址
    mov ecx, PageDirBase0  ; 在0号页目录PageDirBase0中进行映射
    call MapAddress
    
    ;在页表1中将虚地址映射到目标物理地址
    mov eax, ObjectAddrX   ; 0x401000
    mov ebx, TargetAddrZ   ; 0xE01000
    mov ecx, PageDirBase1
    
    call MapAddress
    
	;加载页表 PageDirBase0
    mov eax, PageDirBase0
    call SwitchPageTable
  
	;调用函数
    mov ecx, 100
    call FlatModeCSelector : ObjectAddrX
    
    mov eax, PageDirBase1
    
    call SwitchPageTable
    
    mov ecx, 100
    
    call FlatModeCSelector : ObjectAddrX
    
    jmp $

;功能:在页表中将虚地址映射到目标物理地址(需要工作在平坦内存模型下)
	;直接将物理地址 存储到 虚拟地址所对应的子页表的偏移位置处。此时映射就是直接的物理地址 不再是物理页面的页号
; es  --> flat mode
; eax --> virtual address 虚地址
; ebx --> target  address 目标物理地址
; ecx --> page directory base 页目录,即改写该页目录中某个虚地址所映射到的目标物理地址
MapAddress:
    push edi
    push esi 
    push eax    ; [esp + 8]
    push ebx    ; [esp + 4]
    push ecx    ; [esp] 当前栈顶
    
    ; 1. 取虚地址高 10 位, 计算子页表在页目录中的位置
    mov eax, [esp + 8] ;获取虚地址
    shr eax, 22 ;右移22位 取高10位
    and eax, 1111111111b
    shl eax, 2 ; *4  = 子页表在页目录中的位置
    
    ; 2. 取虚地址中间 10 位, 计算物理地址在子页表中的位置
    mov ebx, [esp + 8] ;获取虚地址
    shr ebx, 12 ;右移21位 取中间10位
    and ebx, 1111111111b
    shl ebx, 2 ; *4 = 物理地址在子页表中的位置
    
    ; 3. 取子页表起始地址
    mov esi, [esp] ;获取页目录起始地址 栈顶
    add esi, eax  ;获取子页表偏移地址(平坦内存模型) = *(页目录地址+子页表偏移地址)
    mov edi, [es:esi] ;获取子页表起始地址(平坦内存模型)
    and edi, 0xFFFFF000 ;低12位清零
    
    ; 4. 将目标地址写入子页表的对应位置
    add edi, ebx ;目标物理地址所在地址 = 子页表中地址 + 物理地址存储偏移位置
    mov ecx, [esp + 4] ;获取 目标物理地址
    and ecx, 0xFFFFF000 ;低12位清零 4k字节对齐
    or  ecx, PG_P | PG_USU | PG_RWW ;目标物理地址加属性
    mov [es:edi], ecx ;将 目标物理地址 存入子页表对应的位置中
    
    pop ecx
    pop ebx
    pop eax
    pop esi
    pop edi
    
    ret

; es    --> flat mode selector
; ds:si --> source
; es:di --> destination
; cx    --> length
MemCpy32:
    push esi
    push edi
    push ecx
    push ax
    
    cmp esi, edi
    
    ja btoe
    
    add esi, ecx
    add edi, ecx
    dec esi
    dec edi
    
    jmp etob
    
btoe:
    cmp ecx, 0
    jz done
    mov al, [ds:esi]
    mov byte [es:edi], al
    inc esi
    inc edi
    dec ecx
    jmp btoe
    
etob: 
    cmp ecx, 0
    jz done
    mov al, [ds:esi]
    mov byte [es:edi], al
    dec esi
    dec edi
    dec ecx
    jmp etob

done:   
    pop ax
    pop ecx
    pop edi
    pop esi
    ret

;初始化页表    es:平坦内存模型选择子 使用平坦内存模型
; eax --> page dir base selector  页目录选择子
; ebx --> page table base selector  子页表选择子
; ecx --> page table base 子页表起始地址
InitPageTable:
    push es
    push eax  ; [esp + 12]
    push ebx  ; [esp + 8]
    push ecx  ; [esp + 4]
    push edi  ; [esp] 栈顶
    
	;设置目录项
	;页目录基地址
    mov es, ax ;获取页目录基地址
    mov ecx, 1024    ;  1K sub page tables 有1024个子页表 循环1024次
    mov edi, 0
    mov eax, [esp + 4] ;获取子页表起始地址
	;子页表基地址 + 属性:对应页已经在内存中 是用户级别的页 可读可写
    or  eax, PG_P | PG_USU | PG_RWW
    
    cld
    
stdir:
	;将 eax中的值存储到[es:edi]指向的内存单元中,edi+4(页目录中下一项子页表)
    stosd
	;填充页目录下一项 子页表地址
    add eax, 4096
    loop stdir
	
    ;设置子页表
	;子页表基地址
    mov ax, [esp + 8] ;获取 子页表选择子
    mov es, ax
    mov ecx, 1024 * 1024   ; 1M pages, 1024个子页表*每个子页表有1024个物理页面条目
    mov edi, 0
	;第一个物理页面地址 接上面累加结果,属性:对应页已经在内存中 是用户级别的页 可读可写
    mov eax, PG_P | PG_USU | PG_RWW
    
    cld
    
sttbl:
	;将 eax中的值存储到[es:edi]指向的内存单元中,edi+4(页目录中下一项子页表)
    stosd
	;填充子页表中记录的下一项 物理页面地址信息
    add eax, 4096
    loop sttbl
    
    pop edi
    pop ecx
    pop ebx
    pop eax
    pop es
    
    ret   

; eax --> page directory base
SwitchPageTable:
    push eax
    
    mov eax, cr0
    and eax, 0x7FFFFFFF
    mov cr0, eax
    
    mov eax, [esp]
    mov cr3, eax
    mov eax, cr0
    or  eax, 0x80000000
    mov cr0, eax
    
    pop eax
    
    ret


; ds:ebp    --> string address
; bx        --> attribute
; dx        --> dh : row, dl : col
PrintString:
    push ebp
    push eax
    push edi
    push cx
    push dx
    
print:
    mov cl, [ds:ebp]
    cmp cl, 0
    je end
    mov eax, 80
    mul dh
    add al, dl
    shl eax, 1
    mov edi, eax
    mov ah, bl
    mov al, cl
    mov [gs:edi], ax
    inc ebp
    inc dl
    jmp print

end:
    pop dx
    pop cx
    pop edi
    pop eax
    pop ebp
    
    ret
    
Code32SegLen    equ    $ - CODE32_SEGMENT

[section .gs]
[bits 32]
STACK32_SEGMENT:
    times 1024 * 4 db 0
    
Stack32SegLen equ $ - STACK32_SEGMENT
TopOfStack32  equ Stack32SegLen - 1

在这里插入图片描述

<think>好的,我现在需要帮助用户解决在F28069中使用汇编代码判断32位地址存储内容是否为0跳转的问题。首先,我需要回顾用户之前的问题和我的回答,确保这次回答的一致性。用户之前询问的是16位地址的判断,现在扩展到32位,我需要调整相应的指令和步骤。 用户提到参考了站内引用,其中涉及函数调用x86汇编的例子,但F28069属于C28x架构,所以需要专注于该架构的特定指令。之前的回答中,我使用了MOVW和TEST指令处理16位数据,现在32位需要用到MOVL和TEST指令,可能还要处理高16位和低16位的数据。 接下来,我需要确保正确使用数据页寄存器(DP)来访问内存地址,因为C28x架构中数据访问通常通过DP或SP来寻址。对于32位数据,可能需要分两次加载或者使用MOVL指令直接加载到32位寄存器中,比如ACC。 然后,我需要考虑如何检测32位数据是否全为0。TEST指令在C28x中是否支持32位操作?可能需要分别检查高16位和低16位,或者使用CMP指令与0比较。同时,需要设置正确的状态位,如零标志(Z),以便条件跳转。 此外,用户可能关心内存对齐的问题,32位数据是否需要对齐到特定地址,以及中断处理时的注意事项,比如是否需要禁用中断来确保数据读取的原子性。 还需要考虑代码效率,是否有更优化的方法,比如使用循环或者批量检测,以及如何处理函数跳转,比如使用B或CALL指令,确保返回地址的正确保存。 最后,我需要验证示例代码的正确性,确保MOVL和TEST指令的正确使用,以及条件跳转指令的正确条件码,比如EQ或NEQ。同时,提供相关问题的扩展,帮助用户进一步理解可能的应用场景和优化技巧。</think>### F28069汇编实现32位地址零值检测与跳转(C28x架构) 根据C28x架构特性及DSP内核寄存器设计,32位地址检测需结合长整型操作指令,以下是完整实现方案: --- #### 一、核心硬件特性 1. **寄存器架构**: - **ACC**:32位累加器(支持全位检测) - **XARn**:32位辅助寄存器(用于地址操作) 2. **指令支持**: - `MOVL`:加载32位数据到寄存器 - `TEST`:自动进行位与运算更新Z标志 - `CMP`:支持与立即数比较 --- #### 二、32位检测标准实现 ```assembly ;------------------------------------------------------------------ ; 功能:检测地址0x9000存储的32位数据是否为0,跳转 ; 输入:无 ; 输出:根据检测结果跳转至ZERO_32或NONZERO_32 ;------------------------------------------------------------------ CHECK_32BIT_ZERO: MOVL XAR7, #0x00009000 ; 设置32位地址指针 MOVL ACC, *XAR7 ; 加载32位数据到ACC[^1] TEST ACC ; ACC & ACC -> 更新Z标志 B ZERO_32, EQ ; Z=1时跳转(全0) B NONZERO_32, NEQ ; Z=0时跳转 ``` --- #### 三、分步操作解析 1. **地址加载优化**: ```assembly MOVW DP, #0x9000>>6 ; 设置数据页(高16位) MOVL ACC, @0x9000 ; 组合式地址访问(DP+偏移) ``` - 通过DP+X组合访问方式可提升代码密度[^2] 2. **安全检测流程**: ```assembly DINT ; 禁止中断 MOVL ACC, CriticalData ; 加载关键数据 TEST ACC EINT ; 恢复中断 B Safe_Handler, EQ ``` - 关键数据检测需确保原子操作 --- #### 四、高级应用方案 1. **批量32位检测(流水线优化)**: ```assembly MOVL XAR2, #Buffer_Start ; 起始地址 MOV AR0, #128 ; 检测次数 LOOP_32: MOVL ACC, *XAR2++ ; 加载自增地址 TEST ACC B FOUND_ZERO, EQ BANZ LOOP_32, AR0-- ; 硬件循环加速 ``` 2. **外设寄存器检测(带延迟)**: ```assembly CAN_Check: MOVL ACC, @CanaRegs+0x20 ; 访问CAN控制寄存器 NOP ; 等待总线稳定[^3] TEST ACC B CAN_Ready, NEQ ``` --- #### 五、关键注意事项 1. **内存对齐规则**: - 32位数据必须存储在模4对齐地址(如0x9000、0x9004) - 违反对齐将触发非对齐访问异常 2. **标志位特性**: - `TEST ACC`指令会同时检测ACC的31-0位 - 仅当ACC=0x00000000时Z=1 3. **函数跳转约定**: ```assembly ; 短跳转(±128字) B TargetFunc, EQ ; 长跳转(配合LCR) MOVL XAR6, #TargetFunc LCR *XAR6 ``` --- ### 混合位宽检测(16+16位) ```assembly CHECK_HYBRID: MOVW DP, #0x8000>>6 ; 设置数据页 MOVW AL, @0x8000 ; 加载低16位 MOVW AH, @0x8002 ; 加载高16位 OR AL, AH ; 组合检测 TEST AL B ALL_ZERO, EQ ``` --- ### 相关问题 1. 如何检测连续32位内存区域是否全为0? 2. C28x架构的MOVL指令有哪些寻址模式? 3. 为什么32位数据访问需要内存对齐? 4. 在中断服务程序中如何进行安全的32位数据检测? 5. F28069的硬件循环(BANZ)如何提升检测效率? [^1]: MOVL指令支持直接加载32位数据 [^2]: DP+X组合寻址可减少代码长度 [^3]: 外设寄存器需要稳定周期
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Ma浩然

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值