Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2117189
  • 博文数量: 438
  • 博客积分: 3871
  • 博客等级: 中校
  • 技术积分: 6075
  • 用 户 组: 普通用户
  • 注册时间: 2011-09-10 00:11
个人简介

邮箱: wangcong02345@163.com

文章分类

全部博文(438)

文章存档

2017年(15)

2016年(119)

2015年(91)

2014年(62)

2013年(56)

2012年(79)

2011年(16)

分类: LINUX

2016-09-14 19:00:50

1.说明
a. 书上《Linux内核完全注释V3.0.pdf》4.9 一个简单的多任务内核实例,看完之后基本上进程切换就没有大问题了
但是书上为了与linux 0.11相似多写了一些无用的代码,这儿我作了一些修改,使代码更加简洁,看起来更加直观。
b.代码流程
mbr.S:     -->这个除了引导之外没有别的作用,看一下就够了,把重点放在loader.S中
    a. 从0x7c00启动,打印1mbr四个字符
    b. 从硬盘的第2个扇区LBA_2读20个sector的loader.bin到0x00
    c. 初始化8259A,将主从8259A的断号配为0x20-0x27,0x28-0x2F
    d. 跳到jmp 0x00:0, 也就是loader.bin处执行
loader.S:
    a. 从0x0处运行,开启保护模式-->打开A20,加载GDT,cr0第0位置1
    b. 跳到保护模式,设置各个段寄存器ds=es=ss=选择子
    c. 初始化IDT,单独设置时钟中断与系统调用int 0x80中断描述符
    d. 初始化时钟
    e. 开中断
    c. 设置iret前的栈,执行iret后进入task0

2. 代码
2.1 mbr.S
  1.     mov ax, cs
  2.     mov ds, ax
  3.     mov es, ax
  4.     mov ss, ax
  5.     mov ax, 0xb800
  6.     mov gs, ax

  7.     ;print 1MBR
  8.     mov byte [gs:0x00], '1'
  9.     mov byte [gs:0x01], 0xA4
  10.     
  11.     mov byte [gs:0x02], 'M'
  12.     mov byte [gs:0x03], 0xA4

  13.     mov byte [gs:0x04], 'B'
  14.     mov byte [gs:0x05], 0xA4
  15.     
  16.     mov byte [gs:0x06], 'R'
  17.     mov byte [gs:0x07], 0xA4
  18.     ;因为loader.bin没有超过0x7c00(31K)如果超过了31K的话需要mbr.S自己把自己移动到0x90000处,
  19.     ;然后再跳到0x9000处执行,这儿直接把loader.bin加载到0x0处就够了,没有必要多次一举
  20.     ;loader loader.bin to 0x00000
  21.     mov ax, 0x0000    
  22.     mov es, ax
  23.     mov bx, 0x00 ;[es:bx]-->dst_add in memory
  24.     mov cl, 20 ;sector cnt of disk
  25.     mov eax, 2 ;LBA addr
  26.     call load_disk

  27.     call init_8259             -->初始化8259A,把前0x20个中断预留出来,主8259A中断从IR[0-7]-->[0x20-0x27]
  28.                                -->从8259A中断 IR[8-15]-->[0x28-0x2F] 

  29.     jmp dword 0x0:0           -->跳到0x0处执行loader.bin的代码了,这个mbr.S就没看的必要的,是不是很简单

  30. load_disk:
  31.     ;eax --> src_addr of disk, in the format of LBA
  32.     ;es:bx --> memory dst_addr
  33.     ;cx --> sector_count
  34.     mov esi, eax
  35.     mov di, cx

  36.     mov dx, 0x1f2 ;sector count
  37.     mov al, cl
  38.     out dx, al
  39.     
  40.     mov eax, esi ;LBA-->0-8
  41.     mov dx, 0x1f3
  42.     out dx, al
  43.     
  44.     mov cl, 8
  45.     shr eax, cl ;LBA--> 8-16
  46.     mov dx, 0x1f4
  47.     out dx, al    

  48.     shr eax, cl ;LBA-->16-24
  49.     mov dx, 0x1f5
  50.     out dx, al

  51.     shr eax, cl ;LBA-->16-24
  52.     and al, 0x0f
  53.     or al, 0xe0
  54.     mov dx, 0x1f6
  55.     out dx, al

  56.     mov dx,0x1f7 ;send read cmd
  57.     mov al, 0x20
  58.     out dx, al

  59. not_ready:
  60.     nop
  61.     in al, dx
  62.     and al,0x88
  63.     cmp al,0x08
  64.     jnz not_ready

  65.     mov ax, di
  66.     mov dx, 256
  67.     mul dx
  68.     mov cx, ax
  69.     mov dx, 0x1f0
  70. goon_read:
  71.     in ax, dx
  72.     mov [es:bx], ax
  73.     add bx, 2
  74.     loop goon_read
  75.     ret

  76. ; ==================================================================================
  77. ; 初始化8259
  78. ; 《自己动手写操作系统》,P113
  79. init_8259:

  80.     ; 主从的ICW1
  81.     ; 边沿触发,级联,需要ICW4
  82.     mov al,0x11
  83.     out 0x20,al
  84.     out 0xa0,al

  85.     ; 主从的ICW2
  86.     ; 中断向量:IRQ0:0x20,IRQ8:0x28
  87.     mov al,0x20
  88.     out 0x21,al ;IR[0-7]-->[0x20-0x27]
  89.     mov al,0x28
  90.     out 0xa1,al ;IR[8-15]-->[0x28-0x2F]

  91.     ; 主从的ICW3
  92.     mov al,0x04
  93.     out 0x21,al
  94.     mov al,0x02
  95.     out 0xa1,al

  96.     ; 主从的ICW4
  97.     mov al,0x01
  98.     out 0x21,al
  99.     out 0xa1,al

  100.     ; 屏蔽所有中断
  101.     mov al,0xff
  102.     out 0x21,al
  103.     out 0xa1,al

  104.     ret

  105. times 510-($-$$) db 0            ; 填充余下空间
  106. dw 0xaa55
  107. ; ==============================end of this file====================================
