【问题标题】:asmlinkage defined to nothing in Linux codeasmlinkage 在 Linux 代码中定义为空
【发布时间】:2019-11-07 02:34:23
【问题描述】:

我正在研究 asmlinkage 标记。

来自https://kernelnewbies.org/FAQ/asmlinkage

这是一些 gcc 魔术的#define,它告诉编译器 函数不应该期望在寄存器中找到它的任何参数 (一种常见的优化),但只在 CPU 的堆栈上。

查看定义,x86_64定义为空:

#ifdef __cplusplus
#define CPP_ASMLINKAGE extern "C"
#else
#define CPP_ASMLINKAGE
#endif

#ifndef asmlinkage
#define asmlinkage CPP_ASMLINKAGE
#endif

另外,我读到 ABI 规范说我们应该将系统调用号放在一个寄存器中,并将参数放在其他一些寄存器中。

那我们为什么要在堆栈中寻找函数参数。系统调用号是否也入栈?

【问题讨论】:

  • 你在哪里找到这个代码的?它看起来不是 x86_64 特定的。
  • 它存在于 include/linux/linkage.h 文件中

标签: c linux linux-kernel system-calls abi


【解决方案1】:

系统调用不是您的用户空间程序或共享库的一部分,它们是内核提供的系统服务并在内核空间中执行..为了从用户空间移动到内核空间,需要执行一些特殊的程序集告诉处理器跳转到超级用户模式的指令,在此之前它需要在寄存器中推送一些信息,以便内核端知道要执行的系统调用 - 它有一个指向各种函数的函数指针表'服务'每个系统调用)-

#include "SYS.h"

ENTRY(syscall)
        pop     %ecx    /* rta */
        pop     %eax    /* syscall number */
        push    %ecx
        KERNCALL
        push    %ecx    /* need to push a word to keep stack frame intact
                           upon return; the word must be the return address. */
        jb      1f
        ret

其中 KERNCALL 依赖于体系结构,是一些汇编语言指令,告诉 cpu 在内核空间中跳转到主管模式 -

./lib/libc/amd64/SYS.h:#define  KERNCALL    movq %rcx, %r10; syscall
./lib/libc/i386/SYS.h:#define   KERNCALL    int $0x80

所以事情就是这样......当你编译一个程序时,优化器偶尔会在寄存器中抛出一个函数的参数,而不是将它们放在程序的堆栈中......这个优化是有效的,因为编译器为调用者和被调用者发出代码所以双方都意识到了这种轻微的手感。然而,内核并非如此......它不知道在哪个寄存器中寻找什么,因此用于系统调用的所有参数都必须在用户态程序的堆栈中。

【讨论】:

  • 令人困惑,你最后说“所有用于系统调用的参数都必须在用户态程序的堆栈上。”
  • 是的。想想“开放”系统调用。它需要三个参数——路径、标志和模式。在进入内核空间之前,所有三个都需要在用户态程序的堆栈上。在内核方面它看起来像这样 - struct sysent sysent[] = { { 0, (sy_call_t)sys_nosys }, / 0 = syscall / .. { AS(open_args), (sy_call_t *)sys_open }, / 5 = open * / .. struct open_args { char * 路径;整数标志;整数模式; } -
  • 没有。 'open' 系统调用的参数,即 open(path, flags, mode) 必须在程序的堆栈上。
  • 寄存器作为系统调用通信机制的一部分进行更新 - 这与正在运行的系统调用类型(fcntl、open、read、write、chdir 等)无关。
  • 换句话说,有一个标准的、通用的机制用于将数据从用户空间传输到内核空间(并且可能在另一个方向),用于所有系统调用..这种机制使用寄存器和汇编指令.
【解决方案2】:

asmlinkage 告诉您的编译器在 CPU 堆栈中查找函数参数,而不是寄存器

实际上,它使用 GCC 的 regparam 属性 (Function-Attributes),或者 IA64 的 syscall_linkage

#define asmlinkage CPP_ASMLINKAGE __attribute__((regparm(0)))

但你的问题是为什么这是必要的。系统调用是用户空间可以调用以请求内核为它们执行某些操作(因此在内核空间中执行)的服务。从某种意义上说,这些函数非常非正统,因为您不能期望它们像普通函数一样运行,其中参数通常通过写入程序堆栈来传递,而是将它们写入寄存器。当仍在用户空间中时,调用系统调用需要将某些值写入某些寄存器被翻译。系统调用号总是写在 eax 中,而其余参数将进入 ebx、ecx 等。 现在,当发生软件中断时,CPU 切换到内核模式,然后执行 system_call()。当 CPU 切换到内核模式时,它首先保存 CPU 堆栈中的所有寄存器(eax、ebx、ecx 等)。在检查其他事情(例如验证参数)后,如果一切正常,它将调用相应的系统调用。所以,因为从用户空间一直传递到这一点的所有参数信息都很好地存储在堆栈中,编译器必须被告知这一点,因此 asmlinkage

另一个原因是内核在处理来自用户空间的系统调用请求时无论如何都需要将所有寄存器保存到堆栈上(以便在返回用户空间之前恢复环境),因此之后参数在堆栈上可用,不需要额外的努力。

【讨论】:

  • 这是否意味着我们正在通知编译器所有参数都已经进入堆栈,不要添加将参数复制到堆栈的指令
  • 是的。这也是 asmlinkage 的结果
猜你喜欢
  • 2023-01-19
  • 2023-03-08
  • 1970-01-01
  • 1970-01-01
  • 2013-01-10
  • 2011-05-03
  • 1970-01-01
  • 2011-02-07
  • 2016-01-27
相关资源
最近更新 更多