其他分享
首页 > 其他分享> > 操作系统学习笔记2 | 操作系统接口

操作系统学习笔记2 | 操作系统接口

作者:互联网

这部分将讲解上层应用软件如何与操作系统交互,理解操作系统到底发生了什么事情,理解操作系统工作原理,为以后扩充操作系统、设计操作系统铺垫。


参考资料:

0815这部分听的比较折磨,反复听了几次,终于基本理解了整个过程。


1. 接口

学习操作系统接口,不仅要关注如何调用接口,还要理解接口内部的工作原理。

2. 操作系统接口

正如生活中的接口,对于上层来讲,接口的存在是十分自然的,当我们有某项需求,才会使用响应接口

如使用电的需求,才会用到插座

我们如何使用操作系统呢?

-- 比如

  1. 我们终端键入一个命令

  2. 操作系统内部进行处理

  3. 屏幕上就显示出来相应内容

    也不一定都是命令

2.1 命令行

命令行是什么?即输入命令后发生了什么?

2.2 图形按钮

图形按钮基于一套消息机制。

说明:

  • linux0.11只有命令行,而没有图形界面
  • linux 有图形界面是比较新的版本如ubuntu
  • Windows也有
  • 可以尝试在linux0.11上实现图形界面

image.png

如何实现?

应用程序接口先不讲。

2.3 总结

从上面可以知道,命令行和图形按钮都是一些程序,就是普通的C程序,只是在C的基础上使用了一些重要的函数,这些函数可以进入操作系统、使用硬件

可见,这些函数就是电源插座,就是操作系统的接口

有哪些具体的系统接口呢?

3. 系统调用的实现

那么,上面提到的重要函数是如何实现的呢?

3.1 为什么不能直接访问内核

这里解释一个事情:

3.2 如何实现内核态和用户态隔离

处理器的硬件设计做到的,从硬件层面保证了这个机制生效。

处理器硬件将内存访问权力(主要)分为了用户态和核心态。对应的实际区域即用户段和内核段。指令在两段之间不能随意跳转。

内核态和用户态隔离的具体实现:

几个名词概念:DPL、CPL、RPL,是基于硬件实现的。

image.png

3.3 系统调用如何实现跨越特权级访问

前面提到过了不能直接访问内核,不应该直接访问内核,计算机是如何做到这种隔离的,下面就来看看在这种隔离下,系统调用如何实现跨越特权级的访问。

同样,也是硬件提供了 "主动进入内核的方法":

对于 Intel ×86 来说,进入内核的 唯一方法中断指令int,其他如jmp和mov都不行。

特意设计了一些特殊中断,可以进入内核。

还是以whoami()为例:

main(){
    whoami();
}
//用户程序,CPL为3,运行到whoami()时检测到DPL为0
----------------------
whoami(){
    printf(100,8);
}
//系统程序
----------------------
100:"lizhijun"
//存放用户名的内存和字符串

系统调用的核心:

问:为什么不能在普通代码里直接使用这个特殊中断进入内核?

答:不使用封装的库函数,直接写int中断编译不通过(可能是编译器的设计)。

以C代码库编写的系统调用,在用户程序调用后,会首先进入C代码库函数,然后用汇编代码在约定的位置(栈或者寄存器)设置参数和系统调用编号,最后执行int指令

关于特殊中断,操作系统也规定好了:int 0x80 中断指令

具体见下图右侧代码。

image.png

所以举例whoami() 中的printf()很复杂,它的实现在软件层面跨越了三个层次:

  1. 应用程序,也就是我们常见的C语言,printf() 调用

  2. C函数库 中printf()执行具体代码,调用库函数write()

    所说的 write()见右侧代码第一个框。

    之所以这么做(中间隔了一层),是因为printf()格式化输出和write()的参数不很协调,所以加了一层。

  3. 在库函数write()中展开为一个包含0x80的中断代码,通过系统调用进入操作系统

    见右侧代码第二个框。

3.4 write 的完整理解

将关于write的故事完整的讲完,看看int 0x80 到底做了什么事情,以及是如何做到的。

对库函数write()来说,内嵌了一个宏:_systemcall3展开为包含int0x80的汇编代码。

