一道 C 语言指针访存题目的引申

2009/11/08 | 13:41 | 分类:计算机科学与编程 | 标签: | 798次阅读

  毕业生求职的时节,非毕业生接触到各种面试、笔试题目的几率也会相应地增加。下面请看一道经典的 C 语言指针访存题目,稍有些经验的朋友应该很快可以看出这个题目考查的是字节序、内存布局等知识点。然后在大脑中略排列一下,就能够给出答案(2000000)。

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5.     int a[5] = {1, 2, 3, 4, 5};
  6.     int *pa = (int)(&a) + 1;
  7.     printf("%x\n", *pa);
  8.     return 0;
  9. }

  不过,这个答案是否绝对正确,还要看题目所处的上下文了。如果题目明确说是在常见的 32 位 x86 平台上运行,那就无可厚非;但如果没有指明机器架构,那就要小心一点了,也许命题者真想考查一下求职者对非 x86 平台的了解程度呢。如果考虑机器架构,这个题目应当如何作答呢?粗想一下,我们需要考虑的是字长、字节序和对齐(alignment)访问规则。不过真要做实验看看,会发现这里面还是有一些花样的。如果没有实际经验,只凭教条加推测,很可能想不到其它平台上的一些细节之处。
  我们换用一段信息量更丰富的程序来进行后续的实验。在不同的平台上,均使用未加特殊参数的 gcc 来编译这段程序——

  1. #include <stdio.h>
  2.  
  3. int main()
  4. {
  5.     int x;
  6.     int a[5] = {0x11121314, 0x21222324, 0x31323334, 0x41424344, 0x51525354};
  7.     for (x = 0; x < 20; x++) {
  8.         printf("%02x ", *(char *)((int)(&a) + x));
  9.     }
  10.     printf("\n");
  11.     for (x = 0; x < 8; x++) {
  12.         printf("%08x ", *(int *)((int)(&a) + x));
  13.     }
  14.     printf("\n");
  15.     return 0;
  16. }

  在 32 位 x86 下的结果不需要多解释。

  1. uname -a
  2. Linux ubuntu 2.6.31-14-generic #48-Ubuntu SMP Fri Oct 16 14:04:26 UTC 2009 i686 GNU/Linux
  3. ./a.out
  4. 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
  5. 11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421

  而在 64 位的 x86_64 下,由于 8 字节的指针被截断到了 4 字节的整型长度,故会引发段错误。同样的情况出现在 64 位的 Alpha 机器下。解决办法自然是把运算地址时的 int 修改成 long 或某种显式的 64 位类型。修改后的结果应该与 32 位 x86 一致。

  1. uname -a
  2. Linux ubuntu 2.6.24-22-generic #1 SMP Mon Nov 24 19:35:06 UTC 2008 x86_64 GNU/Linux
  3. ./a.out
  4. Segmentation fault
  1. uname -a
  2. NetBSD sdf 2.1.0_STABLE NetBSD 2.1.0_STABLE (sdf) #0: Fri Mar 30 02:24:32 UTC 2007  root@ol:/var/sys/arch/alpha/compile/sdf alpha
  3. ./a.out
  4. Memory fault (core dumped)

  有趣的是在 XScale(Intel 实现的 ARMv5)下,虽然同属 little-endian,但非对齐取数时出现了在字内按字节循环的移位的结果。查查 ARM 的官方文档,这确实是 ARMv5 的特性;而在 ARMv6 以后,非对齐访问则是完全支持的。

  1. uname -a
  2. Linux zaurus 2.4.18-rmk7-pxa3-embedix #1 Sat, 06 Aug 2005 12:22:55 +0000 armv5tel unknown
  3. ./a.out
  4. 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
  5. 11121314 14111213 13141112 12131411 21222324 24212223 23242122 22232421

  接下来看看 PowerPC,它是 big-endian 的代表,允许 32 位以内的非对齐访问,结果是容易理解的。有关 PowerPC 非对齐访问的一些细节可以参考这篇文章

  1. uname -a
  2. AIX aix 3 5 00C97AC04C00 powerpc unknown AIX
  3. ./a.out
  4. 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
  5. 11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233

  同样是 big-endium 的 SPARC 则不允许非对齐访问。它会对非对齐访问抛出 SIGBUS。

  1. uname -a
  2. SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris
  3. ./a.out
  4. 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
  5. Bus Error (core dumped)

  最后看看我们中科院计算所的龙芯(Loongson)2E,它是兼容 MIPS 架构的处理器。很多教科书告诉我们说通常的 MIPS 是不允许非对齐访问的(部分 MIPS 实现提供了非对齐访问指令,并申请了专利),但我们在龙芯下却得到了和 x86 相同的、允许非对齐访问的结果,这又是为什么呢?初步查到的原因是“(针对龙芯修改过的 Linux)内核里确实有一个异常处理函数负责处理 lw 访问非对齐地址引起的异常”。这也许是龙芯绕开 MIPS 专利的一种办法?我会向龙芯团队的同学求证一下,也希望熟悉 MIPS 或龙芯的朋友给我一个确切的答案。

  1. uname -a
  2. Linux Loongson-1 2.6.18.1lemote #1 Sat Jan 13 16:02:26 CST 2007 mips GNU/Linux
  3. ./a.out
  4. 14 13 12 11 24 23 22 21 34 33 32 31 44 43 42 41 54 53 52 51
  5. 11121314 24111213 23241112 22232411 21222324 34212223 33342122 32333421

  不过用心思考的朋友也许会发现上面一系列实验存在的一个疏漏:没有考虑编译器的影响。一方面,编译器可能对整型的字长有不同的规定(例如 Windows 下的某些编译器即使在 32 位 x86 上也会把 int 定义为 16 位);另一方面,编译器可以对不支持非对齐访问的处理器生成一定的指令序列、通过多次访存来模拟非对齐访问。我们看下面的例子:还是在 SPARC 平台上,改用 Solaris 自带的 Sun CC 来编译实验程序,这时就不会出现“Bus Error”,而会输出和 PowerPC 一样的结果。因为 SunCC 默认会使用“-xmemalign”参数来生成适当的访存指令序列。

  1. uname -a
  2. SunOS t1000 5.10 Generic_118833-33 sun4v sparc SUNW,Sun-Fire-T1000 Solaris
  3. cc data.c
  4. ./a.out
  5. 11 12 13 14 21 22 23 24 31 32 33 34 41 42 43 44 51 52 53 54
  6. 11121314 12131421 13142122 14212223 21222324 22232431 23243132 24313233

  这样看来,在不指定机器架构和编译器等上下文的情况下,要正确且完美地回答一开始的那道题目还是需要一定知识积累的。答案省略,留给大家自己求解。在面试、笔试诸如 Sun SPARC、IBM PowerPC、中科院计算所微处理器中心等部门或者做 ARM 等嵌入式开发的公司时,最好先了解清楚它们的产品常识。
  (部分实验环境来源于 Unix-Center.Net,在此致谢)

