学习过C语言的人都知道,写一个可执行的C语言程序,首先就要写一个名字叫作main的函数。这个函数是C程序的入口。程序的运行就是从main函数开始的。但是,是谁调用了这个main函数?它的参数是如何传入的?它的返回值又传递给了谁?这些问题的答案将随着我们学习的进程慢慢的展现出来。在本节中,我们只来讨论两个问题:
C语言的函数在内存是以什么样子存在的?
C是谁调用了main函数?
首先,我们先来写一个C语言程序,程序源代码文件为main.c。这个程序非常简单,里面只有2个函数,一个函数叫作main,另一个函数叫作myfunc。程序代码如下,并编译和反汇编:
void myfunc()
{
}
void main()
{
myfunc();
}
编译main.c并生成main可执行文件,再对main反汇编,将反汇编的结果写入文件main.S当中去。这两个步骤的执行命令如下:
gcc -m32 main.c -o main
objdump -S -D -m i386 -M att main > main.S
执行完毕之后,我们使用文本编辑器打开main.S文件,在其中搜索<main>或者<myfunc>就可以看到main和myfunc这两个C函数的反汇编代码:
080483eb <myfunc>:
80483eb: 55 push %ebp
80483ec: 89 e5 mov %esp,%ebp
80483ee: 5d pop %ebp
80483ef: c3 ret
080483f0 <main>:
80483f0: 55 push %ebp
80483f1: 89 e5 mov %esp,%ebp
80483f3: e8 f3 ff ff ff call 80483eb <myfunc>
80483f8: 5d pop %ebp
80483f9: c3 ret
第一列为elf可执行文件的内存偏移地址,我们先不去管它,这部分内容将在后续章节里学习;第二列为程序可以被CPU识别的机器码;第三列是这些机器码代表的汇编指令。
可以看到在main函数中执行了call <myfunc>也就是在main函数中调用了myfunc函数。那么又是谁调用的main函数呢?我们看到main函数和myfunc函数的反汇编代码,发现他们并没有什么本质区别,只不过是函数名不同罢了,C语言规定C程序的入口函数就是main,那么,我们只需要自己写一个汇编指令来调用一个叫main的函数就可以了。比如:
calll main
当然,你也可以使用其它函数来做为你C程序的入口,比如事先定义了一个函数叫myfunc然后写一个汇编指令来调用它:
calll myfunc
这里需要说明一下,我们采用的编译方式是先将.S和.c文件使用gcc分别编译成.o文件,再通过ld命令将这些.o文件链接在一起,所以在ld链接时.S里的汇编指令可以定位到.c文件中的C语言函数。比如:
在main.c文件中定义
void main()
{
}
在boot.S文件中调用main
calll main
学会了如何使用汇编指令来调用C语言函数,我们就可以有一个很大的进展了,也就是说,以后的内核程序我们都可以采用C语言来实现,而不需要再去写冗长的汇编指令代码了。
上一节我们讲到,boot程序将自身复制到0x90000处并跳转到0x90000处执行,再将0x9c00处的系统内核程序复制到0x0处。当这些工作完成之后,我们就需要写一条指令来调用C程序的main函数了,这样我们就可以使用C语言来编写我们的操作系统内核了。
在工程中boot目录下添加main.c文件,并在include/boot下添加main.h头文件,如下图:
在main.h中声明main函数:
int main(int argc, char **args);
在main.c中定义我们的主函数main:
#include <boot/code16.h>
#include <boot/main.h>
int main(int argc, char **args)
{
for (;;)
{
}
return 0;
}
这里我们除了include <main.h>之外还,使include <code16.h>,这个code16.h里只有一个语言,如下:
__asm__(".code16gcc");
__asm__表示在C语言里嵌入汇编指令,这里的意思是告诉gcc编译器,我们现在写的main.c文件在编译时要采用16位的操作数和寻址模式。
另外在这个main函数中我们使用了一个死循环:for(;;){}让我们main函数在执行到这里时不再做任何事。
在boot.S中调用main.c中的main函数:
calll main
为了让运行结果一目了然,我们在main函数中加入了一段测试代码,它的作用是在显示器上显示一个‘a’字符,表示程序已经成功的进入main函数:
int main(int argc, char **args)
{
//测试,如果显示字符a说明main已经执行
__asm__(
"movw $0xb800, %ax \n\t"
"movw %ax, %es \n\t"
"movw $0x0, %ax \n\t"
"movw %ax, %di \n\t"
"movw $0x0761, %ax \n\t"
"movw %ax, %es:(%di) \n\t"
);
for (;;)
{
}
return 0;
}
另外别忘记修改Makefile文件,将main.c加入boot程序的编译环境中,执行make all命令。启动VirtualBox可以看到新的运行结果,显示器上成功的显示了‘a’字符,运行结果正确:
源代码的下载地址为:
https https://github.com/magicworldos/lidqos.git
git git@github.com:magicworldos/lidqos.git
subverion https://github.com/magicworldos/lidqos
branch v0.5
Copyright © 2015-2023 问渠网 辽ICP备15013245号