作者LoganChien (简子翔)
看板b97902HW
标题[系程] 教学: 简介 Kernel/User Mode 的概念
时间Wed Feb 24 21:34:55 2010
简介 Kernel Mode 与 User Mode 的概念
前言
今天郑卜壬的系程提到了 Kernel Mode 与 User Mode 的概念。这
个概念和 Operating System 高度相关,不过这对大多数的同学来说
应该是非常陌生的。第一次听到的同学可能不太能够完全搞懂这个机
制是怎麽 work 的。我因为一些机缘先修了作业系统,加上我的组语
期未也是做相关的主题,所以我写了这一篇文章,希望能帮助大家进
一步认识 Kernel Mode 与 User Mode。
为什麽要有 Kernel Mode 与 User Mode?
首先我们先从为什麽要有 Kernel Mode 与 User Mode 谈起。大家桌
机或者是笔电用的作业系统,不论是 Windows 或者是 Linux,都可以
在同一个时间执行很多程式。
然而,当我们可以执行多个程式,我们如何确保二个程式不会相互干
扰?
我们如何确保 A 使用者的程式不会更动 B 使用者程式的变数?
我们如何确保一个使用者不会占着资源不放手(CPU、RAM...)?我们
如何确保一个使用者的程式不会恶意的更动作业系统内部的资料结构
以窃取更多的资源?我们如何确保硬体可以被正确地操作?
为了避免一个使用者的程式修改其他使用者的程式甚至是系统核心,
并且更进一步,让作业系统可以垄断所有的硬体资源,大部分的机
器(或者 CPU)
至少会有二个执行特权(privilege):Kernel mode
(又称 System mode) 与 User mode。
以 x86 为例:一般是使用 ring 0 做为
Kernel mode,ring 0 拥有最
多的特权,可以
直接操纵所有的硬体,包括可以存取所有的实体记忆体
位址、处理键盘中断..等;而 User mode 的部分,一般是使用 ring 3
做为
User mode,不但
所有存取记忆体的行为都会有边界检查(用硬体
实作的,所以很快),而且很多 ring 0 专用的指令也都不能执行,例
如:在 User mode 你就不能执行关机指令。
以上废话这麽多,看不懂没有关系,反正 OS 会再学一次,现在只要
记住以下三点就可以了:
1. 之所以要有 Kernel mode 和 User mode 之分,是因为我们希望
作业系统可以垄断所有的硬体操作,让一般的程式不能乱搞。
2. Kernel mode 就是万能的,只要是 CPU 能管的硬体,Kernel
mode 的程式就可以透过 machine code 来操作该硬体。
3. User mode 基本上就是「受限」的模式。除了一些没有伤害的行
为之外什麽都不能做。
从 Kernel Mode 到 User Mode
如果 Kernel mode 可以操作硬体,那麽在 User mode 执行的指令如
何执行一些需要特权的指令呢?例如:关机需要特权、写入档案需要
特权、输入输出要特权、另外多要更多记忆体空间也要特权,没有特
权的 User mode 程序(process)要怎麽做这些工作呢?
这时我们就要使用作业系统的 System call。不过在讨论 System
call 之前,请容我先介绍中断(或者例外)的概念,因为他和 System
call 息息相关。
中断 Interrupt
不知道大家有没有想过键盘的电子讯号如何变成标准输入(stdin)的
字元?或者大家有没有想过作业系统如何知道一个程式的指标有问题
进而显示「本程式即将要关闭」?其实以上二个例子,中断(Interrupt)
都是幕後的功臣!
中断(Interrupt),是机器的一个「特别状态」,当中断产生时,正
在执行的工作会暂停下来,若干个必要的 register 也会被储存到记
忆体,CPU 会先执行对应的 Exception handler。如果中断的接收者
不是其他的硬体,CPU 就会执行作业系统提供的 Exception Handler。
例如:当我们按下键盘的时候,键盘的控制晶片会将按键的信号编码
为中断,CPU 收到中断之後会停下手边的工作,改为呼叫作业系统的
键盘中断处理函式,该函式会再把按键的组合解码为我们熟知的
ASCII 让我们的程式读取。
而「本程式即将要关闭」的道理也是一样的,当硬体在检查 access
的时候发现 address 有问题的时候,就会触发中断,而作业系统的
Exception Handler 在接收到中断之後就会终止程式。
在 Exception Handler 执行时,其一定会进到 Kernel mode,因为
Exception Handler 算是作业系统的一部分,所以理所当然地应该
要有 Kernel mode 的特权。这样 Exception Handler 才可以修
改对应的资料结构。
软体中断
当然,事实上不是只有硬体能产生中断,我们也可以透过 instruction
来触发中断,我们称之为软体中断。在不同的机器上,我们对能产生软
体中断的指令也有不同的称呼,在
x86 我们就是使用 int,在 mips
就是使用 syscall,当然还有一些机器是叫做 trap。
而软体中断就是 System Call 可以从 User mode 跳到 Kernel mode
的秘密。一般的作业系统都会提供一个
用 Assembly 写的,但是可以
和 C object file 连结在一起的函式库。
为了可以和 C object file 连结在一起,这个 Assembly 写的函式库
会符合一些调用规范(Calling Convention),以便和 C 的函式呼叫
衔接在一起。
还记得在组合语言课堂中所学到的 Function call 与 Stack 吗?
在 Jump 到一个函式之前,我们必须先把引数(argument) 等等推入堆
叠,再呼叫 Jump 指令跳到该函式。对於 C compiler 而言,他们也要
做类似的事情,当 Compiler 看到以下的函式:
extern void logan_write(void const *, int);
int main()
{
logan_writes("hello world\n", 12);
}
其中一种编译结果就是:
main:
pushl %ebp
movl %esp, %ebp
andl $-16, %esp
subl $16, %esp
movl $12, 4(%esp)
movl $.LC0, (%esp)
call logan_write
movl $0, %eax
leave
ret
ps. 当选用的 Calling Convention 不同,会产生不同的结果。参见
wikipedia: x86 calling convention。
除此之外,Compiler 什麽都不会做。接下来是作业系统开发人员
的工作。这部分就要使用组合语言来撰写。这里的函式必需设定
register 的值,然後触发软体中断。
例如我自己乱写的函式:(使用 GAS syntax 与 Linux System Call)
.globl logan_write
logan_write:
movl $4, %eax
; Linux 系统呼叫: Write
movl $1, %ebx ; File descriptor (stdout)
movl $LOGANSTR, %ecx ; 字串
movl $7, %edx ; 字串长度
int $0x80
; 触发软体中断 (进入作业系统)
movl $4, %eax
movl $1, %ebx
movl 4(%esp), %ecx
movl 8(%esp), %edx
int $0x80
; 触发软体中断 (进入作业系统)
ret
如同老师在上课所说的,在 MS-DOS 之中,如果要做系统呼叫,就必
需设定好 register 之後使用 int 21 触发软体中断,让作业系统接
手之後的工作。当然,作业系统接手之後,Privilege 就会从 User
mode 晋升到 Kernel mode,进而让没有特权的程式可以执行被作业系
统垄断的一些指令。
最後,如果 Exception Handler 执行完毕,视中断的种类,有些会
return 到原来中断发生前的同一个指令,有些会 return 到下一个
指令。当然,一旦所有的 Exception Handler 执行完毕,特权会从
Kernel mode 降回 User mode,因此在 User mode 执行的程式是无
法用 System Call API 之外的方法得到 Kernel mode 的执行机会。
结语
我们从 Kernel/User mode 的目的开始,一路说到 User mode 的程
式如何进行系统呼叫。希望大家看完之後,可以对这些跳来跳去的流
程有进一步的认识。
:-)
--
LoganChien ----- from PTT2 个板 logan -----
--
※ 发信站: 批踢踢实业坊(ptt.cc)
◆ From: 140.112.247.159
※ 编辑: LoganChien 来自: 140.112.247.159 (02/24 21:35)
※ 编辑: LoganChien 来自: 140.112.247.159 (02/24 22:49)
1F:推 fereshte:推!! 02/24 21:45
2F:推 purplebleed:推一个 02/24 21:51
3F:推 kate2008:push 02/24 21:53
4F:推 hanabi:赞!! 02/24 21:57
5F:推 Bingojkt:推@w< 02/24 22:02
6F:推 iForests:推~ 02/24 22:11
7F:推 qcl:推! 02/24 22:12
8F:推 benck:推 02/24 22:38
9F:推 clywin123:大推 02/24 22:40
10F:推 davidpanda:推 02/24 22:41
※ 编辑: LoganChien 来自: 140.112.247.159 (02/24 23:01)
11F:推 andy74139:推!! 02/24 23:08
※ 编辑: LoganChien 来自: 140.112.247.159 (02/24 23:58)
12F:推 vanillaXleft:推!! 02/24 23:55
※ 编辑: LoganChien 来自: 140.112.247.159 (02/25 00:00)
※ 编辑: LoganChien 来自: 140.112.247.159 (02/25 00:01)
13F:推 hrs113355:推!! 02/25 00:16
14F:推 demundo:推! 02/25 00:17
15F:推 Daniel1147:推 02/25 00:19
※ 编辑: LoganChien 来自: 140.112.30.84 (02/25 08:31)
16F:推 godgunman:推! 02/25 08:36
17F:推 r44:推! 02/25 08:47
18F:推 Allen624:感谢分享 02/25 11:28
19F:推 Hseuler:推~ 02/25 13:51
20F:推 iownthegame:推一个 02/28 23:32
21F:推 averangeall:有看有推 03/02 13:10
22F:推 sn6783:有看有推 03/03 00:22
23F:推 wens:其实现在x86都是用 syscall/sysenter 这个指令了 03/03 00:42
24F:推 wens:无聊可以去翻glibc source code 03/03 00:45
25F:→ wens:sysdeps/unix/sysv/linux/i386/sysdep.h 找 ENTER_KERNEL 03/03 00:46
26F:→ wens:或 sysdeps/unix/sysv/linux/x86_64/syscall.S 03/03 00:46
27F:→ LoganChien:谢谢学长。我有空会去看看。 03/08 14:11
28F:→ LoganChien:-) 03/08 14:14
29F:推 dennis2030:有看有推 对不起我现在才静下心看完他QQ 03/10 01:05
30F:推 penut85420: 推推 10/13 15:13