0%

os实验lab1

bochs调试Linux0.00

实验报告

请简述 head.s 的工作原理

head.s工作中主要包括初始化和任务切换两部分:

初始化部分主要是重新建立和设置IDT和GDT表,以及设置中断计时器,构建IDT表里的中断门描述符和系统调用陷阱门描述符等。

之后执行到任务0,在中断计时器的作用下,实现任务0和任务1之间的切换操作。

head.s 的内存分布状况,写明每个数据段,代码段,栈段的起始与终止的内存地址

刚跳转到0x0的时候:

数据段起始地址为0x00000000,终止地址为0x007fffff

代码段起始地址为0x00007c00,终止地址为0x00007c00+0x0000ffff

栈段起始地址为0x00007c00,终止地址为0x00007c00+0x0000ffff

Untitled

初始化结束:

数据段起始地址为0x00000000,终止地址为0x007fffff

代码段起始地址为0x00000000,终止地址为0x007fffff

栈段起始地址为0x00000000,终止地址为0x007fffff

Untitled

切换到任务0:

数据段起始未初始化

代码段起始地址为0x00000000,终止地址为0x003fffff

栈段起始地址为0x00000000,终止地址为0x007fffff

Untitled

初始化完:

数据段起始地址为0x00000000,终止地址为0x003fffff

代码段起始地址为0x00000000,终止地址为0x003fffff

栈段起始地址为0x00000000,终止地址为0x003fffff

Untitled

简述 head.s 57 至 62 行在做什么?

汇编指令:

1
2
3
4
5
6
pushl $0x17 ;任务0当前局部空间数据段(堆栈段)选择符入栈
pushl $init_stack ;将堆栈指针入栈,也可以直接把ESP入栈
pushfl ;将标志寄存器入栈
pushl $0x0f ;把当前局部空间代码段选择符入栈
pushl $task0 ;将代码指针入栈
iret ;执行中断返回指令,从而切换到特权级3的任务0中执行

也就是这个时候要进行内核态到用户态之间的切换,从内核程序将控制权转移给任务0程序。

所以按顺序压栈了各种寄存器以及段选择符。

之所以要压栈+iret实现内核态到用户态的访问,

内核是不会调用用户层的代码,要想实现这逆向的转移,一般做法是在用户进程的核心栈 (tss->esp0)压入用户态的SS,ESP,EFLAGS,CS,EIP,伪装成用户进程是通过陷阱门进入核心态,之后通过iret返回用户态

简述 iret 执行后, pc 如何找到下一条指令?

iret等价于

1
2
3
4
5
pop ip
pop cs
pop eflags
pop esp
pop ss

所以iret执行后,会重置大部分寄存器,这个时候ip寄存器发生变化,pc就能找到下一条指令了。

记录 iret 执行前后,栈是如何变化的?

iret前栈帧:

Untitled

Untitled

iret后:

Untitled

Untitled

可见,主要的有关栈的变化在于ss段寄存器,这里从0x0010变为了0x0017,段选择子发生了变化,选择了不同的段,对内存访问权限发生了变化等,表明从内核态转移到了用户态,访问的是权限等级为用户等级的栈段。

当任务进行系统调用时,即 int 0x80 时,记录栈的变化情况。

切换到任务0之后在0x10e9、0x10fd

系统调用号0x41和0x42

66 sys_setsid
65 sys_getpgrp

Linux系统调用 int 80h int 0x80-CSDN博客

不过该系统调用是进入了我们写好的内核段代码,不会执行上面的linux系统调用列表,这里相当于布置了一个陷阱,int80将会进入我们的函数里。

实际上这段汇编是在无限循环打印A

每次int80 不都会进入一个函数,这个函数是内核态的,用于打印一个字符。

Untitled

int80实现了用户态到内核态的切换,随后从内核态通过iret返回用户态

切换前:

Untitled

Untitled

切换后:

Untitled

Untitled

发生了四个寄存器的切换,切换到了中断处理函数那里,所以cs、ss段寄存器发生了变化。