Linux内核分析实验五

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

刘旸 + 原创作品转载请注明出处 

《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000 

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


    在操作系统上运行的某个应用程序,如果想完成一些实际有用的功能,必然会用到操作系统提供的接口,这些接口被称为系统调用(System Call)。

    由操作系统提供的功能,通常应用程序本身是无法实现的。例如对文件进行操作,应用程序必需通过系统调用才能做到,因为只有操作系统才具有直接管理外围设备的权限。又如进程或线程间的同步互斥操作,也必需经由操作系统对内核变量进行维护才能完成。

    应用程序的进程通常在用户态下运行,当它调用一个系统调用时,进程进入内核态,执行的是kernel内部的代码,从而具有执行特权指令的权限,完成特定的功能。换句话说,系统调用是应用程序主动进入操作系统内核的入口。

 

下面我们尝试用gdb跟踪分析一下系统调用的过程。

1.进入实验楼环境,打开终端,定位到LinuxKernel文件夹下。


2.使用rm menu -rf删除掉旧的menu文件夹。


3.输入命令git clonehttps://github.com/mengning/menu.git,下载最新版本的MenuOS


 

4.进入到menu文件夹下,使用make rootfs命令编译运行MenuOS,效果如下:

 


      此时运行的MenuOS中还没有我们想要跟踪的系统调用,需要先将其加入到MenuOS中。

 

5.打开Menu文件夹下的test.c文件,在其中加入两个函数GetPidGetPidAsm,具体代码如下:

 

int GetPid()
{
        pid_t tt;
        tt = getpid();
        printf("%u\n",tt);
        return 0;
}

int GetPidAsm()
{
        pid_t tt;
        asm volatile (
                "mov $0,%%ebx\n\t"
                "mov $0x14,%%eax\n\t"
                "int $0x80\n\t"
                "mov %%eax,%0\n\t"
                :"=m"(tt)
        );
        printf("%u\n",tt);
        return 0;
}

 

6.找到test.c文件的main函数,在其中加上以下两条语句:

 

 

7.再次使用make rootfs命令,重新编译运行MenuOS

 

 

8.MenuOS中输入命令getpid()getpid-asm同理)测试我们加入的函数功能是否正常。

 

 

9.返回到LinuxKernel文件夹下,输入命令qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S :


10.另开一个终端,输入gdb -q开始gdb调试。


11.按照实验四的方法在start_kernel() 处与 sys_getpid()处设置断点,继续运行MenuOS。


12.第二次继续运行后,在MenuOS中输入命令getpid-asm,可以看到gdb已经开始跟踪sys_getpid():

 

 

13.按照实验四的方法列出getpid()的代码,逐条跟踪getpid():


        发现在跟踪到syscall_exit_work之后,gdb无法正常跟踪,而是出现了下图的情况:

 

 

14.再尝试在system_call处设置断点,发现MenuOS没有在system_call处停下,而是又停在了getpid()处:

 

 

分析与总结:

    当系统调用发生时,通过中断机制,系统调用例程system_call被调用。system_call由汇编语言和C的代码构成,它的执行过程大概分为4个步骤:

    1. 从寄存器中取出系统调用号(system call number)和输入参数,然后将这些寄存器的值压入kernel栈中。这一部分的代码用汇编写成。

    2. 根据系统调用号(system call number)查找系统调用分派表(system call dispatch table),找到系统调用服务例程(system call service routine)。用汇编写成。

    3.调用查到的系统调用服务例程。这一部分用C语言写成,因为第1步中已经将输入参数保存在kernel栈中,所以在C函数的参数表中能够拿到输入参数,使得系统调用服务例程在表面上看与一个普通的C函数没有区别。

    4. 将系统调用服务例程的返回值出栈,重新保存在寄存器中。也采用汇编语言。

 

    第1步中将输入参数寄存器的值压入kernel栈的操作由汇编代码__SAVE_ALL宏完成。

    第2步中的系统分派表在kernel代码中以变量sys_call_table表示。查找系统调用服务例程的动作就是从sys_call_table里找系统调用号(存在eax寄存器中)指向的那一项,而sys_call_table中的项在sys_call_table.c文件中定义。

    第3步,执行C函数实现的系统调用例程。该例程最多接受6个参数(包括系统调用号),返回值是一个整型。返回值为非负,表示执行成功;返回值为负,表示执行出错。

    第4步,调用syscall_exit_work退出系统调用,并从内核态回到用户态。

  最后,回到用户态的封装函数中,对返回值eax进行检查。如果eax小于0,则将eax的相反数(即绝对值)存到errno全局变量中,同时将eax值置为-1,这时封装函数返回-1;如果eax大于等于0,则封装函数返回eax的值。

 

   通过以上分析我们猜测,实验第12步中出现的gdb无法继续正常跟踪,应该是因为执行完syscall_exit_work之后,程序执行了一些gdb不支持或者无法跟踪的特殊代码;而第13步中出现的无法在system_call处停止的现象,应该是因为system_call只是包含分析中列出的4个步骤的过程的入口,且参数压栈和查找系统调用服务例程都是用汇编写成,因此只有到第三步调用服务例程(本实验中为getpid)时才能停下。

 

   这里附上system_call执行的简易流程图:

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值