SIGILL 生成,因为存在非法指令 ud2/ud2a。根据http://asm.inightmare.org/opcodelst/index.php?op=UD2:
该指令导致#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的代码
在验证 ud2a 是由 gcc 有意插入后(在早期的 openmp 阶段),我试图理解代码。函数main._omp_fn.0
是并行代码的主体;它将调用 _GOMP_sections_start
并解析其返回码。如果代码等于1,那么我们将跳转到一个无限循环;如果为 2,则跳转到第二个无限循环。但在其他情况下 ud2a 将被执行。(不知道为什么,但根据 Hristo Iliev 的说法,这是一个 GCC错误 54017。)
我认为,这个测试很好地检查了有多少 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...