操作系统基础之系统调用

1.用户态和内核态

用户程序是如何调用内核程序的呢?考虑实现下面的一个whoami的系统调用:
这里写图片描述
在内核中100地址处有一个用户“lizhijun”,whoami函数的功能是要打印出这个用户名,那可以直接打印出100地址处的内容吗?答案当然是否定的,因为用户程序不能随意的访问内核程序,内核态可以访问任何数据,用户态却不能访问内核数据,这是一种处理器的硬件设计所决定的,如下图:
这里写图片描述
判断一段程序能不能访问另一段是根据程序的特权级来判断的,DPL 是指目标程序的特权级(内核态代码DPL存在于GDT表中,在head.s已经初始化好),CPL指当前程序的特权级,当前程序执行在什么态,使用CS的最低两位来表示的,0表示内核态,3表示用户态,当DPL大于CPL时,当前程序可访问目标程序。

2.主动进入内核的方法

首先硬件提供了主动进入内核的方法,对于intel x86而言,就是中断指令,通过中断指令将CS的CPL改为0,从而进入内核,这是用户程序调用内核代码的唯一方法。
这里写图片描述

2.1系统调用的实现

以c语言中printf函数为例,printf函数实际上是调用了内核的write函数,库函数write最终通过展开成包含指令int 0x80的代码,从而进入内核执行内核的write函数:
这里写图片描述
那么具体流程是怎样的呢?当调用库函数write时,执行下面的代码:
这里写图片描述
展开后的代码如下:

int write(int fd, const char *buf, off_t count)
{  long   _res;\
  _ _asm__ volatile("int 0x80":"=a"(_res):" "(__NR_write), "b"(long(fd)),"c"((long)(*buf)), "d"((long)(count))); if(_res>=0) return (int)_res; error = -_res; return -1;}

代码是内嵌汇编代码,一共3条语句,分别用:隔开,第一条是中断指令,第二条是输出,是指把_res的内容付给eax寄存器,第三条是输入,是指把三个参数和_NR_write的值分别付给ebx 、ecx、edx和eax寄存器,首先执行第三条语句,然后执行中断指令,最后执行输出指令,然后再执行c语言的return语句。总的来说就是将系统调用号(NR_write)置给eax,然后调用中断指令进入内核。
那么调用中断又是怎么进入内核呢?首先调用中断时需要查询idt表,早在系统初始化时就已经初始化好了idt表,初始化函数(set_system_gate(0x80,&system_call);)做了如下操作:
这里写图片描述
将0x80号中断表的表项赋值:DPL赋值为3,&system_call赋给处理函数入口偏移,段选择符赋为0x0008,即CS=8,IP = &system_call,(通过CS=8找到gdt表中的内核代码段,同jmpi 0,8的那个用法,而且8的最后一位为0,所以CPL被置为0),然后跳转到内核区域执行。
接下来就调用system_call函数:
这里写图片描述
该函数首先做了一些铺设操作,然后将数据段寄存器赋值为0x10,现在数据指针和代码指针都指向了内核区域,因此接下来就真正进入内核操作,执行call _sys_call_table(,%eax,4)指令,_sys_call_table是一个系统调用表的起始地址,通过地址和eax的值找到要调用的系统调用的真正地址,即_sys_call_table+4*%eax,eax中是之前存入的NR_write(4),找到表中的第4个元素:
这里写图片描述
确实为sys_write,因此接下来就调用sys_write,此函数就是真正要做写操作的函数了。
总结一下,就是如下图:
这里写图片描述
当从第一个框图到第二框图时,此时CPL=3,在初始化时,又将int 0x80指令的DPL做成了3,因此可以调到执行int 0x80指令。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值