如何在不使用程序集的情况下以 cpu 独立的方式从 C/C++ 应用程序进行 Linux 系统调用?

How can I make Linux system calls from a C/C++ application, without using assembly, and in a cpu-independent manner?

我正在寻找一个程序,它需要对进程进行低级别的工作(即使用 fork 系统调用等)。该程序将用 C++ 编写,并且仅在 Linux 上 运行。理想情况下,它可以跨 CPU 架构(即 x86、x86_64 和 arm)移植,只需重新编译,但我只 真的 需要 x86_64支持。

因为每个 Linux 系统调用都需要一些参数,并且 return 在 cpu 寄存器中有一些参数(通常只有 1 个 return 值),然后每个系统调用的 C 函数包装器很容易制作。另外,因为据我所知,在内核中实现的系统调用具有相同的参数和 return 值,如果不同的汇编级实现,则可以公开相同的 C 接口。

有这种东西吗?如果是这样,我该如何访问它?

它的文档在哪里(可用函数的列表、它们的参数及其解释,以及函数的确切作用的解释)?

libc 已经包含了您正在寻找的包装函数。其中许多的原型在 #include <unistd.h> 中,由 POSIX.

指定

C 是 Unix(和 Linux)上的低级系统程序语言,所以自从 Unix 存在以来这就是一回事。 (在 libc 中提供包装函数比教编译器函数调用和系统调用之间的区别更容易,并且允许在错误时设置 errno。它还允许像 LD_PRELOAD 这样的技巧来拦截用户中的系统调用space.)


系统调用的手册页在第 2 节,而库函数的第 3 节(可能会或可能不会使用系统调用作为其实现的一部分:math.h cos(3),ISO C stdio printf(3)fwrite(3),对比 POSIX write(2)).

execve(2)是系统调用。

看到execl(3)和朋友也是libc的一部分,最终调用execve(2)。它们是构建 argv 数组、执行 $PATH 查找以及传递当前进程环境的便捷包装器。因此它们被归类为函数,而不是系统调用。

请参阅 syscalls(2) 了解系统 Linux 的概述和完整列表,以及指向其手册页包装器 的链接。 (我已经链接了 Linux 手册页,但还有 POSIX 所有标准系统调用的手册页。)


在不太可能的情况下,您没有链接 libc,您可以使用 MUSL 的 syscall2 / syscall3 / 等宏(数字是 arg 计数)内联正确的在任何平台上汇编。您使用 asm/unistd.h 中的 __NR_write 来获取系统调用号码。

但请注意,原始 Linux 系统调用可能与 libc 包装器提供的接口略有不同。例如,他们不会检查 pthreads 取消点,并且 brk / sbrk 需要 libc 在 user-space 中进行簿记。

有关使用 MUSL 宏的可移植原始 sys_write() 内联包装器,请参阅

但是如果你像普通人一样使用 libc 来实现 mallocprintf 等函数,你应该只使用它的系统调用包装函数。

syscalls(2) man page lists every system call available on Linux (and gives a link to the documentation of each of them). Most of them have their C wrapper in libc (for example, write(2), fork(2) etc etc...). A typical system call wrapper manages the calling conventions (see x86 ABI specifications here) and sets errno(3) on failure. ALP is a good but old introduction to Linux system programming, but you might find something newer (and ALP don't mention recent system calls like signalfd(2)因为在写ALP的时候,这些系统调用是不存在的)。

Linux 上的大多数 C 标准库实现(例如您的 libc.so)为系统调用提供了 POSIX 接口。他们通常是 free software (e.g. GNU glibc or musl-libc 和其他人)。因此,如果您关心血淋淋的实现细节(您通常不应该关心),请研究(或改进)他们的源代码。

很少有系统调用不由 libc 接口,因为它们不常见并且在 C 代码中没有多大意义。例如,sigreturn(2), socketcall(2), gettid(2) (or renameat2(2); you'll use renameat instead). If you really need to use these directly (which is improbable and likely to be a design bug in your program) you need to code some assembler code (specific to your system and instruction set architecture) or perhaps use syscall(2).

一些系统调用随时间演变或出现在后来的内核中但十年前并未退出。系统调用号(内核所理解的)可能会列在某些 asm/unistd_64.h 文件中(您可能不想包含它,而更喜欢 sys/syscalls.h )。例如,preadv(2) 系统调用被重定向到 __NR_preadv__NR_preadv2,但您的 libc 应该足够聪明,可以做到最好。

一些新的系统调用在旧内核中不存在。在这种情况下,最近的 libc 可能 "emulate" 否则。但是大多数时候你应该相信你的 libc 实现(和你的内核)。在实践中,libc.so 是你的 Linux 系统和发行版的基石(你最好将它用作共享库并避免静态链接它,因为 nsswitch.conf(5)). If you need to understand in details how shared libraries work, read Drepper's How to Write Shared Libraries. If you want some gory details about the system call mechanism in userland, see perhaps Assembler HowTo.

几乎在所有情况下,您都以某种方式编写可移植的 C 代码并仅使用 syscalls(2) (as having a C wrapper) and intro(2).

中记录的函数

在实践中,您的 shell 类程序会使用 fork(2), execve(2), waitpid(2) etc. All these are specified by POSIX and available (and wrapped) in libc. You could study the source code of some free software shell 作为灵感。

为了在 Linux 上进行 C 编程,将 syscalls(2) and having a C wrapper (e.g. almost all of them). So socket(2) or bind(2) is also in practice a system call (even if both internally use socketcall(2), which you won't call directly) Notice that system(3) -a very poorly named function for historical reasons- is not a system call. It is implemented above fork(2), execve(2), signal(2), waitpid(2) 等中列出的任何函数视为系统调用...并且需要 /bin/sh ...