![11123915f717964babd5ca610ebb9d94.png](https://img-blog.csdnimg.cn/img_convert/11123915f717964babd5ca610ebb9d94.png)
操作系统是管理计算机硬件与软件资源的计算机程序,同时也是计算机系统的内核与基石。操作系统需要处理如管理与配置内存、决定系统资源供需的优先次序、控制输入设备与输出设备、操作网络与管理文件系统等基本事务。操作系统也提供一个让用户与系统交互的操作界面。本系列希望能够长期更新,对操作系统的原理逐一的进行实现。
一、内容介绍
本小结的内容相对较少,主要是直观的感受一下C语言是怎么引入的(目前的代码还存在许多问题,后面会慢慢完善)。另外简单介绍一下如何用gdb调试我们的二进制代码。
二、引入C语言
先看一下代码:
主要文件有5个,mbr.s(和前面的一样,没有修改,不再粘贴了),boot.s(修改了读写扇区的大小,将C语言部分的代码加载到内存中,另外添加了跳转,跳转到C语言代码段执行),main.c(这个是今天写的两个C语言函数,主要功能就是通过指针来修改显存对应内存的值,从而能够打印字符到显示器上),Makefile 和 linker.ld 完成文件的编译连接和执行。
boot.s
/*
* Title: bootloader
*/
.set KERNEL_SIZE, 2 # number of 512 byte blocks
.set KERNEL_ADDRESS, 0x8200 # load kernel to this address
.section .text
.code16
_restart:
movw %cs, %ax
xorw %ax, %ax # setup stack and segments
movw %ax, %ds
movw %ax, %es
movw %ax, %ss
movb $0x00, %bh
movb $0x00, %dh
movb $0x00, %dl
call set_cursor2
call clear_screen2
movw $0x07d0, %cx #size of char 80*25 0x50*0x19
movb $0x00, %bh
movb $0x20, %al
movb $0x3f, %bl
movb $0x09, %ah
int $0x10
movw $msg2, %ax
movw %ax, %bp
movw len2, %cx
movb $0x0d, %dh
movb $0x1c, %dl
movb $0x01, %al
movb $0x13, %ah
movb $0x3f, %bl
movb $0x00, %bh
int $0x10
movw $msg1, %ax #
movw %ax, %bp
movw len1, %cx #
movb $0x0c, %dh
movb $0x21, %dl
movb $0x01, %al
movb $0x13, %ah
movb $0x3f, %bl
movb $0x00, %bh
int $0x10
load:
movw $0x00, %dx #dh->head,dl->driver
movw $0x0820, %ax
movw %ax, %es
xorw %bx, %bx #es:bx->the data 0x0820:0000
movb $0, %ch #ch->cidaohao
movb $4, %cl #cl->sector
movb $2, %ah #param
movb $8, %al #how many blocks to read
int $0x13
jnc gotonext
jmp load
gotonext:
movb $0x86, %ah
movw $0x001e, %cx
movw $0x8480, %dx
int $0x15
call a20_enable
_end1:
jmp preswitch
set_cursor2:
movb $0x02, %ah
int $0x10
ret
clear_screen2:
movb $0x06, %ah #
movb $0, %al #
movb $0, %ch #
movb $0, %cl #
movb $24, %dh #
movb $79, %dl #
movb $0x07, %bh #
int $0x10
ret
a20_enable:
call a20_wait
movb $0xd1, %al # write command
outb %al, $0x64
call a20_wait
movb $0xdf, %al # enable a20 movb $0xdd, %al # disable a20
outb %al, $0x60
call a20_wait
ret
a20_wait:
inb $0x64, %al
testb $2, %al
jnz a20_wait
ret
preswitch:
cli
lgdt gdt_pointer
movl %cr0, %eax
orl $1, %eax
movl %eax, %cr0
movl $init_prot, %eax
ljmp $0b1000, $init_prot
.code32
.extern kernal
init_prot:
movw $0b10000, %ax
movw %ax, %ds
movw %ax, %es
movw %ax, %fs
movw %ax, %gs
movw %ax, %ss
#jmp kernal
ljmp $0b1000, $0x8200 # jump to C file
#movl $hw, %edi # print string in code32
#call puts
myend:
jmp myend
puts:
call clear
xorl %ecx, %ecx
putschar:
movb (%edi,%ecx), %dl
test %dl, %dl
jz putsend
movb $0x9f, %dh
movw %dx, (%ebx,%ecx,2)
incl %ecx
jmp putschar
putsend:
ret
clear:
xorl %ecx, %ecx
movl $0xb8000, %ebx
clearchar:
movb $0x20, %dl
movb $0x9f, %dh
movl $0x0f200f20, (%ebx,%ecx,4)
incl %ecx
cmpl $1000, %ecx
jl clearchar
ret
gdt:
.long 0,0
# code segment
.word 0xffff # limit (lower 16 bits)
.word 0x0000 # base (lower 16 bits)
.byte 0x00 # base (next 8 bits)
.byte 0b10011010 # access byte
.byte 0b11001111 # granularity byte
.byte 0x00 # base (last 8 bits)
# data segment
.word 0xffff # limit (lower 16 bits)
.word 0x0000 # base (lower 16 bits)
.byte 0x00 # base (next 8 bits)
.byte 0b10010010 # access byte
.byte 0b11001111 # granularity byte
.byte 0x00 # base (last 8 bits)
gdt_end:
gdt_pointer:
.word gdt_end - gdt - 1 # size of GDT-1
.long gdt # GDT offset
hw:
.string "Hello, World!"
msg1:
.asciz "I'm bootloader!"
len1:
.int . - msg1
msg2:
.asciz "My job is loading our OS!"
len2:
.int . - msg2
.fill 0x1fe - (. - _restart) ,1,0
.word 0xaa55
main.c (目前没有任何头文件,因为我们是从0开始写的,除了GCC编译器支持的语法外,我们没有任何库函数可以使用)
void cleanscreen();
void writechar();
int kernalmain()
{
cleanscreen();
writechar();
while(1);
return 0;
}
void cleanscreen()
{
unsigned int base = 0xb8000;
for(int i=0; i<1024; ++i)
{
unsigned int *num = (unsigned int *)(base+i*4+1);
*num = 0x0f200f20;
}
}
void writechar()
{
char str[30]={'W', 'e', ' ', 'a', 'r', 'e', ' ', 'n', 'o', 'w',' ', 'i', 'n', ' ',
't', 'h', 'e', ' ', 'k', 'e', 'r', 'n', 'a', 'l', ' ', 'C', '!', '!', '!', '0'};
unsigned char * putschar = (void *)(0xb8000);
int i=0;
while(str[i]!='0')
{
putschar[i*2+1]=str[i];
i++;
}
}
linker.ld
ENTRY(main)
SECTIONS
{
. = 0x007c00;
.text :
{
mbr.o(.text)
. += 0x000200;
boot.o(.text)
main.o(.text)
}
.data :
{
*.o(.data)
}
}
makefile
GCCPARAMS = -nostartfiles -e kernalmain
objects = mbr.o boot.o main.o
inputs = main.c
main.o:
gcc $(GCCPARAMS) -o $@ $(inputs)
%.o: %.s
as -o $@ $<
mykernel.bin: linker.ld $(objects)
ld $(objects) --oformat=binary -o $@ -T $<
run: mykernel.bin
qemu-system-i386 -fda $< -boot a -monitor stdio #-S -s #
clean:
rm -f ./*.o ./*.bin
make run运行,我们会看到C语言打印出来的字符。但是目前的程序还存在很大的问题,我们并没有关心数据段在内存是怎么运作的,所以我们目前的代码不能处理全局变量或者静态变量。后面会一点点加进来。
这节其实还是在理解保护模式下,代码间是怎么跳转的。ljmp指令会修改段寄存器和IP指针的值,修改的过程上一节提到的视频讲解中讲的很清楚。
三、调试
上一节我们提到使用monitor stdio来显示寄存器信息(info registers),此外我们也可以查看内存数据(比如 x /10x 0x8b000, 表示以16进制形式显示从0x8b000开始的10的32位内存数据)以及一些其他信息。 但是当我的程序存在不确定问题,能不能单步调试我们的代码呢?目前我还没找到完美的单步调试的方法,但是gdb调试qemu程序,可以实现断点调试,只不过我们的断点是内存位置,(而且存在一个缺陷,一旦我们的断点位置不合适,将连贯指令分离,后面的程序将无法运行),不是太好用,但还是记录一下。
首先qemu支持gdb远程调试,只需要添加-s命令,默认通过1234端口与gdb链接。qemu启动后,在运行代码前会通过1234端口给gdb发送信号,等待gdb的调试指令。因为启动qemu后,我们可以在新的命令行启动gdb,然后输入target remote localhost:1234,和qemu完成连接,然后我们就可以使用gdb的命令来调试内核程序了。在使用时,next下一行等一些指令不太能用,因为我们的二进制中没有下一行的边界限定,gdb无法确定到底要执行哪些命令,只能通过在内存中打断点的方式,确实不太方便。如果以后碰到更好的方法再跟新 。
下节会考虑数据段,代码段在内存中如何安排,此外通过I/O来控制光标位置和打印字符,实现一个简单的显示器驱动功能。
以后代码会慢慢变多,一点点粘出来不太现实,会考虑放到Git上。文章只简单记录一下这节需要重要注意的地方。