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

上面三行是为了使EFLAGSNT位=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:

  1. 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.
  2. The saved EFLAGS image is popped off of the stack and loaded into the EFLAGS register.
  3. The return-program stack pointer is popped off of the stack, loading both the SS register and ESP register with the saved values.
  4. 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). eipeflags不是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 代码解析(三)相关推荐

  1. Linux 0.00 代码解析(一)

    <Linux内核完全剖析>这本书在第4章给出了一个简单多任务内核示例程序,作者称之为Linux 0.00系统. 源码的下载地址和实验方法可以参考我的博文 http://blog.csdn. ...

  2. Linux-0.00 代码解析(四)

    Linux 0.00 的编译.运行.源码下载: http://blog.csdn.net/longintchar/article/details/78757065 Linux 0.00 Makefil ...

  3. Linux0.00 代码解析(二)

    Linux 0.00 的编译.运行.源码下载: http://blog.csdn.net/longintchar/article/details/78757065 Linux 0.00 Makefil ...

  4. Linux 0.00 的编译和运行

    <Linux内核完全剖析>这本书在第4章给出了一个简单多任务内核示例程序,我们称之为Linux 0.00系统.本文介绍了一种把它跑起来的方法. 一.实验环境 Win7(64位)+Vmwar ...

  5. Linux 0.00简单多任务内核head.s超详注释

    # head.s包含32位保护模式初始化设置代码.时钟中断代码.系统调用中断代码和两个任务的代码. # 在初始化完成之后程序移动到任务0开始执行,并在时钟中断控制下进行任务0和1之间的切换操作. LA ...

  6. Linux 0.11 内核解析:中断相关(1)asm.s文件中断处理分析

    0 源代码 有两个版本的,一个是带中文注释,Intel格式的:一个是不带注释是AT&T格式的. Linux 0.11 中文注释版 Linux 0.11 源码,基于<Linux内核完全注释 ...

  7. Linux 0.11 代码解读(一)bootsect.s

    最近在研究赵炯老师的<Linux 内核 0.11 详细注释>,将自己的粗浅理解做个简单的注记,以供有相同兴趣爱好的朋友查阅. 文章目录 一.程序功能 二.代码注记 一.程序功能 当 PC ...

  8. Linux 0.00 Makefile 说明

    关于Linux-0.00 的编译和运行可以参考我的博文:http://blog.csdn.net/longintchar/article/details/78757065 为了能在64位的Ubuntu ...

  9. AFNetworking2.0源码解析三

    本篇说说安全相关的AFSecurityPolicy模块,AFSecurityPolicy用于验证HTTPS请求的证书,先来看看HTTPS的原理和证书相关的几个问题. HTTPS HTTPS连接建立过程 ...

最新文章

  1. 求助大佬6——1种贪心
  2. 成功解决ModuleNotFoundError: No module named 'utils'
  3. REALM后续:最近邻搜索,MIPS,LSH和ALSH
  4. FPGA机器学习之stanford机器学习第三堂1
  5. python与access选哪个_从Python连接到Access
  6. input复选框checkbox默认样式纯css修改
  7. (转)ScriptManager.RegisterStartupScript方法和Page.ClientScript.RegisterStartupScript() 方法...
  8. Python极简代码压缩图像十到百倍
  9. 第5章 C++内存模型和原子类型操作
  10. OpenCV中将RGB数组在内存中压缩成JPEG文件
  11. php服务端搜索,功能改进
  12. 决策树的实现及可视化方法总结
  13. 网站加了CDN后,字体图标报错Access-Control-Allow-Origin
  14. ictclas4j java_使用继续完善前人写的文章:使用ICTCLAS JAVA版(ictclas4j)进行中文分词...
  15. java包裹邮费计算_猿实战16——承运商之搭建你的运费基石
  16. 关于Mongodb的全面总结,学习mongodb的人,可以从这里开始
  17. Virtualbox源码分析17 APIC虚拟化2.APIC设备模拟
  18. 每日一道SQL题(第N高的薪水)
  19. OpenCV快速入门篇(Python实现)
  20. 2021年全球与中国椭圆形板簧行业市场规模及发展前景分析

热门文章

  1. Ubuntu16.04LTS Install Intel® RealSense™ ROS from Sources
  2. 如何安装Windows10+CentOS7双系统_自用成功版——注意看评论的注意事项
  3. CentOS 7.6 64位安装docker并设置开机启动
  4. jmeter中生成UUID作为唯一标识符
  5. python操作docx学习资料
  6. 【Java】 大话数据结构(13) 查找算法(4) (散列表(哈希表))
  7. jquery实现滚动条滚动到一定高度导航固定不变
  8. 用C/C++扩展你的PHP
  9. 吴恩达 coursera AI 专项五第一课(上)总结+作业答案
  10. cuda-Block和Grid设定