编译器如何知道您使用的函数是系统调用?

时间:2010-08-23 10:39:56

标签: c linux system-calls

对于以下代码段,

int n;
char buf[100];
int fd = open ("/etc/passwd", O_RDONLY);
n = read ( fd, buf, 100);

编译器如何知道read是系统调用而不是任何库函数?

如何检索系统调用号码(__NR_read)?

5 个答案:

答案 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 80sysenter,这是用于正确系统调用的机制(无论如何在我的平台上 - YMMV)。

libc中的包装函数是完成访问系统调用接口的实际工作的地方。

来自维基百科的摘录system calls

  

通常,系统提供位于普通程序和操作系统之间的库,通常是C库(libc)的实现,例如glibc。该库存在于OS和应用程序之间,并提高了可移植性。

     

在基于exokernel的系统上,库作为中介特别重要。在exokernel上,库保护用户应用程序免受极低级内核API的影响,并提供抽象和资源管理。

     

术语“系统调用”和“系统调用”通常被错误地用于引用C标准库函数,特别是那些充当相同系统调用的包装器的函数。对库函数本身的调用不会导致切换到内核模式(如果执行尚未处于内核模式)并且通常是正常的子例程调用(即,在某些ISA中使用“CALL”汇编指令)。实际的系统调用确实将控制转移到内核(并且比抽象它的库调用更依赖于实现)。例如,forkexecve是GLIBC函数,后者又调用forkexecve系统调用。

并且,经过一些搜索后,__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负责处理它。