unix上机LA8学习笔记

一、知识点复习

1.CPU执行`call 标号`时相当于进行:
push ip
jmp near ptr 标号

2.三个ret指令:
1)iret指令的功能用汇编语法描述为:

pop ip
pop cs
popf

2)ret指令:
pop ip

3)retf指令:

pop ip
pop cs

3.pushf的功能是将标志寄存器的值压栈,POPF是从栈中弹出数据,送入标志寄存器中

4.中断过程:发生中断时,CPU的硬件自动利用中断类型码找到中断向量,并用它设置CS和IP。中断过程中的操作有:
1)从中断信息中取得中断类型码
2)标志寄存器的值入栈(因为中断过程中要改变标志寄存器的值,所以先将其保存在栈中)
3)设置标志寄存器的TF和IF的值为0
4)CS的内容入栈
5)IP的内容入栈
6)从内存地址为中断类型码*4和终端类型码*4+2的两个字单元中读取中断处理程序的入口地址设置IP和CS

题目及代码
实验一: 实验代码在second.asm中,请补全缺少的代码。最终效果是程序运行,进入work函数的死循环,按下1和2(位于字母区上方,扫描码是2和3)的时候,程序会在某个地方打印按下1和2的次数(至少2位,即显示到99),按下4(扫描码是5)则结束程序。
assume cs:code
;函数应该保存用到的寄存器,我为了简化代码并没有这样做,这样做写代码时需要很小心安排寄存器使用
;大家在写的时候请尽量保存使用到的寄存器,简化编程;
;大项目请务必保存,C语言编译完在函数入口也是会保存使用到的寄存器的

;观察程序,体会程序在死循环中是怎么"抽空"执行其他代码的
;操作键为123是退出

;===========================================
;请补充init、restore、int9、work



;==========================================
stack segment 
    db 128 dup (0)
stack ends

data segment 
    dw 0, 0
    db 0 ;A 计数器
    db 0 ;B 计数器
data ends

code segment
start:
        mov ax, stack
        mov ss, ax
        mov sp, 128;使用申请的栈

        call init ;安装新的中断例程
        call work ;假装在工作,体会"代码执行地址即cs:ip"切换的基本方式
        call restore ;恢复默认中断例程
        mov ax, 4c00h
        int 21h

;死循环,模拟在干活
work:
		mov dh,0
		begin:cmp dh,1
		jne begin
		ret
		

;es:si目标显存地址,十位在ch,个位在cl
print:
        add ch, '0'
        add cl, '0'
        mov byte ptr es:[si], ch
        mov byte ptr es:[si + 1], 21h
        mov byte ptr es:[si + 2], cl
        mov byte ptr es:[si + 3], 21h
        ret

;参数在ax,把十位放到ch,个位放到cl
; ch = (ax / 10) % 10, cl = ax % 10 
calculate:
        push bx
        mov bl, 10
        div bl
        mov cl, ah
        xor ah, ah
        div bl
        mov ch, ah
        pop bx
        ret
;按下A键,运行A函数
func1:
        inc byte ptr ds:[4]
        xor ah, ah
        mov al, ds:[4]
        call calculate
        mov si, 5 * 160 + 40 * 2 ;初始化 指向屏幕第5行第40列
        call print
        ret

;按下B键,运行B函数
func2:
        inc byte ptr ds:[5]
        xor ah, ah
        mov al, ds:[5]
        call calculate
        mov si, 6 * 160 + 40 * 2 ;初始化 指向屏幕第6行第40列
        call print
        ret


;初始化中断例程
init:
        mov ax, data
        mov ds, ax
        mov ax, 0
        mov es, ax
        cli     ;关中断修改中断处理程序
        ;保存原来的9号中断例程地址
		push es:[9*4]
		pop ds:[0]
		push es:[9*4+2]
		pop ds:[2]
        ;将键盘中断处理程序指向自己编写的例程
		mov word ptr es:[9*4],offset int9
		mov word ptr es:[9*4+2],cs
        sti
        mov ax, 0b800h ;指向显存
        mov es, ax

        ret
;恢复之前的中断例程
restore:
        mov ax, 0
        mov es, ax
        mov ax, data
        mov ds, ax
        cli
        ;恢复原来的中断
		push ds:[0]
		pop es:[9*4]
		push ds:[2]
		pop es:[9*4+2]
        sti
        ret

