一步步写操作系统(三) C语言和asm语言的相互调用

一步步写操作系统(三)
3.C语言和asm语言的相互调用
前面的boot和loader都准备好了,就差kernel了。
首先我们要明确,kernel用什么语言写。boot和loader使用asm语言编写,是因为系统的特性,它需要最基本的元素来操作磁盘、端口、内存。然而如果kernel使用asm编写,对于开发难度来说是一个很大的挑战。虽然人生要面临很多挑战,我们都因该从容接受,但是对于只要了解操作系统的我们来说这个挑战无疑是浪费时间的,因为我们本来就是在重复造轮子。我们的期望只是,通过重复造出一个简单的轮子,来更加了解并熟练运用已有的轮子。所以不希望使用asm而是用C语言来编写。至于为什么不用C++,我已经在帖子 使用C语言的struct来实现C++的class中说明了,在很不明确C++编译器而又要使用编译器结果的地方,还是不要贸然使用C++。但是一些系统的底层操作,比如端口操作,以及中断门、调用门等,还是离不开asm的。接下来我们看一下C语言和asm语言的相互调用的用法:
a.使用__asm__来实现C调用asm
__asm__("asm" : "=out" (outParam) : "in"(inParam));
比如将port端口通过in读取数据:
__asm__("in %%dx, %%al" : "=a" (result) : "d"(port));
b.更一般的方法:
asm中使用global引出函数名,编译成.o文件,和使用这个函数名的.c文件编译成的.o文件进行连接,就可以实现调用:
system_lib.asm
[bits 32]
global disp_pos
global disp_str_asm
[section .data]
disp_pos dd 0xb8000
[section .text]
disp_str_asm:
   .....

lib.asm

extern unsigned int disp_pos;
void disp_str(char* str){
    disp_str_asm(str);
}

这样的代码,在编译器链接阶段会自动找到disp_str_asm和disp_pos标签。

c. C语言和asm语言的相互调用
使用中断门时需要调用汇编的中断处理子程序,子程序又会有一个回调,在这样一个互相调用的模式中,一方需要先声明,而asm是不可能包含这个外部声明的,所以可以将asm的声明放置到.h中:
system_call.asm
[bit 32]
global int_handler21_asm
[section .text]
[extern int_handler21]
; void int_handler21_asm()
int_handler21_asm:
    push ebp
    mov    ebp, esp
    
    call int_handler21
    
    leave
    iretd

system_call.h
extern void int_handler21_asm();
key.c
#include "../kernel/system_call.h"
void int_handler21() {
.....
}
interrupt.h
#include "../kernel/system_call.h"
void init_gdtidt() {
    .......
    // key IRQ 0x21
    set_gatedesc(idt + 0x21, (DWORD)int_handler21_asm, 1 * 8, 0x008e);
    .......
}

这里应该就能看明白了,asm编译出来的变量、函数label和c语言编译出来的label如出一辙,因此可以调用。而C++编译出来的并不是这种形式的label,因此不能调用。
-----------------------------------------------------------------------------------------------------------------------------------------------------------

使用上面b的例子,可以写一个简单的内核了

我们使用第一帖使用一步步写操作系统(二) Boot启动或者经过扩展的一步步写操作系统(二) Boot启动 解决boot过大的问题的代码进行引导,这里只给出kernel的代码。

我们将引导代码放到boot/文件夹中,kernel放到kernel/文件夹中,lib放到lib/文件夹中。

kernel.c
#include "../lib/lib.h"
int main() {
 disp_pos = 0xb8000;
 disp_str("Hello OS World");
}

system_lib.asm
[bits 32]
; system_lib.asm
; author stophin
;
; System lib functions, will be invoked by C kernel
[bits 32]
global    disp_pos, disp_base
global    disp_str_asm

[section .data]
disp_base    dd    0xb8000
disp_pos    dd     0xb8000
[section .text]
; void disp_str_col_asm(char *)
disp_str_asm:
    push ebp
    mov    ebp, esp

    mov esi, [ebp + 8]
    mov edi, [disp_pos]
    mov    ah, 0Fh