2.2 loader.S
  1. jmp begin
  2. align 8
  3. gdt_base:
  4.     dw 0,0,0,0            ; 第一个须为空,不使用;select=0x0
  5.     dw 0x07ff, 0x0000, 0x9a00, 0x00c0    ; 0x08第二个GDT:代码段描述符,[0-8M]段限长2KB*4KB=8MB
  6.     dw 0x07ff, 0x0000, 0x9200, 0x00c0    ; 0x10第三个GDT:数据段描述符,[0-8M]
  7.     dw 0x0002, 0x8000, 0x920b, 0x00c0    ; 0x18第四个GDT:显存数据段描述符
  8.     dw 0x0068, tss0, 0xe900, 0x0000    ; 0x20第五个GDT:TSS0描述符 G=0,DPL=3,type=9<LDT>
  9.     dw 0x0040, ldt0, 0xe200, 0x0000    ; 0x28第六个GDT: LDT0描述符 G=0,DPL=3,type=2<TSS>
  10.     dw 0x0068, tss1, 0xe900, 0x0000    ; 0x30第七个GDT:TSS1描述符 G=0,DPL=3,type=9<TSS>
  11.     dw 0x0040, ldt1, 0xe200, 0x0000    ; 0x38第八个GDT:LDT1描述符 G=0,DPL=3,type=2<LDT>
  12. end_gdt:
  13.     times 8 db 0        
  14. gdt_ptr:
  15.     dw (end_gdt-gdt_base) - 1
  16.     dd gdt_base
  17. idt_base:
  18.     times (256*8) db 0        ; 共256个
  19. idt_ptr:
  20.     dw 256*8-1
  21.     dd idt_base
  22. end_idt:
  23.     times (256*8) db 0        ;stack --> 2kstack --> 2k
  24. stack_ptr:                ; 初始化时的堆栈指针
  25.     dd stack_ptr
  26.     dw 10h

  27. ldt0:
  28.     dw 0,0,0,0            ; 第一个须为空,不使用
  29.     dw 0x03ff, 0x0000, 0xfa00, 0x00c0    ;slt=0x0F-->[0-4M]代码段,P=1,DPL=3,S=1,TYPE=0x0a(ra),G=1
  30.     dw 0x03ff, 0x0000, 0xf200, 0x00c0    ;slt=0x17-->[0-4M]数据段,P=1,DPL=3,S=1,TYPE=0x02(rw),G=1

  31. tss0:
  32.     dd 0                      ; 前一任务链接
  33.     dd kernel_stack0, 0x10    ; esp0,ss0
  34.     dd 0,0,0,0,0              ; esp1,ss1,esp2,ss2,cr3
  35.     dd task0, 0x200           ; EIP为task0,EFLAGS为0x200(IF)   -->task0的EIP是iret L172行设定的,这儿的task0其实是个假的。
  36.     dd 0,0,0,0                ; eax,ecx,edx,ebx
  37.     dd stack0_ptr,0,0,0       ; esp,ebp,esi,edi
  38.     dd 0x17, 0x0f, 0x17, 0x17, 0x17, 0x17    ; es,cs,ss,ds,fs,gs
  39.     dd LDT0_SEL, 0x8000000    ; ldt,trace bitmap
  40.     times 128 dd 0            ; 再留一些内核栈空间

  41. kernel_stack0:                ; esp0,任务0的核心态使用的栈
  42.     dd 0
  43. ldt1:
  44.     dw 0,0,0,0            ; 第一个须为空
  45.     dw 0x03ff, 0x0000, 0xfa00, 0x00c0    ;slt=0x0F-->[0-4M]代码段,P=1,DPL=3,S=1,TYPE=0x0a(ra),G=1
  46.     dw 0x03ff, 0x0000, 0xf200, 0x00c0    ;slt=0x17-->[0-4M]数据段,P=1,DPL=3,S=1,TYPE=0x02(rw),G=1

  47. tss1:
  48.     dd 0                        ; 前一任务链接
  49.     dd kernel_stack1, 0x10        ; esp0,ss0
  50.     dd 0,0,0,0,0                ; esp1,ss1,esp2,ss2,cr3
  51.     dd task1, 0x200                ; EIP为task1,EFLAGS为0x200(IF)
  52.     dd 0,0,0,0                    ; eax,ecx,edx,ebx
  53.     dd stack1_ptr,0,0,0            ; esp,ebp,esi,edi
  54.     dd 0x17, 0x0f, 0x17, 0x17, 0x17, 0x17    ; es,cs,ss,ds,fs,gs
  55.     dd LDT1_SEL, 0x8000000        ; ldt,trace bitmap
  56.     times 128 dd 0                ; 再留一些内核栈空间

  57. kernel_stack1:                    ; esp0,任务1的核心态使用的栈
  58.     dd 0

  59. ; 一些选择子,依据GDT计算出来的,以8h递增
  60. CODE_SEL    equ    0x08        ; 数据
  61. DATA_SEL    equ    0x10        ; 数据
  62. SCRN_SEL    equ    0x18        ; 显存
  63. TSS0_SEL    equ    0x20        ; TSS0
  64. LDT0_SEL    equ    0x28        ; LDT0
  65. TSS1_SEL    equ    0x30        ; TSS1
  66. LDT1_SEL    equ    0x38        ; LDT1

  67. LATCH    equ    11930

  68. align 4
  69. begin:
  70.     mov ax,0x0
  71.     mov ds,ax

  72.     mov cx, ax
  73.     ;----------------- 打开A20 ----------------
  74.     in al,0x92
  75.     or al,0000_0010B
  76.     out 0x92,al

  77.     ;----------------- 加载GDT ----------------
  78.     lgdt [gdt_ptr]

  79.     ;----------------- cr0第0位置1 ----------------
  80.     mov eax, cr0
  81.     or eax, 0x00000001
  82.     mov cr0, eax

  83.     ;----------------- 跳到保护模式 ----------------
  84.     jmp dword CODE_SEL:protect_mode
  85. [bits 32]
  86. ; ==================================================================================
  87. protect_mode:
  88.     mov ax,DATA_SEL        ; 首先设置数据段,注意,是选择子
  89.     mov ds,ax
  90.     mov es,ax
  91.     mov fs,ax
  92.     mov gs,ax
  93.     lss esp,[stack_ptr]    ; 设置堆栈,stack_ptr在下面定义:GDT总长加上余留空间

  94.     call setup_idt        ; 初始化IDT

  95.     ; //////////////////////////////////////////////////////////////////
  96.     ;单独设置时钟中断中断描述符
  97.     mov eax,0x00080000    ; 0008 是选择子
  98.     mov ax,timer_int    ; 时钟中断地址
  99.     mov dx,0x8e00        ; 属性
  100.     mov ecx,0x20        ; 放至IDT中第20h个描述符中
  101.     lea esi,[idt_base+ecx*8]
  102.     mov [esi],eax
  103.     mov [esi+4],edx

  104.     ; //////////////////////////////////////////////////////////////////
  105.     ; 单独设置系统调用中断描述符
  106.     ; 在用户级任务中可用 int 80h系统调用
  107.     mov ax, sys_int
  108.     mov dx, 0xef00
  109.     mov ecx, 0x80
  110.     lea esi,[idt_base+ecx*8]
  111.     mov [esi],eax
  112.     mov [esi+4],edx
  113.     ; //////////////////////////////////////////////////////////////////
  114.     ; 初始化时钟
  115.     ; 36二进制:    00    11        011    0
  116.     ; 含义:    通道0    先写低字节    方式3    二进制
  117.     ; 43h为对应的端口
  118.     ; 《Linux内核完全剖析》P292
  119.     mov al,0x36
  120.     mov dx,0x43
  121.     out dx,al

  122.     mov ax,LATCH*4    ; 这里可以修改初始值
  123.     mov dx,0x40        ; 此时端口为40h
  124.     out dx,al        ; 注意:out指令源操作数只有AL和AX的,这里分两次输出
  125.     mov al,ah
  126.     out dx,al


  127.     ; //////////////////////////////////////////////////////////////////
  128.     ; 将21H端口的值清第0位,作用未调查过
  129.     mov dx, 0x21
  130.     in al,dx
  131.     and al,0xfe
  132.     out dx,al

  133.     ; //////////////////////////////////////////////////////////////////
  134.     ; 清零EFLAGS中的NT标志
  135.     pushf
  136.     and dword [esp], 0xffffbfff
  137.     popf

  138.     ; //////////////////////////////////////////////////////////////////
  139.     ; 开工,首先是任务0
  140.     mov ax,TSS0_SEL
  141.     ltr ax
  142.     mov ax,LDT0_SEL
  143.     lldt ax
  144.     mov dword [current],0
  145.     sti            ; 开中断,响应之

  146.     push 0x17        ; 任务0数据段选择子
  147.     push stack_ptr    ; 初始的堆栈
  148.     pushf            ; 标志寄存器入栈
  149.     push 0x0f        ; 任务0代码段选择子
  150.     push task0        ; 将代码指针入栈
  151.     iret            ; 从中断返回,切换至用户级任务0,执行  L172

  152. ; ==================================================================================
  153. ; 设置IDT--全部设置为默认的中断处理程序
  154. setup_idt:
  155.     lea edx,[default_int]
  156.     mov eax,0x00080000 ;selector=0x8=code
  157.     mov ax,dx
  158.     mov dx, 0x8e00 ;set prop=0x8e
  159.     lea edi,[idt_base]        ; 目标地址
  160.     mov ecx,256        ; 256个
  161. _rp_idt:
  162.     mov [edi],eax
  163.     mov [edi+4],edx

  164.     ; 执行到此处时,eax和ebx如下:
  165.     ; eax中:高16位为选择子(即8),低16位为中断入口地址低16位
  166.     ; edx中:高16位为中断入口地址高16位,低16位为属性(此处是8e,即386中断门),
  167.     ; 更详细请参见门描述符
  168.     add edi,8        ; 下一个
  169.     dec ecx            ; 看是否够256个
  170. jne _rp_idt

  171.     lidt [idt_ptr]    ; 最后,加载IDT的基地址到lidt
  172.     ret

  173. ; ==================================================================================
  174. ; 显示一个字符
  175. write_char:
  176.     push gs
  177.     push ebx
  178.     push eax
  179.     mov ebx,SCRN_SEL    ; 显存选择子
  180.     mov gs,bx
  181.     mov ebx,[scr_loc]    ; 位置
  182.     shl ebx,1        ; 注意,屏幕上显示一字符,需在前加一字节的属性。
  183.     mov [gs:ebx],al
  184.     shr ebx,1
  185.     inc ebx            ; 下一位置
  186.     cmp ebx,1824        ; 一个经过我计算的值
  187.     jb .next
  188.     mov ebx,0        ; 重新计数
  189. .next:
  190.     mov [scr_loc],ebx
  191.     pop eax
  192.     pop ebx
  193.     pop gs
  194.     ret

  195. ; ==================================================================================
  196. ; 中断处理一:默认中断,只是打印一个“!”
  197. align 4
  198. default_int:
  199.     push ds
  200.     push eax
  201.     ; 要继续接收中断,则要发送一个EOI给8259,如下所示
  202.     ; 《自己动手写操作系统》P115
  203.     mov al,20h
  204.     out 20h,al
  205.     mov eax,DATA_SEL
  206.     mov dx,ax
  207.     mov al,'!'        ; !
  208.     call write_char
  209.     pop eax
  210.     pop ds
  211.     iret

  212. ; ==================================================================================
  213. ; 中断处理二:时钟中断
  214. align 4
  215. timer_int:
  216.     push ds
  217.     push edx
  218.     push ecx
  219.     push ebx
  220.     push eax

  221.     mov eax,DATA_SEL
  222.     mov ds,ax

  223.     ; 要继续接收中断,则要发送一个EOI给8259,如下所示
  224.     ; 《自己动手写操作系统》P115
  225.     mov al,20h
  226.     out 20h,al

  227.     ; 两任务在这里切换
  228.     mov eax,1
  229.     cmp [current],eax
  230.     je .switch_task0    ; 为1,则在下面跳至任务0
  231.     mov [current],eax    ; 为0,则赋值为1,并跳至任务1
  232.     jmp TSS1_SEL:0
  233.     jmp .quit
  234. .switch_task0:
  235.     mov dword [current],0    ; 重新赋值为0
  236.     jmp TSS0_SEL:0

  237. .quit:
  238.     pop eax
  239.     pop ebx
  240.     pop ecx
  241.     pop edx
  242.     pop ds
  243.     iret

  244. ; ==================================================================================
  245. ; 中断处理三:系统调用,调用打印字符函数
  246. align 4
  247. sys_int:
  248.     push ds
  249.     push edx
  250.     push ecx
  251.     push ebx
  252.     push eax

  253.     mov edx,DATA_SEL
  254.     mov ds,dx
  255.     call write_char

  256.     pop eax
  257.     pop ebx
  258.     pop ecx
  259.     pop edx
  260.     pop ds
  261.     iret

  262. ; **********************************************************************************
  263. current: dd 0    ; 任务
  264. scr_loc: dd 0    ; 屏幕位置
  265. ; =============================== end of this file =================================

  266. task0:
  267.     mov ax,0x17            ; LDT中数据描述符选择子
  268.     mov ds,ax
  269.     mov al,'F'
  270.     int 0x80
  271.     mov ecx,0fffh
  272. .t0:
  273.     loop .t0
  274.     jmp task0

  275.     times 128 dd 0        ; 用户级任务0堆栈空间大小
  276. stack0_ptr:
  277.     dd 0

  278. task1:
  279.     mov ax,0x17
  280.     mov ds,ax
  281.     mov al,'N'
  282.     int 0x80
  283.     mov ecx, 0x0fff
  284. .t1:
  285.     loop .t1
  286.     jmp task1

  287.     times 128 dd 0        ; 用户级任务1堆栈空间大小
  288. stack1_ptr:
  289.     dd 0
