《操作系统真象还原》第七章(2)——中断
前言
这篇博客分为三部分:完成第6章put_int函数,完成第7章中断函数并改进,前7章知识点拾遗。
上一篇博客连接:《操作系统真象还原》第七章(1)——中断-优快云博客
整数打印函数put_int
jg:有符号大于则跳转
jl:有符号小于泽跳转
section .data
put_int_buffer dq 0 ;8字节
global put_int
put_int:
pushad
mov ebp,esp
mov eax,[ebp+4*9]
mov edx,eax ;待处理的16进制整数
mov edi,7 ;初始在缓冲区的偏移量,逆向存储,正向读取
mov ecx,8 ;8字节循环8次
mov ebx,put_int_buffer
.16based_4bits: ;按位处理16进制数字并逆序
and edx,0x0000000f
cmp edx,9
jg .is_A_F ;大于9,是16进制a-f,转化成相应的字符
add edx,'0' ;如果小于9,是数字,转化成字符0-9
jmp .store
.is_A_F:
sub edx,10
add edx,'A'
.store:
mov [ebx+edi],al
dec edi ;缓冲区指针自减
shr eax,4 ;右移4位相当于16进制右移一位,最低位变成下一个要处理的数字
mov edx,eax ;转移给edx
loop .16based_4bits
.ready_to_print:
inc edi ;目前是-1,经过这一步变成0
.skip_prefix_0: ;去除前导零
cmp edi,8
je .full0 ;全0
.go_on_skip:
mov cl,[put_int_buffer+edi]
inc edi
cmp cl,0
je .skip_prefix_0 ;如果本位是0,返回,检查下一位是不是0
dec edi ;本位不是0,需要把指针回溯
jmp .put_each_num
.full0:
mov cl,'0' ;全0只需要打印一个0
.put_each_num:
push ecx
call put_char
add esp,4
inc edi
mov cl,[put_int_buffer+edi]
cmp edi,8
jl .put_each_num
popad
ret
内联汇编
c语言只支持at&a语法。
intel和at&a汇编风格对比
基本内联汇编
模板:asm [volatile] ("assembly code")
说明:
- asm用于声明是内联汇编表达式。
- volatile可选写不写,作用是声明不要进行O优化,原样保留我的代码。
- 整个代码体必须被括号括起来。
- 指令必须被双引号括起来。
- 除了最后一个指令,每个指令结束必须要用
;
或\n
或\n\t
间隔。 - 一对双引号不能跨行,如果跨行用反斜杠
\
转义。 - 如果汇编代码要引用c语言变量,c语言变量必须定义为全局变量
扩展内联汇编
扩展内联汇编解决两个问题:
- 在内联汇编代码插入点之前的 C 代码,其编译后也要被分配寄存器等资源,插入的汇编代码也要使用寄存器,这是否会造成资源冲突?
- 汇编语言如何访问 C 代码中的变量?
模板:asm [volatile] (“assembly code” :output :input :clobber/modify)
新增部分说明:
-
圆括号里4部分均可省略,省略要保留:占位,但是如果省略了后面一部分或者连续的多部分,可以不保留:。
-
output: output用来指定汇编代码的数据如何输出给 C 代码使用。内嵌的汇编指令运行结束后,如果想将运行结果存储到 c 变量中,就用此项指定输出的位置。output 中每个操作数的格式为:
“操作数修饰符约束名”(C 变量名)
其中的引号和圆括号不能少,操作数修饰符通常为等号’=‘。多个操作数之间用逗号’,'分隔。
-
input:input 用来指定 C 中数据如何输入给汇编使用。要想让汇编使用 C 中的变量作为参数,就要在此指定。input 中每个操作数的格式为:
“[操作数修饰符] 约束名”(C 变量名)
其中的引号和圆括号不能少,操作数修饰符为可选项。多个操作数之间用逗号’,'分隔。
-
单独强调一下,以上的 output()和 input()括号中的是 C 代码中的变量,output(c 变量)和 input(c 变量)就像 C 语言中的函数,将 C 变量(值或变量地址)转换成汇编代码的操作数。
-
clobber/modify:汇编代码执行后会破坏一些内存或寄存器资源,通过此项通知编译器,可能造成寄存器或内存数据的破坏,这样 gcc 就知道哪些寄存器或内存需要提前保护起来
-
assembly code 中引用的所有操作数其实是经过 gcc 转换后的复本,“原件”都是在 output 和 input 括号中的 c 变量
操作数:寄存器名、内存地址、立即数等
约束:用来描述c语言操作数如何变为汇编语言操作数,分为寄存器约束、内存约束、立即数约束、通用约束四类,具体看p285页。
占位符:用来引用汇编操作数,分为序号占位符和名称占位符。(个人感觉可以类比c语言prinf、scanf里面的占位符)
序号占位符:%1、%2等,对应后面input里面的操作数
名称占位符:相当于“引用”,格式是[名称]"约束名"(C 变量)
,使用时直接%[名称]。
操作数修饰符:这部分请看P290页。
clobber/modify:声明汇编代码修改内存/寄存器等,P293页。
实现中断程序
先贴一下刚弄完没调试时的情况吧。idt表没问题,但是系统打印部分有问题。
有几个问题:1.滚屏出错,看不到int vector 0x20 2.光标位置出错
最后完善好的中断程序截图,完善了IDT和时钟相关。
顺带写一下编译相关代码
gcc -m32 -I lib/kernel/ -m32 -I lib/ -m32 -I kernel/ -c -o build/timer.o device/timer.c
gcc -m32 -I lib/kernel/ -m32 -I lib/ -m32 -I kernel/ -c -fno-builtin -o build/main.o kernel/main.c
gcc -m32 -I lib/k ernel/ -m32 -I lib/ -m32 -I kernel/ -c -fno-builtin -o build/interrupt.o kernel/interrupt.c
gcc -m32 -I lib/kernel/ -m32 -I lib/ -m32 -I kernel/ -I device/ -c -fno-builtin -o build/init.o kernel/init.c
nasm -f elf -o build/print.o lib/kernel/print.S
nasm -f elf -o build/kernel.o kernel/kernel.S
ld -m elf_i386 -Ttext 0xc0001500 -e main -o build/kernel.bin build/main.o build/init.o build/interrupt.o \
build/print.o build/kernel.o build/timer.o
dd if=/home/hongbai/bochs/build/kernel.bin of=/home/hongbai/bochs/bin/c.img bs=512 count=200 seek=10 conv=notrunc
cd bin
./bochs -f bochsrc.disk
结语
第七章cv的代码不少,大家可以参考love6的博客。
等第八章学完会写一下上周的周报总结。