1. 操作系统调用的直观想法
之前说过,操作系统接口就是给操作系统调用的函数,那么这里假设有一个操作系统接口函数whoami(),调用这个函数就可以取出一串在操作系统中已经写好的字符串”linzhijun”。
为什么不能直接访问该字符串,而是通过whoami()函数才能取出它呢?反正数据都在内存里边,为什么不能直接访问?
因为如果可以直接访问,那就意味着上层程序:
• 可以随意的调用数据, 可以随意的jmp。
• 可以看到root密码, 可以修改它…
• 可以通过显存看到别 人word里的内容…
而这样做是极其危险的。
2. 内核态与用户态
操作系统接口存在的目的除了统一接口方便访问硬件以外,另一个重要作用就是作为操作系统的大门,以保护操作系统的核心数据不会被随意访问。
这个大门门里和门外的概念,就是操作系统中“用户态”和“内核态”的概念。
3. 深入理解内核态与用户态
无论是操作系统内核的代码还是应用程序的代码都必须要装入内存后才能执行,因此可以这样理解用户态与内核态:
放置操作系统内核代码的那一段内存区域被称为“内核态区域”,而放置用户态代码的内存区域被成为“用户态区域”
操作系统可以在内存中划定一个区域,并且设置这个区域的访问特权级,操作系统会将自己所在的内存区域的特权级设置的很高,用户程序所在的特权级设置得低一些。
这样,用户态的代码就不能通过jmp指令跳转到内核态得内存区域中,用户态的代码也不能通过mov指令访问(取走)存放在内核态的数据。
CPU会使用硬件提供一种特权级检查机制(使用硬件而不是软件是为了提高执行效率),用户态的程序每次访问内存都会执行一次这种特权级检查,如果CPU发现访问内存的区域特权级比当前自己的特权级高,就会拒绝执行这条指令,以达到保护内核的目的。
但是用户态程序需要进入内核的时候该怎么进入呢?其实操作系统给上层应用留了一个进入操作系统的入口:0x80号系统中断,应用程序可以通过调用系统接口函数,而系统接口函数会使用 int 0x80指令进入对应的中断处理程序,而操作系统会利用这个中断处理程序来实施各种检查,让上层应用只能按照操作系统规定的格式来使用系统。具体来说就是调用这个指令后,操作系统会修改接下来执行指令的内核态特权,提高这个特权级后,就可以通过系统接口进入操作系统内核了。
4.具体例子:printf(”Hello World“)在系统调用的时候经历了什么
第一阶段是使用库函数完成的,首先printf(“hello world”)会被C的库函数内进行格式处理,处理完格式之后再调用write系统调用进行输出。C的库函数会将printf(“hello world”)变成这么一段代码:
之后就是write进行宏展开的事情了,write会展开成一段包含int 0x80的代码。这段展开用宏展开完成。
第三阶段就是解释和执行int 0x80中断的代码了,这样write就会获得内核的访问权限,从而调用系统硬件,打印输出hello world。 这里不再详述,有兴趣可以查看李治军老师的《操作系统原理,实现与实践》P51页。