Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/facebook/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C 当用户空间程序调用系统调用时,执行如何转移回内核空间?_C_Assembly_Linux Kernel_Kernel_X86 - Fatal编程技术网

C 当用户空间程序调用系统调用时,执行如何转移回内核空间?

C 当用户空间程序调用系统调用时,执行如何转移回内核空间?,c,assembly,linux-kernel,kernel,x86,C,Assembly,Linux Kernel,Kernel,X86,我一直在研究很多关于x86-64的ABI,编写汇编,以及研究堆栈和堆是如何工作的 给定以下代码: #include <linux/seccomp.h> #include <stdlib.h> #include <unistd.h> int main(int argc, char *argv[]) { // execute the seccomp syscall (could be any syscall) seccomp(...);

我一直在研究很多关于x86-64的ABI,编写汇编,以及研究堆栈和堆是如何工作的

给定以下代码:

#include <linux/seccomp.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    // execute the seccomp syscall (could be any syscall)
    seccomp(...);

    return 0;
}
#包括
#包括
#包括
int main(int argc,char*argv[]){
//执行seccomp系统调用(可以是任何系统调用)
seccomp(…);
返回0;
}
在x86-64的汇编中,这将执行以下操作:

  • 对齐堆栈指针(默认情况下关闭8字节)
  • 为调用
    seccomp
    的任何参数设置寄存器和堆栈
  • 执行以下程序集
    调用seccomp
  • seccomp
    返回时,据我所知,C很可能会调用
    exit(0)
  • 我想谈谈上面第三步和第四步之间发生了什么

    我目前拥有当前正在运行的进程的堆栈,在寄存器和堆栈中有自己的数据。用户空间进程如何将执行移交给内核?内核是否只是在调用时拾取,然后推到同一堆栈并从同一堆栈中弹出

    我相信我在某个地方听说系统调用不会立即发生,而是在某些CPU滴答声或中断时发生。这是真的吗?例如,在Linux上,这是如何发生的

    系统调用不会立即发生,但会在某些CPU滴答声或中断时发生

    当然,系统调用的效果可能取决于许多因素,包括滴答声。调度程序粒度或时间分辨率可以限制为滴答周期,例如,但调用本身应该“立即”(与执行内联)发生

    用户空间进程如何将执行移交给内核?内核是否只是在调用时拾取,然后推到同一堆栈并从同一堆栈中弹出

    它可能在不同的体系结构之间略有不同,但一般来说,syscall参数由
    libc
    组装,然后生成处理器异常以更改上下文

    有关其他详细信息,请参阅:“

    系统调用不会立即发生,但会在某些CPU滴答声或中断时发生

    完全错了。CPU不只是坐在那里无所事事,直到计时器中断。在大多数体系结构上,包括x86-64,切换到内核模式需要几十到几百个周期,但这并不是因为CPU在等待什么。这只是一个缓慢的操作


    请注意,glibc几乎为每个系统调用都提供了函数包装器,所以如果您查看反汇编,您将只看到一个外观正常的函数调用


    实际发生的情况(以x86-64为例): 请参阅从标记wiki链接的AMD64 SysV ABI文档。它指定将参数放入哪些寄存器,以及使用
    syscall
    指令进行系统调用。英特尔的insn ref手册(也链接自tag wiki)详细记录了
    syscall
    对CPU体系结构状态所做的每一项更改。如果您对它的设计历史感兴趣,请参阅AMD架构师和内核开发人员之间的amd64邮件列表。AMD在第一款AMD64硬件发布之前更新了这一行为

    32位x86使用
    int 0x80
    指令进行系统调用,或
    sysenter
    syscall
    在32位模式下不可用,
    sysenter
    在64位模式下不可用。您可以在64位代码中运行
    int0x80
    ,但仍然可以得到32位API,该API将指针视为32位。(即不要这样做)。顺便说一句,您可能对系统调用必须等待中断感到困惑,因为
    int0x80
    ?运行该指令会立即触发中断,直接跳到中断处理程序<代码>0x80也不是硬件可以触发的中断,因此中断处理程序只能在软件触发的系统调用之后运行


    AMD64系统调用示例: glibc的
    write
    包装函数只在eax中放入1并运行
    syscall
    ,然后检查返回值并设置errno。还处理重新启动对EINTR和其他东西的系统调用

    // objdump -R -Mintel -d /lib/x86_64-linux-gnu/libc.so.6
    ...
    00000000000f7480 <__write>:
       f7480:       83 3d f9 27 2d 00 00    cmp    DWORD PTR [rip+0x2d27f9],0x0        # 3c9c80 <argp_program_version_hook+0x1f8>
       f7487:       75 10                   jne    f7499 <__write+0x19>
       f7489:       b8 01 00 00 00          mov    eax,0x1
       f748e:       0f 05                   syscall
       f7490:       48 3d 01 f0 ff ff       cmp    rax,0xfffffffffffff001   // I think that's -EINTR
       f7496:       73 31                   jae    f74c9 <__write+0x49>
       f7498:       c3                      ret
       ... more code to handle cases where one of those branches was taken
    
    //objdump-R-Mintel-d/lib/x86_64-linux-gnu/libc.so.6
    ...
    00000000000f7480:
    f7480:83 3d f9 27 2d 00 cmp DWORD PTR[rip+0x2d27f9],0x0#3c9c80
    f7487:7510JNE f7499
    f7489:b8 01 00 mov eax,0x1
    f748e:0f 05系统调用
    f7490:48 3d 01 f0 ff ff cmp rax,0xFFFFFFFFFFF001//我想那是-EINTR
    f7496:7331 jae f74c9
    f7498:c3 ret
    ... 更多代码用于处理其中一个分支被执行的情况
    
    对于函数调用,您是正确的。但是,对于系统调用,没有。函数
    seccomp()
    将以特定顺序从堆栈中加载参数并加载到特定寄存器中,将
    seccomp
    的系统调用号加载到
    rax
    ,然后执行指令
    int 0x80
    syscenter
    syscall
    。CPU“陷阱”并将控制权交给内核,内核执行一个中断服务例程,该例程确定请求的系统调用编号为
    rax
    ,并执行它。当内核将控制权返回给进程时,它会在
    rax
    中报告返回值。@iwillnotexistidnotexist极好的回答,应该是一个答案@彼得考德斯对我的评论作了极好的阐述。正如他所指出的,CPU不会等待下一个滴答声。如果CPU被中断,它将尽可能快地为中断提供服务(由于上下文切换,在几百个周期内)。不同之处在于,程序可以通过
    syscall
    指令自动中断自身,或者计时器可以非自愿中断程序的执行,将控制权交给内核。内核为inte的明确目的设置此计时器
    .rodata
    msg:
            .string "hello world!\n"
    
    .text
    main:   // using an in-line syscall
            mov     eax, 1    # __NR_write
            mov     edx, 13   # string length
            mov     esi, OFFSET FLAT:msg      # string pointer
            mov     edi, eax  # file descriptor = 1 happens to be the same as __NR_write
            syscall
            xor     eax, eax  # zero the return value
            ret
    
    glibcwrite:  // using the normal way that you get from compiler output
            sub     rsp, 8       // keep the stack 16B-aligned for the function call
            mov     edx, 13      // put args in registers
            mov     esi, OFFSET FLAT:msg
            mov     edi, 1
            call    write
            xor     eax, eax
            add     rsp, 8
            ret
    
    // objdump -R -Mintel -d /lib/x86_64-linux-gnu/libc.so.6
    ...
    00000000000f7480 <__write>:
       f7480:       83 3d f9 27 2d 00 00    cmp    DWORD PTR [rip+0x2d27f9],0x0        # 3c9c80 <argp_program_version_hook+0x1f8>
       f7487:       75 10                   jne    f7499 <__write+0x19>
       f7489:       b8 01 00 00 00          mov    eax,0x1
       f748e:       0f 05                   syscall
       f7490:       48 3d 01 f0 ff ff       cmp    rax,0xfffffffffffff001   // I think that's -EINTR
       f7496:       73 31                   jae    f74c9 <__write+0x49>
       f7498:       c3                      ret
       ... more code to handle cases where one of those branches was taken