一、 实验问题描述
静态创建两个task
修改gdt表
实现四个task的顺序循环调度
实现屏幕滚屏效果
添加键盘中断服务程序,实现按下 number+char 可以更改第number号task的打印字符为char,其他形式的组合则无反应
二、 实验原理
- 静态创建两个task
(1) 任务的tss和ldt选择符的定义
(2) 静态添加tss、ldt、堆栈(2个)和任务代码
a) tr寄存器记录的是当前任务(运行进程)的tss信息,task0是从内核返回启动的所以初始值大部分是空的,下次任务切换时会被保存信息所覆盖。
b) ldt由ldtr指向。切换任务时从tss获取选择符,然后根据选择符的值查gdt(本linux的设计)的描述符,进一步获取ldt的起始地址,并加载给ldtr。当段寄存器的值的第2比特为是1时,会去到ldt中查找描述符(段表项),为0时,去找gdt。本例中选择符的值为0x0f、0x17时都是查ldt(段表)。选择符为SCRN_SEL = 0x18、TSS0_SEL = 0x20、LDT0_SEL = 0x28、TSS1_SEL = 0X30、LDT1_SEL = 0x38、TSS2_SEL = 0X40、LDT2_SEL = 0x48都是查gdt(段表)。
c) 段寄存器CS的第1、0比特位为0、0时表示当前特权级为0级(特权最大)就是内核级,为1、1时为3级,就是用户级。只有通过中断向量表的中断门才能由3级到0级,其它操作会产生异常,导致错误,无法运行。当为0级时会将ss和esp的值用tss中的0级栈ss0、esp0的值替换,为3级时会用ss3、esp3替换ss和Esp的值。因此不同级拥有不同堆栈,内核态用当前进程中的内核栈,而用户态用当前进程用户栈,从而把用户空间与内核空间隔开。
d) 任务代码中的int 0x80是属于系统调用,被定义为中断向量表中的陷阱门(不是中断门)。
e) 在用户空间里一般是将gdt屏蔽掉的。用户态的段寄存器的值不可有访问gdt选择符,如0x18等等。
- 修改gdt表
添加TSS2、LDT2的段描述符,添加TSS3、LDT3的段描述符。当切换任务的时候会根据选择符的值查gdt。
- 实现四个task的顺序循环调度
修改timer_interrupt,其中current代表当前的任务,依次判断当前的任务号,跳转到current-1,即实现0-3-2-1-0的顺序循环调度。跳转任务使用ljmp长转移指令,对于造成任务切换的长跳转偏移值无用,但需要写上。ljmp 选择符, 32位偏移, 跳转到任务的任务段选择符会造成CPU切换到该任务运行。此时偏移值无用,可以任意填写一个0。
- 实现屏幕滚屏效果
修改write_char,原本的程序中scr_loc为当前屏幕光标位置,ebx为屏幕两千个字符的当前输出到第几个,如果ebx小于2000即还没有铺满屏幕,那就不断+1,一直到最后一位。如果已经打印到最后一个,那就开始滚屏:
(1) 先将ds和es数据段保存,将当前的ds和es赋值为gs
(2) 设置方向标志为cld递增
(3) 每个字符占两个字节,一行有八十个字符,因此第二行的第一个起始地址为160,将160赋给si,即存放字符串的内存地址
(4) di为移动到目的处的地址,为0
(5) cx为要移动的数量,总共是1920个字符
(6) 开始rep
(7) 最后将最后一行赋值为0清空
- 添加键盘中断服务程序,实现按下 number+char 可以更改第number号task的打印字符为char,其他形式的组合则无反应.
这里采用直接在keyboard_interrupt中修改输出值的方法实现。
修改keyboard_interrupt,实现按键组合为number+char时可以保存在SETNOW和INPUTNOW中
(1) 在按下键盘时,数据会被送到0x60端口,从0x60读出数据
(2) 根据扫描码判断是否是数字0-3,如果是则保存到SETNOW中
(3) 如果不是0-3,则判断是否是a-z
(4) 如果是则先判断SETNOW是否有值,即上一个按键是否是0-3,如果是则说明输入是合法的,根据SETNOW的值去改变四个task的输出字符,INPUT0-INPUT4,并且将SETNOW设置为无效值。若SETNOW没有值则什么也不做直接退出
(5) 如果不是a-z则直接退出
三、 实验内容和步骤
- 静态创建两个task
(1) 定义tss和ldt选择符
图表 1 定义tss和ldt选择符
(2) 添加tss、ldt和堆栈
图表 2 添加tss2和ldt2
图表 3 添加tss3和ldt3
图表 4 创建两个新的task
- 修改gdt表
图表 5 gdt中添加tss和ldt
- 实现四个task的顺序循环调度
代码实现:
1 | /* Timer interrupt handler */ |
- 实现屏幕滚屏效果
修改write_char,原本的程序中scr_loc为当前屏幕光标位置,ebx为屏幕两千个字符的当前输出到第几个,如果ebx小于2000即还没有铺满屏幕,那就不断+1,一直到最后一位。如果已经打印到最后一个,那就开始滚屏:
(1) 先将ds和es数据段保存,将当前的ds和es赋值为gs
(2) 设置方向标志为cld递增
(3) 每个字符占两个字节,一行有八十个字符,因此第二行的第一个起始地址为160,将160赋给si,即存放字符串的内存地址
(4) di为移动到目的处的地址,为0
(5) cx为要移动的数量,总共是1920个字符
(6) 开始rep
(7) 最后将最后一行赋值为0清空
write_char代码实现:
1 | write_char: |
- 添加键盘中断服务程序,实现按下 number+char 可以更改第number号task的打印字符为char,其他形式的组合则无反应
这里采用直接在keyboard_interrupt中修改输出值的方法实现。
修改keyboard_interrupt,实现按键组合为number+char时可以保存在SETNOW和INPUTNOW中
(1) 在按下键盘时,数据会被送到0x60端口,从0x60读出数据
(2) 根据扫描码判断是否是数字0-3,如果是则保存到SETNOW中
(3) 如果不是0-3,则判断是否是a-z
(4) 如果是则先判断SETNOW是否有值,即上一个按键是否是0-3,如果是则说明输入是合法的,根据SETNOW的值去改变四个task的输出字符,INPUT0-INPUT4,并且将SETNOW设置为无效值。若SETNOW没有值则什么也不做直接退出
(5) 如果不是a-z则直接退出
Keyborad_interrupt代码实现:
1 | SETNOW:.byte 8 |
四、 实验结果
四个任务循环调度与滚屏效果:
图表 6 四个任务循环调度与滚屏效果
输入0P,更改第0号task输出为P,A已经不见了
图表 7 更改第0号task输出为P
连续输入0Q1W2E3R,实现将0号输出改成Q,1号输出改成W,2号输出改成E,3号输出改成R
图表 8 依次更改4个任务的输出