从《创新求索录》看龙芯的“自主创新”

2009/06/22 | 22:00 | 分类:IT杂谈 | 标签: | 590次阅读

  上周龙芯购买 MIPS 专利授权后,针对龙芯的新一轮质疑又在网络上涌现了。虽说我本科时就玩过学校申请试用的龙芯盒子,现在又来到计算所读书,但我没有直接接触过龙芯项目,也没有上过胡伟武老师的相关课程。我原先对龙芯的了解更多的是来源于网络媒体。上个周末,我翻阅了所长李国杰院士去年出版的《创新求索录》,里面有一章专门讲曙光机和龙芯 CPU 的发展。简单地谈一下我的想法。
从《创新求索录》看龙芯的“自主创新”
  1.龙芯不是骗局。有人认为龙芯这次被迫购买 MIPS 的授权,揭穿了以前所谓的“自主知识产权”的外衣,认为龙芯之前打着“自主知识产权”的旗号骗了纳税人的钱。但我们从李老师本世纪初考虑 CPU 战略,直到龙芯设计、生产以来的一系列文章看,无论是提交给国家决策层的申请与汇报,或是针对业界报告和内刊,还是在面向大众的平面或网络媒体上,李老师都已经明确地说过:龙芯不是另起炉灶,而要走兼容技术路线,要使用 MIPS 指令系统。他当时已经预料到 MIPS 公司会因此收取许可费用(见《研制龙芯CPU的策略考虑》,原载于《计算机世界》,2002年10月)。但那时作出的这一决策是在多种方案中选择出来的成本、自主权、可用性等因素折衷的方案。外界可以找理由来批评这一方案制定得不好,但不能就此认为这是骗局。因为龙芯团队从来没有掩饰自己的技术路线,在一开始选择 MIPS 方案就是对国家和社会公开的,现在被 MIPS 公司要求支付授权费用也是预料之内的。龙芯团队对现在这些质疑的回应并非新编的辩解,因为这些文字都来自于既定的、已公开的方案,可以检索他们早期发表的文章来印证。
  2.“完全自主知识产权”不是龙芯的幌子。李老师多篇文章中提到,在 CPU 设计中“要抛弃所谓‘完全自主知识产权’的旧观念”,“与‘平等待我’的国外大企业合作,率先突破芯片设计,然后带动国内芯片生产企业”,“你中有我,我中有你”。他认为“完全自主知识产权”是一种“左的干扰”;与之对应,“技术来源主要应靠外国”则是一种“右的干扰”(见《对提高自主创新能力的几点认识》,原载于《科学时报》,2005年3月22日)。李老师强调的概念是“自主创新”:自主创新“不排斥开放与集成”,“引进技术的消化吸收改进也是自主创新的组成部分”,“成果不但不一定有知识产权,反而可能侵犯别人的知识产权”,“主要是指应尽量争取避免完全受制于人,减少‘路径依赖’”,目的是“实现‘珍珠换玛瑙’的技术发展路线”。我们姑且不谈龙芯的技术是“引进”的多一点还是“创新”的多一点,单从李老师的这些主张看,龙芯的发展没有背离他所制订的轨道。当然,“爱国人士”们可以不认同李老师定义的自主创新理念,进而提出一套更纯粹的自主创新观。但如果认为龙芯打着“完全自主知识产权”的幌子却使用国外技术就大错特错了,因为这是李老师本人明确反对的路线。
  3.媒体、决策层、产业化开发商对龙芯的社会认知偏差有一定的责任。从《创新求索录》上可以看出,李老师个人在措辞上还是比较保守的,对龙芯常用的提法是“自主创新”、“(技术合作中)珍珠换玛瑙”、“(国际协作中)有价值的探索”等(见《将龙芯产业化推上新台阶》,原载于人民网,2007年3月29日)。龙芯官方网站上出现“自主知识产权”的提法(注意没有“完全”二字)也并不为过,因为龙芯团队毕竟在国内外发表了不少文章、著作及专利,走的不是一点自主发挥都没有的完全仿制道路,龙芯 CPU 的研发工作有它的客观价值。而在宣传提法上过火,标榜“完全国货”的,正是一些媒体或产业化开发商。媒体在宣传报导时,常常依赖于一些时政因素,特别是针对“知识经济”、“创新型国家”一类的主题性报导,媒体的主观性、政治性认识往往会将对主旨不利的技术事实掩盖,主动回避“兼容 MIPS”之类有可能造成疑问的技术话题。对于产业化开发商,他们基于商业利益的需要,对政府和大众宣传的更多的是产品级特性。为了迎合政府采购计划以及部分支持国货的“粉丝”,这些商人会比技术人员更懂得措辞的艺术。因此,社会上对龙芯的神化、过高期望、国货性质的理解、知识产权的认识,很多是源于这类为政策或业绩左右了的宣传。相比之下,龙芯团队本身是诚信而踏实的,在其学术论文中并不回避其使用国外已有技术的事实,他们自己的言辞一如既往地忠于客观技术。
  龙芯的发展策略是否合理,产、学、研界各人可以有各自的观点。龙芯的产业化是否会成功,自然会由市场说了算。但龙芯的性质是否清白,客观史实自会证明。要实事求是,不要人云亦云。