系统调用与函数调用
系统调用和函数调用有什么区别? fopen()是系统调用还是函数调用?
系统调用是对内核代码的调用,通常通过执行中断来执行。 中断导致内核接管并执行请求的操作,然后将控制权交还给应用程序。 此模式切换是系统调用执行速度比等效应用程序级函数慢的原因。
fopen
是C库中的一个函数,它在内部执行一个或多个系统调用。 通常,作为C程序员,您很少需要使用系统调用,因为C库会为您包装它们。
fopen是一个函数调用。
系统调用与底层操作系统交互,后者管理资源。 它的数量级比函数调用更昂贵,因为必须采取许多步骤来保持进行系统调用的进程的状态。
在* nix系统上,fopen打开,这使得系统调用(open是系统调用的C – 包装器)。 fread / read,fwrite / write等也会发生同样的情况。
这里有一个很好的描述unix系统调用执行的任务。
实际上,系统调用与函数调用无关。 这两种机制中唯一常见的是它们都为呼叫者提供服务。
-
从线程执行的角度来看系统调用:
系统调用是应用程序模式程序的function,用于请求由下划线OS提供的服务。 系统调用将运行线程从用户模式带入内核模式,执行系统调用处理函数,然后返回用户模式。
-
系统调用参数:
系统调用的参数是(系统调用号,参数…)。 params的含义和格式取决于系统调用号。
-
从提供给用户程序的系统调用库的视图:
用户模式程序通常调用glibc的库来调用系统调用。 例如,glibc中的open()函数:
- 将系统调用号SYS_OPEN放入eax寄存器
- 通过调用软件中断或sys_enter指令请求系统调用
如果您使用的是Linux,则可以监视应用程序执行的系统调用:
strace appname …
它的输出可以让你很好地了解libc中发生了什么,以及哪些函数实际上是系统调用。
系统调用实际上调用了由内核空间执行的API。 假设所有相关成本(请参阅Wiki或此链接以获取详细信息)
函数调用是对用户空间中的一段代码的调用。
但请注意,一个函数调用可能是一个函数,它在执行过程中会进行系统调用 – “fopen”就是其中一个例子。 因此,虽然对fopen本身的调用是对函数的调用,但并不意味着系统调用不会碰巧处理实际的IO。
添加到这个讨论中的一个观点是,在最乐观的情况下,函数调用在x86中具有几个8位指令(平均4-10个)的开销。
系统调用具有以下属性。
- 它执行更多的指令,它必须冻结进程而不是简单的堆栈状态。
- 所涉及的时间主要是非确定性的。
- 它通常是一个调度点,调度程序可能会选择重新安排。
由于这三个原始原因(可能还有更多),应尽可能减少系统调用量 – 例如,联网系统软件保持套接字句柄(以及连接使用的其他应用程序特定的内部数据结构)以分配给新的连接,为什么要打扰内核?
请记住,软件的构建就像一个颠倒的金字塔。 系统调用是基础。
fopen
是一个函数调用,但它有时可能被称为系统调用,因为它最终由“系统”(OS)处理。 fopen
内置于C运行时库中 。
只是为了完成其他人提供的图片, fopen
通常被实现为open
的包装器,它也是一个用户可访问的function。 从某种意义上说, fopen
比open
更高级,因为它返回的FILE*
结构为用户封装了东西。 有些用户直接使用open
来满足特殊需求。 因此,以任何方式将fopen
称为“系统调用”是不对的。 它也不直接执行系统调用,因为open
也是用户可调用的函数。
这个问题已经有了很好的答案,但我认为我可以添加一些内容(来自ostep的一个部分尚未出现在其他答案中)
有时系统调用和函数调用具有相同的签名,例如open()
:
open()
系统调用
--- ~/Documents » man open(2) OPEN(2) Linux Programmer's Manual OPEN(2) int open(const char *pathname, int flags); int open(const char *pathname, int flags, mode_t mode); ...
open()
函数调用
$ man open(3) --- ~/Documents » OPEN(3P) POSIX Programmer's Manual OPEN(3P) ... int open(const char *path, int oflag, ...); ...
引用OSTEP表格
您可能想知道为什么对系统调用的调用 (例如
open()
或read()
看起来与C中的典型过程调用完全相同; 也就是说,如果它看起来就像一个过程调用 ,系统如何知道它是一个系统调用 ,并做了所有正确的事情? 原因很简单:它是一个过程调用 ,但隐藏在过程调用内部的是着名的陷阱指令 。 更具体地说,当您调用open()
(例如)时,您正在执行对C库的过程调用 。 其中,无论是对于open()
还是提供的任何其他系统调用 ,库都使用与内核达成一致的调用约定来将参数置于众所周知的位置(例如, 堆栈或特定寄存器中) ),将系统调用号放入一个众所周知的位置(再次,放到堆栈或寄存器中 ),然后执行上述陷阱指令 。 陷阱解压缩返回值后,库中的代码将控制权返回给发出系统调用的程序。 因此,进行系统调用的C库部分是在汇编中手工编码的,因为它们需要仔细遵循约定,以便正确处理参数和返回值,以及执行特定于硬件的陷阱指令 。 现在你知道为什么你个人不必编写汇编代码来陷入操作系统; 有人已经为你写了那个集会。
系统调用在kernet级别执行,而不是在用户spce中执行,因为它需要一些权限才能访问硬件。
因此,当在用户空间中进行编程并在C语言中进行一些普通的函数调用时,libc通常将此函数包装到特定的代码中,在该代码中生成中断以从用户空间切换到内核空间,然后在内核空间中将所需的系统调用在硬件级别执行函数调用的function将在内核空间中执行。