实验背景
1. 描述符表
操作系统加载程序 setup.s
读取系统参数至 0x90000
处覆盖 bootsect.s
程序, 然后将 system
模块下移动到 0x00000
处。.同时,加载中断描述符表寄存器 idtr
和全局描述符表寄存器 gdtr
,设置CPU的控制寄存器 CR0
/程序状态字 PSW
,从而进入32位保护模式,跳转到 head.s
开头执行。
为了能让 head.s
在32位保护模式下运行,程序临时设置中断描述符表 idt
和全局描述符表 gdt
,并在 gdt
中设置当前内核代码段的描述符和数据段的描述符。
数据段描述符和代码段描述符存放在gdt
表内,寄存器 gdtr
由基地址和段限长组成,处理器通过寄存器 gdtr
定位 gdt
表。
段选择符由描述符索引、表指示器 TI
和请求者特权级字段组成,描述符索引用于选择指定描述符表中 8192 ( 2 1 3 ) 8192(2^13) 8192(213) 个描述符的一个,表指示器 TI
值为 0 0 0 表示指定 gdt
表,值为 1 1 1 表示指定 idt
表,而请求者特权级用于保护机制。
2. 特权级
处理器的段保护机制可以识别4个特权级 R0~R3
,数值越大特权越小。环中心为核心态,最外层为用户态。处理器利用特权级防止运行在较低特权级的程序或人物访问具有较高特权级的一个段。
为了在各个代码段和数据段之间进行特权级检测处理,处理器可以识别以下三种类型的特权级:
- 当前特权级
CPL(Current Privilege Level)
:CPL
存放在CS
和SS
段寄存器的位0和位1,代表正在执行的程序或任务的特权级。 - 描述符特权级
DPL(Descriptor Privilege Level)
:当程序访问数据时,DPL
存放在数据段的DPL
字段,代表访问当前数据段所需要的特权级。 - 请求特权级别
RPL(Request Privilge Level)
:RPL
通过段选择符的第0和第1位表现出来的,RPL
相当于附加的一个权限控制,防止低特权级程序出现高特权级代码,从而能够越权访问数据段,但只有当RPL>DPL
的时候,才起到实际的限制作用。
2. 中断过程
中断来源包括外部硬件和内部软件两部分,系统调用需要使用 int 0x80
软件中断指令修改 CPL
值,实现处理器内核态和用户态的切换。
中断描述符表 idt
可以驻留在内存的任何地方,处理器使用启动时设置的 idtr
寄存器定位 idt
表的位置。idtr
寄存器包含 idt
表32位的基地址和16位的长度值。
idt
表可存放中断门、陷阱门和任务门三种类型的门描述符。中断门含有一个长指针(段选择符和偏移值),处理器使用该长指针把程序执行权转移到代码段的中断处理过程中。
系统调用包含中断指令切换CPU的用户态和内核态,而此处阐述中断指令发出后寻找中断描述符表和中断处理程序的过程,详细的中断过程可阅读 《操作系统导论》学习笔记(三):CPU虚拟化(机制)
3. 系统调用使用
库函数 printf()
对应的指令本质实际上是将一些数据 write()
到显存的某些位置,而且输出到屏幕是IO操作,所以需要使用中断指令进入内核执行系统调用例程。
下面给出库函数API和C代码中嵌入汇编代码两种方式使用同一个系统调用 write()
:
#include <fcntl.h> /* open() */
#include <unistd.h> /* write() */
#include <string.h> /* strlen() */
int main()
{
int fd = open("write.txt", O_RDWR|O_CREAT);
char *buf = "hello,world!\n";
int count = strlen(buf);
write( fd, buf, count);
close( fd );
return 0;
}
编译执行,成功生成文件并写入字符串:
下面使用C代码内嵌汇编的方法自制系统调用 my_write()
:
查询当前LINUX操作系统的系统调用表(System Call Table),可知 write
系统调用号为 1,而LINUX0.11的 write
系统调用号为4。
内嵌汇编的语法如下:
_asm_ _volatile_ (
汇编语句模版;
输出部分;
输入部分;
破坏描述部分;
);
汇编语言部分将 write
系统调用号保存至 rax
,使用 syscall
而非 int $0x80
触发系统调用,输入部分从内存 m
获取文件描述符 fd
,字符串指针 buf
和字符串长度 count
,输出部分返回错误信息 res
。
int my_write(int fd, const void *buf, int count)
{
int res = 0;
asm("mov $1, %%rax\n\t" /* 系统调用号sys_write(1) */
"syscall\n\t" /* 触发系统调用 */
:"=a"(res) /* 输出部分:变量res */
:"m"(fd), "m"(buf), "m"(count) /* 输入部分:文件描述符fd, 字符串指针buf,字符串长度count */
);
return res;
}
C代码中嵌入汇编代码方式使用系统调用 my_write()
:
#include <fcntl.h> /* open() */
#include <unistd.h> /* write() close() */
#include <string.h> /* strlen() */
int my_write(int fd, const void *buf, int count)
{
int res = 0;
asm("mov $1, %%rax\n\t" /* 系统调用号sys_write(1) */
"syscall\n\t" /* 触发系统调用 */
:"=a"(res) /* 输出部分:变量res */
:"m"(fd)