系统调用:
用户在编程是可以调用的操作系统功能(使CPU可以从用户态陷入内核态)
应用程序,C函数,API,和内核函数关系
系统调用和函数调用区别:
系统调用:
INT,IRET指令用于系统调用,堆栈切换,特权级切换
函数调用:
CALL,RET指令用于函数调用,不涉及堆栈切换和特权级改变
系统调用机制的设计
①中断/异常机制
支持系统调用服务的实现:选择一条陷入指令(访管指令)即可
②选择一条特殊指令:陷入指令(亦称访管指令)
引发异常完成用户态到内核态的切换
③系统调用号和参数:
每个系统调用都实现给定一个编号(功能号)
④系统调用表:
存放系统调用服务例程入口地址
参数传递问题(怎样实现用户程序的参数(存在于用户栈)传递给内核(存在于内核栈)?):
①由陷入指令自带参数:陷入指令的长度有限,且还要携带系统调用功能号,只能自带有限的参数
②通过通用寄存器传递参数:这些寄存器是操作系统和用户程序都能访问的,但寄存器的个数会限制传递参数的数量
③在内存中开辟专用堆栈区来传递参数
现代参数传递使用第2种方法
系统调用例子:
1 #include <unistd.h> 2 int main(){ 3 char string[5] = {‘H’, ‘e’, ‘l’, ‘l’, ‘o’, ‘!’, ‘ ’}; 4 write(1, string, 7); 5 return 0; 6 } 7 输出结果:Hello!
这是用高级语言写的程序,编译后转成汇编指令为:
1..section .data 2.output: 3. .ascii “Hello! ” 4.output_end: 5. .equ len, output_end - output 6..section .text 7..globl _start 8._start: --- 9. movl $4, %eax #eax寄存器存放系统调用号 | 10. movl $1, %ebx |这一段是write函数的简要的汇编指令代码 11. movl $output, %ecx | 12. movl $len, %edx | 13. int $0x80 #引发一次系统调用 0x80 = 128(d)即为Linux中断向量表中专门用于陷入指令的中断号,调用异常机制 | 14. end: --- 15. movl $1, %eax #1这个系统调用的作用? ----- 1是系统调用号,对应的是返回退出程序 16. movl $0, %ebx | 这一段属于return 0 简要的汇编代码 17. int $0x80 -----
系统调用执行过程
当CPU执行到特殊的陷入指令时:
1.中断/异常机制:软中断发出中断信号,硬件保护现场;通过查中断向量表把控制权转给系统调用总入口程序 (之前查到的都是包含中断处理程序入口地址的中断向量,现在是系统调用总入口程序地址)
2.系统调用总入口程序:保存现场;将参数保存在内核堆栈里(参数传递);根据eax寄存器中的系统调用号,通过查系统调用表把控制权转给相应的系统调用处理例程或内核函数
3.执行系统调用例程
4.恢复现场,返回用户程序
Linux系统调用:
陷入指令中断号选择128号
int $0x80
门描述符
①系统初始化时:对IDT表中的128号门初始化
②门描述符(中断描述符表)的2、3两个字节设置为内核代码段选择符
0、1、6、7四个字节则设置为偏移量(段选择符从GDT中选择段描述符,然后段描述符有段基地址,从而最终指向system_call(),由sched_init()中set_system_gate(0x80, &system_call)设置)
③门类型:15,陷阱门,没有禁止中断
④DPL:3,与用户级别相同,因为只有当前执行程序的特权级大于等于门的DPL,特权级才能发生由用户态变成内核态(数值越小特权级越高)
Linux内核中Include/ASM-1386/UNISTD.h文件:
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
存了大量的系统调用号
系统执行INT 0x80具体过程:
1.由于特权级的改变,要切换栈
用户栈 → 内核栈
2.CPU从任务状态段TSS中装入新的栈指针(SS︰ESP),指向内核栈 PS.任务状态段(Task State Segment, TSS)是x86架构电脑上是一个保存任务信息的数据结构,保存了内层堆栈指针
3.用户栈的信息(SS︰ESP)、EFLAGS、用户态CS 、EIP 寄存器的内容压栈(返回用)
4.将EFLAGS压栈后,复位TF,IF位保持不变 PS.TF(bit 8) [Trap flag] 将该位设置为1以允许单步调试模式,清零则禁用该模式。
5.用128在IDT中找到该门描述符,从中找出段选择符装入代码段寄存器CS
6.段描述符中的基地址 + 陷阱门描述符中的偏移量 → 定位 system_call()的入口地址
Linux执行流程
对应的SAVE_ALL执行内容
底层工作总结:
1. 硬件压栈:程序计数器等
2. 硬件从中断向量装入新的程序计数器等
3. 汇编语言过程保存寄存器值
4. 汇编语言过程设置新的堆栈
5. C语言中断服务程序运行(例:读并缓冲输入)
6. 进程调度程序决定下一个将运行的进程
7. C语言过程返回至汇编代码
8. 汇编语言过程开始运行新的当前进程
作者水平有限,文章肯定有错还请各位指点!!!感谢!!!
参考链接:
http://blog.csdn.net/jn1158359135/article/details/7761011