上回书我们说到了链接以前,今天我们来研究最后的链接问题。

链接这个话题延伸之后完全可以跑到九霄云外去,为了避免本文牵扯到过多的话题导致言之泛泛,我们先设定本文涉及的范围。我们今天讨论只链接进行的大致步骤及其规则、静态链接库与动态链接库的创建和使用这两大块的问题。至于可执行文件的加载、可执行文件的运行时储存器映像之类的内容我们暂时不讨论。

首先,什么是链接?我们引用CSAPP的定义:链接(linking)是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可被加载(或被拷贝)到存储器并执行。

需要强调的是,链接可以执行于编译时(compile time),也就是在源代码被翻译成机器代码时;也可以执行于加载时,也就是在程序被加载器(loader)加载到存储器并执行时;甚至执行于运行时(run time),由应用程序来执行。

说了这么多,了解链接有什么用呢?生命这么短暂,我们干嘛要去学习一些根本用不到的东西。当然有用了,继续引用CSAPP的说法,如下:

  1. 理解链接器将帮助你构造大型程序。
  2. 理解链接器将帮助你避免一些危险的编程错误。
  3. 理解链接将帮助你理解语言的作用域是如何实现的。
  4. 理解链接将帮助你理解其他重要的系统概念。
  5. 理解链接将使你能够利用共享库。
    ……

言归正传,我们开始吧。为了避免我们的描述过于枯燥,我们还是以C语言为例吧。想必大家通过我们在上篇中的描述,已经知道C代码编译后的目标文件了吧。目标文件最终要和标准库进行链接生成最后的可执行文件。那么,标准库和我们生成的目标文件是什么关系呢?

Read More

有位学弟想让我说说编译和链接的简单过程,我觉得几句话简单说的话也没什么意思,索性写篇博文稍微详细的解释一下吧。其实详细的流程在经典的《Linkers and Loaders》和《深入理解计算机系统》中均有描述,也有国产的诸如《程序员的自我修养——链接、装载与库》等大牛著作。不过,我想大家恐怕很难有足够的时间去研读这些厚如词典的书籍。正巧我大致翻阅过其中的部分章节,干脆也融入这篇文章作为补充吧。

我的环境:Fedora 16 i686 kernel-3.6.11-4 gcc 4.6.3

其实MSVC的编译器在编译过程中的流程是差不多的,只是具体调用的程序和使用的参数不同罢了。不过为了描述的流畅性,我在行文中不会涉及MSVC的具体操作,使用Windows的同学可以自行搜索相关指令和参数。但是作为Linuxer,我还是欢迎大家使用Linux系统。如果大家确实需要,我会挤时间在附言中给出MSVC中相对应的试验方法。

闲话不多说了,我们进入正题。在正式开始我们的描述前,我们先来引出几个问题:

  1. C语言代码为什么要编译后才能执行?整个过程中编译器都做了什么?
  2. C代码中经常会包含头文件,那头文件是什么?C语言库又是什么?
  3. 有人说main函数是C语言程序的入口,是这样吗?难道就不能把其它函数当入口?
  4. 不同的操作系统上编译好的程序可以直接拷贝过去运行吗?

如果上面的问题你都能回答的话,那么后文就不用再看下去了。因为本文是纯粹的面向新手,所以注定了不会写的多么详细和深刻。如果你不知道或者不是很清楚,那么我们就一起继续研究吧。

Read More

前段时间我在微博上看到了阮一峰的一篇日志 《计算机是如何启动的?》 才想起来自己之前也尝试探索x86架构计算机的启动流程来着,趁着还没遗忘就先记录下一部分结论吧。不过相对于阮一峰的这篇博文,我的侧重点在于BIOS查找“启动顺序”(Boot Sequence)之前,也就是从按下电源到BIOS移交权限之间的这一段。关于之后的过程,阮一峰描述的很详细,我就不重复造轮子了。

顺便罗嗦一下,有关“扩展分区”(Extended partition)的细节,阮一峰这里是正确的,多个扩展分区是“链式”的串起来的。网上有不少说法是错误的。倘若读者质疑,不妨用WinHex之类的工具直接以二进制打开磁盘,一看便知。Linux也可以用dd命令拷贝出磁盘的内容到文件,再查看文件内容。比如dd if=/dev/sda of=/tmp/xxx bs=512 count=1什么的。

