实现打印函数

第六章:完善内核
参考书籍:《操作系统 真象还原》 作者:郑钢
这一章,没有什么难点。主要是实现一个打印函数。


1 、打印函数

首先,打印函数,打印的内容要显示在屏幕上,肯定要调用硬件。所以,这部分功能,肯定要放在内核之中。用户进程只能调用。用户进程的权限是3,系统内核的权限是0。所以肯定涉及到权限的转换。但是这一部分我还不会。虽然在上一章节说过,但是还是不懂。因为本章,暂时不涉及到权限转换,固暂时不管。
至于打印函数。我们要显示的东西,必定是放入显存。控制的时候,是控制显卡的端口。代码中使用了,光标的位置寄存器~~(,起始位置寄存器)~~。我们每次看到的是一个屏幕大小的内存内容。但是我们还可以向上滚动。这说明显存前面的内容还在。我们只需要改变显示的起始位置就好。(但是,咱们的代码使用的方法是,将内容向上复制一行,简单点,但不是大道。)

代码见:https://github.com/da1234cao/tiny-os/tree/master/chapter6
首先实现的是打印一个字符。这中间要考虑换行,删除,滚动。
实现打印字符串和数字,则要调用打印字符。
在这里插入图片描述

2、在64位linux下编译32位程序

咱们的虚拟机中的内容是按照32位设计的。虽然配置文件中,没有明确是32位。
build.sh文件

sudo nasm -I include/ -o mbr.bin mbr.asm
sudo nasm -I include/ -o loader.bin loader.asm
sudo nasm -f elf -o lib/kernel/print.o lib/kernel/print.asm
#gcc -I lib/kernel/ -c -o kernel/main.o kernel/main.c
gcc -m32 -I lib/kernel/ -c -o kernel/main.o kernel/main.c #让gcc 产生32位的代码

#ld -Ttext 0xc0001500 -e main -o kernel/kernel.bin kernel/main.o lib/kernel/print.o
#链接的时候以32位的方式进行链接
ld -m elf_i386 -Ttext 0xc0001500 -e main -o kernel/kernel.bin kernel/main.o lib/kernel/print.o

sudo dd if=mbr.bin    of=/usr/share/bochs/hd60M.ing bs=512 count=1   seek=0 conv=notrunc
sudo dd if=loader.bin of=/usr/share/bochs/hd60M.img bs=512 count=4   seek=2 conv=notrunc
sudo dd if=kernel/kernel.bin of=/usr/share/bochs/hd60M.img bs=512 count=200 seek=9 conv=notrunc

gcc 不加-m32生成的是64位的程序。和nasm生成的32文件在链接的时候,会报错。

通过查阅资料,发现可以使用-m32来使得gcc编译32位程序(在x86_64系统上),使用-m elf_i386参数可以使得64位的ld能够兼容32位的库
我们可以用 man gcc |grep “m32”,来简单查看一下。但是grep显示的是一行一行的。(详细的可以去查看文件,我没查,不知道怎么看)
在这里插入图片描述

在ubuntu上如果要用 -m32 参数就要安装如下的库:

sudo apt-get install build-essential module-assistant
sudo apt-get install gcc-multilib g++-multilib

追问一下,这几个是什么包?为什么需要装这几个包,我不知道(见参考文章2)。
如果路过知道,麻烦告知一声。
为Ubuntu安装build-essential软件包
module-assistant 模块助手
gcc-multilib:这是GNU C编译器,一个相当便携的C优化编译器。

3、内联汇编

直接控制寄存器,肯定是汇编来的快。但是C语言是高级语言。所以,有时候,将汇编语言插在C语言中。
在内联汇编之前,我们还得先看先C语言中的函数传递。

int sub(int a,int b)
{
	return a-b
}
int ans=sub(3,2)

这个是高级语言,我们不需要考虑,传入的时候3,2传入哪里,又是如何调用的。
但是,C语言还是需要进过编译链接,然后在CPU上跑。
所以,这当中,编译器肯定帮我们干了些了不起的事情。
我们可以先将C语言换成等价的汇编语言看看。;

push 2 
push 3 ;压入参数
call subtract ;调用函数 subtract
push ebp 
mov ebp, esp 
mov eax, [ebp+8] 
sub eax, [ebp+12] 
pop ebp 

那么:
参数是如何被传递的(放置在堆栈上,或是寄存器中,亦或两者混合)
被调用者应保存调用者的哪个寄存器
调用函数时如何为任务准备堆栈,以及任务完成如何恢复
详细内容见:X86调用约定
当然,我推荐去书上看,详细易懂些。
在这里插入图片描述至于内联汇编,书上给出的是32位的,和64位的不一样(因为我编译,它提示 * is not supported in 64-bit mode)
64位的,我暂时用不到,偷点懒,没去查。所以,不知道64位的内联汇编和32内联汇编的差别在哪里。
虽然是32位的,但是有的思想,还是nice的:建立一个模板。

asm [volatile] ("assembly code" : output : input : clobber/modify )

GCC-内联汇编 : https://www.ibiblio.org/gferg/ldp/GCC-Inline-Assembly-HOWTO.html
详细内容见书上。

4、不定长参数的传入

我当时,好像在《Head First Java》中看到一个有趣的问题。
printf函数,是如何实现,传入不同的参数。
比如:

printf("%d,%d",a,b)
printf("%d,%d,%d",a,b,c)

首先想到的是,函数的重载。但是不同的输入方式,这个排列组合也太多,不能实现。
如果使用前面三点的知识,我们似乎可以实现这个printf函数。
(我简单google了下,没有查到,所以下面开启胡扯模式。)
首先,我们假设函数的参数传入栈中。根据("%d,%d",a,b),我们便可以知道压栈方式(内容和大小)。
接着,根据每个传入的方式,将他们放入显存,得以显示。
所以,这样便实现了不定长参数的传入。

参考文章:
1.操作系统真象还原 – ld: i386 架构于输入文件 lib/kernel/print.o 与 i386:x86-64 输出不兼容
2.在64位linux下编译32位程序

发布了104 篇原创文章 · 获赞 134 · 访问量 17万+

猜你喜欢

转载自blog.csdn.net/sinat_38816924/article/details/97560652