3. 调试
3.1 从loader.lst中确定timer_int的地址
  1. timer_int:
  2. 000016A8 1E push ds
  3. 000016A9 52 push edx

  4. tss0:
  5. 0000107A 00000000 dd 0 ; 前一任务链接
  6. 0000107E [E2120000]10000000 dd kernel_stack0, 0x10 ; esp0,ss0
  7. kernel_stack0: ; esp0,任务0的核心态使用的栈
  8. 000012E2 00000000 dd 0



  9. tss1:
  10. 000012FE 00000000 dd 0 ; 前一任务链接
  11. 00001302 [66150000]10000000 dd kernel_stack1, 0x10 ; esp0,ss0
  12. kernel_stack1: ; esp0,任务1的核心态使用的栈
  13. 00001566 00000000 dd 0
3.2 用bochs调试 
  1. (0) [0x0000fffffff0] f000:fff0 (unk. ctxt): jmpf 0xf000:e05b ; ea5be000f0
  2. <bochs:1> b 0x16A8           -->在timer_int的起始打断点,不知道为毛这个不起作用
  3. <bochs:2> b 0x16a9           -->在timer_int的起始打断点,再打一个
  4. <bochs:3> c
  5. (0) Breakpoint 2, 0x000016a9 in ?? ()          -->task0运行一段时间后,进入timer中断
  6. Next at t=158838059
  7. (0) [0x0000000016a9] 0008:000016a9 (unk. ctxt): push edx ; 52
  8. <bochs:4> n
  9. Next at t=158838060
  10. (0) [0x0000000016aa] 0008:000016aa (unk. ctxt): push ecx ; 51
  11. <bochs:5>
  12. Next at t=158838061
  13. (0) [0x0000000016ab] 0008:000016ab (unk. ctxt): push ebx ; 53
  14. <bochs:6>
  15. Next at t=158838062
  16. (0) [0x0000000016ac] 0008:000016ac (unk. ctxt): push eax ; 50
  17. <bochs:7>
  18. Next at t=158838063
  19. (0) [0x0000000016ad] 0008:000016ad (unk. ctxt): mov eax, 0x00000010 ; b810000000
  20. <bochs:8> r
  21. eax: 0x00080046 524358
  22. ecx: 0x0000009d 157
  23. edx: 0x00000021 33
  24. ebx: 0x00002800 10240
  25. esp: 0x000012ba 4794
  26. ebp: 0x00000000 0
  27. esi: 0x00000456 1110
  28. edi: 0x00000856 2134
  29. eip: 0x000016ad
  30. eflags 0x00000082: id vip vif ac vm rf nt IOPL=0 of df if tf SF zf af pf cf
  31. <bochs:9> print-stack
  32. Stack address size 4
  33.  | STACK 0x000012ba [0x00080046]        -->刚刚入栈eax
  34.  | STACK 0x000012be [0x00002800]        -->刚刚入栈ebx
  35.  | STACK 0x000012c2 [0x0000009d]        -->刚刚入栈ecx
  36.  | STACK 0x000012c6 [0x00000021]        -->刚刚入栈edx
  37.  | STACK 0x000012ca [0x00000017]        -->刚刚入栈ds
  38.  | STACK 0x000012ce [0x0000171a]        -->中断时入栈eip,task0从中断返回后运行的eip
  39.  | STACK 0x000012d2 [0x0000000f]        -->中断时入格CS
  40.  | STACK 0x000012d6 [0x00000282]        -->中断时入格PSW
  41.  | STACK 0x000012da [0x0000105c]        -->中断时入格ESP
  42.  | STACK 0x000012de [0x00000017]        -->中断时入格SS
  43.  | STACK 0x000012e2 [0x00000000]
  44.  | STACK 0x000012e6 [0x00000000]
  45.  | STACK 0x000012ea [0x00000000]
  46.  | STACK 0x000012ee [0x000003ff]
  47.  | STACK 0x000012f2 [0x00c0fa00]
  48.  | STACK 0x000012f6 [0x000003ff]
  49. <bochs:10> n
  50. Next at t=158838064
  51. (0) [0x0000000016b2] 0008:000016b2 (unk. ctxt): mov ds, ax ; 8ed8
  52. <bochs:11>
  53. Next at t=158838065
  54. (0) [0x0000000016b4] 0008:000016b4 (unk. ctxt): mov al, 0x20 ; b020
  55. <bochs:12>
  56. Next at t=158838066
  57. (0) [0x0000000016b6] 0008:000016b6 (unk. ctxt): out 0x20, al ; e620
  58. <bochs:13>
  59. Next at t=158838067
  60. (0) [0x0000000016b8] 0008:000016b8 (unk. ctxt): mov eax, 0x00000001 ; b801000000
  61. <bochs:14>
  62. Next at t=158838068
  63. (0) [0x0000000016bd] 0008:000016bd (unk. ctxt): cmp dword ptr ds:0x00001703, eax ; 390503170000
  64. <bochs:15> xp /4 0x1703
  65. [bochs]:
  66. 0x00001703 <bogus+ 0>:    0x00000000    0x000001e4    0x0017b866    0x46b0d88e
  67. <bochs:16> n
  68. Next at t=158838069
  69. (0) [0x0000000016c3] 0008:000016c3 (unk. ctxt): jz .+14 (0x000016d3) ; 740e
  70. <bochs:17>
  71. Next at t=158838070
  72. (0) [0x0000000016c5] 0008:000016c5 (unk. ctxt): mov dword ptr ds:0x00001703, eax ; a303170000
  73. <bochs:18>
  74. Next at t=158838071
  75. (0) [0x0000000016ca] 0008:000016ca (unk. ctxt): jmpf 0x0030:00000000 ; ea000000003000   -->jmp TSS1_SEL:0
  76. <bochs:19>
  77. Next at t=158838072
  78. (0) [0x000000001922] 000f:00001922 (unk. ctxt): mov ax, 0x0017 ; 66b81700
  79. xp /32 0x107A
    [bochs]:
    0x0000107a : 0x00000000 0x000012e2 0x00000010 0x00000000
    0x0000108a : 0x00000000 0x00000000 0x00000000 0x00000000
    0x0000109a : 0x000016d1 0x00000097 0x00000001 0x0000009d    -->这个0x16d1就是再次切换到任务0时要执行的下一条指令,
    0x000010aa : 0x00000021 0x00002800 0x000012ba 0x00000000    -->即jmp .quit,这个地方不好理解,多看看
    0x000010ba : 0x00000456 0x00000856 0x00000000 0x00000008
    0x000010ca : 0x00000010 0x00000010 0x00000000 0x00000000
    0x000010da : 0x00000028 0x08000000 0x00000000 0x00000000
    0x000010ea : 0x00000000 0x00000000 0x00000000 0x00000000
  80. <bochs:20> c
  81. (0) Breakpoint 2, 0x000016a9 in ?? ()
  82. Next at t=160837759
  83. (0) [0x0000000016a9] 0008:000016a9 (unk. ctxt): push edx ; 52
  84. <bochs:21> n
  85. Next at t=160837760
  86. (0) [0x0000000016aa] 0008:000016aa (unk. ctxt): push ecx ; 51
  87. <bochs:22>
  88. Next at t=160837761
  89. (0) [0x0000000016ab] 0008:000016ab (unk. ctxt): push ebx ; 53
  90. <bochs:23>
  91. Next at t=160837762
  92. (0) [0x0000000016ac] 0008:000016ac (unk. ctxt): push eax ; 50
  93. <bochs:24>
  94. Next at t=160837763
  95. (0) [0x0000000016ad] 0008:000016ad (unk. ctxt): mov eax, 0x00000010 ; b810000000
  96. <bochs:25> print-stack
  97. Stack address size 4
  98.  | STACK 0x0000153e [0x0000004e]
  99.  | STACK 0x00001542 [0x00000000]
  100.  | STACK 0x00001546 [0x000000c9]
  101.  | STACK 0x0000154a [0x00000000]
  102.  | STACK 0x0000154e [0x00000017]
  103.  | STACK 0x00001552 [0x00001931]
  104.  | STACK 0x00001556 [0x0000000f]
  105.  | STACK 0x0000155a [0x00000202]
  106.  | STACK 0x0000155e [0x00001b35]
  107.  | STACK 0x00001562 [0x00000017]
  108.  | STACK 0x00001566 [0x00000000]
  109.  | STACK 0x0000156a [0x00b89090]
  110.  | STACK 0x0000156e [0x89d88e00]
  111.  | STACK 0x00001572 [0x0c92e4c1]
  112.  | STACK 0x00001576 [0x0f92e602]
  113.  | STACK 0x0000157a [0x00501601]
  114. <bochs:26> r
  115. eax: 0x0000004e 78
  116. ecx: 0x000000c9 201
  117. edx: 0x00000000 0
  118. ebx: 0x00000000 0
  119. esp: 0x0000153e 5438
  120. ebp: 0x00000000 0
  121. esi: 0x00000000 0
  122. edi: 0x00000000 0
  123. eip: 0x000016ad
  124. eflags 0x00000002: id vip vif ac vm rf nt IOPL=0 of df if tf sf zf af pf cf
  125. <bochs:27> n
  126. Next at t=160837764
  127. (0) [0x0000000016b2] 0008:000016b2 (unk. ctxt): mov ds, ax ; 8ed8
  128. <bochs:28>
  129. Next at t=160837765
  130. (0) [0x0000000016b4] 0008:000016b4 (unk. ctxt): mov al, 0x20 ; b020
  131. <bochs:29>
  132. Next at t=160837766
  133. (0) [0x0000000016b6] 0008:000016b6 (unk. ctxt): out 0x20, al ; e620
  134. <bochs:30>
  135. Next at t=160837767
  136. (0) [0x0000000016b8] 0008:000016b8 (unk. ctxt): mov eax, 0x00000001 ; b801000000
  137. <bochs:31>
  138. Next at t=160837768
  139. (0) [0x0000000016bd] 0008:000016bd (unk. ctxt): cmp dword ptr ds:0x00001703, eax ; 390503170000
  140. <bochs:32>
  141. Next at t=160837769
  142. (0) [0x0000000016c3] 0008:000016c3 (unk. ctxt): jz .+14 (0x000016d3) ; 740e
  143. <bochs:33> xp /4 0x1703
  144. [bochs]:
  145. 0x00001703 <bogus+ 0>:    0x00000001    0x000003c8    0x0017b866    0x46b0d88e
  146. <bochs:34> n
  147. Next at t=160837770
  148. (0) [0x0000000016d3] 0008:000016d3 (unk. ctxt): mov dword ptr ds:0x00001703, 0x00000000 ; c7050317000000000000
  149. <bochs:35>
  150. Next at t=160837771
  151. (0) [0x0000000016dd] 0008:000016dd (unk. ctxt): jmpf 0x0020:00000000 ; ea000000002000    -->jmp TSS0_SEL:0
  152. <bochs:36>
  153. Next at t=160837772
  154. (0) [0x0000000016d1] 0008:000016d1 (unk. ctxt): jmp .+17 (0x000016e4) ; eb11             -->jmp .quit 在切到task1时,把task0的eip 0x16d1入栈,这个jmp时又跳到这儿执行 
  155. <bochs:37>
  156. Next at t=160837773
  157. (0) [0x0000000016e4] 0008:000016e4 (unk. ctxt): pop eax ; 58
  158. <bochs:38>
  159. Next at t=160837774
  160. (0) [0x0000000016e5] 0008:000016e5 (unk. ctxt): pop ebx ; 5b
  161. <bochs:39>
  162. Next at t=160837775
  163. (0) [0x0000000016e6] 0008:000016e6 (unk. ctxt): pop ecx ; 59
  164. <bochs:40>
  165. Next at t=160837776
  166. (0) [0x0000000016e7] 0008:000016e7 (unk. ctxt): pop edx ; 5a
  167. <bochs:41>
  168. Next at t=160837777
  169. (0) [0x0000000016e8] 0008:000016e8 (unk. ctxt): pop ds ; 1f
  170. <bochs:42>
  171. Next at t=160837778
  172. (0) [0x0000000016e9] 0008:000016e9 (unk. ctxt): iret ; cf         -->iret后运行的地址0x171a,这个是在task0切换之前就保存在栈中的
  173. <bochs:43> print-stack
  174. Stack address size 4
  175.  | STACK 0x000012ce [0x0000171a]
  176.  | STACK 0x000012d2 [0x0000000f]
  177.  | STACK 0x000012d6 [0x00000282]
  178.  | STACK 0x000012da [0x0000105c]
  179.  | STACK 0x000012de [0x00000017]
  180.  | STACK 0x000012e2 [0x00000000]
  181.  | STACK 0x000012e6 [0x00000000]
  182.  | STACK 0x000012ea [0x00000000]
  183.  | STACK 0x000012ee [0x000003ff]
  184.  | STACK 0x000012f2 [0x00c0fb00]
  185.  | STACK 0x000012f6 [0x000003ff]
  186.  | STACK 0x000012fa [0x00c0f300]
  187.  | STACK 0x000012fe [0x00000000]
  188.  | STACK 0x00001302 [0x00001566]
  189.  | STACK 0x00001306 [0x00000010]
  190.  | STACK 0x0000130a [0x00000000]
  191. <bochs:44> n
  192. Next at t=160837779
  193. (0) [0x00000000171a] 000f:0000171a (unk. ctxt): loop .-2 (0x0000171a) ; e2fe
