2.3 系统调用的实现
1 内核态和用户态
(1)内核态:操作系统代码执行时的状态。
(2)用户态:应用程序代码执行时的状态。
(3)内核态代码和用户态代码存放在内存中的不同区域。
(4)为什么要区分内核态和用户态? 保护操作系统。
2 怎么从用户态到内核态呢?
(1)系统调用
(2)外围设备中断
(3)异常
从用户态到内核态详细介绍
3 系统调用:又叫系统接口,是活动的进程进入操作系统内核(从用户态切换到内核态)的入口。系统调用通过0x80号中断进入内核。
4 特权环
(1)特权环机制的作用:操作系统通过设置特权级,实现了内核态和用户态内存区域的隔离。特权级高的可以访问特权级低的区域,反过来不行。内核的特权级别最高,应用程序特权级最低。
(2)特权级检查:
- CPL,current特权级,CS寄存器的低两位二进制数。
- DPL,destination描述符特权级,存放在描述符表(IDT、GDT中的两位二进制数)。
2.4 printf 一个完整的系统调用故事
1 库函数printf调用wtite系统调用
2 write被展开成一段包含int 0x80的代码
write宏展开.png
3 执行write展开
- 把输入参数fd、buf、count传入寄存器ebx ecx edx;
- 执行 int 0x80进入内核:通过查询idt表中0x80对应的表项,跳转到内核中对应的中断处理程序 system_call 。
【补充,0x80对应的中断处理函数在IDT表项如何初始化】
set_system_gate(0x80,&system_call);// 函数sched_init 中
#define set_system_gate(n,addr) \
_set_gate(&idt[n],15,3,addr)
#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
"movw %0,%%dx\n\t" \
"movl %%eax,%1\n\t" \
"movl %%edx,%2" \
: \
: "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
"o" (*((char *) (gate_addr))), \
"o" (*(4+(char *) (gate_addr))), \
"d" ((char *) (addr)),"a" (0x00080000))
问题:int 0x80都干什么了呢?
4 system_call的几件事情
(1)
- 将目前仍然指向用户态区域的段寄存器DS,ES,FS数据保存(压入栈);
- 将ebx,ecx,edx寄存器存储的系统调用处理函数输入参数压入栈中;
- 重新设置这三个段寄存器指向内核态区域,当处理完系统调用函数后会通过弹栈方式将DS,ES,FS重新赋值到用户态。
system_call:
cmpl $nr_system_calls-1,%eax
ja bad_sys_call
push %ds
push %es
push %fs
pushl %edx
pushl %ecx # 将ebx,ecx,edx寄存器存储的系统调用处理函数输入参数压入栈中。
pushl %ebx
movl $0x10,%edx # 设置ds,es 指向内核
mov %dx,%ds
mov %dx,%es
movl $0x17,%edx # fs points to local data space
mov %dx,%fs
call sys_call_table(,%eax,4)
pop %fs # 恢复fs es ds 指向用户态区域
pop %es
pop %ds
iret
(2)接下来就是根据sys_call_table+4×%eax,eax存储系统调用号。找到对应系统调用处理函数入口。
(3)执行系统调用函数 sys_read,把压入栈中的参数弹出(弹出的即为系统调用函数的参数)。
int sys_read(unsigned int fd,char * buf,int count)//对应write函数的输入参数
{
/*具体代码省略*/
}
(4)返回到system_call后,执行iret指令回到用户态(这其中包括寄存器信息恢复到用户态)。