言归正传,开始我们的探索之旅吧。不过,我们不会深入到开机电路之类的硬件问题上去,毕竟,我们还是以一个程序员的角度来看待计算机。同时,我们假定读者能完全读懂并理解上面我提到的那篇阮一峰的博文并且能理解寻址空间,实模式、保护模式、端口独立编址和端口统一编址等相关术语名词。

我们从按下电源开始。

首先,是CPU Reset主板加电之后在电压尚未稳定之前,北桥控制芯片会向CPU发出重置信号Reset,此时CPU进行初始化。当电压稳定后,控制芯片会撤销Reset信号,CPU开始工作。我们要探讨的第一个问题就是CPU执行的第一条指令的位置。

现在网上流传的资料基本上是8086 CPU的资料,给出的说法一般是这样:

CS寄存器初始化为0xF000IP寄存器初始化为0xFFF0,所以按照CPU实模式地址计算法则,CPU执行的第一条指令地址是CS*10h+IP,即0xFFFF0这里。

8086 CPU确实如此,但我们的问题是,80386及其以上的CPU怎么处理呢?其计算地址法则还是如此吗?当然不是,否则我说这些废话做什么。如果读者之前对实模式和保护模式寻址以及地址计算的理念根深蒂固的话,那么请先暂时忘却以前的认知,因为我下文说的也许有些惊世骇俗(我指的是相对于国产的某些教材来说)。

第一点,80386及其以上的现代CPU(以下CPU说的都是指80386以上的)加电Reset之后并不是直接进入实模式

第二点,CPU在合成地址的时候并不区分实模式和保护模式。

Read More

这学期被自愿的选择了《Web应用程序设计》这门课,还是自学课。好吧,反正基础的HTML和CSS迟早也是要学习的,就提前学吧。

按照我的性子,当学习新的语言以及语法规则的时候,我就又开始折腾编辑器的语法高亮和配置自动补全功能了。其实接触到新的语言时,我个人建议还是不要急着去寻找相关的IDE去使用,还是先用基本的文本编辑器写,尝试自己手工去构建。等到理解了之后再使用IDE提高编码效率也不晚。IDE的方便是建立在对很多细节的屏蔽之上的,这样对学习新的知识没有益处。没有手写HTML的经验,全靠IDE点点按钮,拖拖控件的设计人员在调试的时候就会是一场噩梦。

另外,个人吐槽下网上到处可见的什么“真正的高手写代码只用记事本”。你确定是notepad?没有++?好吧,个人感觉用记事本写代码如果不是临时找不到替代品之外,除了装逼就再没有什么意义了。即便是不需要IDE的自动补全和错误检测,个人认为代码编辑器的语法高亮和格式调整还是很重要的。手工调整格式很麻烦,而语法高亮除了看起来赏心悦目还能指出来明显的拼写错误。方便的代码编辑器notapad++、Vim,Emacs等等是很好的选择。

废话少说,言归正传。我们今天给熟悉了HTML和CSS的程序员推荐一款文本编辑器的插件——Emmet。如果你没有听说过Emmet,那你至少听说过大名鼎鼎的Zen coding吧?Emmet就是Zen coding的新名字。什么?你没有听说过?太好了,你可以继续看下去了,否则,也就没有看的必要了……

简单介绍下Emmet,官方是这么说的“Emmet is a plugin for many popular text editors which greatly improves HTML & CSS workflow”。

官方主页在 http://www.emmet.io/

Emmet作为文本编辑器的插件提供给 Eclipse/Aptana,Sublime Text 2,TextMate 1.x,Coda 1.6 and 2.x 等等编辑器作为扩展。我们以我比较喜欢的编辑器 Sublime Text 为例介绍下安装与使用方法吧。(暂时先委屈下Vim,因为Vim的插件自动补全是“Ctrl+Y+逗号/分号”,这个快捷键很不好用,而我还没有找到修改的方法 我暂时在vimrc文件里加入 imap ; 映射到Ctrl+E,官方的重定义方法太麻烦了)

Read More