调用系统调用的不同方式
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 加载器必须有一些针对 open
和 read
的内置系统调用
此外,大多数系统都有一个 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_*
在一些代码中,我可以看到系统调用以一种奇怪的方式调用,以 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 加载器必须有一些针对 open
和 read
此外,大多数系统都有一个 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_*