上一篇分析了c语言的函数调用栈情况,知道了c语言的函数调用机制后,我们来看一下,linux0.11中起动部分的代码是如何从汇编跳入c语言函数的。在LINUX 0.11中的head.s文件中会看到如下一段代码(linux0.11的启动分析部分会在另一部分中再分析,由于此文仅涉及c与汇编代码的问题,)。

after_page_tables:
pushl $ # These are the parameters to main :-)
pushl $
pushl $
pushl $L6 # return address for main, if it decides to.
pushl $main
jmp setup_paging

这段代码执行后程序的执行流程会跳转到main函数中运行(init/main.c),通过上一篇文件的c语言的汇编代码我们可以知道,main.c函数经过汇编后会生成main符号(汇编语言),而且是全局可见的。所以这里的pushl $main就是将main符号(函数)的地址压入栈中,然后在跳到setup_paging去执行,进行内存的全局页目录的处理,为进入保护模式做最后的准备,代码如下。

setup_paging:
movl $*,%ecx /* 5 pages - pg_dir+4 page tables */
xorl %eax,%eax
xorl %edi,%edi /* pg_dir is at 0x000 */
cld;rep;stosl
movl $pg0+,pg_dir /* set present bit/user r/w */
movl $pg1+,pg_dir+ /* --------- " " --------- */
movl $pg2+,pg_dir+ /* --------- " " --------- */
movl $pg3+,pg_dir+ /* --------- " " --------- */
movl $pg3+,%edi
movl $0xfff007,%eax /* 16Mb - 4096 + 7 (r/w user,p) */
std
: stosl /* fill pages backwards - more efficient :-) */
subl $0x1000,%eax
jge 1b
xorl %eax,%eax /* pg_dir is at 0x0000 */
movl %eax,%cr3 /* cr3 - page directory start */
movl %cr0,%eax
orl $0x80000000,%eax
movl %eax,%cr0 /* set paging (PG) bit */
ret /* this also flushes prefetch-queue */

这段代码中的ret语句会将上一段代码压入栈内的main符号(函数)地址弹出然后跳到main函数中执行。而main是c语言函数,在执行后会进行一系列的栈帧处理,如果执行完毕返回时会跳到L6符号地址去执行,但实际上main函数是不会返回的,如果真的返回则说明内核出错了,只能停机。

现在我们来分析一下这几行的入栈操作

1 pushl $0

2 pushl $0

3 pushl $0

4 pushl $L6

5 pushl $main

根据《注释》一书的说法,行1-3是为main函数准备的参数,但实际上main函数并不需要参数,虽然这里的main函数对于汇编代码来说,仅仅是一个c语言的函数而已,早已不是c程序中的入口函数的概念了,而只是名称上叫main罢了。所以main在执行时并不需要参数,按照上一篇我们分析的结果看,这里不用pushl $0这三次入栈操作也应该是完全可以的。 但结果是不是这样呢?让我们先来做个实验。

实验1、在linux下进行汇编调用c函数

我们用汇编和c语言混合来写一个最简单的hello world程序。在汇编中直接调用c文件中的main函数。起初我们也模仿linux 0.11中那样加入三个入栈动作,看看结果。然后再把这三个入栈动作去掉(因为在c文件中main函数没有参数)。再次运行看看结果会怎么样。如果去掉这三行入栈操作也没有问题的话,我们基本可以断定linux 0.11中那三行入栈操作也是可以省略的。

hello.s文件:

