本周的实验浅析了系统调用的工作过程,下面通过使用库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用加深对其过程的理解。
系统调用列表:http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl
本次实验使用了20号系统调用getpid来获取当前内核名称和其它信息。
getpid 简单事例
// C语言使用库函数API #include <stdio.h> #include <unistd.h> int main() { pid_t tt; tt = getpid(); printf("%u ", tt); return 0; }
// C代码中嵌入汇编代码 #include <stdio.h> #include <unistd.h> int main() { pid_t tt; asm volatile ( "movl $0x20, %%eax " "int $0x80 " "movl %%eax, %0 " :"=m"(tt) ); printf("%u ", tt); return 0; }
上述两种方法都已实现,截图如下:
C语言内嵌汇编代码要求先声明输出参数,然后声明输出参数值。因为上述代码中不需要传入参数,所以只有一个输出值。
对于内嵌汇编调用 system_call():
1. 系统调用号放入 eax 中。
2. 系统调用的参数,按顺序存入相应寄存器中。
3. 返回值使用 eax 传递值。
因为中断( 包括异常 )是从用户态进入内核态的唯一方式,所以在上述代码中使用了中断( "int $0x80 " 这句 ),然后中断处理程序SAVE_ALL保存现场,随后就进入了内核态进行下一步的操作。
通过本周的作业,更加熟悉了系统调用的本质,以及系统调用和中断的关联。系统调用是用户态和内核态的桥梁,而具体的措施就是中断。上面我们采用内嵌汇编编写的代码,在运行时,通过eax准备系统调用号,使用ebx、ecx等传递具体参数,当我们触发0x80中断时,经过中断处理程序,我们就进入了内核态。
总结:
通过这一周的学习,更加熟悉了系统调用的本质,和系统调用与中断的关联。
一、中断处理是从用户态进入内核态主要的方式,系统调用是一种特殊的中断。
中断处理的完整过程( 由中断信号或者 int 指令完成 ):
- 从当前进程的描述符中提取其内核栈的 ss0 及 esp0 信息。
- 使用 ss0 和 esp0 指向的内核栈将当前进程的 cs, eip, eflags, ss, esp 信息保存起来,这个过程也完成了由用户栈到内核栈的切换过程,同时保存了被暂停执行的程序的下一条指令。
- 将先前由中断向量检索得到的中断处理程序的 cs, eip 信息装入相应的寄存器,开始执行中断处理程序,这时就转到了内核态的程序执行了。
- 若完成中断服务之后不发生进程调度,则继续执行指令( RESTORE_ALL 和 iret ),然后返回到原来的状态;若发生进程调度,那么当前发生的状态都会暂时的保存在系统中,当下一次发生调度再次回到当前进程时就继续执行指令 RESTORE_ALL 和 iret。
二、系统调用的工作机制:
- 用户态中的 xyz() 函数就是系统调用所对应的系统 API;
- 在这个 API 中将系统调用封装好,并在执行时触发 int 0x80 这个中断。这个中断在对应了内核态中的 System_call();
- System_call() 中可能会执行中断服务程序 sys_xyz()。
- 中断服务完成后有可能会发生进程调度。如果没有发生进程调度,那么执行 iret 返回用户态继续执行下面的其他指令。
总结:
系统调用的三层皮:xyz( API )、system_call( 中断向量 )和 sys_xyz( 服务程序 )。
李若森
原创作品转载请注明出处
《Linux内核分析》MOOC课程 http://mooc.study.163.com/course/USTC-1000029000