.1:
    lodsb
    test al, al
    jz    .2
    cmp al, 0Ah        ; return?
    jnz .3
    push eax
    mov eax, edi
    sub eax, [disp_base]
    mov    bl, 160        ; 80 * 2
    div bl
    and eax, 0FFh
    inc eax
    mov bl, 160
    mul bl
    add eax, [disp_base]
    mov edi, eax
    pop eax
    jmp .1
.3:
    mov [edi], ax
    add edi, 2
    jmp .1
    
.2:
    mov [disp_pos], edi
    
    mov esp, ebp
    pop ebp

    ret
lib.c
extern unsigned int disp_pos;
void disp_str(char* str){
    disp_str_asm(str);
}

然后是最重要的一步,因为我们在连接所有.o文件时,不论是.asm生成的还是.c生成的都会按照ld参数顺序连接起来。我们希望从kernel的main开始,

但是有时候kernel也需要调用一些库,这些库需要连接在kernel前面否则找不到定义。那么我们可以使用一个特定的库,使得它被连接在kernel.bin的最前面,

然后让它调用kernel的main就可以了。这个库就是kernel_entry.asm

; kernel_entry.asm
; author stophin
;
; ensure that we jump straight into the kernel's entry function
[bits 32]					; we're in protected mode by now, so use 32-bit instructions
[extern main]				; declate that we will be referencing the external symbol main
							; so the linke can substitute the final address
	mov byte [0xb8000], 102
	call main				; invoke main() in our c kernel
	jmp $					; hang forever when we return from the kernel
首先打印一个'f'字符说明进入entry,然后调用唯一的kernel的main函数,进入kernel。

然后修改Makefile来制作操作系统:
# Automatically generate lists of sources using wildcard
SOURCES = $(wildcard kernel/*.c lib/*.c )
SYSTEMC = $(wildcard kernel/*.asm)
HEADERS = $(wildcard kernel/*.h lib/*.h)

# Convert the *.c filenames to *.o to give a  list of object files to build
# please notice the order of the object
# you can add external object which created by other tools
ENREY =  kernel/kernel_entry.o
EXTERNAL =  
OBJECT = $(SYSTEMC:.asm=.o) ${SOURCES:.c=.o}

# Output image directory
IMAGE_DIR = ./

# OS name
IMAGE = ${IMAGE_DIR}nano

# Default build target
all: image

# This is the actual disk image that the computer loads
# which is the combination of our compiled bootsector and kernel
image : boot/boot.bin boot/loader.bin ${IMAGE_DIR}kernel.bin
    cp $< ${IMAGE_DIR}
    cat $^ > ${IMAGE}.bin
    dd if=${IMAGE}.bin of=${IMAGE}.img bs=1440K count=1 conv=notrunc

# This builds the binary of our kernel from two object files:
# - the kernel entry, which jumps into main() in our kernel
# - the compiled C kernel
${IMAGE_DIR}kernel.bin : ${ENREY} ${EXTERNAL} ${OBJECT}
    ld -o ${IMAGE_DIR}kernel.elf -s -Ttext 0x9000 -e main -m elf_i386 $^
    objcopy -I elf32-i386 -O binary -R .note -R .comment -S ${IMAGE_DIR}kernel.elf $@

# Generic rule for compiling C code to an object file
# For simplicity, we C files depend on all header files
%.o : %.c ${HEADERS}
    gcc -O0 -ffreestanding -m32 -c $< -o $@

# Assemble the kernel entry
%.o : %.asm
    nasm $< -f elf32 -o $@

# Assemble binary
%.bin : %.asm
    nasm $< -f bin -o $@ -I boot/

# Clean
clean:
    rm -rf ${IMAGE_DIR}*.bin ${IMAGE_DIR}*.elf boot/boot.bin boot/loader.bin
    rm -rf ${ENTRY} ${OBJECT}


# White image
raw :
    dd if=/dev/zero of=${IMAGE}.img bs=1440K count=1
如上Makefile使用wildcard来自动生成编译对象,make一下应该就可以了,使用make clean和make raw初始化。(注意Makefile期望每个命令前面是Tab但本站Tab被转码为空格,所以要将空格转换为TAB再编译)不过这里使用的是-tText 0x9000,因此请使用 一步步写操作系统(二) Boot启动或者经过扩展的 一步步写操作系统(二) Boot启动 解决boot过大的问题的代码进行启动。
启动完成配置好bochs,运行会在屏幕上出现一个"Hello OS World"字样。
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值