分析system_call中断处理过程

来源:互联网 发布:快手上的淘宝优惠群 编辑:程序博客网 时间:2024/06/10 02:30

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

实验截图

github上下载menu最新代码,上传到实验楼git,讲menu复制到LinuxKernel目录下,修改test.c文件
这里写图片描述
这里写图片描述

在menu目录下执行 make rootfs
改造后的MenuOS系统会自动编译并启动运行

这里写图片描述

用gdb调试:
qemu -kernel linux-3.18.6/arch/x86/boot/bzImage -initrd rootfs.img -s -S
打开另一个shell窗口,,执行:

gdb  //打开gdb调试file linux-3.18.6/vmlinux  //加载符号表target remote:1234 //连接端口b sys_getpid //设置断点

这里写图片描述

c 继续执行list 查看断点处代码n 进行单步调试

这里写图片描述

如果继续进行单步调试的话,会进入汇编语言部分,此时就无法看到代码执行的细节了

分析

系统调用在内核代码中的工作机制和初始化
一、从int 0x80触发一个系统调用到执行系统调用处理的函数sys_time,到后边返回用户态整体过程理解

1,什么时候把0x80和system_call绑定起来的?什么时候初始化中断向量?

2,system_call处理过程以及哪里调用了sys_xyz(xyz和sys_xyz是通过系统调用号匹配起来的),xyz和system_call是通过中断向量匹配起来的。

3 ret_from_sys_call的单步调试过程
这里写图片描述

初始化:
这里写图片描述
可以看到代码有:set_system_trap_gate包含系统调用中断向量和system_call代码入口。
之后系统一旦出现int 0x80就会立即调用system_call。

二、系统调用也是一种中断,所以也存在着保存和恢复现场的问题,下图可看到SAVE_ALL;

call sys_call_table %eax是系统调用表,在menuos中即为sys_time;

之后syscall_after_call:先保存返回值,退出之前有jne syscall_exit_work如果没有这个就直接restore_all返回用户态
这里写图片描述
这里写图片描述
进入复杂的syscall_exit_work,里边会有进程调度时机。
这里写图片描述
简化成伪代码:
这里写图片描述

exit时会检查当前任务current->work需不需要syscall_exit_work去处理,不需要直接ireturn
这里写图片描述
当前进程有一些信号处理或需要调度就需要syscall_exit_work去处理了:
这里写图片描述
流程是跳转到work_pending,里边有jz work_notifysing处理信号,
和work_resched需要重新调度,就call schedule,调用完成后再跳转到restore_all把他返回系统调用;

三、浏览system_call到iret之间的代码
system_call:
这里写图片描述
syscall_exit_work:
这里写图片描述
work_pending:
这里写图片描述
这里写图片描述
这里写图片描述
调用schedule,调用完之后可以跳转restore_all,把后面恢复现场工作结束掉。

这里写图片描述

总结

  1. 系统调用”是操作系统提供给用户程序进行调用的一些服务。这些服务是系统预先提供的函数,在这一点上系统调用与普通的用户程序是没有区别的。而区别则在于“系统调用”是由操作系统提供给用户的,这些服务更接近底层或者要求的安全性更高,因此由操作系统来统一实现和管理。

  2. 处理器在eax寄存器中拿到系统调用号之后,会到系统调用表中找到该系统调用所对应的入口函数地址,然后执行该函数。

  3. 函数的入口地址在在syscall.c(entry_32s)中。

  4. 中断处理中读取中断号及参数,然后找到中断服务例程并执行,退出中断后进行堆栈切换,返回用户态,继续执行用户程序。

系统调用system_call的处理过程

syscall_call 函数到系统调用服务例程通过系统调用号联系起来:在上面执行软中断 0x80 时,系统调用号会被放入eax寄存器(参数的传递),system_call 函数读取eax寄存器获取参数(当前系统调用的调用号),将其乘以4生成偏移地址。然后以中断向量表(sys_call_table)为基址,以系统调用号所确定的为偏移地址相加得到最后的物理地址:基址+偏移地址 => 系统调用服务例程的地址。其中 sys_call_table 基址在文件 arch/x86/kernel/syscall_table_32.S 中定义,同时表中每一项例程的地址占用4个字节,所以上面乘以4。

参数保存方式:由于系统调用例程在定义时时用 asmlinkage 标记,所以编译器仅从堆栈中获取该函数的参数。在进入system_call函数前,用户应用会把参数存放到寄存器中,system_call 函数执行时会首先把这些寄存器压入堆栈。这样对系统调用服务例程可以直接从堆栈照片能够获取参数。

从system_call开始到iret结束之间的过程

从整体过程来看,系统通过 int 0x80 从用户态进入内核态。在这个过程中系统先保存了中断环境,然后执行系统调用函数。system_call() 函数通过系统调用号查找系统调用表 sys_cal_table 来查找到具体的系统调用服务进程。在执行完系统调用后在执行 iret 之前,内核做了一系列检查,用于检查是否有新的中断产生。如果没有新的中断,则通过已保存的系统中断环境返回用户态。这样就完成了一个系统调用过程。

系统调用通过 INT 0x80 进入内核,跳转到 system_call() 函数,然后执行相应服务进程。因为代表了用户进程,所以这个过程并不属于中断上下文,而是属于进程上下文!这点一定要分清楚。

无论是中断返回(ret_from_intr) ,还是系统调用返回,都使用了 work_pending 和resume_userspace。对于宏SAVE_ALL来说,这条语句会把将寄存器的值压入堆栈当中,压入堆栈的顺序对应struct pt_regs出栈时这些值传递到struct pt_regs的成员,实现从汇编代码向C程序传递参数。struct pt_regs可以在arch/x86/include/asm/ptrace.h中查看。用户态到内核态需要int 0x80进行中断,只有生成了中断向量后才可以切换状态。中断处理让CPU停止当前工作转为执行系统内核中预设的一些任务,因此必须要对当前CPU执行的任务进行执行现场的保护工作,并对一些其他工作进行检查,完成调用后,再进行检查,才能执行iret返回。系统内部调用涉及CPU架构等内容,不同的CPU对于系统调用的汇编具体代码是不一样的。

0 0
原创粉丝点击