;根据中断引发的键位,暂停work函数,执行对应的操作,然后继续执行work函数
int9:
        cli
        push ax ;使用了al 所以要保存ax
		push bx
		push es

        in al, 60h
		
		pushf
		pushf
		pop bx
		and bh,11111100b
		push bx
		popf
		call dword ptr ds:[0]

        pushf
        call dword ptr ds:[0];调用之前的中断处理程序处理一些硬件的问题
        
       
       ;根据按键123来执行操作
	   cmp al,2
	   jne s1
	   call func1
	   jmp int9ret
	   s1:cmp al,3
	   jne s2
	   call func2
	   jmp int9ret
	   s2:cmp al,4
	   jne int9ret
	   mov dh,1
	   
	   
int9ret:
		pop es
		pop bx
        pop ax
        sti
        iret

code ends
end start




实验二:
有A B C三个函数,逻辑大体一样,都是死循环,然后每次循环递增一次计数器并打印,然后调用delay函数延时一秒。现在要求,运行程序后,按下A键,执行A函数,A计数器逐渐递增;按下B则是执行B,B对应的计数器递增;C同样。程序机制大致同实验一,也是通过修改键盘中断例程完成。
额外约束:三个函数用同一个寄存器作为计数器,更进一步除了打印位置不一样,三个函数用的寄存器一致,不得错开使用不同的寄存器。下面是代码:

;示例代码
assume cs:code

stack segment 
    StackA db 128 dup (0)
    StackB db 128 dup (0)
    StackC db 128 dup (0)
    UniStack       db 128 dup (0) ;不能够共用栈
stack ends

data segment
            dw 0, 0 ;保存原来的中断例程地址
    Current db 0;记录当前时谁正在运行,1A,2B,3C
    A_SS_SP dw 0, 0
    B_SS_SP dw 0, 0
    C_SS_SP dw 0, 0
data ends

code segment
start:
        mov ax, stack
        mov ss, ax
        mov sp, offset UniStack
        add sp, 128
        
        call init ;初始化中断例程
        ;初始化3个函数运行情况,也就是将CS:IP指向对应函数位置
        ;以及设置正确的栈,避免共用栈
        call init_state         
    lp:
        jmp lp 
    
;修改对应data段保存中内容,也就是程序运行状态
;主要是将cs:ip指向对应函数,然后设置正确的栈空间
; ax bx cx dx si di es ds bp ip cs psw
; 0  2  4  6  8  10 12 14 16 18 20 22
init_state:
        push ax
        push bx
        push cx;这段push似乎用不用都无所谓?因为是初始化,所以谈不上保护寄存器的值吧
        push bp
        mov bp, sp
    initA:
        mov sp, offset StackB
        mov cx, 5 * 160 + 80
        mov bx, offset A_SS_SP
        mov dx, 0
        pushf
        pop ax
        push ax ;push pwd
        push cs ;push cs
        mov ax, offset func
        push ax ; push ip
        push dx; push bp
        push ds
        push es
        push dx; push di  计数器di预设为0
        push cx; push si
        push dx ;dx
        push dx ;cx
        push dx ;bx
        push dx ;ax
        mov ds:[bx], ss
        mov ds:[bx + 2], sp 
    initB: 
        mov sp, offset StackC
        mov cx, 6 * 160 + 80
        mov bx, offset B_SS_SP
        ;init_stack
        mov dx, 0
        pushf
        pop ax
        push ax ;push pwd
        push cs ;push cs
        mov ax, offset func
        push ax ; push ip
        push dx; push bp
        push ds
        push es
        push dx; push di  计数器di预设为0
        push cx; push si
        push dx ;dx
        push dx ;cx
        push dx ;bx
        push dx ;ax
        mov ds:[bx], ss
        mov ds:[bx + 2], sp 
    initC:
        mov sp, offset StackC
        add sp, 128
        mov cx, 7 * 160 + 80
        mov bx, offset C_SS_SP
        ;init_stack
        mov dx, 0
        pushf
        pop ax
        push ax ;push pwd
        push cs ;push cs
        mov ax, offset func
        push ax ; push ip
        push dx; push bp
        push ds
        push es
        push dx; push di  计数器di预设为0
        push cx; push si
        push dx ;dx
        push dx ;cx
        push dx ;bx
        push dx ;ax
        mov ds:[bx], ss
        mov ds:[bx + 2], sp 
    Back:
        mov sp, bp
        pop bp
        pop cx
        pop bx
        pop ax
        ret

