x86 process switch implementation (0)

楼主: descent (“雄辩是银,沉默是金”)   2014-10-18 00:03:41
曾经在这篇 ( http://goo.gl/mgVNVM )提到 os porcess 的切换, 那时候令我兴奋不已
。但当时的概念还是很模糊, 我决定要用我自己的话来说明这个概念, 也证明我真的搞懂

化繁为简是我的学习原则, 对于 process 切换的掌握度还不够, 打算用自己的方法来实
作一个 process 切换的程式。希望这程式符合几点:
程式码小
在 dos 下执行
使用 x86 real mode
上述这些条件都是为了简单, 若能用小小的程式以及简单的执行环境就可以完成
process 切换, 相信理解起来会容易些。在 dos 使用 .com 执行档, 可以让我的程式最
大有 64 K 那么大 (那么小), 对于所有程式码都是自己打造的来说, 已经非常够用,
boot 磁区那 512 byte 才真的不够人用。
那这程式有多小呢?大概像下面这样:
simple_proc.S
1 #define STACK_FRAME_OFFSET 6
2 .code16
3 .text
4 .global begin
5 begin:
6 xchg %bx, %bx #bochs magic break point
7 cli
8
9 xor %eax, %eax
10 mov %cs,%ax
11 mov %ax,%ds
12
13 ## reset 0x30 interrupt
14 movw $0x0, %bx
15 movw %bx, %es
16 movw $switch_proc, %es:0xc0 # isr offset
17 movw %ax, %es:0xc2 #isr seg
18
19
20 movw $0xb800, %ax
21 movw %ax, %gs
22
23 ## set stack frame eip
24 movw $proc_a, stack_frame
25 movw $proc_b, stack_frame+STACK_FRAME_OFFSET
26
27 ## set stack frame cs
28 movw %cs, %ax
29 movw %ax, stack_frame+2
30 movw %ax, stack_frame+STACK_FRAME_OFFSET+2
31
32 ## set stack frame flag
33 # get flag
34 pushf
35 movw (%esp), %ax
36 popf
37 movw %ax, stack_frame+4
38 movw %ax, stack_frame+STACK_FRAME_OFFSET+4
39
40 int $0x30
41
42 mov $0x4c00, %ax
43 int $0x21
44
45 cur_proc:
46 .word 0x0
51
52 .space 256, 0
53 proc_stack_top_a:
54 .space 256, 0
55 proc_stack_top_b:
56
57 stack_frame:
58 .word 0x0# eip
59 .word 0x1# cs
60 .word 0x2# flag
61
62 .word 0x0# eip
63 .word 0x1# cs
64 .word 0x2# flag
65
66 .global proc_a
67 proc_a:
68 1:
69 mov $0x1, %ax
70 int $0x30
71 jmp 1b
72
73 .global proc_b
74 proc_b:
75 1:
76 mov $0x2, %bl
77 int $0x30
78 jmp 1b
79
80 .global switch_proc
81 switch_proc:
82 movw cur_proc, %dx
83 cmp $stack_frame, %dx
84 je 1f
85 movw $stack_frame, cur_proc
86 jmp 2f
87 1:
88 movw $stack_frame+STACK_FRAME_OFFSET, cur_proc
89 2:
90 movw cur_proc, %sp
91 iret
66 .global proc_a
67 proc_a:
68 1:
69 mov $0x1, %ax
70 int $0x30
71 jmp 1b
72
73 .global proc_b
74 proc_b:
75 1:
76 mov $0x2, %bl
77 int $0x30
78 jmp 1b
proc_a 和 proc_b 便是我们的两个 process, 你可能会抗议那明明就只是两个 function
。是的, 你没说错, 但你写的 c main 程式是不是也只是个 function, 而你却认为他是
一个 process 呢?若使用 call proc_a, call proc_b, 那是 function 的用法, 不是
process, 所以接下来的程式码要用很其特的方法 (iret) 来执行 proc_a, proc_b。
这两个 process 只有 3 行, 够简单, 只要略懂组合语言的程式员, 几乎不用说明就可看
懂。使用的 process 切换方式是类似 windows 3.1 的 non-preemptive 方式, 需要由
process 自己释放 cpu 来让其他 process 执行。int $0x30 就是用来做这件事情。当然
我可以把 int $0x30 包装成类似 os_yield(), 不过这样复杂度就提高了。
( http://goo.gl/trgST8 )
上图可以说明一切, 也许你会嫌字很丑, 应该用电脑画才是, 不过手工可是很难得的。重
点在 stack_frame, 里头有 3 个字段: 分别代表 eip, cs, flag, 用来储存 proc_a,
proc_b 目前的这 3 个值。int 0x30 isr 便是切换 stack_frame, stack_frame+6 来执
行 proc_a, proc_b。
节录 ref 1 的 int 指令做的事情:
把旗标暂存器 push 堆叠
禁止其他中断发生
清除陷阱旗标
把 CS 暂存器 push 堆叠
把 INT n 的下一指令位址 push 堆叠
由 0000:(4n) 位址取出中断服务程式所在位址,并执行长程跳跃指令,至该处继续执行
节录 ref 1 的 iret 指令做的事情:
由堆叠中 pop 4 bytes (cs:ip),并把控制权交到该 4 bytes 所指位址
由堆叠 pop 旗标暂存器 (2 bytes)
所以就是这样来让 proc_a, proc_b 可以轮流执行。先把 proc_a, %cs 的值填到
stack_frame eip, cs 的地方 (stack_frame, stack_frame+2), 将 %sp 指到
stack_frame 或是 stack_frame+6 的地方, 然后发动 iret 即可跳到 proc_a 或是
proc_b。因为 iret 会把 stack_frame, stack_frame+2 加载到 %eip, %cs, 而 cpu 会
执行 %cs:%eip 指向的程式码, 就会去执行 proc_a, 这就是和直接 call proc_a 不同的
执行方式。
int 指令则会把下一个指令的 %cs:%eip 存到 %ss:%esp 指到的地方, 所以 int 发动的
时候, 会把 proc_a 下个指令存到 stack_frame, stack_frame+2 里头, 等着我们下次发
动 iret 再让 proc_a 执行起来; 执行 proc_b 也是同样的道理, 很容易理解吧!
这程式的执行结果不重要, 重要的是执行过程, 怎么感受这个执行过程? 我是用 bochs
内建 debugger single step, 观察所有暂存器, stack 来检查程式是否有真的执行切换

6 xchg %bx, %bx #bochs magic break point
是 bochs magic break point, 程式执行到这行, 会让 bochs 中断停下来, 就可使用
single step指令来观察整个程式行为。
而程式的过程便是在 proc_a, proc_b 之前相互执行, 为了简单, 我没有印出任何字符,
所以从萤幕上看不出任何事情, 为了有趣, 我自己倒是写了一个印出 a, b 的版本, 有兴
趣的朋友可以自己改看看, 在 proc_a 印出 a, prob_b 印出 b。
这个程式该怎么执行呢? makefile 规则会把这只程式编译成 .com, 直接 copy 到 dos
执行即可, 再使用 bochs 的内建除错器就可以追踪整个流程。dos 环境最好不要加载任
何内存管理程式, ex: himem.sys, emm386.exe, 在我测试改写 0x30 中断时, 会造成
一些问题, 我花了不少时间排除这问题。
程式很简单, 说明也很简单, 希望不要造成误会, 如果你已经理解这篇的解释, process
switch 并没有这么简单, 我简化很多东西, 这没有考虑很周严 (ex: 没有保存所有的暂
存器), 真正的 process switch 还要加上不少 code, 而且我还没搞定 x86 real mode
如何保存 %esp 的问题。x86 保护模式在权限切换时, iret/int 指令会保存 %ss:%esp。
这程式若用上保护模式, 那得加上不少 code, 模糊了我要表达的事情, 就先这样。
尽管有如此缺失, 但用来作为 process switch 的实作理解, 不到 100 行的组合语言程
式能发挥如此功用, 已经足够。
下篇文章 x86 process switch implementation (1) ( http://goo.gl/A7n5ig ) 就会复
杂一点了。
soure code:
https://github.com/descent/process ( http://goo.gl/IflJmV )
git commit : d25cb21e036b953f19ec69610c411c550dcfa8d6
x86 中断改写参考资料:
第 36 章 中断 ( http://goo.gl/ZtwD1T )
中矢量表的构 ( http://goo.gl/ITR76N )
中服程序 ( http://goo.gl/gKGIzS )
f=false ( http://goo.gl/36DfaE )
channel=fflb ( http://goo.gl/H3JeLD )
// 本文使用 Blog2BBS 自动将Blog文章转成缩址的BBS纯文字 http://goo.gl/TZ4E17 //
blog 原文
http://descent-incoming.blogspot.tw/2013/03/x86-process-switch-implementation-0-in.html

Links booklink

Contact Us: admin [ a t ] ucptt.com