调用系统调用的不同方式

Different ways to invoke system calls

在一些代码中,我可以看到系统调用以一种奇怪的方式调用,以 sched_yield 为例:

#define __NR_sys_sched_yield    __NR_sched_yield

inline _syscall0(void, sys_sched_yield);

然后我们可以使用sys_sched_yield()。 我很好奇直接使用 sched_yield 和这种方式有什么区别。 在src/include/asm/unistd中,_syscall0定义为:

#define _syscall0(type,name)  \
type name(void)     \
{        \
    long __res;      \
    __asm__ volatile ("int [=11=]x80"  \
    : "=a" (__res)    \
    : "0" (__NR_##name));   \
    __syscall_return(type,__res);  \
}

大概是针对 sched_yield 可能不可用的系统。 至于差异,sched_yield returns -1 出错并设置 ERRNO 而此实现大概 returns 来自内核的原始值。无法确定,因为您没有提供必须是宏的 _syscall0 的定义。

这是使用 glibc 的 linux。 BSD 有一个 sched_yield 但有自己的 libc.

这并不奇怪。 syscall0 宏发出汇编程序 int 0x80 指令,系统调用号位于 rax 寄存器 [x86 架构] 中。这是 linux 的标准系统调用接口。在幕后,所有 linux 作为系统调用包装器的 glibc 函数都将执行此操作 [或使用更现代的 sysenter/sysexit x86 指令对]

glibc 倾向于在其包装函数中进行 "usurp" 系统调用并在它们周围添加一些东西。例如,当您调用 fork 时,它 [最终] 会调用 __libc_fork,它会执行大量与线程和文件关闭等相关的额外工作。

一般来说,glibc 是不错的选择。但是,有时经验丰富的 linux 应用程序程序员 想要 原始系统调用行为,特别是当他们正在编写系统实用程序或 programs/libraries 必须与内核密切交互时、设备驱动程序或设备硬件。

实际上,__libc_fork 不调用 fork 系统调用 ,它调用 clone 系统调用 ,这是 fork 的 [更难使用] 超集。但是,普通的旧 fork 系统调用仍然存在。所以,如果你想要那个,你需要宏的东西——我敢打赌某处有一个 sys_fork 定义。

另一方面,glibc 可能将 sched_yield ala POSIX 实现为 nop returning -1 并将 errno 设置为 ENOSYS。我刚刚检查了最新的 glibc 源代码,但找不到 "real" 实现,除了 mach。它可能确实可以做真实的事情,我只是找不到它。

有时,linux 有一个系统调用,但 glibc 不想支持它,或者他们认为它对应用程序程序员来说太危险了,所以他们省略了包装函数。所以,宏是 "end around" glibc.

的一种方式

glibc 将 sched_yield 实现为 nop 的可能原因,除了 posix 之外,他们认为它 "bad" 并且可能告诉您改用 nanosleep。我都用过,它们一样,这取决于你的用例和期望的效果。

有时,您需要执行原始的内联系统调用。例如,ELF 加载器[每个支持 ELF 二进制文件的系统都必须有一个并且 linux 的是 ld-linux.so] 由内核调用以加载 ELF 二进制文件。它必须在 glibc.so 可用之前运行,因为它是 glibc.so 中实际链接的内容,ELF 加载器必须有一些针对 openread

的内置系统调用

此外,大多数系统都有一个 syscall 函数 ,它接受可变数量的参数。您可以实施:

#define my_sched_yield() syscall(__NR_sched_yield)
#define my_read(_fd,_buf,_len) syscall(__NR_read,_fd,_buf,_len)

此函数处理内核的系统调用 return value/error 并设置 errno。这就是 __syscall_return 宏必须做的。

__NR_* 前缀是 linux 使用的,但其他系统有 AUE_*SYS_*