4. 这儿最不好理解的就是这个timer_int
4.1 由task0切换到task1(只关注task0部分,task1是一样的)
   task0运行运行 -->发生时钟中断-->进入timer_int-->需要切换到task1 
     jmp TSS1_SEL:0时还是在task0中,这个jmp做了两件事
     a. 把task0的下一条指令jmp .quit保存在tss0中
     b. 跳到task1中执行,执行的EIP是tss1中写好的task1这个入口地址
4.2 由task1切换到task0(只关注task0部分,task1是一样的)
     jmp TSS0_SEL:0  ,这时要执行的下一条地址是TSS0中保存的EIP,也就是jmp .quit处
4.3 再捋一遍
  jmp TSS1:SEL:0    -->下一条要执执的地址是TSS1中保存的EIP也就是标签task1处,并把jmp .quit保存在tss0的EIP处
  jmp TSS0:SEL:0    -->下一条要执执的地址是TSS0中保存的EIP也就是jmp .quit处
  1. 241 align 4
  2. 242 timer_int:
  3. 243 push ds
  4. 244 push edx
  5. 245 push ecx
  6. 246 push ebx
  7. 247 push eax
  8. 248
  9. 249 mov eax,DATA_SEL
  10. 250 mov ds,ax
  11. 251
  12. 252 ; 要继续接收中断,则要发送一个EOI给8259,如下所示
  13. 253 ; 《自己动手写操作系统》P115
  14. 254 mov al,20h
  15. 255 out 20h,al
  16. 256
  17. 257 ; 两任务在这里切换
  18. 258 mov eax,1
  19. 259 cmp [current],eax
  20. 260 je .switch_task0 ; 为1,则在下面跳至任务0
  21. 261 mov [current],eax ; 为0,则赋值为1,并跳至任务1
  22. 262 jmp TSS1_SEL:0
  23. 263 jmp .quit
  24. 264 .switch_task0:
  25. 265 mov dword [current],0 ; 重新赋值为0
  26. 266 jmp TSS0_SEL:0
  27. 267
  28. 268 .quit:
  29. 269 pop eax
  30. 270 pop ebx
  31. 271 pop ecx
  32. 272 pop edx
  33. 273 pop ds
  34. 274 iret