#hello.s
#关于汇编中直接使用系统调用
#在汇编中使用系统调用,均通过0x80调用来实现。在具体编程时要将调用号放在eax中,
#其他的参数按定义的逆序入栈即可。这里用到两个调用号,一个是4号调用,即:sys_write,这个调用有三个参数:fd,msg,len;
#另一个调用是1号调用,好:sys_exit,这个调用只有一个参数,就是返回值。
.section .data
msg: .string "In hello.s!\n"
len=.-msg
.text
.global _start,myprint
_start:
#第一次显示In hello.s!
movl $,%eax
movl $,%ebx
movl $msg,%ecx
movl $len,%edx
int $0x80 #模仿linux .11中的入栈动作
pushl $
pushl $
pushl $ #调用c函数,显示hello, in hello.c!
call main #调用汇编函数,第二次显示In hello.s!
call echohello
movl $,%eax
movl $,%ebx
int $0x80
#
echohello:
movl $,%eax
movl $,%ebx
movl $msg,%ecx
movl $len,%edx
int $0x80
ret #c函数中显示字串使用的函数,其在c中的原型如下:
#myprint(char * msg,int len)
#由于使用ld直接连接,并没有连接标准库,所以c函数中不能使用printf等库函数。
myprint:
movl (%esp),%ecx #这里esp指向谁?想想上篇文章就明白了。
movl (%esp),%edx #参数的入栈是在c函数中完成的
movl $,%ebx
movl $,%eax
int $0x80
ret

hello.c文件:

//hello.c
//这个程序段不用任何解释
#include <stdio.h>
void myprint(char * msg,int len);
int main()
{
myprint("Hello, In hello.c!\n",);
return ;
}

汇编及链接运行:

as -o hello.o hello.s ;如果在64位系统下,要加上 --32选项来编译

gcc -c -o hello_c.o hello.c ;如果在64位系统上,要加上-m32选项

ld -o hello hello.o hello_c.o ;如果在64位系统上,要加上 -m elf_i386选项

然后运行 ./hello

如果没有输入等错误,就可以得到预期的输出。共输出三次hello,二次是汇编代码输出的,一次是c代码输出的。

然后,我们去掉那三句入栈语句再次进行编译链接以及运行。会如何呢?

...

毫无悬念,程序依旧会正确执行,也没有任何warning。得到同上一次一样的结果。

实验2、linux 0.11内核部分修改验证

