为什么 Linux 中的所有系统调用都使用 "call by reference" 将参数传递给内核?

Why all system calls in Linux passes arguments to kernel using "call by reference"?

如果我们查看 Linux 内核中的 syscalls.h 文件,我们可以看到几乎所有系统调用的参数都是通过引用传递的。例如

 asmlinkage long sys_open_by_handle_at(int mountdirfd,
                          struct file_handle __user *handle,
                          int flags);

这里,file_handle作为指针传递。为什么不简单的值不传递给内核?

用户模式和内核模式的内存space不同。当您进行系统调用时,Linux 子系统的 MMU 确保用户 space 进程 运行 在其自己的虚拟地址 space 中的正确内存映射已完成内核的物理地址space。

用户态变量留在进程的虚拟地址space。它们不能只是在系统调用中传递并期望映射到物理地址 space .

这是我的理解。如果需要,很乐意讨论和澄清。

原则上我理解函数sys_open_by_handle_at(()struct file_handle参数是一个"in"参数,即它不会被函数修改。因此它也可以按值传递。我看到了为什么没有这样做的三个原因。对于此特定功能,所有理由肯定都是有效的;至少最后一个参数 (K&R) 适用于所有系统调用中的所有结构参数。

  1. 结构可以有一个 size of e.g. 128 bytes,复制到堆栈会很慢。

  2. 传递一个指针就不需要知道调用方的结构定义。该结构是一个 "opaque handle",由先前对 [sys_]name_to_handle_at() 的调用填充。调用者不想也不应该被结构内容的细节所累。 (让调用者无辜避免了重新编译程序的需要,因为结构的布局发生了变化。我也可以想象文件系统类型之间的内容不同。)

  3. Unix 甚至它的开源补充 Linux 都比 C99 老。我想在最长的时间里,K&R C 是内核源代码所遵循的最小公分母 C 标准。在 K&R C 中,根本不可能按值传递结构。

效率。

许多(大多数?)系统通过将参数值压入堆栈来实现函数调用。如果按值传递结构或任何其他复杂数据类型,则需要将其复制到堆栈。没有理由这样做,因为内核可以访问进程的整个内存 space。除了复制成本之外,您还需要增加堆栈 space。

此外,内核需要将需要保留的任何数据复制到内核内存中space。内核不能依赖用户 space 代码行为。 (它也不会释放从用户 space 获得的任何东西,这消除了一些关于混淆回收内存责任的担忧。)

最后,实际上,在内核中工作的编码人员需要非常熟悉使用指针。一旦您完全熟悉指针,按值传递就没有任何优势了。

这部分更多的是一种意见,但我认为也有很强的遗留效应。 Unix 内核和 C 在某种程度上是同步发展的。有关一些历史,请参阅 https://en.wikipedia.org/wiki/C_(programming_language)。已经有很长时间了,但如果我没记错的话,旧版本的 C 不允许您按值传递结构。无论如何,使用指针在 C 中是非常惯用的(我想说现在仍然是)。换句话说,事情一直都是这样的。