Linux系统调用和内核模式

7

我理解系统调用的存在是为了提供用户空间无法访问的功能,例如使用 read() 系统调用访问硬盘。我也知道这些被抽象为用户模式层的库调用,例如 fread(),以提供跨硬件的兼容性。

因此从应用程序开发者的角度来看,我们有像这样的 东西

//library    //syscall   //k_driver    //device_driver
fread()  ->  read()  ->  k_read()  ->  d_read()

我的问题是:为什么我不能直接将fread()read()函数中的所有指令内联到我的程序中?这些指令都是相同的,所以CPU应该以相同的方式运行,对吧?虽然我没有尝试过,但我认为这不起作用的原因是我没有考虑到的某些原因。否则,任何应用程序都可以获得任意内核模式操作。
简而言之:是什么使系统调用“进入”无法被应用程序复制的内核模式?
2个回答

9
系统调用本身不会进入内核。更确切地说,例如你调用的read函数,在你的应用程序看来仍然是一个库调用。内部实际上使用某些中断或syscall(2)汇编指令来调用实际的系统调用,这取决于CPU架构和操作系统。
这是用户空间代码执行特权代码的唯一方式,但它是一种间接的方式。用户空间和内核代码在不同的上下文中执行。
这意味着您不能将内核源代码添加到您的用户空间代码中并期望它能做任何有用的事情,只能导致崩溃。特别地,内核代码可以访问与硬件交互所需的物理内存地址。用户空间代码只能访问没有这种功能的虚拟内存空间。此外,用户空间代码被允许执行的指令是CPU支持的子集。多个I/O,中断和虚拟化相关指令都是被禁止的代码的例子。它们被称为特权指令,并且需要处于较低的环或监管模式,具体取决于CPU架构。

啊,原来是关于内存映射的问题。有意思。还有更多东西需要去了解,谢谢。 - lynks
更不用说在用户模式下禁止使用的特权指令了(答案已更新)。 - jlliagre
1
嗯,我仍然不清楚切换是如何发生的,以及为什么我不能手动执行“switch instructions”:<usermode execution><*switch*><kernelmode execution>如果我将所有这些内容内联到比如一个shellcode中,为什么我没有得到内核模式执行? - lynks
1
切换通过调用syscall(2)或等效调用发生。问题在于切换不是线性的,即执行切换后执行的不是您的指令,而是操作系统为您调用的特定系统调用号注册的指令。只有在系统调用返回并且您回到非特权模式后,才会执行您的指令。这就是为什么在运行现代操作系统时,您无法以内核模式执行您的代码,除非编写和安装内核模块或自己编写该操作系统。 - jlliagre
1
@lynks,旧的x86方法是将系统调用号和参数放置在正确的寄存器和堆栈位置,然后调用“int 80”指令。如果您愿意,您可以直接自行完成此部分。CPU通过切换到内核模式并跳转到中断描述符表中定义的内核系统调用处理程序的地址来实现“int 80”硬件指令。一旦进入内核模式,系统就可以访问实现该调用所需的内存和硬件。 - psusi
2
啊,现在变得更清晰了。执行内核模式指令的唯一方法是通过中断描述符表。因此,您要么必须写入IDT,要么写入IDT指向的内存区域。这两种方式都不会被映射为用户模式进程可写。感谢你们两个。 - lynks

0

你可以将它们内联。你可以通过syscall(2)直接发出系统调用,但这很快会变得混乱。请注意,系统调用的开销(来回上下文切换,在内核中进行检查等),更不用说系统调用本身所需的时间了,使您通过内联获得的收益消失在噪声中(如果有任何收益,代码越多意味着缓存就不那么有用,性能也会受到影响)。相信libc/kernel开发人员已经研究过这个问题,并且在相关的*.h文件中为您做好了内联工作,如果确实有可衡量的收益。


1
我的意思是内联read()的内容,但这肯定行不通,否则我只需停在程序进入ring 0的任何时刻,并分支到某些恶意软件。 - lynks

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接