asmlinkage 是指堆栈还是寄存器?

Does asmlinkage mean stack or registers?

在大多数语言中,包括 C,堆栈用于函数调用。这就是为什么在递归时不小心会出现“堆栈溢出”错误的原因。 (不是双关语)。

如果这是真的,那么 asmlinkage GCC 指令有什么特别之处。

它说,从#kernelnewbies

The asmlinkage tag is one other thing that we should observe about this simple function. This is a #define for some gcc magic that tells the compiler that the function should not expect to find any of its arguments in registers (a common optimization), but only on the CPU's stack.

我的意思是我认为 寄存器不会用于正常的函数调用

更奇怪的是,当你学会它的时候是implemented using the GCC regparm function attribute on x86

regparm的文档如下:

On x86-32 targets, the regparm attribute causes the compiler to pass arguments number one to number if they are of integral type in registers EAX, EDX, and ECX instead of on the stack.

这基本上与 asmlinkage 正在尝试做的相反。

那么会发生什么?它们是在堆栈上还是在寄存器中。

我哪里错了?

信息不是很清楚

在 x86 32 位上,根本没有 asmlinkageexpands to __attribute__((regparam(0))), which basically tells GCC that no parameters should be passed through registers (the 0 is the important part). As of Linux 5.17, x86-32 and Itanium64 seem to be the only two architectures re-defining this macro, which by default expands to no attribute

所以asmlinkage本身并不意味着“参数在堆栈上传递”。默认情况下,使用正常的调用约定。这包括 x86 64 位,它遵循 System V AMD64 ABI 调用约定,通过 RDI、RSI、RDX、RCX、R8、R9、[XYZ]MM0–7.

传递函数参数

HOWEVER 有一个重要的澄清:即使没有特殊的 __attribute__ 强制编译器使用参数堆栈,最近内核版本中的系统调用仍然通过指向 pt_regs 结构的指针 间接 从堆栈中获取参数(在系统调用入口保存所有用户-space 寄存器保存在堆栈中)。这是通过一组中等复杂的宏 (SYSCALL_DEFINEx) 实现的,这些宏可以透明地执行所有操作。

所以技术上,虽然asmlinkage不改变调用约定,但参数不会在寄存器内部传递通过简单地查看系统调用函数签名来思考。

例如,以下系统调用:

SYSCALL_DEFINE3(something, int, one, int, two, int, three)
{
    // ...
    do_something(one, two, three);
    // ...
}

实际变成(大致):

asmlinkage __x64_sys_something(struct pt_regs *regs)
{
    // ...
    do_something(regs->di, regs->si, regs->dx);
    // ...
}

编译成类似的东西:

/* ... */
mov    rdx,QWORD PTR [rdi+0x60]
mov    rsi,QWORD PTR [rdi+0x68]
mov    rdi,QWORD PTR [rdi+0x70]
call   do_something
/* ... */

至少在 i386 和 x86-64 上,asmlinkage 意味着使用没有 GCC 选项和 __attribute__[=54= 的标准调用约定]. (就像 user-space 程序通常用于该目标的内容。)

对于 i386,这意味着仅堆栈参数。对于 x86-64,它仍然是与往常一样的寄存器。


对于 x86-64,没有区别;内核已经在各处使用 AMD64 System V ABI 文档中的标准调用约定,因为它 well-designed 为了提高效率,在寄存器中传递前 6 个整数参数。

但 i386 有更多历史包袱,标准调用约定 (i386 SysV ABI) 无法有效地传递堆栈上的所有参数。大概在古代历史的某个时刻,Linux 是由 GCC 使用此约定编译的,并且调用 C 函数的 hand-written asm 入口点已经使用该约定。

所以(我在这里猜测),当 Linux 想要从 gcc -m32 切换到 gcc -m32 -mregparm=3 以使用更有效的调用约定构建内核时,他们有一个选择要么同时修改 hand-written asm 以使用新约定,要么强制一些特定的 C 函数仍然使用传统调用约定,以便 hand-written asm 可以保持不变。

如果他们做出了前一个选择,i386 的 asmlinkage 将是 __attribute__((regparm(3))) 以强制执行该约定,即使内核是以不同的方式编译的。

但是,他们选择保持 asm 不变,#define asmlinkage __attribute__((regparm(0))) 对于 i386,它确实是零寄存器参数,立即使用堆栈。

我不知道这是否有任何调试好处,比如能够看到哪些 args 从 asm 传递到 C 函数而没有可能立即修改唯一副本。

如果 -mregparm=3 和相应的属性是新的 GCC 功能,Linus 可能希望保持使用旧 GCC 构建内核的可能性。这将排除将 asm 更改为需要 __attribute__((regparm(3)))。他们实际做出的 asmlinkage = regparm(0) 选择还具有不必修改任何 asm 的优点,这意味着没有正确性问题,并且可以通过使用 new(?)-[=63 来解决任何可能的 GCC 错误=]调用约定。

此时我认为完全可以修改调用asmlinkage 函数的asm 代码,并将其交换为regparm(3)。但这是一件很小的事情。现在不值得做,因为 32 位 x86 内核对于几乎所有用例来说早已过时。您几乎总是需要 64 位内核,即使使用 32 位 user-space.

如果将寄存器保存在 system-call 入口点涉及在最低地址使用 EBX 保存它们,那么堆栈 args 甚至可能有效率优势,它们已经就位以用作函数参数。您将全部设置为 call *ia32_sys_call_table(,%eax,4)。但这实际上并不安全,因为被调用者拥有他们的堆栈参数并被允许写入它们,即使 GCC 通常不使用传入的堆栈参数位置作为临时 space。所以我怀疑 Linux 会这样做。


其他 ISA 可以很好地处理在寄存器中传递 args 的 asmlinkage,因此对于 Linux 的工作方式而言,堆栈 args 没有什么重要的基础知识。 (可能 i386 特定代码除外,但我什至对此表示怀疑。)

整个“asmlinkage 意味着在堆栈上传递参数”纯粹是 i386 的事情。

运行 Linux 的大多数其他 ISA 都比 32 位 x86 更新(and/or 是 RISC-like 具有更多寄存器),并且具有更高效的标准调用约定对于现代 CPU,前几个参数使用寄存器。这包括 x86-64。