func:
        xor di, di ;di是计数器
    _loop:
        call delay

        inc di
        mov ax, di
        call calculate
        call print

        jmp _loop

exit:
        cli
        mov ax, 0
        mov es, ax
        mov ax, data
        mov ds, ax
        
        push ds:[0]
        pop es:[9 * 4]
        push ds:[2]
        pop es:[9 * 4 + 2]
        sti
        mov ax, 4c00h
        int 21h

;delay函数空循环 0x7fffff 次
delay: 
        push ax
        push cx
        mov cx, 7
    s1:
        mov ax, 0ffffh
    s2:
        dec ax
        jnz s2

        dec cx
        jnz s1

        pop cx
        pop ax
        ret

;es:si目标显存地址,十位在ch,个位在cl
print:
        add ch, '0'
        add cl, '0'

        mov byte ptr es:[si], ch
        mov byte ptr es:[si + 1], 21h
        mov byte ptr es:[si + 2], cl
        mov byte ptr es:[si + 3], 21h

        ret

;参数值放在ax
calculate:
        push bx
        
        mov bl, 10

        div bl
        mov cl, ah

        xor ah, ah
        div bl
        mov ch, ah

        pop bx
        ret


;初始化中断例程
init:
        push ax

        mov ax, data
        mov ds, ax

        mov ax, 0
        mov es, ax

        cli     ;关中断修改中断处理程序
        push es:[9 * 4]
        pop ds:[0]
        push es:[9 * 4 + 2]
        pop ds:[2] ;保存原来的9号中断例程地址

        mov word ptr es:[9 * 4], offset int9 ; 将键盘中断处理程序指向自己编写的例程
        mov es:[9 * 4 + 2], cs
        sti

        mov ax, 0b800h ;指向显存
        mov es, ax

        pop ax
        ret

;恢复之间的中断例程
restore:
        push ax

        mov ax, 0
        mov es, ax
        mov ax, data
        mov ds, ax
        cli
        push ds:[0]
        pop es:[9 * 4]
        push ds:[2]
        pop es:[9 * 4 + 2]
        sti

        pop ax
        ret

; ax bx cx dx si di es ds bp ip cs psw
; 0  2  4  6  8  10 12 14 16 18 20 22  24
;                            ↑sp此时指向这里
save_all macro
        cli
        push bp
        push ds
        push es
        push di
        push si
        push dx
        push cx
        push bx
        push ax

        mov bx, offset Current
        mov al, ds:[bx]

        cmp al, 1
        jne cmp2
        mov bx, offset A_SS_SP
        jmp update_ss_sp
    cmp2: 
        cmp al, 2
        jne cmp3
        mov bx, offset B_SS_SP
        jmp update_ss_sp
    cmp3:
        cmp al, 3
        jne DoNotSave
        mov bx, offset C_SS_SP
    update_ss_sp:

        mov ds:[bx], ss
        mov ds:[bx + 2], sp
    DoNotSave:
        endm

restore_all macro
        pop ax
        pop bx
        pop cx
        pop dx
        pop si
        pop di
        pop es
        pop ds
        pop bp
        sti
        iret
        endm

switch macro
        cmp al, 3 ; 3
        je swC
        cmp al, 2 ; 2
        je swB
        cmp al, 1 ; 1
        je swA
        jmp done

    swA:
        mov bx, offset A_SS_SP
        jmp __switch
    swB:
        mov bx, offset B_SS_SP
        jmp __switch
    swC:
        mov bx, offset C_SS_SP

    __switch:
        mov bp, offset Current
        mov ds:[bp], al

        mov ax, ds:[bx]
        mov cx, ds:[bx + 2]
        mov ss, ax
        mov sp, cx
        
    done:
        endm

int9:
        save_all
        
        in al, 60h
        dec al ;从而按下1234 获得的扫描码就是1234
        
        pushf
        call dword ptr ds:[0];调用系统自带的例程


        switch

        cmp al, 5 ;5
        je stop

        jmp int9ret

    stop:
        call exit
    int9ret:
        restore_all


