Linux-0.00 代码解析(三)
6. 安装中断门和陷阱门
# setup timer & system call interrupt descriptors.movl $0x00080000, %eax movw $timer_interrupt, %axmovw $0x8E00, %dxmovl $0x08, %ecx # The PC default timer int.lea idt(,%ecx,8), %esimovl %eax,(%esi) movl %edx,4(%esi)movw $system_interrupt, %axmovw $0xef00, %dxmovl $0x80, %ecxlea idt(,%ecx,8), %esimovl %eax,(%esi) movl %edx,4(%esi)
中断门的格式是:
代码中edx是高32位,eax是低32位
movl $0x00080000, %eax
代码段的选择子就绪
movw $timer_interrupt, %ax
偏移15..0就绪
movw $0x8E00, %dx
edx的低16位就绪
我的疑问是:edx的高16位呢?
movl $0x08, %ecx
从表格中可以看出BIOS把8253的中断号设置为8.
lea idt(,%ecx,8), %esi
esi = idt + ecx * 8,计算出第8个中断门的位置,乘以8是因为一个中断门描述符占8字节。
movl %eax,(%esi)
movl %edx,4(%esi) # 安装中断门
此中断门用于切换任务,每隔10ms切换一次。
中断门timer_interrupt的代码是:
timer_interrupt:push %dspushl %eaxmovl $0x10, %eax # 0x10是内核数据段的选择子mov %ax, %dsmovb $0x20, %aloutb %al, $0x20 # 向8259发送中断结束(EOI)命令,端口是0x20, 命令字是0x20,不用深究movl $1, %eax # eax=1cmpl %eax, currentje 1f #相等跳转到1处movl %eax, current # current = 1ljmp $TSS1_SEL, $0 #切换到任务1jmp 2f
1: movl $0, current #切换到任务0ljmp $TSS0_SEL, $0
2: popl %eaxpop %dsiret
ljmp $TSS1_SEL, $0
当处理器执行这条指令时,首先用指令中给出的段选择子访问GDT或LDT(这里是GDT),分析它的描述符类型,这里发现是TSS描述符,于是执行任务切换,指令中的偏移量(这里是0)被忽略。
下面是安装陷阱门。
陷阱门的格式是:
movw $system_interrupt, %ax
eax的低16位就绪,高16位在上面已经设置成了代码段的选择子
movw $0xef00, %dx
DPL=3 的陷阱门
疑问:edx的高16位呢?
movl $0x80, %ecx
系统调用向量号是0x80,这个0x80应该是作者指定的
lea idt(,%ecx,8), %esi #这三句同上,不赘述 movl %eax,(%esi) movl %edx,4(%esi)
这个陷阱门其实是一个系统调用,用AL传参,把AL代表的字符打印到屏幕上。其内部调用了内核过程write_char
system_interrupt: # 0x80系统调用,把AL中的字符打印到屏幕上push %dspushl %edxpushl %ecxpushl %ebxpushl %eaxmovl $0x10, %edxmov %dx, %ds #以上两句是否可以不要??call write_charpopl %eaxpopl %ebxpopl %ecxpopl %edxpop %dsiret
7. 开始执行任务0
# Move to user mode (task 0)pushflandl $0xffffbfff, (%esp)popflmovl $TSS0_SEL, %eaxltr %axmovl $LDT0_SEL, %eaxlldt %ax movl $0, currentstipushl $0x17pushl $init_stackpushflpushl $0x0fpushl $task0iret
上面这段代码要想说清楚,就说来话长了。
pushfl #把EFLAGS入栈andl $0xffffbfff, (%esp) #设NT=0popfl #加载EFLAGS
上面三行是为了使EFLAGS
的NT
位=0;为什么要这样做呢?因为后面要用iret
指令返回,当返回的时候,如果NT=1,表示返回到前一个任务,而这种情况不是我们想要的。
movl $TSS0_SEL, %eax #TSS0_SEL是任务0的TSS选择子ltr %ax
以上两行是把任务0的TSS选择子装入任务寄存器TR;
LTR指令的格式是
ltr r/m16
操作数是16位的通用寄存器或者是指向16位单元的内存地址。TSS选择子是16位的,所以没有用eax,而用ax; LLDT指令用法类似。
movl $LDT0_SEL, %eaxlldt %ax #把任务0的LDT段选择子装入LDTR(局部描述符表寄存器)movl $0, current #表示当前运行的是任务0sti #开中断
7.1 中断处理过程
咱们先复习一下异常或中断的处理过程。
当目标代码段描述符的DPL(可以用门描述符中的段选择子,从GDT或LDT中找到)在数值上<=CPL(当前特权级)时,才允许将控制转移到中断或异常处理程序。
如果 DPL < CPL,将发生栈切换。栈切换的过程:
(1)根据DPL,从当前任务的TSS中取得对应的SS和ESP,作为中断或异常处理过程使用的SS和ESP(新栈)。
(2)处理器把旧栈的选择子和栈指针压入新栈。
(3)把EFLAGS、CS、EIP压入新栈。
(4)对于有错误码的异常,还要把错误代码压入新栈(我们模拟返回的时候没有错误码)。
有了上面的铺垫,我们可以继续看代码了。
pushl $0x17 #把任务0的局部空间数据段(也是栈段)选择子入栈pushl $init_stack #把任务0的ESP入栈pushfl #把EFLAGS入栈pushl $0x0f #把任务0的代码段选择子入栈pushl $task0 #把任务0的EIP入栈iret #环境已经设置完毕,这里模拟从中断返回,返回后开始执行任务0,其特权级为3
7.2 中断返回过程
先不管后面的操作数是怎么来的,总之压栈该压什么,顺序是什么我们理解了。接下来看看IRET指令。这个指令的含义,我首先看了Intel指令集手册,感觉说得很复杂,某些地方还有歧义。我不甘心,又搜了很多网上的资料,没有一个令我满意。最后,我打算用AMD的解释,因为很简明:
IRET, Less Privilege. If an IRET changes privilege levels, the return program must be at a lower privilege than the interrupt handler. The IRET in this case causes a stack switch to occur:
- The return pointer is popped off of the stack, loading both the CS register and EIP register with the saved values. The return code-segment RPL is read by the processor from the CS value stored on the stack to determine that a lower-privilege control transfer is occurring.
- The saved EFLAGS image is popped off of the stack and loaded into the EFLAGS register.
- The return-program stack pointer is popped off of the stack, loading both the SS register and ESP register with the saved values.
- Control is transferred to the return program at the target CS:EIP.
说到这里,文件head.s
的主体就完了。
8. 任务0的LDT
ldt0: .quad 0x0000000000000000.quad 0x00c0fa00000003ff # 0x0f.quad 0x00c0f200000003ff # 0x17
任务0的局部描述符表分析如下:
索引号 | 描述符类型 | 基地址 | 段界限 | 粒度 | P | DPL | 备注 | 选择子 |
---|---|---|---|---|---|---|---|---|
0 | 空描述符 | - | - | - | - | - | - | - |
1 | 代码段 | 0 | 0X3FF | 4KB | 1 | 3 | 代码段,非一致性,可读 | 0x0F |
2 | 数据段 | 0 | 0X3FF | 4KB | 1 | 3 | 数据段,向上扩展,可写 | 0x17 |
为了少耗费点脑细胞,我写了一个C语言的小程序,专门用来分析各种类型的段描述符,拿走不谢。
http://blog.csdn.net/longintchar/article/details/78881396
运行截图如下:
9. 任务0的TSS
tss0: .long 0 /* back link */.long krn_stk0, 0x10 /* esp0, ss0*/.long 0, 0, 0, 0, 0 /* esp1, ss1, esp2, ss2, cr3 */.long 0, 0, 0, 0, 0 /* eip, eflags, eax, ecx, edx */.long 0, 0, 0, 0, 0 /* ebx esp, ebp, esi, edi */.long 0, 0, 0, 0, 0, 0 /* es, cs, ss, ds, fs, gs */.long LDT0_SEL, 0x8000000 /* ldt, trace bitmap */.fill 128,4,0
krn_stk0:
以上填0的字段就不分析了,我们重点看看非0字段。
krn_stk0是ESP0,就在代码的最后一行。
0x10是内核数据段的选择子。
LDT0_SEL是任务0的LDT的选择子,数值上 = 0x28,在GDT中有定义。
复习一下,GDT中的描述符。
索引号 | 选择子 | 描述符类型 | 基地址 | 段界限 | 粒度 | P | DPL | 备注 |
---|---|---|---|---|---|---|---|---|
1 | 0x08 | 代码段 | 0 | 0X7FF | 4KB | 1 | 0 | 内核代码段,非一致性,可读 |
2 | 0x10 | 数据段 | 0 | 0X7FF | 4KB | 1 | 0 | 内核数据段,向上扩展,可写 |
3 | 0x18 | 数据段 | 0XB8000 | 0X2 | 4KB | 1 | 0 | 内核显存段,向上扩展,可写 |
4 | 0x20 | TSS段 |
tss0
|
0X68 | 1B | 1 | 3 | 任务0的TSS段,不忙 |
5 | 0x28 | LDT段 |
ldt0
|
0X40 | 1B | 1 | 3 | 任务0的LDT描述符 |
6 | 0x30 | TSS段 |
tss1
|
0X68 | 1B | 1 | 3 | 任务1的TSS段,不忙 |
7 | 0x38 | LDT段 |
ldt1
|
0X40 | 1B | 1 | 3 | 任务1的LDT描述符 |
0x8000000是I/O许可位串。如果这个值大于或者等于TSS的段界限(在TSS描述符中),则表明没有I/O许可位串。因为TSS的段界限是0x68,所以表示没有许可位串。
10. 任务1的TSS
tss1: .long 0 /* back link */.long krn_stk1, 0x10 /* esp0, ss0 */.long 0, 0, 0, 0, 0 /* esp1, ss1, esp2, ss2, cr3 */.long task1, 0x200 /* eip, eflags */.long 0, 0, 0, 0 /* eax, ecx, edx, ebx */.long usr_stk1, 0, 0, 0 /* esp, ebp, esi, edi */.long 0x17,0x0f,0x17,0x17,0x17,0x17 /* es, cs, ss, ds, fs, gs */.long LDT1_SEL, 0x8000000 /* ldt, trace bitmap */.fill 128,4,0
krn_stk1:
和任务0相比,任务1的TSS有几点不同。
(1). eip
和eflags
不是0,而是task1和0x200,task1是任务1代码的入口点,当任务0首次被时钟中断打断时,将会切换到任务1,这时CPU把tss1作为任务1的EIP初始值。同理,0x200作为任务1的eflags初始值。为什么eflags初始值是0x200呢?根据下图,可以知道eflags中IF为1,表示允许中断。必须要允许中断,因为任务切换靠中断实现。
【System Flags in the EFLAGS Register】
(2). 任务0的esp=0,ss=0,任务1的esp=usr_stk1,ss=0x17。这是因为TSS中的esp和ss对应的特权级别是3,任务0的esp、ss的初始值通过iret指令从栈中获取,而任务1的要从TSS中获取。
(3). 任务0的es,cs,ds,fs,gs都为0,任务1的es=ds=fs=gs=0x17,cs=0x0f,道理同上,任务0的cs初始值从栈中获得,任务1的所有段寄存器的初始值都从TSS中获得。
下图是32位任务状态段(TSS)的格式,贴出来方便复习。
11. 任务0的代码段
task0:movl $0x17, %eax #0x17是任务0的数据段的选择子movw %ax, %ds #因为任务0没有用到局部数据段,所以这两句可以不要movb $65, %al # print 'A' int $0x80 # 系统调用movl $0xfff, %ecx
1: loop 1b # 为了延时jmp task0 # 死循环
任务1的代码段类似,不再赘述。
用了3篇博文,基本把代码说完了。下篇文章打算修改几个地方并做实验。
【未完待续】
Linux-0.00 代码解析(三)相关推荐
- Linux 0.00 代码解析(一)
<Linux内核完全剖析>这本书在第4章给出了一个简单多任务内核示例程序,作者称之为Linux 0.00系统. 源码的下载地址和实验方法可以参考我的博文 http://blog.csdn. ...
- Linux-0.00 代码解析(四)
Linux 0.00 的编译.运行.源码下载: http://blog.csdn.net/longintchar/article/details/78757065 Linux 0.00 Makefil ...
- Linux0.00 代码解析(二)
Linux 0.00 的编译.运行.源码下载: http://blog.csdn.net/longintchar/article/details/78757065 Linux 0.00 Makefil ...
- Linux 0.00 的编译和运行
<Linux内核完全剖析>这本书在第4章给出了一个简单多任务内核示例程序,我们称之为Linux 0.00系统.本文介绍了一种把它跑起来的方法. 一.实验环境 Win7(64位)+Vmwar ...
- Linux 0.00简单多任务内核head.s超详注释
# head.s包含32位保护模式初始化设置代码.时钟中断代码.系统调用中断代码和两个任务的代码. # 在初始化完成之后程序移动到任务0开始执行,并在时钟中断控制下进行任务0和1之间的切换操作. LA ...
- Linux 0.11 内核解析:中断相关(1)asm.s文件中断处理分析
0 源代码 有两个版本的,一个是带中文注释,Intel格式的:一个是不带注释是AT&T格式的. Linux 0.11 中文注释版 Linux 0.11 源码,基于<Linux内核完全注释 ...
- Linux 0.11 代码解读(一)bootsect.s
最近在研究赵炯老师的<Linux 内核 0.11 详细注释>,将自己的粗浅理解做个简单的注记,以供有相同兴趣爱好的朋友查阅. 文章目录 一.程序功能 二.代码注记 一.程序功能 当 PC ...
- Linux 0.00 Makefile 说明
关于Linux-0.00 的编译和运行可以参考我的博文:http://blog.csdn.net/longintchar/article/details/78757065 为了能在64位的Ubuntu ...
- AFNetworking2.0源码解析三
本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题. HTTPS HTTPS连接建立过程 ...
最新文章
- 求助大佬6——1种贪心
- 成功解决ModuleNotFoundError: No module named 'utils'
- REALM后续:最近邻搜索,MIPS,LSH和ALSH
- FPGA机器学习之stanford机器学习第三堂1
- python与access选哪个_从Python连接到Access
- input复选框checkbox默认样式纯css修改
- (转)ScriptManager.RegisterStartupScript方法和Page.ClientScript.RegisterStartupScript() 方法...
- Python极简代码压缩图像十到百倍
- 第5章 C++内存模型和原子类型操作
- OpenCV中将RGB数组在内存中压缩成JPEG文件
- php服务端搜索,功能改进
- 决策树的实现及可视化方法总结
- 网站加了CDN后,字体图标报错Access-Control-Allow-Origin
- ictclas4j java_使用继续完善前人写的文章:使用ICTCLAS JAVA版(ictclas4j)进行中文分词...
- java包裹邮费计算_猿实战16——承运商之搭建你的运费基石
- 关于Mongodb的全面总结,学习mongodb的人,可以从这里开始
- Virtualbox源码分析17 APIC虚拟化2.APIC设备模拟
- 每日一道SQL题(第N高的薪水)
- OpenCV快速入门篇(Python实现)
- 2021年全球与中国椭圆形板簧行业市场规模及发展前景分析
热门文章
- Ubuntu16.04LTS Install Intel® RealSense™ ROS from Sources
- 如何安装Windows10+CentOS7双系统_自用成功版——注意看评论的注意事项
- CentOS 7.6 64位安装docker并设置开机启动
- jmeter中生成UUID作为唯一标识符
- python操作docx学习资料
- 【Java】 大话数据结构(13) 查找算法(4) (散列表(哈希表))
- jquery实现滚动条滚动到一定高度导航固定不变
- 用C/C++扩展你的PHP
- 吴恩达 coursera AI 专项五第一课(上)总结+作业答案
- cuda-Block和Grid设定