对于以下代码段,
int n;
char buf[100];
int fd = open ("/etc/passwd", O_RDONLY);
n = read ( fd, buf, 100);
编译器如何知道read是系统调用而不是任何库函数?
如何检索系统调用号码(__NR_read
)?
答案 0 :(得分:13)
我非常怀疑编译器知道它是系统调用。 open
更有可能在库中某处,而库中的代码调用相关的内核接口。
简单程序的程序集输出:
#include <stdio.h>
int main (void) {
int fd = open("xyz");
return 0;
}
是(删除了无关的位):
main:
pushl %ebp ; stack frame setup.
movl %esp, %ebp
andl $-16, %esp
subl $32, %esp
movl $.LC0, (%esp) ; Store file name address.
call open ; call the library function.
movl %eax, 28(%esp) ; save returned file descriptor.
movl $0, %eax ; return 0 error code.
leave ; stack frame teardown.
ret
.LC0:
.string "xyz" ; file name to open.
你会注意到的第一件事是调用到open
。换句话说,它是一个功能。看不到int 80
或sysenter
,这是用于正确系统调用的机制(无论如何在我的平台上 - YMMV)。
libc中的包装函数是完成访问系统调用接口的实际工作的地方。
来自维基百科的摘录system calls:
通常,系统提供位于普通程序和操作系统之间的库,通常是C库(libc)的实现,例如glibc。该库存在于OS和应用程序之间,并提高了可移植性。
在基于exokernel的系统上,库作为中介特别重要。在exokernel上,库保护用户应用程序免受极低级内核API的影响,并提供抽象和资源管理。
术语“系统调用”和“系统调用”通常被错误地用于引用C标准库函数,特别是那些充当相同系统调用的包装器的函数。对库函数本身的调用不会导致切换到内核模式(如果执行尚未处于内核模式)并且通常是正常的子例程调用(即,在某些ISA中使用“CALL”汇编指令)。实际的系统调用确实将控制转移到内核(并且比抽象它的库调用更依赖于实现)。例如,
fork
和execve
是GLIBC函数,后者又调用fork
和execve
系统调用。
并且,经过一些搜索后,__open
文件中的glibc 2.9中找到了io/open.c
函数,weakref
编译为open
。如果你执行:
nm /usr/lib/libc.a | egrep 'W __open$|W open$'
你可以在那里看到它们:
00000000 W __open
00000000 W open
答案 1 :(得分:6)
read是一个库调用。碰巧的是,libc实现定义了read以生成具有正确数字的软件中断。
答案 2 :(得分:2)
编译器可以看到此函数的声明,并生成调用该函数的目标代码。
尝试使用gcc -S
进行编译,您会看到类似的内容:
movl $100, %edx
movq %rcx, %rsi
movl %eax, %edi
call read
系统调用是从C库的read(2)实现中完成的。
编辑:具体来说,GNU libc(很可能是你在Linux上拥有的)在glibc-2.12.1/sysdeps/syscalls.list
中建立系统调用号和函数名之间的关系。该文件的每一行都转换为汇编语言源代码(基于sysdeps/unix/syscall-template.S
),编译并在构建libc时添加到库中。
答案 3 :(得分:1)
open()是一个库函数,它位于libc.a / libc.so
中答案 4 :(得分:1)
以下是Android实现的bionic读取(Android等效于libc)
/* autogenerated by gensyscalls.py */
#include <sys/linux-syscalls.h>
.text
.type read, #function
.globl read
.align 4
.fnstart
read:
.save {r4, r7}
stmfd sp!, {r4, r7}
ldr r7, =__NR_read
swi #0
ldmfd sp!, {r4, r7}
movs r0, r0
bxpl lr
b __set_syscall_errno
.fnend
你可以看到它将__NR_read
加载到r7然后调用SWI,SWI是将先行者切换到内核模式的软件中断。所以编译器不需要知道如何进行系统调用,libc负责处理它。