宏展开:C语言中的宏展开 ,可以简单理解为文本替换,相比于C基础中的宏定义,这个宏能够替代一段程序。

这个宏做了什么事情?

上面的_syscall3的3的意思就是:有三个参数。只要都是3个参数都可以使用这部分代码的套路。

初始化一个描述 int 0x80 中断的门描述符,并添加到IDT表,门描述符中的段选择符是0x0008,可以定位到GDT表的第二个表项,即内核代码段

3.5 int 0x80 执行理解

上部分大致讲了write库函数的展开与实现过程,其中int 0x80还没有细说,现在看看这个指令是如何工作的。

image.png

这部分老师讲的很多,如果基础不牢,会感觉很晕,先捋一下思路:

image.png

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

//linux/include/asm/system.h中
#define set_system_gate(n,addr)
//n为中断处理号,addr是中断处理号。
_set_gate(&idt[n],15,3,addr);
//idt是中断向量表基址,传向gate_addr,15传向type,3传向dpl
//到这里应当明白dpl的设置过程,在这里目标态被设为了用户态
#define _set_gate(gate_addr, type, dpl, addr)
//又是一段C内嵌汇编的代码
__asm__("movw %%dx,%%ax\n\\t""movw %0,%%dx\n\t"
       "mov1 %%eax,%1\n\t""mov1 %%edx %2":
       :"i"((short)(ox8000+(dpl<<13)+type<<8))),"o"(*((char*)(gate_addr))),"o"(*(4+(char*)(gate_addr))),
"d"((char*)(addr),"a"(0x00080000));
//意思就是将表中的高四位和第四位分别贴到edx和eax

从上面的init()函数可知,系统初始化时就已经做了:int 0x80 通过system_call 来进行处理.


再来详细说说DPL=3的操作。


如何跳到80号中断?

3.6 system_call 理解

上面讲到使用system_call 来处理 int 0x80,它是如何做的呢?

image.png

关键代码:

mov1 $0x10,%edx 
mov %dx,%ds
mov %dx,%es
## 内核数据
###ds=es=0x10
###8是内核代码段,16(十进制)是内核数据段
###意味着从现在开始真正执行内核代码
###这里有疑问,老师说,既是内核代码段也是数据段?

## 跳到一个表里取执行内核代码
call _sys_call_table(,%eax,4)#a(,%eax,4)=a+4*eax
### eax正是前面的__NR_write,系统调用号
### _sys_call_table+4*%eax就是响应的调用处理函数入口

为什么要乘4呢?

eax是4(表示write的系统调用号为4,为第四系统调用)

而前面的4是,每个系统调用占4个字节。

再具体一点说,每个系统调用函数指针是4个字节

  1. 一个内存地址对应8位就是一个内存地址存1个字节,所以*4就是找4个字节
  2. 中断号为4,那从中断向量表里找中断服务函数入口的时候就是4*4,
  3. 即从表的初始地址往下加16个地址,就正好是16个字节,每四个字节一个入口

可以理解/推测 sys_call_table,就是一个函数表

3.7 sys_call_table/sys_write理解

image.png

从上面代码得知,_sys_call_table果然是一个函数指针数组,第4个位置上放的是sys_write。

从0开始数。

根据上面的4*4,最终计算得到的入口是sys_write,所以3.6图中的call _sys_call_table(,%eax,4),实际上就是call sys_write

sys_write 要做什么呢?

3.8 系统调用总结

如上图所示的链条:

  1. 用户调用 printf;

  2. printf 在库函数中展开为 包含 int 0x80 的代码;

  3. --------------用户态结束,内核态开始------------------

    这里用一种特殊的方式开启了后门(IDT表 将 DPL 置为3)

  4. system_call 中断处理;

  5. 查表 _sys_call_table;

  6. 根据__NR_write=4拿到对应函数;

  7. 调用 sys_write;

我们已经推进到了sys_write,也就是接口的边界,再向内部,才能解释sys_write 最后发生了什么。


回到最开始whoami的例子,参考上面write的过程:

image.png

实验二可以做了。

标签:调用,操作系统,int,笔记,write,0x80,接口,内核
来源: https://www.cnblogs.com/Roboduster/p/16589449.html