code ends
end start

请按照题目思路,分析程序的运行,重点是如何实现栈的切换。
这是我的答案:
我用一个例子来说明一下代码的执行逻辑和流程吧:程序在执行主程序的死循环时按下1切换到A,在A运行的时候又按下2切换到B,接着在B运行的时候按下1回到A。

1) 程序先是把stack段的地址送到段寄存器ss中,然后用mov sp,offset UniStack和add sp,128把栈指针知道UniStack段的栈顶。这是stack段的栈分布图:

UniStack(128个字节)
StackC(128个字节)
StackB(128个字节)
StackA(128个字节)

2) 然后call init初始化中断例程

3) call init_state初始化StackC、StackB、StackA。这三段栈空间的初始化过程都差不多,不同之处在于sp、cx寄存器的值,cx寄存器用于表示三个函数计数器在显存中的位置。初始化后,StackA段的栈分布图为(StackB、StackC段的相同):

在这里插入图片描述

而且这时候data段的A_SS_SP,B_SS_SP,C_SS_SP 三个段的内容为:
在这里插入图片描述

4) 然后就是主程序的死循环了

5) 按下1,开始运行int9中断例程,先save_all,这时候我们保存的是主程序的运行状态,不是中断后跳转到的A函数,所以这时候就不分析UniStack段的结构了

6) mov bx,offset Current和mov al,ds:[bx]记录当前被中断的程序,这时候al为0,三个cmp条件都不符合,所以直接跳到DoNotSave,结束这段宏,回到中断例程,然后读取输入+调用例程,接着进入switch段,满足cmp al,1,可以跳到swA 中,mov bx,offset A_SS_SP把A的栈段信息送入bx中保存,然后跳转到__switch:mov bp, offset Current和mov ds:[bp], al这两条指令就是更改Current处的信息,这时候我们就把这里的值更改为1了,表示当前正在运行的函数是函数A。下面这四条指令的作用就是把ss,sp切换到A的栈中。因为这时候ds:[0]和ds:[2]两个地址存的数据就是A_SS_SP中的A栈段的SS和SP信息。
mov ax, ds:[bx]
mov cx, ds:[bx + 2]
mov ss, ax
mov sp, cx
这样我们就实现了栈的切换

7) 然后回到中断例程中执行jmp int9ret指令,也就是执行restore_all段的指令:执行9个pop指令+开中断+iret。我们回到init_state段中可以看到栈顶的第10个位置存的刚好是offset func,也就是func函数的起始地址,第11个就是cs的值,我们第12个就是程序标记字,我们用iret指令刚好能得到这三个值。在这里,A函数是初次被调用,所以它的IP值就是func的起始地址,也就是第一条指令的位置。这样我们就可以开始执行A函数。这时候stack段中,A栈段没什么内容,但是B、C栈段仍保持刚刚初始化的样子。

8) A运行的时候如果按下2,那么它会切换到B函数。切换过程先是把A 函数的PWS,CS,IP压入A的栈段中,然后把其余寄存器压入,A的栈段为:

在这里插入图片描述

9)执行save_all段中的mov bx, offset Current和 mov al, ds:[bx]后,al存的数据为1,然后mov bx,offset A_SS_SP 和jmp update_ss_sp,就是用来更新A_SS_SP处的栈地址以及栈顶指针,最后结束宏,回到中断例程中进行切换。从A切换到B的过程与从主函数切换到A的过程类似。

10)B函数运行时,如果按下1,会切换回A函数。B函数状态的保存与A函数的保存过程类似,主要来分析一下A函数的恢复。恢复是在restore_all段里。

11)A是被中断的函数,那么我们在从A切换到B时save_all段里已经push了9个寄存器,注意,在save_all段开始push前,已经把PWS,CS,IP依次入栈了,这时候我们就实现了保存A函数被中断时的IP值。然后我们回到A函数时就可以通过switch段切换栈段,然后在restore_all段里得到这个函数的状态。

12)综上,我们保存被中断函数的状态是在save_all段里。切换栈段是在switch段里,恢复以及运行函数是在restore_all段里。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值