【OS课设日志】《Orange‘S:一个操作系统的实现》第五章——内核雏形
实验内容:在Linux下用汇编写Hello, World!;进一步,汇编和C同时使用;从loader到kernel内核,把kernel内核加载到内存;将控制权交给kernel内核;跳入保护模式,并显示内存的使用情况。
5.1 在Linux下用汇编写Hello World
实验步骤
①创建hello.asm
文件
②编译链接(每执行一步都用ls
命令查看文件的增加情况)
> ls
hello.asm
> nasm -f elf hello.asm -o hello.o # 指定输出文件的格式为ELF格式,hello.o为01文件
> ls
hello.asm hello.o
> ld -s hello.o -o hello # 将 hello.o与系统库文件连接,并生成可执行文件 hello
!
①执行最后一条指令时报错
\textcolor{red}{!①执行最后一条指令时报错}
!①执行最后一条指令时报错
上面为Linux系统下执行汇编文件的操作过程(编译 链接 执行)
5.2 汇编和C同时使用
源文件包含foo.asm
和bar.c
,其调用关系如下:
本例重点是汇编代码和C代码之间相互调用
实验步骤
①将foo.asm
和bar.c
文件编译为.o目标文件
②将bar.o
和foo.o
文件链接为可执行foobar
文件
!
②执行指令再次出现不兼容问题
\textcolor{red}{!②执行指令再次出现不兼容问题}
!②执行指令再次出现不兼容问题
解决后界面如下
③执行foobar
文件
./foobar
重点就是关键字global
和extern
!!!!!!!!!!
- 汇编文件中,
global
是一个汇编符号,用于声明一个全局变量,当一个变量被声明为global时,它可以被程序中的任何模块访问 - C语言中,
extern
是关键字,用于声明一个变量,表明这个变量并不是在这里声明的,而是在别的文件中定义的。使用extern
关键字可以让多个文件共享同一个变量
5.3 ELF(Executable and Linkable Format)
ELF header
:是一个描述文件本身的结构体,它包含了文件的基本信息,如文件类型、机器类型(目标文件所运行的机器平台)、入口地址、程序头表和节头表等信息。Program header table
:段表,描述一个段在文件中的位置、大小以及它被放进内存后所在的位置和大小
在Linux系统中,当一个ELF文件被加载到内存中时,操作系统会根据Program header table中的信息将文件中的各个段映射到内存中的相应位置,并设置相应的权限。这样,程序就可以正确地执行了
- 暂时告一段落
5.4 从Loader到内核
Loader的工作:
- 加载内核到内存
- 跳入保护模式
5.4.1 用Loader加载ELF
文件介绍:
fat12hdr.inc
:包含了FAT12文件系统的头文件,其中定义了FAT12文件系统的各种数据结构和常量boot.asm
:装入引导扇区的启动程序loader.asm
:将内核放进内存
第一次运行:
①创建新软盘a.img
②将boot.bin
装载到引导扇区:
nasm boot.asm -o boot.bin
dd if=boot.bin of=a.img bs=512 count=1 conv=notrunc
③将kernel.bin
装载到软盘
nasm -f elf -o kernel.o kernel.asm
ld -m elf_i386 -s -o kernel.bin kernel.o
sudo mount -o loop a.img /mnt/floppy/
sudo cp kernel.bin /mnt/floppy/ -v
sudo umount /mnt/floppy/
④运行报错
loader没装进去
⑤装载loader
⑥再次运行,完成
5.4.2 跳入保护模式(文件夹d)
代码改变:
第三章中程序由BIOS或DOS加载,描述符的段基址都是运行时计算后填入相应的位置,我们不知道段地址,于是也就不知道程序运行时在内存中的位置
现在Loader由我们自己加载,文件中定义段地址为BaseOfLoader
即,在loader中增加跳入保护模式的代码
实验步骤
同5.4.1节一模一样
运行结果:
5.4.3 重新放置内核(文件夹e)
我们要做的工作是根据内核(ELF文件格式)的 Program header table 的信息进行类似下面这个C语言语句的内存复制
memcpy(p_vaddr, BaseOfLoaderPhyAddr + p_offset, p_filesz);
函数解释:从源内存地址的起始位置开始拷贝若干个字节到目标内存地址
参数解释:
- p_vaddr:目标内存地址
- BaseOfLoaderPhyAddr + p_offset:源内存地址
- p_filesz:需要拷贝的字节数
实验步骤
①编译链接程序时,将程序入口地址改为0x30400
nasm -f elf -o kernel.o kernel.asm
ld -m elf_i386 -s -Ttext 0x30400 -o kernel.bin kernel.o
内核放置函数为
memcpy(3000h, 9000h + 0, 40Dh);
②查看kernel.bin
的内容
xxd -u -a -g 1 -c 16 kernel.bin
5.4.4 向内核交出控制权
使用jmp指令跳转到事先设定的物理内存地址即可
5.5 扩充内核
5.5.1 切换堆栈和GDT(文件夹f)
呃呃呃
附录:Bug
①ld: i386 架构于输入文件 hello.o 与 i386:x86-64 输出不兼容
原因分析
输入文件hello.o
编译为 i386 架构,与输出架构 i386:x86-64 不兼容。这可能是因为编译器和链接器正在尝试将不同架构的目标文件进行链接,导致链接失败。
解决办法
更改输入指令为
ld -m elf_i386 -s -o hello hello.o
其中-m 选项指定目标文件的架构为 elf_i386,与输入的hello.o
文件架构匹配
参考
②问题同上
原因分析
错误提示是由于foo.o
和bar.o
的架构不同,foo.o
是 i386 架构,而 bar.o
是 x86-64 架构
解决办法
将文件全部编译为i386框架下的.o
文件,再进行链接,即执行下述指令
nasm -f elf -o foo.o foo.asm
gcc -m32 -c bar.c -o bar.o
ld -m elf_i386 -s -o foobar foo.o bar.o