5. 关于task0与task1的tss中的EIP
  1. 167 push 0x17      ; 任务0数据段选择子
  2. 168 push stack_ptr ; 初始的堆栈
  3. 169 pushf ; 标志寄存器入栈
  4. 170 push 0x0f ; 任务0代码段选择子
  5. 171 push task0 ; 将代码指针入栈
  6. 172 iret ; 从中断返回,切换至用户级任务0,执行
5.1 关于task0的运行
task0是通过这个iret从ring0-->切换到了ring3并跳到task0中运行的,显式运行的task0
此时task0的tsss中的EIP部分并未改变,
所以在初始化时task0的tss中的EIP时,没有必要写task0,写任意数都可以运行
5.2 关于task1的运行
task1是在时钟中断后发生任务切换时,才由task0切换到了task1,
 切换过程是这样的:  在时钟中断中jmp TSS1_SEL:0, 这时会把tss1中EIP(即task1)作为下一条指令的地址
所以tss1的EIP部分初始化时必须得写上task1,以后就靠cpu自动的覆盖这个值了。

上图出自《Linux内核完全注释V3.0书签版.pdf》P122
6. 代码打包
mod000.rar(下载后改名为mod000.tar.gz)

附录:
1. IA-32指令手册关于IRET的描述:
As with a real-address mode interrupt return, the IRET instruction pops the return instruction pointer, return code segment selector, and EFLAGS image from the stack to the EIP, CS, and EFLAGS registers, respectively, and then resumes execution of the interrupted program or procedure. If the return is to another privilege level, the IRET instruction also pops the stack pointer and SS from the stack, before resuming program execution. 

    push 0x17        ; 任务0数据段选择子
    push stack_ptr  ; 初始的堆栈
    pushf                ; 标志寄存器入栈
    push 0x0f         ; 任务0代码段选择子
    push task0       ; 将代码指针入栈
    iret                   ; 从中断返回,切换至用户级任务0,执行


iret之后的出栈顺序是:
     EIP --> CS --> FLAGS --> ESP --> DS


附2. 参考文章
附2.1 原先的代码出处:
分享0.11注解v3.0中的“一个简单的多任务内核实例“可运行程序
http://www.oldlinux.org/oldlinux/viewthread.php?tid=14160&highlight=%B6%E0%C8%CE%CE%F1
附2.2 
一个简单的多任务内核实例  <原代码在csdn的下载地址>
http://download.csdn.net/download/subfate/1389518
原作者将at的代码改成nasm的了,我是在这个基础上改的

阅读(1280) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~