既然上一个实验中可以去掉那三种入栈动作的语句,那我们回到linux 0.11中,将head.s文件中那三句pushl $0全部注释掉(汇编语言的注释是用#),然后重新编译linux0.11内核,再次运行。

可以看到,内核没有提示任何错误,进行正常的操作也没有任何问题。所以这里为何要有这三行入栈操作其实很存疑的。

ps:我们可以得到如下结论。

1、内核代码也不是完全不可动的。至少看起来是这样。linux 0.11中代码之所以那样写,想来也许是当时编译器等原因造成的。但现在的环境下是可以去掉的。

2、我们复习了汇编和c语言互相调用的方法。

从linux0.11中起动部分代码看汇编调用c语言函数的更多相关文章

  1. [转]在C#中调用C语言函数(静态调用Native DLL,Windows & Microsoft.Net平台)

    原文:https://blog.csdn.net/yapingxin/article/details/7288325 对于不太了解.Net的人,如果想要了解.Net,我必须给他介绍P/Invoke.P ...

  2. 对Linux0.11 中 进程0 和 进程1分析

    1. 背景 进程的创建过程无疑是最重要的操作系统处理过程之一,很多书和教材上说的最多的还是一些原理的部分,忽略了很多细节.比如,子进程复制父进程所拥有的资源,或者子进程和父进程共享相同的物理页面,拥有 ...

  3. 在Linux-0.11中实现基于内核栈切换的进程切换

    原有的基于TSS的任务切换的不足 进程切换的六段论 1 中断进入内核 2 找到当前进程的PCB和新进程的PCB 3 完成PCB的切换 4 根据PCB完成内核栈的切换 5 切换运行资源LDT 6 利用I ...

  4. Linux0.11内核剖析--内核代码(kernel)--sched.c

    1.概述 linux/kernel/目录下共包括 10 个 C 语言文件和 2 个汇编语言文件以及一个 kernel 下编译文件的管理配置文件 Makefile.其中三个子目录中代码注释的将放在后面的 ...

  5. C语言中递归什么时候能够省略return引发的思考:通过内联汇编解读C语言函数return的本质

    事情的经过是这种,博主在用C写一个简单的业务时使用递归,因为粗心而忘了写return.结果发现返回的结果依旧是正确的.经过半小时的反汇编调试.证明了我的猜想,如今在博客里分享.也是对C语言编译原理的一 ...

  6. Linux0.11中对文本文件进行修改的策略

    现在,假设 hello.txt 是硬盘上已有的一个文件,而且内容为 "hello, world" ,在文件的当前指针设置完毕后,我们来介绍 sys_read , sys_write ...

  7. Linux0.11 中对地址的管理

    个字节,段信息无法直接存放在段寄存器中(段寄存器只有2字节).Intel的设计是段描述符集中存放在GDT或LDT中,而段寄存器存放的是段描述符在GDT或LDT内的索引值(index). Linux中逻 ...

  8. (原创)c++11中 function/lamda的链式调用

    关于链式调用,比较典型的例子是c#中的linq,不过c#中的linq还只是一些特定函数的链式调用.c++中的链式调用更少见因为实现起来比较复杂.c++11支持了lamda和function,在一些延迟 ...

  9. linux0.11改进之四 基于内核栈的进程切换

    这是学习哈工大李治军在mooc课操作系统时做的实验记录.原实验报告在实验楼上.现转移到这里.备以后整理之用. 完整的实验代码见:实验楼代码 一.tss方式的进程切换 Linux0.11中默认使用的是硬 ...

随机推荐

  1. linux中输入输出和重定向问题

    输入输出解释 当我们执行shell的时候,每个进程都和三个打开的文件有关系,并使用文件描述符来引用这些文件.但这些文件不容易记忆,所以shell给了相应的文件名: 0:输入文件-标准输入(它的命令是输 ...

  2. java 开发业务逻辑的思考(1)- 通知短信发送

    坚持每天写一个总结的博客,今天又是一个新的开始! 今天我要说的是一个关于发送短信通知发送的问题.具体的业务流程是这样的,现在需要对用户的一个提现的申请进行审核,审核的内部需要控制很多的业务, 1.检查 ...

  3. [转] 评 WOW技能天赋设计

    本文转至:http://bbs.chinaunix.net/thread-1692302-8-1.html(只作转载, 不代表本站和博主同意文中观点或证实文中信息)再比如,传说中的面向对象本该大显神威 ...

  4. MyEclipse相关部署问题

    部署Tomcat如果用MyEclipse自动部署方式很有可能出现一个问题: 服务器关联的这个项目却关联到其他的项目上,导致运行时还在运行以前的项目 解决方法: 将状态提示栏的service里的tomc ...

  5. favicon.ico 404

    favicon.ico是一个图标文件.就是浏览网站时显示在地址栏的那个图标. 类似是百度的 显示在网站地址最前面的一张图片 可以在网站根目录(TOMCAT_HOME/webapps/ROOT/favi ...

  6. db2学习笔记--数据类型对表的影响

    创建表的时为列选择合适的数据类型,可以提高数据库性能. 1.选择合适的数据类型,避免出现数据类型转换.例如日期,有人使用字符串来存放日期.时间戳,最后我们还要在程序中使用to_date做数据类型转换, ...

  7. Ubuntu15.04YouCompleteMe插件安装

    0x00. 简介 YouCompleteMe号称Vim的自动补全神器,YouCompleteMe: a code-completion engine for Vim,该项目在github的地址:You ...

  8. zip命令的基本用法

    zip命令的基本用法是: zip [参数] [打包后的文件名] [打包的目录路径] linux zip命令参数列表: -a 将文件转成ASCII模式 -F 尝试修复损坏的压缩文件 -h 显示帮助界面  ...

  9. 连载《一个程序猿的生命周期》-《发展篇》 - 3.农民与软件工程师,农业与IT业

    相关文章:随笔<一个程序猿的生命周期>- 逆潮流而动的“叛逆者”        15年前,依稀记得走出大山,进城求学的场景.尽管一路有父亲的陪伴,但是内心仍然畏惧.当父亲转身离去.准备回到 ...

  10. ArrayList<E>源码分析

    ArrayList是按照线性表结构实现的 ArrayList的主要继承结构 public class ArrayList<E> extends AbstractList<E> ...