跟我一起写操作系统

    返回首页    发表留言
本文作者:李德强
          第二节 进入main函数
 
 

        学习过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-2018 问渠网 辽ICP备15013245号