了解 pre-processor 宏中的内联汇编与函数中的内联汇编
Understanding Inline assembly in a pre-processor macro vs Inline assembly in a function
GGC 的内联汇编很难正确实现并且容易出错 。从更高层次的角度来看,内联汇编有一些规则,除了内联汇编语句可能发出的指令之外,还必须考虑这些规则。
C/C++ 标准考虑 asm
to be an option and implementation defined. Implementation defined behaviour is documented in GCC 包括:
Do not expect a sequence of asm statements to remain perfectly consecutive after compilation, even when you are using the volatile qualifier. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement.
没有任何输出约束的基本内联汇编或扩展内联汇编是隐式的 volatile
。文档说易失性并不能保证连续的语句将按照源代码中的出现顺序排列。此代码没有保证顺序:
asm ("cli");
asm ("mov $'M', %%al; out %%al, [=11=]xe9" ::: "eax");
asm ("mov $'D', %%al; out %%al, [=11=]xe9" ::: "eax");
asm ("mov $'P', %%al; out %%al, [=11=]xe9" ::: "eax");
asm ("sti");
如果打算使用CLI和STI关闭(并重新打开)外部中断并输出一些字母命令 MDP
到 QEMU 调试控制台(端口 0xe9)然后这不能保证。您可以将它们全部放在一个内联汇编语句中,或者您可以使用扩展的内联汇编模板将虚拟依赖项传递给每个保证顺序的语句。
为了使事情更易于管理 OS 众所周知,开发人员会围绕此类代码创建方便的包装器。一些开发人员将其用作 C pre-processor 宏。理论上这看起来很有用:
#define outb(port, value) \
asm ("out %0, %1" \
: \
: "a"((uint8_t)value), "Nd"((uint16_t)port))
#define cli() asm ("cli")
#define sti() asm ("sti")
然后您可以像这样使用它们:
cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();
当然,C pre-processor 是在 C 编译器开始处理代码本身之前首先完成的。 pre-processor 将连续生成这些语句,代码生成器也不保证以特定顺序发出这些语句:
asm ("cli");
asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9));
asm ("sti");
我的问题
一些开发人员自行使用宏,将内联汇编语句放在复合语句中,如下所示:
#define outb(port, value) ({ \
asm ("out %0, %1" \
: \
: "a"((uint8_t)value), "Nd"((uint16_t)port)); \
})
#define cli() ({ \
asm ("cli"); \
})
#define sti() ({ \
asm ("sti"); \
})
像以前一样使用这些宏会使 C pre-processor 生成以下代码:
({ asm ("cli"); });
({ asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9)); });
({ asm ("sti"); });
问题 1:在复合语句中放置 asm
语句是否保证顺序?我的观点是我不这么认为,但实际上我不确定。这是我避免使用 pre-processor 宏生成内联汇编的原因之一,我可能会在这样的序列中使用它。
多年来,我一直在 headers 中使用 static inline
函数来进行内联汇编语句。函数提供类型检查,但我也相信函数中的内联汇编确实保证副作用(包括内联汇编)由下一个序列点(函数调用末尾的 ;
)发出。
如果我要调用实际函数,我的期望是这些函数中的每一个都会按相对于彼此的顺序生成内联汇编语句:
cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();
问题2 : 将内联汇编语句放在实际函数中(无论是外部链接还是内联)是否保证顺序?我的感觉是,如果不是这种情况,代码如下:
printf ("hello ");
printf ("world ");
可以输出为hello world
或world hello
。 C 的 as-if rule 表明优化不能改变可观察到的行为。我相信编译器无法假设内联汇编实际上是否改变了可观察到的行为,因此不允许编译器以另一个顺序发出函数的内联汇编。
Do not expect a sequence of asm statements to remain perfectly consecutive after compilation, even when you are using the volatile qualifier. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement.
您实际上是误读了(或过度阅读了)。这并不是说 volatile asm 语句可以重新排序;它们不能被重新排序或删除——这就是 volatile 的全部意义所在。它的意思是其他 (non-volatile) 的东西可以根据 asm 语句重新排序,特别是,可以在这些 asm 语句的任何两个之间移动。因此,在优化器处理完它们之后,它们可能不会连续,但它们仍然是有序的。
请注意,这仅适用于 volatile
asm 块(包括所有没有输出的块——它们是隐式易变的)。如果允许,任何其他 non-volatile asm 块或语句都可以在 volatile asm 块之间移动。
GGC 的内联汇编很难正确实现并且容易出错
C/C++ 标准考虑 asm
to be an option and implementation defined. Implementation defined behaviour is documented in GCC 包括:
Do not expect a sequence of asm statements to remain perfectly consecutive after compilation, even when you are using the volatile qualifier. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement.
没有任何输出约束的基本内联汇编或扩展内联汇编是隐式的 volatile
。文档说易失性并不能保证连续的语句将按照源代码中的出现顺序排列。此代码没有保证顺序:
asm ("cli");
asm ("mov $'M', %%al; out %%al, [=11=]xe9" ::: "eax");
asm ("mov $'D', %%al; out %%al, [=11=]xe9" ::: "eax");
asm ("mov $'P', %%al; out %%al, [=11=]xe9" ::: "eax");
asm ("sti");
如果打算使用CLI和STI关闭(并重新打开)外部中断并输出一些字母命令 MDP
到 QEMU 调试控制台(端口 0xe9)然后这不能保证。您可以将它们全部放在一个内联汇编语句中,或者您可以使用扩展的内联汇编模板将虚拟依赖项传递给每个保证顺序的语句。
为了使事情更易于管理 OS 众所周知,开发人员会围绕此类代码创建方便的包装器。一些开发人员将其用作 C pre-processor 宏。理论上这看起来很有用:
#define outb(port, value) \
asm ("out %0, %1" \
: \
: "a"((uint8_t)value), "Nd"((uint16_t)port))
#define cli() asm ("cli")
#define sti() asm ("sti")
然后您可以像这样使用它们:
cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();
当然,C pre-processor 是在 C 编译器开始处理代码本身之前首先完成的。 pre-processor 将连续生成这些语句,代码生成器也不保证以特定顺序发出这些语句:
asm ("cli");
asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9));
asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9));
asm ("sti");
我的问题
一些开发人员自行使用宏,将内联汇编语句放在复合语句中,如下所示:
#define outb(port, value) ({ \
asm ("out %0, %1" \
: \
: "a"((uint8_t)value), "Nd"((uint16_t)port)); \
})
#define cli() ({ \
asm ("cli"); \
})
#define sti() ({ \
asm ("sti"); \
})
像以前一样使用这些宏会使 C pre-processor 生成以下代码:
({ asm ("cli"); });
({ asm ("out %0, %1" : : "a"((uint8_t)'M'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'D'), "Nd"((uint16_t)0xe9)); });
({ asm ("out %0, %1" : : "a"((uint8_t)'P'), "Nd"((uint16_t)0xe9)); });
({ asm ("sti"); });
问题 1:在复合语句中放置 asm
语句是否保证顺序?我的观点是我不这么认为,但实际上我不确定。这是我避免使用 pre-processor 宏生成内联汇编的原因之一,我可能会在这样的序列中使用它。
多年来,我一直在 headers 中使用 static inline
函数来进行内联汇编语句。函数提供类型检查,但我也相信函数中的内联汇编确实保证副作用(包括内联汇编)由下一个序列点(函数调用末尾的 ;
)发出。
如果我要调用实际函数,我的期望是这些函数中的每一个都会按相对于彼此的顺序生成内联汇编语句:
cli ();
outb (0xe9, 'M');
outb (0xe9, 'D');
outb (0xe9, 'P');
sti ();
问题2 : 将内联汇编语句放在实际函数中(无论是外部链接还是内联)是否保证顺序?我的感觉是,如果不是这种情况,代码如下:
printf ("hello ");
printf ("world ");
可以输出为hello world
或world hello
。 C 的 as-if rule 表明优化不能改变可观察到的行为。我相信编译器无法假设内联汇编实际上是否改变了可观察到的行为,因此不允许编译器以另一个顺序发出函数的内联汇编。
Do not expect a sequence of asm statements to remain perfectly consecutive after compilation, even when you are using the volatile qualifier. If certain instructions need to remain consecutive in the output, put them in a single multi-instruction asm statement.
您实际上是误读了(或过度阅读了)。这并不是说 volatile asm 语句可以重新排序;它们不能被重新排序或删除——这就是 volatile 的全部意义所在。它的意思是其他 (non-volatile) 的东西可以根据 asm 语句重新排序,特别是,可以在这些 asm 语句的任何两个之间移动。因此,在优化器处理完它们之后,它们可能不会连续,但它们仍然是有序的。
请注意,这仅适用于 volatile
asm 块(包括所有没有输出的块——它们是隐式易变的)。如果允许,任何其他 non-volatile asm 块或语句都可以在 volatile asm 块之间移动。