这个最小的OpenMP计划
#include <omp.h>
int main()
{
#pragma omp parallel sections
{
#pragma omp section
{
while(1) {}
}
#pragma omp section
{
while(1) {}
}
}
}
编译并使用gcc test.c -fopenmp
运行时将产生此错误:
Illegal instruction (core dumped)
当我用
更改其中一个循环时 int i=1;
while(i++) {}
或其编译并运行且没有错误的任何其他条件。看来,1
作为不同线程中的循环条件会导致一些奇怪的行为。为什么呢?
编辑:我正在使用gcc 4.6.3
编辑:这是gcc中的一个错误,并以Bug 54017的形式提交给gcc开发人员。
答案 0 :(得分:7)
这显然是海湾合作委员会的一个错误。 GCC使用GOMP_sections_start()
中的libgomp
例程实现OpenMP部分,该例程返回调用线程应执行的基于1
的部分ID,或者0
如果所有工作项都已分发。基本上,转换后的代码应如下所示:
main._omp_fn.0 (void * .omp_data_i)
{
unsigned int .section.1;
.section.1 = GOMP_sections_start(2);
L0:
switch (.section.1)
{
case 0:
// No more sections to run, exit
goto L2;
case 1:
// Do section 1
while (1) {}
goto L1;
case 2:
// Do section 2
while (1) {}
goto L1;
default:
// Impossible section value, possible error in libgomp
__builtin_trap();
}
L1:
.section.1 = GOMP_sections_next();
goto L0;
L2:
GOMP_sections_end_nowait();
return;
}
在您的情况下,default
和0
案例都会导致__builtin_trap()
。 __builtin_trap()
是一个内置的GCC,它应该异常终止你的程序,并且在x86上它会发出ud2
指令,使CPU发出非法的操作码异常。它通常放在代码永远不会执行的地方,例如来自GOMP_sections_start()
和GOMP_sections_next()
的所有可能正确的返回值应涵盖交换机中的案例,并且如果达到默认值(表示libgomp
中可能存在的错误)它应该失败,你会向开发者抱怨:)
修改:这绝对不是预期的OpenMP行为,icc
或suncc
不会发生这种情况。我已将Bug 54017提交给GCC Bugzilla。
编辑2:我更新了文字,以更准确地反映GCC应该产生的内容。看起来GCC对并行区域中的控制流有错误的印象,并进行了一些“优化”,进一步破坏了代码生成。
答案 1 :(得分:3)
生成SIGILL,因为有一条非法指令,ud2 / ud2a。 根据{{3}}:
此指令导致#UD。英特尔在未来的英特尔保证 CPU指令将导致#UD。当然以前所有的CPU (186+)在此操作码上导致#UD。该指令由软件使用 用于测试#UD异常服务例程的编写者。
让我们看看里面:
$ gcc-4.6.2 -fopenmp omp.c -o omp
$ gdb ./omp
...
(gdb) r
Program received signal SIGILL, Illegal instruction.
...
0x08048544 in main._omp_fn.0 ()
(gdb) x/i $pc
0x8048544 <main._omp_fn.0+28>: ud2a
(gdb) disassemble
Dump of assembler code for function main._omp_fn.0:
0x08048528 <main._omp_fn.0+0>: push %ebp
0x08048529 <main._omp_fn.0+1>: mov %esp,%ebp
0x0804852b <main._omp_fn.0+3>: sub $0x18,%esp
0x0804852e <main._omp_fn.0+6>: movl $0x2,(%esp)
0x08048535 <main._omp_fn.0+13>: call 0x80483f0 <GOMP_sections_start@plt>
0x0804853a <main._omp_fn.0+18>: cmp $0x1,%eax
0x0804853d <main._omp_fn.0+21>: je 0x8048548 <main._omp_fn.0+32>
0x0804853f <main._omp_fn.0+23>: cmp $0x2,%eax
0x08048542 <main._omp_fn.0+26>: je 0x8048546 <main._omp_fn.0+30>
0x08048544 <main._omp_fn.0+28>: ud2a
0x08048546 <main._omp_fn.0+30>: jmp 0x8048546 <main._omp_fn.0+30>
0x08048548 <main._omp_fn.0+32>: jmp 0x8048548 <main._omp_fn.0+32>
End of assembler dump.
汇编程序文件中已有ud2a:
$ gcc-4.6.2 -fopenmp omp.c -o omp.S -S; cat omp.S
main._omp_fn.0:
.LFB1:
pushl %ebp
.LCFI4:
movl %esp, %ebp
.LCFI5:
subl $24, %esp
.LCFI6:
movl $2, (%esp)
call GOMP_sections_start
cmpl $1, %eax
je .L4
cmpl $2, %eax
je .L5
.value 0x0b0f
.value 0xb0f
是ud2a的代码
在通过gcc意图插入ud2a之后(在早期的openmp阶段),我试图理解代码。函数main._omp_fn.0
是并行代码的主体;它将调用_GOMP_sections_start
并解析其返回码。如果代码等于1,那么我们将跳转到一个无限循环;如果是2,跳转到第二个无限循环。但在其他情况下,ud2a将被执行。 (不知道为什么,但根据Hristo Iliev的说法,这是一个GCC http://asm.inightmare.org/opcodelst/index.php?op=UD2。)
我认为,这个测试很适合检查有多少CPU内核。默认情况下,GCC的openmp库(libgomp)将为系统中的每个CPU核心启动一个线程(在我的例子中有4个线程)。将按顺序选择部分:第一个部分用于第一个线程,第二个部分用于第二个线程,依此类推。
没有SIGILL,如果我在1或2个CPU上运行程序(taskset的选项是十六进制的cpu掩码):
$ taskset 3 ./omp
... running on cpu0 and cpu1 ...
$ taskset 1 ./omp
... running first loop on cpu0; then run second loop on cpu0...