操作系统笔记(1)------printf()的故事

一.引例

在这里插入图片描述

  • 1.为什么用户程序不能直接调用内核程序中的whoami()函数呢?

    当我们实现一个whoami()的系统调用时,不能随意的调用数据,不能随意jump。如果随意调用,就可以看到root密码,可以修改它;可以通过显存看到别人world里的内容。这是十分不安全的。

  • 2.凭什么不让jump呢?是如何做到的呢?
    一种处理器"硬件设计"可以区分内核态和用户态。
    计算机对内存的使用是一段一段的的。由于CS:IP是当前指令,所以CS的最低两位来表示:0是内核态,3是用户态。内核态可以访问任何数据,用户态则不能访问内核数据。

    特权级检查:DPL>=CPL
    DPL表示目标特权级,CPL为当前特权级。
    DPL初始状态为0

    硬件检查当前程序是否满足特权级检查,如果满足条件,才可以调到内核中进行函数调用和执行程序。
    简单总结一下:
    在系统初始化时,会针对内核态的代码和内核态的数据建立GDT表,它对应的DPL为0。在系统初始化好后,当用户态执行时,会启动一个用户程序。用户程序的CPL=3,这时无法进入内核;而当系统调用时,会将CPL变为0,这样便可以进入内核执行。内核处理完毕后又会将程序的CPL变为3

  • 3.如何"主动进入内核呢?"
    硬件提供了"主动进入内核的方法"
    对于Intel x86来说,中断指令int 0x80可以机内内核中。

二.printf()如何执行的故事

1.简单的图解
在这里插入图片描述
2.详细解释

  • (1)调用write()变成一段包含"int 0x80"的中断代码这一过程是如何进行的呢?

在linux/lib/write.c中
#include <unistd.h>
_syscall3(int,write,int,fs,const char *buf,off_t,count)

在linux/include/unistd.h中
#define   _NR_write  4
#define   _syscall3(type,name,atype,a,btype,b,ctype,c)   type name(atype a,btype b,ctype c){   long _res;    _asm_volatile(“int 0x80”:"=a"(_res):""(_NR_##name),“b”((long)(a)),“c”((long)(b)),“d”((long)(c)));if(_res>=0)   return   (type)_res;…}(汇编代码)

这两段代码其实就相当于

在linux/lib/write.c中
#include <unistd.h>
int   write(int  fd,const  char  *buff…){
         把_NR_write置给eax
         把第一个参数置给ebx
         把第二个参数置给ecx
         把第三个参数置给edx
         执行"int 0x80"
         把"=a"(也就是eax)赋给_res
         返回_res
}

简单的总结一下,当调用write()时,在指向这个write()函数的过程中,把一个系统调用号置给了eax,然后调用"int 0x80"就到了内核中。

  • (2)"int 0x80"做了什么事呢?它为什么就能进入内核中呢?

void sched_init(void){
set_system_gate(0x80,&system_call);
}

在linux/include/asm/system.h中
#define   set_system_gate(n,addr)   _set_gate(&idt[n],15,3,addr)
define _set_gate(gate_addr,type,dpl,addr) …

这段代码的执行就相当于
<1>.在IDT表中,将DPL设置成3,从而使它可以跳进80号中断里。
<2>.跳进该中断后,在IDT表中用段选择符(CS变成了8,CPL就变成了0)和处理函数入口点偏移(IP)设置成新的PC指针(CS+IP)。
<3>.CS为8就相当于汇编代码jumpi 0,8。就会通过GDP表找到内核的代码段,处理system_call这一函数。

简单总结一下:"int 0x80"这段代码将80号中断的DPL由0变成3,使CPL=3的用户程序能够跳进80号中断中。然后在80号中断中将CPL由3变成0,在IDT表中取出中断处理函数,跳到内核(内核中的DPL为0)中的那个函数去执行。中断处理完毕后也就代表着内核处理已经完成。

  • (3)system_call又做了什么呢?
    system_call函数中有这样的一句代码
    call _sys_call_table(,%eax,4)会进入到_sys_call_table函数中执行。
  • (4)_sys_call_table又做了什么呢?
    它会找到 _sys_call_table的起始地址+4*eax 这一地址所对应的函数,也就是sys_write函数。然后执行call sys_write这样就真正调用了sys_write进入到内核中。
  • (5)接下来的事就真正交给了内核来办。到这里printf的故事就结束了。

三.小结

通过whoami无法直接进入内核程序的故事和printf的故事,我们可以总结一下系统调用的核心:
(1)用户程序包含一段含有int的代码
(2)操作系统写中断处理,获取要调用程序的编号。
(3)操作系统根据编号执行相应的代码

猜你喜欢

转载自blog.csdn.net/qq_44875145/article/details/104711756