没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
CPU Bug 与 Linux Kernel
软件有 Bug,司空见惯,但 CPU 有 Bug,对 Programmer 而言确不多见.当自己写的程序发现错误
后,我想任何理智的 Programmer都会想,肯定是我错了,因为 CPU 是不会犯错的.这当然几乎是
百分之一百正确的,但也只能说是"几乎",否则在 Linux Kernel Source 的相关体系结构的目录
下为什么会有 bugs.h 这个文件呢?(如果是 x86 CPU 的话,请看 include/asm-i386/bugs.h)
CPU 是人设计的,人会犯错,自然城门失火,殃及池鱼,CPU 自然也有 bug.
读 Linux source,读着读着,难免会有泄气的感觉,因为涉及面实在太广,且代码量实在大,但不
可否认,也确有"好玩"的时候,比如能发觉 CPU 也是有 Bug 的,而且也不少.有些 Kernel 能补救
一下,有些对 kernel 而言,实在无能为力,只能报告 CPU 犯了错.
下面举 3
个 x86 架构的 CPU bug --- 2 个是 Intel 的,一个是 Cyrix 的,来看看 Linux kernel 是怎
么处理的.
FDIV BUG --- Intel 奔腾处理器浮点除法的 bug
在 include/asm-i386/bugs.h 头文件的 check_fpu()函数中有下面一段代码片段
static double __initdata x = 4195835.0;
static double __initdata y = 3145727.0;
...
1 /* Test for the divl bug.. */
2 __asm__("fninit "
3 "fldl %1 "
4 "fdivl %2\n\t"
5 "fmull %2\n\t"
6 "fldl %1\n\t"
7 "fsubp %%st,%%st(1)\n "
8 "fistpl %0\n\t"
9 "fwait "
10 "fninit"
11 : "=m" (*&boot_cpu_data.fdiv_bug)
12 : "m" (*&x), "m" (*&y));
13 if (boot_cpu_data.fdiv_bug)
14 printk("Hmm, FPU with FDIV bug.");
从代码的注释中就可看出这段代码在测试"divl bug".上面的 gcc 中的嵌入汇编是一段浮点运
算指令.我来把它翻译成对应的 c 代码.
double x = 4195835.0;
double y = 3145727.0;
double temp = x / y * y - x;
int fdiv_bug;
fdiv_bug = (int)temp;
if(fdiv_bug != 0)
{
printf("Hmm, FPU with FDIV bug.\n");
}
够简单了,看懂了吧!
原来在 Intel 奔腾处理器上当 4195835.0 / 3145727.0 时,CPU 得出的结论是 1.3337,但正
确的结论应该是 1.3338(不信,那计算器算一下).这样 x / y * y - x 原本应该等于
在精度范围内的零(由于计算机只能表示有限位数,所以上面的结果实际上也是非零,但那
已经超出 CPU 的 double 精度范围,即如此微小差异不是当前 CPU 的 double 浮点精度能表
示的,比如一个 CPU 只能分辨 0.00001,那么对于 0.000005,对于 CPU 而言无法表示,即等于
零,但由于 x / y 这一步的误差太大,造成 x / y * y - x 的最终结果的值是
Intel 奔腾处理器精度范围内可辨认的非零值.由于碰上这个 bug 的机遇比你中全国体育彩
票头奖的机会还要难,所以一开始,Intel 不承认,但在证据确凿的情况下,承认了该 bug,
但认为这个 Bug 涉及面极小,没有采取什么措施.但 Intel 没想到的是一帮屁计算机不懂的
记者会大作文章,包括 CNN 的,把这个非常技术性,也确实很难碰到---有人甚至说要 one
trillion 次才能碰到---的问题爆炒了一下,弄得 Intel 很被动,不得不宣布大规模回收有问题
的 CPU,大大的破了一次财.
对这个浮点除法 Bug, Linux Kernel 如果在系统初始化阶段检测到该 bug,只是打印一行警告,
没有什么措施.我的理解是对这种 Bug, Kernel 没有 fix 的机会(kernel 也不是万能的,只有
硬件给软件机会,软件才可能有所发挥.下面的 Intel 的 f00f bug 就是典型).
F00F Bug --- Intel 奔腾处理器死锁
在 Intel 奔腾处理器下,无论是什么操作系统 --- Linux, Windows 9.X, Windows NT, DOS,
FreeBSD 等等,你只要运行下面的代码,你就可以搞死 CPU.
char code[4] = {0xf0, 0x0f, 0xc7, 0xc8};
int main()
{
void (*func)() = code;
func();
return 0;
}
这可是很严重的问题.因为要让 CPU 去执行那一行代码,真太容易了.比如你可以写个
ActiveX 控件包括那行代码,嵌在网页里,当有奔腾处理器的计算机浏览你的网页,从而下载
你的 ActiveX 控件执行时,突然他发现他的机器没任何反应了---没有报错,没有 crash,
甚至连那臭名昭著的 Blue Screen 都没有,就像科幻片里时间凝固的场景.
0xf00fc7c8 是什么呢?
在反汇编器里,它是这样一条指令"lock cmpxchg8b %eax"
cmpxchg8b (Compare and Exchange 8 Bytes)的正常用法是带 1 个存储器操作数,例如
" cmpxchg8b (%ebx)",将(%ebx)中所指的 64 位内存操作数与 edx:eax 比较并相互交换。在 Intel
的 Instruction Manual 中是这么描述的
Compares the 64-bit value in EDX:EAX with the operand (destination operand). If the values
are equal, the 64-bit value in ECX:EBX is stored in the destination operand. Otherwise, the
value in the destination operand is loaded into EDX:EAX. The destination operand is an 8-byte
memory location. For the EDX:EAX and ECX:EBX register pairs, EDX and ECX contain the
high-order 32 bits and EAX and EBX contain the low-order 32 bits of a 64-bit value.
举例
movl $0x08002500, %esi
movl $1, %eax
mov $0, %edx
xor %ebx, %ebx
xor %ecx, %ecx
cmpxhg8b (%esi)
比较内存 0x08002500 的内容与%edx:%eax 中的值进行比较,如果内存 0x08002500 中的值为
1,则把它置为%ecx:%ebx 中的值;如果不相等,则把内存 0x08002500 中的值赋给%edx:%eax。
但跟据 Intel 指令的构成规律(指令的二进制 pattern),可以构造出象"cmpxhg8b %eax"这样
的无意义的指令,CPU 在执行这条指令时会产生异常 6,这时 CPU 将从中断描述符表中启
动第 6 号中断向量---invalid opcode trap.但如果这条指令前面又加上 lock 前缀(指令码为 F0),
CPU 会错误的认为取中断向量是一个需要改写存储器的过程,于是就等待写操作的完成,
这样就进入了死锁状态。
具体死锁原因要分析一下,在 Intel manual 上有对该指令的如下描述
This instruction can be used with a LOCK prefix to allow the instruction to be executed
atomically.To simplify the interface to the processor’s bus, the destination operand receives a write
cycle without regard to the result of the comparison. The destination operand is written back if
the comparison fails; otherwise, the source operand is written into the destination. (The processor
never produces a locked read without also producing a locked write.)
最关键的是最后在括号中的那一句
The processor never produces a locked read without also producing a locked write.
也即是说 lock read 与 lock write 必须是成对出现的,在一个 lock read 以后必须跟着一个 lock
write,就好像"lock read"是某个人进一间房子,然后锁上门,防止其他人在进入。只有里面的
人通过"lock write"来开门出房间后,才能允许另外的人再次进入该房子。lock read 好比锁,
lock write 好比锁的钥匙。
当执行 f00fc7c8 指令时,f00f bug 发生了。CPU 知道这是一条非法指令,所以它试图跳转到
invalid opcode 的处理代码。但由于前面的 lock 前缀,CPU 被搞糊涂了。当 CPU 发出总线
read 请求去读取 invalid opcode handler(在执行该代码以前,总得得到该代码吧)时,CPU 错
误的发出了 LOCK# 信号。而正象上面说的,LOCK# 信号只有在修改内存的具有
Read-Modify-Write 的指令时才可以发出。在这种情况下,在获得 invalid opcode handler 地址
时的两次连续内存读请求时 LOCK#信号保持着,但关键是并没有 write 请求,因为发生 trap
6 并不会引起改写 trap 6 handler 的地址。CPU 在等待一个永远不会到的期待,他将永远等待
下去,
for ever.
上面是跟据 Intel 的 manual 分析的,但毕竟不是电子专业出身,还是没有感性任知。要是能
在逻辑分析仪上看一下当 CPU 发出这条命令后的波形与相关数据就容易理解多了。
这实在是一个很严重的问题,Intel 马上给出了 2 个用软件来修补的解决方案。Linux 选择了
一种,并实现了它。
修补对内核的改动很少,主要在两个部分
1。系统初始化时,当在编译内核时加入对 bug 的 fix 时,要做一些特殊处理。由于该 bug
不能象 fdiv bug 那样动态的去检测---一检测就锁死 CPU,所以只能由用户通过编译内核静态
的加入。所谓特殊处理就是使得执行这条指令时首先发出的是 page fault exception, 而不是
invalid opcode exception.
2。 在 page fault exception handler 中加入对该 bug 的辨认,如果是由此 bug 引起的 page fault,
剩余17页未读,继续阅读
资源评论
wzhou1974
- 粉丝: 5
- 资源: 33
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功