Linux内核分析学习笔记:system_call中断处理过程

来源:互联网 发布:登陆艇升级数据经验值 编辑:程序博客网 时间:2024/05/18 08:35

原创内容(陈晓爽 cxsmarkchan)
转载请注明出处
《Linux内核分析》MOOC课程 学习笔记

前两篇博文从汇编的角度分析了linux系统的系统调用方法,本博客在实验楼平台下写了一个简单的系统调用程序,并分析系统调用的实际过程。

1 实验内容

本文实验平台为实验楼Linux内核分析的第5个实验:分析system_call中断处理过程。
在实验平台下,切换到~/LinuxKernel/menu文件夹下。该文件夹下的内容会被写入磁盘镜像rootfs.img中。Linux加载磁盘后,启动的第一个用户态进程(可参考linux内核分析学习笔记:用gdb跟踪linux内核启动过程)即在该文件夹的test.c中。
运行make rootfs,可以编译并启动系统,结果如下:
这里写图片描述
该系统中仅有3个命令,分别是help, version, quit。下面在该系统中添加两个命令:writewrite-asm。顾名思义,这两个函数是系统调用sys_write的C语言版本和汇编版本。
打开test.c,可以看到main函数如下:

int main(){    PrintMenuOS();    SetPrompt("MenuOS>>");    MenuConfig("version","XXX V1.0(Menu program v1.0 inside)",NULL);    MenuConfig("quit","Quit from XXX",Quit);    ExecuteMenu();}

可见main函数中给出了命令和对应函数。此处不再关心SetPromptExecuteMenu函数的细节(与Linux操作系统无关),只需了解MenuConfig函数的定义如下:

int MenuConfig(char *cmd, char *desc, int (*handler)());

其中,cmd为命令名称,desc为命令说明(在help中显示),handler是命令处理程序。
将main函数改写如下:

int main(){    PrintMenuOS();    SetPrompt("MenuOS>>");    MenuConfig("version","XXX V1.0(Menu program v1.0 inside)",NULL);    MenuConfig("quit","Quit from XXX",Quit);    MenuConfig("write", "Print to screen", Write);    MenuConfig("write-asm", "Print to screen(asm)", Write_asm);    ExecuteMenu();}

这样就添加了两条命令。接下来,给出WriteWrite_asm函数:

int Write(int argc, char *argv[]){    int i;    for(i = 1; i < argc; i++){        write(0, argv[i], strlen(argv[i]));        write(0, "\n", 1);    }    return 0;}void Write_asm(int argc, char *argv[]){    int i;    int len;    char *str;    for(i = 1; i < argc; i++){        len = strlen(argv[i]);        str = argv[i];        asm volatile(                "int $0x80\n\t"                "mov $4, %%eax\n\t"                "mov $0, %%ebx\n\t"                "mov %4, %%ecx\n\t"                "mov $1, %%edx\n\t"                "int $0x80\n\t"                :                :"a"(4), "b"(0), "c"(str), "d"(len), "D"("\n")                );    }    return 0;}

这段代码也不再解释,可以参照前一篇博文用asm内联汇编实现系统调用。
添加了如下代码后,运行系统,点击help命令,就会出现更多的选择。同时,write命令和write-asm命令都可以执行,其功能是将参数表中的所有内容按顺序输出,并自动添加换行。
运行效果如下图:
这里写图片描述

2 系统调用的过程

下面分析系统调用的过程。在汇编代码中,我们通过int $0x80产生中断,系统会在中断向量表中查找相应的中断处理函数。0x80对应的函数即为系统调用函数system_call,位于arch/x86/kernel/entry_32.S
遗憾的是,entry_32.S是一段汇编代码,system_call也不是一个函数,只是一个程序入口标志。因此,无法用gdb来对代码进行单步调试。因此,我们仅通过汇编代码的分析来了解系统调用的过程。
系统调用的汇编代码可以简化如下,其中省略了部分代码:

ENTRY(system_call)    #……    SAVE_ALL    #……syscall_call:    call *sys_call_table(,%eax,4)syscall_after_call:    movl %eax,PT_EAX(%esp)      # store the return valuesyscall_exit:    #……    movl TI_flags(%ebp), %ecx    testl $_TIF_ALLWORK_MASK, %ecx # current->work    jne syscall_exit_workrestore_all:    TRACE_IRQS_IRET    #……irq_return:    INTERRUPT_RETURNENDPROC(system_call)

在该段代码执行时,系统已经进入内核态。该段代码首先通过SAVE_ALL宏保存现场,接下来根据eax寄存器的内容,查找sys_call_table表,跳转到合适的系统调用,并把返回值存入eax寄存器中。完成这一步之后,系统调用就基本结束了,会执行restore_all部分的代码恢复现场,最后执行iret(此处即为INTERRUPT_RETURN)返回到中断调用的位置,继续执行。
值得注意的是,在返回之前,系统还会根据情况,决定是否要调用syscall_exit_work函数。该函数的意义在于退出系统调用前,执行一些清理工作。其中最重要的工作是进程调度,这是因为有些系统调用函数可能会改变进程执行情况,例如挂起当前进程。此时,在该函数中就会调用进程调度函数,实现进程切换。

3 小结

系统调用处理过程本质是一种中断过程,因此遵循中断处理的一些共性,包括:

  • 根据中断号和中断向量表,跳转到相应的中断处理程序;
  • 在中断开始的时候保存中断现场,在中断结束的时候恢复中断现场;
  • 中断结束时通过iret返回到中断发生前的程序执行位置;

同时,在系统调用的过程中,还会根据eax寄存器中存储的系统调用号,进一步查找系统调用表,执行系统调用操作。系统调用结束后,程序会根据系统调用的结果,进行一些收尾工作,例如进程调度等,最后退出内核态,返回用户态。

0 0