提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
系统调用的实现
系统调用:上层软件通过函数调用进入操作系统,使用操作系统。表面上是函数,内部是什么呢?
应用程序在内存中,而操作系统也在内存中,应用程序想访问操作系统提供的功能,为什么不直接跳进去jmp或者直接mov?直接跳进去那是函数调用,而不是系统调用,所以不能直接跳过去。也就是说不能随意的调用数据,不能随意的jmp。
DPL用来描述目标内存段特权级,DPL就在GDT表中,GDT表项就用来描述一段内存,GDT表用来描述操作系统,操作系统无论是数据段还是代码段,GDT表项对应的DPL全等于0,head.s在初始化表的时候就把DPL初始化位为0,因此前面图片红色代码放在内核态。
CPL:当前特权级,CS=3用户态,数字越大特权级越低。
只有当当前的特权级大于等于目标内存的特权级时,才允许访问,也就是DPL>=CPL,因为数字越小特权级越大。
只有满足特权级要求,才可以进行访问。
这就解释了为啥用户调用进不去操作系统的原因。因为此时CPL = 3,DPL = 0,不满足特权级要求。
怎样才能进入内核呢?中断,中断不是jmp也不是mov,中断时进入内核的唯一方法。
中断只能是0x80,通过这个中断进入操作系统。
系统调用的实现:printf()格式化输出跟write搭不上边,write只有缓冲区和输出的个数,所以库函数将格式化输出采用c语言方式把它变成write所需要的缓冲区和字符个数,然后在调用write变成一段包含int 0x80的中断代码,然后中断代码在通过系统调用进入操作系统。
int 0x80到底做了哪些事情?
定义了宏,_syscall3
c嵌套汇编,通过宏展开汇编来实现write的实现
总结:将系统调用号置给eax,然后调用int 0x80,这样就到了内核了
在宏展示时,type = int ,name = write, atype = int,a = fd, btype,b = const char *buf, ctype = off_t, c = count
内嵌汇编:MOV _NR_write,%eax fd->ebx
INT指令需要查找IDT表,通过IDT表找到中断需要转去哪里去执行。
初始化时,int 0x80通过system_call函数去进行处理,set_system_gate设置中断处理门,其实每一个IDT表项就是中断处理门, 核心就是把IDT表初始化好,一旦初始化好,再遇到int 0x80中断,就从表中取出0x80中断,然后跳到中断处理函数处,中断处理函数&system_call肯定是一个地址。
set_system_gate(n,addr),n是中断处理号,addr是中断的地址,idt[n]:idt代表IDT表的起始地址,而通过n就可以找到80中断所对应的表项,addr就是地址,15和3分别传给type和dpl,此时DPL=3。
CS的最后两位就是CPL,此时CS = 8,而IP= system_call,CS的8代表GDT表项的8。
中断使得DPL=3,原先的CPL=3,满足DPL>=CPL,此时用户进入内核态,一旦进入内核,根据CS=8将CPL设为0,所以此时到了内核,到了内核就可以做了。等中断结束以后,CPL又会变为3,变为用户态。
system_call干什么事情?
ds = es = 0x10,10的最后两位也是0,0x0008是内核的代码的,而0x10是内核的数据段,从现在开始执行内核的代码,然后通过call跳到表执行,eax = _NR_write(系统调用号4),
_sys_call_table+4*%eax,_sys_call_table代表表的起始地址,前面的4代表每个系统调用占4个字节,每个调用的函数占4个字节。
总结
不能直接进入的原因?是因为会被上层应用看到,破坏,这是不允许的。
刚开始上面CPL=3,下面DPL=0,不能进去;要想进去,硬件提供唯一渠道,用户根据系统调用号eax=72,然后通过int 0x80指令才能穿过接口到达系统,怎么穿过呢?
CPL = 3,DPL = 3,一旦穿过去以后,CPL置成0,然后执行system_call,在system_call里面通过移动查表调用sys_whoami,这时候访问100就可以,因为CPL=0,访问哪个内存地址都可以,此时使用printk打印,不能使用printf,printf是上层应用软件使用的,printfk的k代表kernel。
CPL是当前进程的权限级别(Current Privilege Level),是当前正在执行的代码所在的段的特权级,存在于cs寄存器的低两位。
DPL存储在段描述符中,规定访问该段的权限级别(Descriptor Privilege Level),每个段的DPL固定。
之后将学习操作系统的内核。