分析system_call中断处理过程

刘文 + 原创作品转载请注明出处 + 《Linux内核分析》MOOC课程http://mooc.study.163.com/course/USTC-1000029000

上一次的实验中,我们选择了一个系统调用即系统调用函数system_write函数,分别使用库函数APIprintf函数和C代码中嵌入汇编代码的方式进行了系统调用。我们先复习一下系统调用的原理和系统调用的过程。首先我们看一下课堂上孟宁老师给出的“系统调用三层皮”的原理图:


系统调用的三层皮分别指的是:APIsystem_call以及系统调用封装例程。它们各自的作用如下:

API:第一层是指Libc中定义的API,这些API封装了系统调用,使用int 0x80触发一个系统调用中断;当然,并非所有的API都使用了系统调用,如完成数学加减运算的API就没有使用系统调用;也有可能某个API使用了多个系统调用;这一层存在的价值就是为应用程序员提供易于使用的API来调用系统调用;

system_call:运行于内核态。system_call是所有系统调用在内核的入口点,在其中的开始处保护用户态程序执行上下文,结束处恢复用户态程序执行上下文,在中间根据传入的系统调用号对应的中断服务程序;

sys_xyz 系统调用封装例程:执行具体的系统调用操作,完成用户的系统调用请求;每个系统调用都对应一个封装例程。

由上面的分析可以知道,理论上要请求一个系统调用,我们既可以使用Libc提供的API,也可以直接在C中内嵌汇编代码触发0x80中断来完成。这次实验,我们就用实际的例子来演示这两种方法使用同一个系统的调用。我们选择的是比较简单的系统调用sys_write,通过查阅系统调用列表发现它对应的系统调用号是4;利用这个系统调用可以在屏幕上打印输出“hello world”;而这个系统调用函数对应的API就是printf

那么本周的实验我们可以更进一步,利用gdb我们可以给系统调用内核处里程序设置断点,并让程序停在断点处,进行断点跟踪系统调用的处理过程。由于system_call是完全用汇编写就一个的函数,虽然我们也可以在system_call处设置断点,但却无法让系统停在system_call处,所以也无法通过单步跟踪学习其处里流程。但system_call是所有系统调用的入口,也是程序由用户态转入内核态执行时无法越过的一个函数,其重要性不言而喻,所以我们跟随老师简化的汇编代码以及源代码学习其主要的流程。

实验环境是使用本课程配备的实验楼虚拟机环境,打开命令行客户端,cd LinuxKernel目录,使用命令rm -rf menu删除原来的代码,使用Git clone https://github.com/mengning/menu.git获取menu的最新代码,然后cd menu进入menu子文件夹,使用gedti test.c打开文件,讲我上周实验的代码拷贝改写成为menu的两个菜单项,主要代码部分如下:

 

int SayHello(int argc, char *argv[])  
{  
        char* msg = "Hello World";  
        printf("%s", msg);  
  
        printf("\n");  
  
        return 0;  
}  
  
int SayHelloAsm(int argc, char *argv[])  
{  
        char* msg = "Hello World";  
        int len = 11;  
        int result = 0;  
  
        __asm__ __volatile__("movl %2, %%edx;\n\r"   
                "movl %1, %%ecx;\n\r"   
                 "movl $1, %%ebx;\n\r"   
                 "movl $4, %%eax;\n\r"   
                  "int  $0x80"   
                :"=m"(result)   
                :"m"(msg),"r"(len)    
                :"%eax");   
  
        printf("\n");  
  
        return 0;  
}  
  
int main()  
{  
    PrintMenuOS();  
    SetPrompt("MenuOS>>");  
    MenuConfig("version","MenuOS V1.0(Based on Linux 3.18.6)",NULL);  
    MenuConfig("quit","Quit from MenuOS",Quit);  
    MenuConfig("time","Show System Time",Time);  
    MenuConfig("time-asm","Show System Time(asm)",TimeAsm);  
<strong>    MenuConfig("sayhello","Say Hello World",SayHello);  
    MenuConfig("sayhello-asm","Say Hello World",SayHelloAsm);</strong>  
    ExecuteMenu();  
}  

这里主要就是加了两个菜单项 sayhello sayhello-asm 及其对应的实现函数。其中sayhello函数是通过库函数API实现的,而sayhello-asm函数是通过C代码中嵌入汇编的方式实现的。保存test.c函数,使用make rootfs编译运行menuos系统,输入help可以看到我们新加入的菜单:

 

之后我们就可以通过利用gdb跟踪函数调用的执行过程,可以在函数中设置断点,逐步来分析系统调用的整个执行步骤。下面我们通过分析源代码来分析一下系统调用的处理过程。

在函数\init\main.c start_kernel中,trap_init函数就是用来完成系统调用初始化的函数,我们进入这个trap_init函数,查看一下源代码,其中这几行代码非常重要:

#ifdef CONFIG_X86_32  
    set_system_trap_gate(SYSCALL_VECTOR, &system_call);  
    set_bit(SYSCALL_VECTOR, used_vectors);  
#endif 

从代码中可以看出,这个函数中通过中断向量,将system_call函数和0x80绑定。

最后,我们根据system_call的源代码和老师在视频中的讲解,画出系统调用的执行流程图如下:

 

 

总结:

通过中断向量表,int 0x80system_call关联起来;system_call又是通过系统调用号,将每一个系统调用和特定的系统调用服务例程关联起来;在system_call返回用户态之前,会执行syscall_exit_workwork_notifysig,schedule等函数以应对可能的进程信号处理和进程调度。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值