<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>阿当正传 &#187; 源代码</title>
	<atom:link href="http://www.adamjiang.com/archives/tag/%e6%ba%90%e4%bb%a3%e7%a0%81/feed" rel="self" type="application/rss+xml" />
	<link>http://www.adamjiang.com</link>
	<description>长脑袋的个人博客</description>
	<lastBuildDate>Tue, 15 May 2012 16:53:14 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.2</generator>
		<item>
		<title>以Funtoo为基础的Linux桌面系统（2）</title>
		<link>http://www.adamjiang.com/archives/1154</link>
		<comments>http://www.adamjiang.com/archives/1154#comments</comments>
		<pubDate>Tue, 15 May 2012 16:53:14 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux tips]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[源代码]]></category>
		<category><![CDATA[环境配置]]></category>
		<category><![CDATA[自由]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=1154</guid>
		<description><![CDATA[构建自己的Linux桌面系统的第一步，就是根据Funtoo网站上提供的wiki页面，编译，安装和配置基础系统（base system）。这包括 制作一个SysRescureCD的live USB。&#187;详情 通过SysRescureCD提供的工具初始化硬盘，并且在硬盘上安装stage3——也就是最基础的根文件系统。 配置这个最基础的系统。 利用该文件系统提供的基础工具编译安装Linux内核。 安装grub2作为bootloader。 如果你对类Unix系统的基本概念非常了解，这个过程看上去应该是水到渠成的。安装的细节虽然无需多言，Funtoo提供的指南已经十分详尽，但这里却有一些非常有趣的概念值得讨论。 第一，这个安装过程的全部魔法都在chroot命令上。我一直以为这个命令是类Unix操作系统设计的精华之一。为什么这么说呢？众所周知，Linux操作系统对它的跟文件系统有一个必要的假设。即，根文件系统是一棵树。而这个树的根就是“/”。像硬盘，SSD之类的块设备可以通过“挂载”（mount）的方式，将实体的存储设备挂载到这棵逻辑中存在的树的任意节点上。当系统在这棵树上创建新文件或者修改任何既有内容时，这种变更就会被反映到实际的存储设备上。树结构的妙处在于，任意一棵子树和拥有该子树的“大树”总是相似的。当你将准备好的硬盘分区挂载到这棵树上，并且chroot进入这个树的子树，你就的到了一个全新的“根文件系统”。但于此同时，系统运行的却是原来大树中预装的Linux内核，由此来帮助新文件系统的生成。这种方法在Windows系统中听起来有点像天方夜谭但是在Linux系统中，却是司空见惯。采用这种方法可也在多个不同的根文件系统之间无缝的切换，比如，你可以在一个64位系统的根文件系统的某一棵子树上创建一个32位系统子树。这对于很多系统测试和环境搭建都不无裨益。一个典型的例子就是debian系统采用chroot的方式帮助开发者创建一个便于创建软件包的标准环境，由此，.deb包才可以避免现有系统不同配置的影响而适用与各种不同配置的系统。 第二，这个安装过程实际上那个暗示了如何从无到有得建造一个操作系统。Linux操作系统的发端就是1990年Linux写给邮件列表的那封信。在已有系统软件——包括编译器，链接器的帮助下，生产内核代码才是可能的。用软件来制造软件。这是一个非常自然的过程。我记得有一句话叫“以机器制造机器标志这第二次工业革命的开始”。而以软件制造软件也是现在软件行业的一个重要标志。也只有当这种衍生和扩展的能力和半导体的几何级数式的蓬勃发展结合起来的时候，这种能力才创造了极大的社会财富。 第三，一些配置和编译的选择也值得讨论一下。 首当其冲的就是UEFI和GPT分区。区别与传统的BIOS和MBR分区，这两个技术都有一些进步。GPT几乎是必要的（desirable）。但是，如果你的系统主板是建立在UEFI之上的话，Linux系统可能会遇到一些问题。这并不是Linux的错，而是因为大多说UEFI固件都缺乏必要的测试问题百出，以至于根本无法保证系统兼容性。对于后面这一点我实在没有什么好的建议，大概能说的只有一条，选择UEFI主板的时候一定要先调查一下再做决定。 创建分区和创建文件系统的分离是让那些在Windows中模糊成一团的操作系统概念变得更加清晰了。对文件系统的选择又是另一个问题。一般来说根据用户的实际应用应该选择不同的文件系统，比如，一些文件在处理“小文件”的时候有明显优势，一些文件系统有灵活配置的特性等等。当然，ext4文件系统适用于大多数桌面应用。虽然这个指南中仅仅提供了传统的采用/dev/sd*方式记述的/etc/fstab，但实际上Funtoo也支持UUID作为标记在/etc/fstab中使用。 对很多中文用户来说，键盘布局不是问题。因为，大多数在中国销售的计算机实际上是直接使用英语键盘布局的，所以/etc/conf.d/keymaps中采用默认值没有什么问题。我的情况比较特殊，因为日语键盘布局与英文键盘布局存在相当大的差异。我必须在上面提到的文件中制定键盘布局为jp106。如果你想在运行时改变系统的键盘布局，可以使用下面的命令。 setxkbmap xx ## xx 是两个字母的国家代码，比如us 当编译安装好的内核和基础系统可以启动以后，我尝试继续跟随Funtoo的指南对基础系统升级并且安装了X Server和必要的系统软件。这样，一个极简洁的Linux操作系统诞生了。]]></description>
			<content:encoded><![CDATA[<p>构建自己的Linux桌面系统的第一步，就是根据Funtoo网站上提供的<a href="http://www.funtoo.org/wiki/Funtoo_Linux_Installation" title="Funtoo installation" target="_blank">wiki页面</a>，编译，安装和配置基础系统（base system）。这包括</p>
<ul>
<li>制作一个SysRescureCD的live USB。&raquo;<a href="http://www.sysresccd.org/Sysresccd-manual-en_How_to_install_SystemRescueCd_on_an_USB-stick" target="_blank">详情</a></li>
<li>通过SysRescureCD提供的工具<a href="http://www.funtoo.org/wiki/Funtoo_Linux_Installation#Prepare_Hard_Disk" target="_blank">初始化硬盘</a>，并且在硬盘上<a href="http://www.funtoo.org/wiki/Funtoo_Linux_Installation#Installing_the_Stage_3_tarball" target="_blank">安装stage3</a>——也就是最基础的根文件系统。</li>
<li><a href="http://www.funtoo.org/wiki/Funtoo_Linux_Installation#Configuring_your_system" target="_blank">配置</a>这个最基础的系统。</li>
<li>利用该文件系统提供的基础工具<a href="http://www.funtoo.org/wiki/Funtoo_Linux_Installation#Configuring_and_installing_the_Linux_kernel" target="_blank">编译安装Linux内核</a>。</li>
<li><a href="http://www.funtoo.org/wiki/Funtoo_Linux_Installation#Installing_a_Bootloader" target="_blank">安装grub2</a>作为bootloader。</li>
</ul>
<p>如果你对类Unix系统的基本概念非常了解，这个过程看上去应该是水到渠成的。安装的细节虽然无需多言，Funtoo提供的指南已经十分详尽，但这里却有一些非常有趣的概念值得讨论。</p>
<p>第一，这个安装过程的全部魔法都在chroot命令上。我一直以为这个命令是类Unix操作系统设计的精华之一。为什么这么说呢？众所周知，Linux操作系统对它的跟文件系统有一个必要的假设。即，<a href="http://en.wikipedia.org/wiki/Filesystem_Hierarchy_Standard" target="_blank">根文件系统是一棵树</a>。而这个树的根就是“/”。像硬盘，SSD之类的块设备可以通过“挂载”（mount）的方式，将实体的存储设备挂载到这棵逻辑中存在的树的任意节点上。当系统在这棵树上创建新文件或者修改任何既有内容时，这种变更就会被反映到实际的存储设备上。树结构的妙处在于，任意一棵子树和拥有该子树的“大树”总是相似的。当你将准备好的硬盘分区挂载到这棵树上，并且chroot进入这个树的子树，你就的到了一个全新的“根文件系统”。但于此同时，系统运行的却是原来大树中预装的Linux内核，由此来帮助新文件系统的生成。这种方法在Windows系统中听起来有点像天方夜谭但是在Linux系统中，却是司空见惯。采用这种方法可也在多个不同的根文件系统之间无缝的切换，比如，你可以在一个64位系统的根文件系统的某一棵子树上创建一个32位系统子树。这对于很多系统测试和环境搭建都不无裨益。一个典型的例子就是debian系统采用<a href="http://wiki.debian.org/Debootstrap" target="_blank">chroot的方式</a>帮助开发者创建一个便于创建软件包的标准环境，由此，<code>.deb</code>包才可以避免现有系统不同配置的影响而适用与各种不同配置的系统。</p>
<p>第二，这个安装过程实际上那个暗示了如何从无到有得建造一个操作系统。Linux操作系统的发端就是1990年Linux写给邮件列表的那封信。在已有系统软件——包括编译器，链接器的帮助下，生产内核代码才是可能的。用软件来制造软件。这是一个非常自然的过程。我记得有一句话叫“以机器制造机器标志这第二次工业革命的开始”。而以软件制造软件也是现在软件行业的一个重要标志。也只有当这种衍生和扩展的能力和半导体的几何级数式的蓬勃发展结合起来的时候，这种能力才创造了极大的社会财富。</p>
<p>第三，一些配置和编译的选择也值得讨论一下。</p>
<p>首当其冲的就是UEFI和GPT分区。区别与传统的BIOS和MBR分区，这两个技术都有一些进步。GPT几乎是必要的（desirable）。但是，如果你的系统主板是建立在UEFI之上的话，Linux系统可能会遇到一些问题。这并<a href="https://plus.google.com/102150693225130002912/posts/QLe3tSmtSM4" target="_blank">不是Linux的错</a>，而是因为大多说UEFI固件都缺乏必要的测试问题百出，以至于根本无法保证系统兼容性。对于后面这一点我实在没有什么好的建议，大概能说的只有一条，选择UEFI主板的时候一定要先调查一下再做决定。</p>
<p>创建分区和创建文件系统的分离是让那些在Windows中模糊成一团的操作系统概念变得更加清晰了。对文件系统的选择又是另一个问题。一般来说根据用户的实际应用应该选择不同的文件系统，比如，一些文件在处理“小文件”的时候有明显优势，一些文件系统有灵活配置的特性等等。当然，ext4文件系统适用于大多数桌面应用。虽然这个指南中仅仅提供了传统的采用<code>/dev/sd*</code>方式记述的<code>/etc/fstab</code>，但实际上Funtoo也支持<a href="http://www.cyberciti.biz/faq/linux-finding-using-uuids-to-update-fstab/" target="_blank">UUID作为标记</a>在<code>/etc/fstab</code>中使用。</p>
<p>对很多中文用户来说，键盘布局不是问题。因为，大多数在中国销售的计算机实际上是直接使用英语键盘布局的，所以<code>/etc/conf.d/keymaps</code>中采用默认值没有什么问题。我的情况比较特殊，因为日语键盘布局与英文键盘布局存在相当大的差异。我必须在上面提到的文件中制定键盘布局为<code>jp106</code>。如果你想在运行时改变系统的键盘布局，可以使用下面的命令。</p>
<pre>setxkbmap xx   ## xx 是两个字母的国家代码，比如us</pre>
<p>当编译安装好的内核和基础系统可以启动以后，我尝试继续跟随Funtoo的指南<a href="http://www.funtoo.org/wiki/Funtoo_Linux_First_Steps" target="_blank">对基础系统升级并且安装了X Server和必要的系统软件</a>。这样，一个极简洁的Linux操作系统诞生了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/1154/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>以Funtoo为基础的Linux桌面系统（1）</title>
		<link>http://www.adamjiang.com/archives/1140</link>
		<comments>http://www.adamjiang.com/archives/1140#comments</comments>
		<pubDate>Mon, 14 May 2012 15:46:09 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux tips]]></category>
		<category><![CDATA[Desktop]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[源代码]]></category>
		<category><![CDATA[环境配置]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=1140</guid>
		<description><![CDATA[这个系列的最初起源是一大堆牢骚。当我觉得自己拯救自己的时候，我问了自己这样的问题——如果Ubuntu是错的，那么什么样的桌面系统是对的？也许我尚未找到答案，但至少我觉得以下几点是一个优秀的Linux桌面系统必须具备的特性 源代码是开源软件的灵魂，优秀的包管理系统是强健的骨骼；这个系统必须无缝的升级并且尽可能的贴近源码。 桌面系统必须是轻量级的；它可以快速的启动和关闭。 使用什么样的应用程序应该由用户选择，而这些应用程序之间可以通过统一接口调用。 第一个要求将我引向了Gentoo。Gentoo Linux是以源代码为基础的发行版。它最重要的特色就是portage/emerge包管理系统。每一个Gentoo软件包全部都会在初次安装时从软件的源代码编译生成。每当软件推出新版本的时候，portage/emerge可以帮助用户重新编译并且安装新版本。同时，portage/emerge还可以根据用户的需要，安装同一个软件的多个不同版本。因为不需要二进制和打包的中间过程，这种升级就真正做到了“无缝”。因为系统组建之间之间无非就是动态链接和静态链接创建的简单耦合。所以，它也消除了以二进制文件为基础的包管理系统所创建的不必要的强耦合关系。这使得用户升级和调试单独软件包是获得了充分的自由。这种自由还体现在一种叫做Overlay的机制上。Overlay就是“覆盖”的意思。Gentoo通过这种机制让用户可以创建自己的软件包,并且将这些软件包按照一定的规则排列成树状结构来覆盖/定制官方默认的编译选项和软件特性。当然，用户也可以给自己的Overlay中添加新软件。很多用户在使用Gentoo的同时会创建自己的Overlay。我也将自己的对软件包的选择固化成了一个Overlay，你可以在这里找到它。Funtoo Linux是在Gentoo项目的基础上发展起来的新项目，它区别于Gentoo的最大特色在于以git为核心的软件包更新机制（虽然现在Gentoo也支持以git为基础的同步和升级）。除此以外，Funtoo还提供了比Gentoo更优秀的用户辅助工具——比如boot-update，所以我选择了Funtoo作为基础来建造自己的桌面系统。 对于第二个问题，我的答案是Openbox。为什么不是Gnome或者KDE这样的Desktop environment？因为Gnome项目已经“误入歧途”，至少这是我通过观察得到的结论。而KDE却也不能称作“轻量级”。那么只有在众多的Window Manager（窗口管理器）中选择一款了。我最初尝试了Fluxbox，它非常优秀，可是对于cjk用户并不友好，因为我的Firefox的窗口标题连不能显示中文或者日语这些双字节文字。于是理所当然的，我选择了Openbox。Openbox的开发相对活跃，并且拥有广泛的用户基础。很多发行版都提供了Openbox软件，更有一款非常引人注目的Crunchbang发行版将Openbox作为桌面系统解决方案的基础。这也正是我学习的对象。 最后一点是最贴近用户的问题。每个人对软件的偏好不一样，所以，桌面系统中使用什么样的软件应该由用户自己来决定。一个Web开发者的桌面系统和一个Android开发者的桌面系统应当有很大的区别。比如，前者可能需要curl，bluefish，python/PHP编辑器和多个不同的浏览器——采用wine或者虚拟机安装的IE，不同版本的Firefox和Chrome——等等；而后者，则需要编译Android软件的必要的库和工具，可以运行虚拟机的Java环境，eclipse集成开发环境等等。核心工具集以外的软件，在大多数情况下只能被称作“冗余”而别无他用。当Ubuntu一股脑塞给我OpenOffice套件和Evolution的时候，往往我在安装结束后做的第一件事情就是卸载掉这些冗余。另一个问题是应用程序之间的接口。比如，Windows的COM组件，还有Mac OS的Apple Script，它们都提供了一个完全或者不完全的统一接口来解决应用程序之间通信和“宏操作”的问题。Linux桌面也同样必须拥有这样的机制，否则可能连copy/paste操作也是问题。DBus就是解决这个问题的不二之选。]]></description>
			<content:encoded><![CDATA[<p>这个系列的最初起源是一大堆<a href="http://www.adamjiang.com/archives/1125" target="_blank">牢骚</a>。当我觉得自己拯救自己的时候，我问了自己这样的问题——如果Ubuntu是错的，那么什么样的桌面系统是对的？也许我尚未找到答案，但至少我觉得以下几点是一个优秀的Linux桌面系统必须具备的特性</p>
<ul>
<li>源代码是开源软件的灵魂，优秀的包管理系统是强健的骨骼；这个系统必须无缝的升级并且尽可能的贴近源码。</li>
<li>桌面系统必须是轻量级的；它可以快速的启动和关闭。</li>
<li>使用什么样的应用程序应该由用户选择，而这些应用程序之间可以通过统一接口调用。</li>
</ul>
<p>第一个要求将我引向了Gentoo。Gentoo Linux是以源代码为基础的发行版。它最重要的特色就是portage/emerge包管理系统。每一个Gentoo软件包全部都会在初次安装时从软件的源代码编译生成。每当软件推出新版本的时候，portage/emerge可以帮助用户重新编译并且安装新版本。同时，portage/emerge还可以根据用户的需要，安装同一个软件的多个不同版本。因为不需要二进制和打包的中间过程，这种升级就真正做到了“无缝”。因为系统组建之间之间无非就是动态链接和静态链接创建的简单耦合。所以，它也消除了以二进制文件为基础的包管理系统所创建的不必要的强耦合关系。这使得用户升级和调试单独软件包是获得了充分的自由。这种自由还体现在一种叫做Overlay的机制上。Overlay就是“覆盖”的意思。Gentoo通过这种机制让用户可以创建自己的软件包,并且将这些软件包按照一定的规则排列成树状结构来覆盖/定制官方默认的编译选项和软件特性。当然，用户也可以给自己的Overlay中添加新软件。很多用户在使用Gentoo的同时会创建自己的Overlay。我也将自己的对软件包的选择固化成了一个Overlay，你可以在<a href="https://github.com/jcadam/picky-overlay" target="_blank">这里</a>找到它。<a href="http://www.funtoo.org/wiki/Welcome" target="_blank">Funtoo Linux</a>是在Gentoo项目的基础上发展起来的新项目，它区别于Gentoo的最大特色在于以git为核心的软件包更新机制（虽然现在Gentoo也支持以git为基础的同步和升级）。除此以外，Funtoo还提供了比Gentoo更优秀的用户辅助工具——比如boot-update，所以我选择了Funtoo作为基础来建造自己的桌面系统。</p>
<p>对于第二个问题，我的答案是Openbox。为什么不是Gnome或者KDE这样的Desktop environment？因为Gnome项目已经“误入歧途”，至少这是我通过观察得到的结论。而KDE却也不能称作“轻量级”。那么只有在众多的Window Manager（窗口管理器）中选择一款了。我最初尝试了Fluxbox，它非常优秀，可是对于cjk用户并不友好，因为我的Firefox的窗口标题连不能显示中文或者日语这些双字节文字。于是理所当然的，我选择了Openbox。Openbox的开发相对活跃，并且拥有广泛的用户基础。很多发行版都提供了Openbox软件，更有一款非常引人注目的<a href="http://crunchbanglinux.org/" target="_blank">Crunchbang</a>发行版将Openbox作为桌面系统解决方案的基础。这也正是我学习的对象。</p>
<p>最后一点是最贴近用户的问题。每个人对软件的偏好不一样，所以，桌面系统中使用什么样的软件应该由用户自己来决定。一个Web开发者的桌面系统和一个Android开发者的桌面系统应当有很大的区别。比如，前者可能需要curl，bluefish，python/PHP编辑器和多个不同的浏览器——采用wine或者虚拟机安装的IE，不同版本的Firefox和Chrome——等等；而后者，则需要编译Android软件的必要的库和工具，可以运行虚拟机的Java环境，eclipse集成开发环境等等。核心工具集以外的软件，在大多数情况下只能被称作“冗余”而别无他用。当Ubuntu一股脑塞给我OpenOffice套件和Evolution的时候，往往我在安装结束后做的第一件事情就是卸载掉这些冗余。另一个问题是应用程序之间的接口。比如，Windows的COM组件，还有Mac OS的Apple Script，它们都提供了一个完全或者不完全的统一接口来解决应用程序之间通信和“宏操作”的问题。Linux桌面也同样必须拥有这样的机制，否则可能连copy/paste操作也是问题。DBus就是解决这个问题的不二之选。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/1140/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Windows的Copycat及其他</title>
		<link>http://www.adamjiang.com/archives/1125</link>
		<comments>http://www.adamjiang.com/archives/1125#comments</comments>
		<pubDate>Sun, 13 May 2012 14:45:27 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[技术漫谈]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[ubuntu]]></category>
		<category><![CDATA[windows]]></category>
		<category><![CDATA[源代码]]></category>
		<category><![CDATA[自由]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=1125</guid>
		<description><![CDATA[Ubuntu 12.04发布以后，这个Linux发行版就彻底地让我失望了。臃肿的身躯加上人机交互混沌的令人发指的Unity桌面，已经跟我需要的Linux桌面系统已经相去甚远。沙扬挪拉，Ubuntu。作为一个从6.04开始使用Ubuntu的用户，我不得不跟这个桌面系统说一声再见了。不是因为它在Xeon(R) CPU E31240 + Dual-channel 8GB内存的PC上居然启动的比Windows 7还慢，也不是因为它让我连应用程序菜单在哪里都找不到，只是因为它越来越像一个Windows的Copycat。 也许Ubuntu深受很多从Windows系统迁移过来的超级用户喜爱，但是它却已经失去了很多真正的Linux Users。Linux用户与Windows用户最根本区别的在于，前者确切地知道自己真正需要什么并且知道如何获得和实现他们自身的需要——因为开源软件提供了这种自由，而后者则恰恰相反。Windows从诞生以来就是救世主，它给你一切，让你毫不怀疑的享受这种安逸，让你无法主动地寻找自己的真正需要。由此，它越来臃肿越来越庞大越来越多的消耗更多的包括硬盘，内存和计算能力在内的系统资源。其实我并不单独厌恶Windows的这种给与和扩张，因为几乎所有的商业软件都具有极强的扩张性——比如很多字处理软件都包含拼写检查这个重复的功能，而我真正厌恶的是它对用户自由的侵害。为什么这么说？因为它们将用户假设为无法学习的群体，与Windows交互的最宏观的过程可以总结为“You pay, you go”。所有的软件问题都是需要钱才能解决，而且最根本的这些问题只能由拥有源代码的私有公司解决。这是最基础的假设。如果说，对于一个家庭主妇或者某位非计算机行业/专业人士，这大概没有什么问题。但是，在这种环境之中成长起来的程序员却会慢慢失去学习的意愿和能力。如果你想用“我要将目光集中到我自己的业务上去”这样的话来辩驳的时候，你已经错了，如果你是一位程序员的话。因为那些用来调查和调整系统错误的时间——所谓的“浪费”——事实上一种必备的能力。因为你需要知道计算机的一切！ “&#8230;&#8230;，我从来不把安逸和快乐看作是生活目的的本身——这种伦理基础我叫它猪栏理想。” ——爱因斯坦 对一个人来说，停止思考之日便是他的死亡之时。对程序员来说更是如此。闭源软件正在带着停止思考的你我渐渐死去。 Ubuntu有了软件商店，这是一件好事。它本来有机会开拓一个新的开源软件的开发模式。但不幸的是它却偏离的开放源代码软件的初衷。开源软件真正的意义在于“开放源代码”！而不是“免费”。假设，你买到Photoshop套件的同时可以获得它的源码，难道你不开心么，难道你不想要这样的软件么？我想有人会提到“盗版”。看上去会很复杂，但“盗版”和开源事实上不是同一个命题。闭源软件同样会被盗版。真正保护版权的是适当的法律和严格的执行。不要跟我提中国市场上猖獗的盗版，因为中国软件市场，软件人才事实上是被上述猪栏思想和猖獗的盗版所戕害的。 没有哪个商业操作系统或者Linux发行版能够提供给你那个你确切需要的软件平台。只有自己动手，对，就是自己动手，你才有可能重新控制并且掌握你的计算机，才能享受到开源软件提供给你的自由。Ubuntu 6.04推出的时候，它最成功的地方不是它的liveCD，而是那个有大约3000多行的HOWTO——一个简洁明快的Wikipage。它提供给用户一个运行良好的基础系统，并且提供了多达2万的可选软件包。它还写了一份翔实的文档帮助你定制自己的系统。这就是最初的Ubuntu。如果它要模范Windows，或者像Mac OS那样仅仅将开源软件作为架子想“给用户一切”，那它就完全错了。 一声看似潇洒的沙扬挪拉不会解决任何问题。作为一个Linux User我只有自己救自己。]]></description>
			<content:encoded><![CDATA[<p>Ubuntu 12.04发布以后，这个Linux发行版就彻底地让我失望了。臃肿的身躯加上人机交互混沌的令人发指的Unity桌面，已经跟我需要的Linux桌面系统已经相去甚远。沙扬挪拉，Ubuntu。作为一个从6.04开始使用Ubuntu的用户，我不得不跟这个桌面系统说一声再见了。不是因为它在Xeon(R) CPU E31240 + Dual-channel 8GB内存的PC上居然启动的比Windows 7还慢，也不是因为它让我连应用程序菜单在哪里都找不到，只是因为它越来越像一个Windows的Copycat。</p>
<p>也许Ubuntu深受很多从Windows系统迁移过来的超级用户喜爱，但是它却已经失去了很多真正的Linux Users。Linux用户与Windows用户最根本区别的在于，前者确切地知道自己真正需要什么并且知道如何获得和实现他们自身的需要——因为开源软件提供了这种自由，而后者则恰恰相反。Windows从诞生以来就是救世主，它给你一切，让你毫不怀疑的享受这种安逸，让你无法主动地寻找自己的真正需要。由此，它越来臃肿越来越庞大越来越多的消耗更多的包括硬盘，内存和计算能力在内的系统资源。其实我并不单独厌恶Windows的这种给与和扩张，因为几乎所有的商业软件都具有极强的扩张性——比如很多字处理软件都包含拼写检查这个重复的功能，而我真正厌恶的是它对用户自由的侵害。为什么这么说？因为它们将用户假设为无法学习的群体，与Windows交互的最宏观的过程可以总结为“You pay, you go”。所有的软件问题都是需要钱才能解决，而且最根本的这些问题只能由拥有源代码的私有公司解决。这是最基础的假设。如果说，对于一个家庭主妇或者某位非计算机行业/专业人士，这大概没有什么问题。但是，在这种环境之中成长起来的程序员却会慢慢失去学习的意愿和能力。如果你想用“我要将目光集中到我自己的业务上去”这样的话来辩驳的时候，你已经错了，如果你是一位程序员的话。因为那些用来调查和调整系统错误的时间——所谓的“浪费”——事实上一种必备的能力。因为你需要知道计算机的一切！</p>
<p style="background-color: #B8B8B8">
“&#8230;&#8230;，我从来不把安逸和快乐看作是生活目的的本身——这种伦理基础我叫它猪栏理想。” ——爱因斯坦
</p>
<p>对一个人来说，停止思考之日便是他的死亡之时。对程序员来说更是如此。闭源软件正在带着停止思考的你我渐渐死去。</p>
<p>Ubuntu有了软件商店，这是一件好事。它本来有机会开拓一个新的开源软件的开发模式。但不幸的是它却偏离的开放源代码软件的初衷。开源软件真正的意义在于“开放源代码”！而不是“免费”。假设，你买到Photoshop套件的同时可以获得它的源码，难道你不开心么，难道你不想要这样的软件么？我想有人会提到“盗版”。看上去会很复杂，但“盗版”和开源事实上不是同一个命题。闭源软件同样会被盗版。真正保护版权的是适当的法律和严格的执行。不要跟我提中国市场上猖獗的盗版，因为中国软件市场，软件人才事实上是被上述猪栏思想和猖獗的盗版所戕害的。</p>
<p>没有哪个商业操作系统或者Linux发行版能够提供给你那个你确切需要的软件平台。只有自己动手，对，就是自己动手，你才有可能重新控制并且掌握你的计算机，才能享受到开源软件提供给你的自由。Ubuntu 6.04推出的时候，它最成功的地方不是它的liveCD，而是那个有大约3000多行的HOWTO——一个简洁明快的Wikipage。它提供给用户一个运行良好的基础系统，并且提供了多达2万的可选软件包。它还写了一份翔实的文档帮助你定制自己的系统。这就是最初的Ubuntu。如果它要模范Windows，或者像Mac OS那样仅仅将开源软件作为架子想“给用户一切”，那它就完全错了。</p>
<p>一声看似潇洒的沙扬挪拉不会解决任何问题。作为一个Linux User我只有自己救自己。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/1125/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Linux内核源码阅读系列(8)-内核的构成 之 二</title>
		<link>http://www.adamjiang.com/archives/374</link>
		<comments>http://www.adamjiang.com/archives/374#comments</comments>
		<pubDate>Tue, 27 Jan 2009 08:08:04 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux kernel]]></category>
		<category><![CDATA[技术漫谈]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[源代码]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=374</guid>
		<description><![CDATA[进程管理 进程 进程是正在运行的程序的实体。它们是Linux用以完成各种应用程序的核心。”链接“系列文章中说明了一个应用程序如何从源代码变成可执行文件，以及如何将这个可执行文件加载进入内存从而使程序运行的过程。可执行的目标文件被加载后就行成了进程的基本组成部分。操作系统本身还会为每一个进程添加一些附加的信息用以对进程进行调度管理和创建/消灭等过程。另一方面，为了方便用户的使用，Linux向用户应用程序提供了一些系统调用帮助用户应用程序进行进程的管理。进程有自己的生命周期，它可以被创建，消灭，使其进入活动状态等等，这些状态之间可以通过系统调用或者进程调度的机制进行转换。下面的图展示了从交互式shell中启动&#8217;yes&#8217;这个应用程序的过程。 bash在使用fork()系统调用之后，只是在虚拟内存空间上复制一个和自己完全相同的拷贝，那么这时我们就可以得到两个bash，其中早先的那个bash进程通常被称作父进程，而其后被创建的那个进程被称作子进程；因为现在用户需要运行的是yes这个程序，其中原来的bash需要使用wait()进入等待状态以便腾出CPU占用时间来运行&#8217;yes&#8217;。在接下来的过程中，刚刚被创建的那个新的bash的副本，会使用exec()系统调用将yes这个应用程序的可执行文件映射并拷贝到内存中，通过这个方式，操作系统可以创建一个信的进程。yes这个程序比较特殊，他的作用事实上就是不断的输出&#8217;yes&#8217;这个字符串，但事实上任何应用程序都可能退出，比如我们这个时候按一下Ctrl-C，其后的动作是通过系统系统调用exit()结束进程。父进程bash在接收到子进程结束的消息后，可能又进入活动状态。 线程 进程管理部分的代码，着重可以看看进程的创建，消灭以及其他状态之间的转换是怎样实现的，此外，还有“线程”需要注意。线程在Linux中的实现和进程非常相似，可以说他是一种特殊的进程。线程的特殊之处在于多个线程之间共享相同的“进程空间”——这一点其实逻辑上很容易想清楚，就是多线程通常用来相应高并发的任务，而这些线程事实上完成的功能是一致的，他们之间不需要有区别。从“调度器”的角度来看，线程和进程是一致的。 信号 进程管理的另外一个重要内容是所谓的“信号”(signal)，它是一个简单的向进程传递非同期时间的功能。收到信号的进程可以选择通过指定的signal handler做一个动作，或者忽略这个信号，等等。收到信号的进程的行为于收到中断的内核非常相似。:)说了等于没说&#8230;&#8230; 内存管理 关于内存管理的内容的文章简直可以说在互联网上泛滥成灾。写文章的人从不同的角度对应该怎样管理内存的问题做了很多讨论。内存管理的策略也是多如牛毛，比如C++标准模板库中用free list实现的内存池等等。所以，看内存管理的话，很多大仙可以拍拍胸脯满怀信心的就把书翻过去了。但是，Linux作为一个经过实战考验的开源操作系统——事实上开源创造了更加安全可靠的操作系统，研究发现Linux2.6版本共5.7million行的源代码中，仅仅存在985个Bug；而如果将总代码量于工业界的平均水平相比，Linux中存在114,000～171,000个Bug都可以被评价为“质量不错”——它的内存管理实现可以被认为在很多地方都具有参考和借鉴的意义。就凭这一点，内存管理简直可以说是内核代码中最值得关注的部分。 Linux内存管理可以被分为两大块。第一是实内存管理，另外一个是虚拟内存的管理方法。 实内存管理 实际的内存分配策略往往区分大块内存划分和小块内存划分以提高内存分配算法的效率。Linux的实内存分配测律也不例外，她采用了以Page为单位的Buddy方法来划分大块内存区域的同时，对于小内存区块却采用了一个叫做Slab的小内存划分方法。这两者的实现都非常精巧，值得仔细研究。 虚拟内存管理 虚拟内存(Virtual Memory)技术可以说是现代计算机系统中非常重要的组成部分。它不但关系到硬件的设计，而且还关系到很多重要的软件技术的实现，比如以前文章中提到的共享库和动态链接技术。MMU(Memory Management Unit)是在计算机体系结构发展这中产生的，这个硬件组件是现代虚拟内存技术的硬件基础。Linux中采用了多重虚拟地址的虚拟内存空间，所以，它让操作系统本身获得了更加强大的能力。所以，虚拟内存管理的部分应该硬件结合软件一起来看才能看个通通透透。虚拟内存同时和内核的其他部分，比如，进程管理有很多关联的地方——比如前文提到的程序的内存映像的生成等，所以，在讨论进程的过程中也不能忘记虚拟内存。 这个部分的关键词有：Demand paging，Swapping, Page fault等等。]]></description>
			<content:encoded><![CDATA[<h3>进程管理</h3>
<h4>进程</h4>
<p><strong>进程</strong>是正在运行的程序的实体。它们是Linux用以完成各种应用程序的核心。”<a href="http://www.adamjiang.com/archives/347">链接</a>“系列文章中说明了一个应用程序如何从源代码变成可执行文件，以及如何将这个可执行文件加载进入内存从而使程序运行的过程。可执行的目标文件被加载后就行成了进程的基本组成部分。操作系统本身还会为每一个进程添加一些附加的信息用以对进程进行调度管理和创建/消灭等过程。另一方面，为了方便用户的使用，Linux向用户应用程序提供了一些系统调用帮助用户应用程序进行进程的管理。进程有自己的<em>生命周期</em>，它可以被创建，消灭，使其进入活动状态等等，这些状态之间可以通过系统调用或者进程调度的机制进行转换。下面的图展示了从交互式shell中启动&#8217;yes&#8217;这个应用程序的过程。</p>
<div class="wp-caption alignnone" style="width: 336px"><img title="从shell运行yes" src="http://farm4.static.flickr.com/3319/3230991228_1e4be053c4.jpg?v=0" alt="从shell运行yes" width="326" height="443" /><p class="wp-caption-text">从shell运行yes</p></div>
<p>bash在使用<code>fork()</code>系统调用之后，只是在虚拟内存空间上复制一个和自己完全相同的拷贝，那么这时我们就可以得到两个<em>bash</em>，其中早先的那个<em>bash</em>进程通常被称作<strong>父进程</strong>，而其后被创建的那个进程被称作<strong>子进程</strong>；因为现在用户需要运行的是<em>yes</em>这个程序，其中原来的bash需要使用<code>wait()</code>进入等待状态以便腾出CPU占用时间来运行&#8217;yes&#8217;。在接下来的过程中，刚刚被创建的那个新的<em>bash</em>的副本，会使用exec()系统调用将<em>yes</em>这个应用程序的可执行文件映射并拷贝到内存中，通过这个方式，操作系统可以创建一个信的进程。<em>yes</em>这个程序比较特殊，他的作用事实上就是不断的输出&#8217;yes&#8217;这个字符串，但事实上任何应用程序都可能退出，比如我们这个时候按一下<code>Ctrl-C</code>，其后的动作是通过系统系统调用<code>exit()</code>结束进程。父进程bash在接收到子进程结束的消息后，可能又进入活动状态。</p>
<h4>线程</h4>
<p>进程管理部分的代码，着重可以看看进程的创建，消灭以及其他状态之间的转换是怎样实现的，此外，还有“<strong>线程</strong>”需要注意。线程在Linux中的实现和进程非常相似，可以说他是一种特殊的进程。线程的特殊之处在于多个线程之间共享相同的“<strong>进程空间</strong>”——这一点其实逻辑上很容易想清楚，就是多线程通常用来相应高并发的任务，而这些线程事实上完成的功能是一致的，他们之间不需要有区别。从“调度器”的角度来看，线程和进程是一致的。</p>
<h4>信号</h4>
<p>进程管理的另外一个重要内容是所谓的“信号”(signal)，它是一个简单的向进程传递非同期时间的功能。收到信号的进程可以选择通过指定的signal handler做一个动作，或者忽略这个信号，等等。收到信号的进程的行为于收到中断的内核非常相似。:)说了等于没说&#8230;&#8230;</p>
<h3>内存管理</h3>
<p>关于内存管理的内容的文章简直可以说在互联网上泛滥成灾。写文章的人从不同的角度对应该怎样管理内存的问题做了很多讨论。内存管理的策略也是多如牛毛，比如C++标准模板库中用free list实现的内存池等等。所以，看内存管理的话，很多大仙可以拍拍胸脯满怀信心的就把书翻过去了。但是，Linux作为一个经过实战考验的开源操作系统——事实上开源创造了更加安全可靠的操作系统，研究发现<a href="http://www.wired.com/software/coolapps/news/2004/12/66022">Linux2.6版本共5.7million行的源代码中，仅仅存在985个Bug</a>；而如果将总代码量于工业界的平均水平相比，Linux中存在114,000～171,000个Bug都可以被评价为“质量不错”——它的内存管理实现可以被认为在很多地方都具有参考和借鉴的意义。就凭这一点，内存管理简直可以说是内核代码中最值得关注的部分。</p>
<p>Linux内存管理可以被分为两大块。第一是实内存管理，另外一个是虚拟内存的管理方法。</p>
<h4>实内存管理</h4>
<p>实际的内存分配策略往往区分大块内存划分和小块内存划分以提高内存分配算法的效率。Linux的实内存分配测律也不例外，她采用了以Page为单位的<strong>Buddy</strong>方法来划分大块内存区域的同时，对于小内存区块却采用了一个叫做<strong>Slab</strong>的小内存划分方法。这两者的实现都非常精巧，值得仔细研究。</p>
<h4>虚拟内存管理</h4>
<p><strong>虚拟内存</strong>(Virtual Memory)技术可以说是现代计算机系统中非常重要的组成部分。它不但关系到硬件的设计，而且还关系到很多重要的软件技术的实现，比如<a href="http://www.adamjiang.com/archives/347">以前文章</a>中提到的共享库和动态链接技术。<strong>MMU</strong>(Memory Management Unit)是在计算机体系结构发展这中产生的，这个硬件组件是现代虚拟内存技术的硬件基础。Linux中采用了多重虚拟地址的虚拟内存空间，所以，它让操作系统本身获得了更加强大的能力。所以，虚拟内存管理的部分应该硬件结合软件一起来看才能看个通通透透。虚拟内存同时和内核的其他部分，比如，进程管理有很多关联的地方——比如前文提到的程序的内存映像的生成等，所以，在讨论进程的过程中也不能忘记虚拟内存。</p>
<p>这个部分的关键词有：Demand paging，Swapping, Page fault等等。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/374/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux内核源码阅读系列(7)-链接4</title>
		<link>http://www.adamjiang.com/archives/347</link>
		<comments>http://www.adamjiang.com/archives/347#comments</comments>
		<pubDate>Fri, 23 Jan 2009 08:13:54 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux kernel]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[源代码]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=347</guid>
		<description><![CDATA[可执行目标文件 静态连接的目标输出是“可执行的目标文件”，它的基本形式当然也是ELF。只是，“可执行目标文件”与“可重定位目标文件”一些重要的区别。产生这种区别的原因在于这两种文件的目的不同。“可重定位目标文件”的目的是提供给链接器（静态链接的ld或者动态链接的ld.so）链接信息以便帮助可执行文件。而“可执行目标文件”的目的在于提供一种形式将程序的代码方便的加载到内存中以便执行。所以，“可执行的目标文件”中增加了一个叫做“段头表”(segment header table)的部分，这个表中描述了文件内容到主存的映射方法，在程序运行时，加载器(loader)将根据这个表的信息把“可执行目标文件”中的代码拷贝到主存中去。 内存布局 下面的图展示了一个典型的可执行目标文件的构成，以及在取其相应的内存“镜像”之间的简单关系（并不严格）。 +----------------------+ &#124; ELF header &#124; +----------------------+ +--------------------+ 0x00000000 &#124; segment header table &#124; &#124; NO USAGE &#124; +----------------------+ +--------------------+ 0x08048000 &#124; .init &#124; - &#62; &#124; read-only segment &#124; +----------------------+ &#124; &#124; &#124; .text &#124; - &#62; &#124; (size &#60;= 4kb*n) &#124; +----------------------+ &#124; &#124; &#124; .rodata &#124; - [...]]]></description>
			<content:encoded><![CDATA[<h3>可执行目标文件</h3>
<p>静态连接的目标输出是“可执行的目标文件”，它的基本形式当然也是ELF。只是，“可执行目标文件”与“可重定位目标文件”一些重要的区别。产生这种区别的原因在于这两种文件的目的不同。“可重定位目标文件”的目的是提供给链接器（静态链接的ld或者动态链接的ld.so）链接信息以便帮助可执行文件。而“可执行目标文件”的目的在于提供一种形式将程序的代码方便的加载到内存中以便执行。所以，“可执行的目标文件”中增加了一个叫做“段头表”(segment header table)的部分，这个表中描述了文件内容到主存的映射方法，在程序运行时，加载器(loader)将根据这个表的信息把“可执行目标文件”中的代码拷贝到主存中去。</p>
<h4>内存布局</h4>
<p>下面的图展示了一个典型的可执行目标文件的构成，以及在取其相应的内存“镜像”之间的简单关系（并不严格）。</p>
<pre class="source-code"> +----------------------+
 |    ELF header        |
 +----------------------+     +--------------------+ 0x00000000
 | segment header table |     |       NO USAGE     |
 +----------------------+     +--------------------+ 0x08048000
 |       .init          | - &gt; |  read-only segment |
 +----------------------+     |                    |
 |       .text          | - &gt; |  (size &lt;= 4kb*n)   |
 +----------------------+     |                    |
 |       .rodata        | - &gt; |  ......            |
 +----------------------+     +--------------------+
 |       .data          | - &gt; | read-write segment |
 +----------------------+     |  (size &lt;= 4kb*m)   |
 |        .bss          | - &gt; |  .....             |
 x----------------------x     +--------------------+
 /        .symtab       /     |       heap         |
 x----------------------x     +--------------------+
 /      .rel.text       /     |        ↓           |
 x----------------------x     |                    |
 /      .rel.data       /     +--------------------+ 0x40000000
 x----------------------x     |   shared library   |
 /       .debug         /     | memory map area    |
 x----------------------x     +--------------------+
 /       .line          /     |        ↓           |
 x----------------------x     |        ↑           |
 /       .strtab        /     +--------------------+
 x----------------------x     |      stack         |
 / section header table /     +--------------------+ 0xbfffffff
 x----------------------x     |      kernel        |
                              +--------------------+</pre>
<p>在IA32结构上，Linux采用<strong>虚拟内存</strong>(virtual memory)技术，所以，每个程序在内存布局的时候都好像已经拿到了所有的内存一样，而程序代码最开始的地方总是在虚拟地址<code>0x08048000</code>处。加载器从这里开始拷贝ELF中定义的只读代码，这些只读代码通常被包含在<code>.init</code><code>.text</code><code>.rodate</code>段；所谓段和节事实上同样的，只是在链接的时候，它被称为“节”，而加载时却被成为“段”。<code>.init</code>段是链接器给每个“可执行目标文件”添加的，在其中包含了程序的初始化代码的一部分；链接器在其中写入了一个叫做<code>_init</code>的函数，这个细节需要注意。只读代码要求4kb<code>对齐</code>，所以虽然它实际的大小为往往小于4kb*n，但是其后紧跟的读/写段却需要从4kb*n的虚拟内存处开始。同样，读/写段也需要4kb对齐（想想why?）。读写段之后紧跟的是堆(heap)的内存区域，众所周知，这个区域是为了malloc函数群动态分配的内存准备的；此外，这个区域将根据需要向上（向高地址区域）增长。ELF文件中的<code>.symtab</code><code>.debug</code>等内容并不会被加载进入内存，上图中用斜线表示了。操作系统还会为这个程序在内存的<code>0x40000000</code>处准备了动态加载的共享库代码区域；<code>0xbfffffff</code>处准备了另一个重要的运行时数据结构“栈”，这个区域主要用于程序中的过程调用，并且区域大小向下增长。用于应用程序的空间到这里结束了，紧跟在运行时栈的栈底后面的区域，也就是从<code>0xc0000000</code>开始就是内核代码了。</p>
<p>加载器(loader)通常是shell呼出的，但是任何应用程序都可以通过系统调用<code>execve()</code>调用加载器。</p>
<p>内存访问越界的时候通常你会被警告很奇怪的消息”segment fault”，并且程序终了。相信这个会帮助你，让你对“段”的记忆更加深刻些了，哈哈哈。这是一句很笼统的提示，但他说的就是你的指针在乱跳，可能对只读内存区域进行了写操作。</p>
<h4>启动代码</h4>
<p>加载器运行时，首先构造一个上面提到的那样的内存映像，然后根据“段头表”的指引，将程序代码拷贝到内存中。接着，他会跳转到程序的入口开始执行程序。提到c语言的程序入口，那可不就是大名鼎鼎的<code>main</code>函数么？这个说法没错，但是也不全对。真正的程序入口是一个叫做<code>_start</code>的函数，这个函数被包含在<code>crtl.o</code>文件中。这个目标文件是C语言运行时环境的一部分。它的大致的示意代码如下所示：</p>
<pre class="source-code">
0x080480c0 &lt;_start&gt;        /*  .text段的入口点                 */
  call _libc_init_first     /* 启动.text节的代码通常是初始化c的库  */
  call _init               /* 启动_init代码，也就是在_init段中   */
  call atexit              /* 注册一些在程序结束时需要作的动作     */
  call main                /* 应用程序的入口点                  */
  call _exit               /* 结束应用程序，将控制权返还给操作系统  */</pre>
<p>很明显，c语言的main函数是约定好的，如果没有这个函数程序将不能被执行。关于<code>ctrl.o</code>这个事情，让我想起面试国内某家公司的时候，曾被面试官问到这个问题；他问c程序在调用main之前需要做哪些动作，当时刚刚毕业，我的回答是现编的&#8230;&#8230;，当然是错的很离谱，恩，往事不堪回首。如果你经常看到编译时或者运行时提示找不到<code>crtl.o</code>文件，恭喜你，你可以记住它了。嘿嘿。</p>
<h3>动态链接和PIC代码</h3>
<p>顾名思义，动态链接就是将链接过程从编译时挪到了运行时。这个内容写起来会有如“懒婆娘的裹脚”，各位看官可以参照<a title="shared library on IBM developerWorks" href="http://www.ibm.com/developerworks/search/searchResults.jsp?searchType=1&amp;searchSite=dW&amp;searchScope=linuxZ&amp;query=shared+library&amp;Search=Search">IBM developerWorks的文章</a>。但是动态链接对于立志做个好程序员的有痔青年是非常重要的内容，所以，如果有时间还是要认真研究的。简单的过程应该是像这样的</p>
<h4>生成.so</h4>
<p><code><br />
gcc -shared -fPIC -o libvector.so x.c y.c z.c<br />
</code><br />
这个过程就是将源代码文件编译成为目标文件，然后在用PIC指定它进行特殊的链接重定位定位信息，其中比较重要的就是添加<strong>PLT</strong>(procedure linkage table)和<strong>GOT</strong>(global offset table)。PLT被添加到<code>.text</code>节，而GOT被添加到<code>.data</code>节。PIC代码有个缺陷，就是因为<a href="http://www.iecc.com/linker/linkerfig10-01.html">对GOT的存储器引用</a>造成的，具有大量寄存器堆的机器上没有太大问题，但是，寄存器不足的机器上却会造成严重缺陷。比如，<a href="http://sourceware.org/ml/binutils/2008-07/msg00314.html">MIPS结构的GOT问题</a>就由来已久。对于外部过程的调用，PIC代码中采用一种叫做“延迟绑定”(lazy binding)技术，这个也是需要好好学习一下的。</p>
<h4>链接共享库</h4>
<p><code><br />
gcc -o prog main.c libvector.so<br />
</code><br />
这个链接过程不像静态链接过程，链接器并不真正的拷贝共享库中的<code>.text</code>和<code>.data</code>节到可执行文件之中。相反，链接器会拷贝一些重定位和符号表信息，以便运行时可以解析对共享库代码和数据的引用。如你说知道的，这个过程就是在那个著名的$LD_LIBRARY_PATH之中去寻找共享库文件。</p>
<h4>动态加载动态链接共享库</h4>
<p><code><br />
gcc -rdynamic -o prog main.c -ldl<br />
</code><br />
通过<code>dlopen()</code>等函数可以在程序中动态地加载和链接共享库，在编译该程序时，只要连接libdl就可以了。而运行时，这个动态的加载和链接过程需要在被称为动态链接器的<code>ld.so</code>帮助下完成。这种方法特别灵活，因此被在各种各样的系统中广泛得应用，比如Java中的JNI(Java Native Interface)，通过它可以让Java程序调用本地的C或者C++函数库。</p>
<p>这篇文章的目的在于说明一个程序是怎样形成，怎样加载，最终怎样在Linux中执行的，目的已经达到了，我就不再罗嗦了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/347/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux内核源码阅读系列(6)-链接3</title>
		<link>http://www.adamjiang.com/archives/323</link>
		<comments>http://www.adamjiang.com/archives/323#comments</comments>
		<pubDate>Tue, 20 Jan 2009 05:06:57 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux kernel]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[源代码]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=323</guid>
		<description><![CDATA[上一篇举例的时候那个例子并不是很恰当，因为用局部变量的生存周期来解释的话，也是行的通的。那个例子只能说是又添加了一种新的解释而已。要找一个比较妖的还挺难，就直接抄书了： /* foo5.c */ #include &#60;stdio.h&#62; void f(void); int x = 15213; int y = 15212; int main() { f(); printf("x = 0x%x y = 0x%x \n", x, y); return 0; } /* bar5.c */ double x; void f() { x = -0.0; ^^^---链接器在处理这个符号x的时候，选择了，foo5.c文件中定义的 “强符号”int型的x，也就是解释为foo5.c中的x的内存位置写入 在这里定义的double型的值。 } 在IA32/Linux机器上，double型是8个字节，而int型是4字节；因此，这里将用double型的”-0.0&#8243;覆盖foo5.c中的x和y的内存位置，于是理所当然的程序出了一个意想不到的意外，而且这类错误是不容易被发现。 静态链接库 静态链接库就是把一堆相关的.o文件使用ar工具打包。最著名的静态连接库恐怕就是libc.a了。这是C语言标准库的静态链接版本。程序跟静态连接库链接的时候一般采用如下形式的命令： $ gcc -O2 -c main.c [...]]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.adamjiang.com/archives/308">上一篇</a>举例的时候那个例子并不是很恰当，因为用局部变量的生存周期来解释的话，也是行的通的。那个例子只能说是又添加了一种新的解释而已。要找一个比较妖的还挺难，就直接抄书了：</p>
<pre class="source-code">/* foo5.c */
#include &lt;stdio.h&gt;
void f(void);

int x = 15213;
int y = 15212;

int main()
{
	f();
	printf("x = 0x%x y = 0x%x \n",
		x, y);
	return 0;
}</pre>
<pre class="source-code">/* bar5.c */
double x;

void f()
{
	x = -0.0;
	^^^---链接器在处理这个符号x的时候，选择了，foo5.c文件中定义的
	      “强符号”int型的x，也就是解释为foo5.c中的x的内存位置写入
	      在这里定义的double型的值。
}</pre>
<p>在IA32/Linux机器上，double型是8个字节，而int型是4字节；因此，这里将用double型的”-0.0&#8243;覆盖foo5.c中的x和y的内存位置，于是理所当然的程序出了一个意想不到的意外，而且这类错误是不容易被发现。</p>
<h3>静态链接库</h3>
<p>静态链接库就是把一堆相关的<code>.o</code>文件使用<code>ar</code>工具打包。最著名的静态连接库恐怕就是<code>libc.a</code>了。这是C语言标准库的静态链接版本。程序跟静态连接库链接的时候一般采用如下形式的命令：</p>
<pre class="source-code">$ gcc -O2 -c main.c
$ gcc -static -o swap_sample main.o libswap.a</pre>
<p>程序在跟静态库链接的时候，首先链接器会按照命令行输入的从前往后的方向对可重定位文件进行符号解析，找出在模块内部未定义的符号，并将在其后找到包含这个符号定义的那个模块的代码和数据拷贝进入将生成的<strong>可执行目标文件</strong>，并对其中的符号进行<strong>重定位</strong>，如果这些未定义的符号全部解决，则链接成功并输出可执行文件，否则链接器会报错。</p>
<h4>重定位</h4>
<p>重定位就是确定一个对象（包括代码和数据）在存储器中的位置的过程。关于每个需要重定位的符号，链接器有两个方面事情要做，1. 对模块中的符号的<strong>定义</strong>(definition)进行定位，这个工作主要是合并各个输入模块的代码和数据节，并给每个节和每个符号定义赋以新的存储器地址； 2. 将模块中的<strong>引用</strong>(reference)指向正确的符号定义位置，这个工作主要依靠“重定位表目”完成，也就是<a href="http://www.adamjiang.com/archives/308">上一篇</a>中提到的实例中的<code>rel.text</code>和<code>rel.data</code>节的总览中提到的”<strong>R_386_PC32</strong>“和”<strong>R_386_32</strong>“等附带有重定位类型的表目。</p>
<p>重定位表目可以用下面的包括下面代码展示的内容：</p>
<pre class="source-code">typedef struct {
	int offset;	/* 需要被重定位的“引用”在所在节中的偏移量 */
	int symbol:24,	/* 这个引用应该指向的符号 */
	    type:8;	/* 重定位类型 */
}</pre>
<p>最重要的两类重定位类型就是”R_386_PC32&#8243;和”R_386_32&#8243;。</p>
<blockquote><p>R_386_PC32: 这个类型的重定位信息主要控制的是程序在执行是的跳转。重定位一个使用32位PC(program counter)相关的地址引用。当CPU执行使用PC相关寻址的指令时，它就将在代码中编码的32位值加上PC当前运行时的值，得到有效地址，而PC值通常默认是存储器中的下一条指令的地址。</p></blockquote>
<blockquote><p>R_386_32：重定位一个使用32位绝对地址的引用。通过绝对寻址，CPU直接使用在指令中编码的32值作为有效地址。————《深入理解计算机系统》</p></blockquote>
<p>重定位符号应用的算法伪代码如下：</p>
<pre class="source-code">foreach section s {
	foreach relocation entry r {
		refptr = s + r.offset; /* 指向需要被重定位的引用的指针 */

	/* relocate a PC-relative reference */
	if (r.type = R_386_PC32) {
		refaddr = ADDR(s) + r.offset; /* 引用的运行时地址 */
		*refptr = (unsigned) ((ADDR(r.symbol) + *refptr - refaddr);
	}

	/* relocate an absolute reference */
	if (r.type == R_386_32)
		*refptr = (unsigned) (ADDR(r.symbol) + *refptr);
	}
}</pre>
<h4>R_386_PC32</h4>
<p>在没有跳转的情况下，众所周知程序是按照从上到下的顺序顺序执行的，而这个事实在机器语言级别的直接反应就是PC的值默认情况下都会指向（经过call指令的计算后）当前执行指令的邻近下一条指令的地址。IA32结构中，一条指令的大小是4字节，所以，call指令的默认参数总是”-4&#8243;(0xfffffc)，以便操作数于PC值相加时，跳转到临近的下一条指令。也就是说上面伪代码中的refptr在PC相关的地址引用中，初始值是”-4&#8243;。那么，如果程序发生非顺序执行的跳转，其重点因素就是要给call等类似的指令一个正确的操作数。这个操作数与PC中的值进行计算之后可以跳转到相应的对象（代码）保证程序的正确执行。”R_386_PC32&#8243;这种类型的重定位过程就是给call或者类似指令计算一个正确的操作数的过程。上面展示的伪代码中的refptr就是这个操作数。因为在给符号定义(definition)定位的过程中，ADDR(r.symbol)是确定的，所以，refptr就是可以计算的。</p>
<h4>R_386_32</h4>
<p>这种情况就简单些，计算方法只是将可重定位引用所在节的首地址和其偏移量相加，这样就能确定符号在虚存中的位置。</p>
<p>未完待续。</p>
<p>&#8212;&#8212;写完后偷偷修改的分割线&#8212;&#8212;-<br />
这两天些的东西非常tmd的艰深难懂，但是硬骨头还是要啃的。市面上有很多SourceReview的书，但是读完之后总是觉得只见树木不见森林。我想要一个从上到下看到通通投投的Linux内核“解析体验”，哈哈哈。理论是比较枯燥，细节是比较烦人，但是所有奇妙的计算效果就是用这些东西为基础的，没有办法。很多时候也许真的需要不求甚解，但是，我是个偏执狂，如果遇到自己感兴趣却没有弄通的东西总觉得如鲠在喉。最终结果是写这样的文章难为自己，看这个样的文章吓走朋友，哈哈哈</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/323/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux内核源码阅读系列(5)-链接2</title>
		<link>http://www.adamjiang.com/archives/308</link>
		<comments>http://www.adamjiang.com/archives/308#comments</comments>
		<pubDate>Mon, 19 Jan 2009 10:15:56 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux kernel]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[源代码]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=308</guid>
		<description><![CDATA[可重定位目标文件实例解析 上回书说到ELF的文件格式，这里看一个真实的例子：用readelf工具窥看一下上篇提到的main.c编译而成的main.o文件。 $ gcc -O2 -g -c main.c -o main.o $ file swap.o swap.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped file命令的结果显示，main.o文件是一个386结构上的“可重定位目标文件”，并且包含调试信息。这个我使用的编译命令是相应的。用readelf工具读出main.o的细节看一下，将是下面这个样子。重要的部分直接插入了注解。 ELF Header: Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00 Class: ELF32 Data: 2's complement, little endian ^^^---小端 Version: 1 [...]]]></description>
			<content:encoded><![CDATA[<h3>可重定位目标文件实例解析</h3>
<p><a href="http://www.adamjiang.com/archives/298">上回书</a>说到ELF的文件格式，这里看一个真实的例子：用readelf工具窥看一下上篇提到的<code>main.c</code>编译而成的<code>main.o</code>文件。</p>
<pre class="source-code">$ gcc -O2 -g -c main.c -o main.o
$ file swap.o
swap.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped</pre>
<p>file命令的结果显示，<code>main.o</code>文件是一个386结构上的“可重定位目标文件”，并且包含调试信息。这个我使用的编译命令是相应的。用readelf工具读出<code>main.o</code>的细节看一下，将是下面这个样子。重要的部分直接插入了注解。</p>
<pre class="source-code">ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
                                                      ^^^---<strong>小端</strong>
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              REL (Relocatable file)
                                     ^^^---ObjectFile的类型，上篇提到的3种之一
  Machine:                           Intel 80386
  Version:                           0x1
  Entry point address:               0x0
  Start of program headers:          0 (bytes into file)
  Start of section headers:          900 (bytes into file)
                                     ^^^---这里指明了“节头表”的位置
  Flags:                             0x0
  Size of this header:               52 (bytes)
                                     ^^^---ELF头的大小
  Size of program headers:           0 (bytes)
  Number of program headers:         0
  Size of section headers:           40 (bytes)
                                     ^^^---这里指明了“节头表”的大小
  Number of section headers:         23
  Section header string table index: 20</pre>
<p>上面这个就是ELF的头部信息，头部信息主要提供了ELF文件适用的体系结构以及文件各个部分的定位信息，比如“节头表”的位置/大小等。当然还包括“节头表”中记录的数量。</p>
<pre class="source-code">Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .text             PROGBITS        00000000 000040 000021 00  AX  0   0 16
  [ 2] .rel.text         REL             00000000 000854 000008 08     21   1  4
  [ 3] .data             PROGBITS        00000000 000064 000008 00  WA  0   0  4
  [ 4] .bss              NOBITS          00000000 00006c 000000 00  WA  0   0  4
       ^^^---未初始化的全局变量将存放在此，但仔细看他的"Size"就知道，这个节是空的；
             事实上它通常就只是一个占位符。.bss这个名字来自于IBM 704汇编语言中的
             Block Storage Start指令的首字母缩写，鉴于它只是占位符号，可以记作
             Best Save Space。（来自于《深入理解计算机系统》）恩，随你怎么叫它。

  [ 5] .debug_abbrev     PROGBITS        00000000 00006c 000060 00      0   0  1
  [ 6] .debug_info       PROGBITS        00000000 0000cc 00006a 00      0   0  1
  [ 7] .rel.debug_info   REL             00000000 00085c 000060 08     21   6  4
  [ 8] .debug_line       PROGBITS        00000000 000136 000037 00      0   0  1
  [ 9] .rel.debug_line   REL             00000000 0008bc 000008 08     21   8  4
  [10] .debug_frame      PROGBITS        00000000 000170 000050 00      0   0  4
  [11] .rel.debug_frame  REL             00000000 0008c4 000010 08     21  10  4
  [12] .debug_loc        PROGBITS        00000000 0001c0 000043 00      0   0  1
  [13] .debug_pubnames   PROGBITS        00000000 000203 000023 00      0   0  1
  [14] .rel.debug_pubnam REL             00000000 0008d4 000008 08     21  13  4
  [15] .debug_aranges    PROGBITS        00000000 000226 000020 00      0   0  1
  [16] .rel.debug_arange REL             00000000 0008dc 000010 08     21  15  4
  [17] .debug_str        PROGBITS        00000000 000246 00004c 01  MS  0   0  1
  [18] .comment          PROGBITS        00000000 000292 00002a 00      0   0  1
  [19] .note.GNU-stack   PROGBITS        00000000 0002bc 000000 00      0   0  1
       ^^^---上面是一堆调试用的二进制内容。忽略之没有什么大碍。

  [20] .shstrtab         STRTAB          00000000 0002bc 0000c5 00      0   0  1
  [21] .symtab           SYMTAB          00000000 00071c 000120 10     22  15  4
       ^^^---<strong>符号表</strong>。这位神仙是链接过程处理的重点。

  [22] .strtab           STRTAB          00000000 00083c 000016 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

There are no section groups in this file.

There are no program headers in this file.
^^^----“可重定位目标文件”并不包含program header table，这个表用于将目标文件
       映射到虚拟存储器，后文详述。</pre>
<pre class="source-code">Relocation section '.rel.text' at offset 0x854 contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000012  00001002 R_386_PC32        00000000   swap
                   ^^^---符号的重定位类型，一共有11种，现在遇到的这个是最重要的两种之一的
                         “与PC相关的地址引用”

Relocation section '.rel.debug_info' at offset 0x85c contains 12 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000006  00000501 R_386_32          00000000   .debug_abbrev
                   ^^^---符号的重定位类型，最重要之二，“32位地址引用”

0000000c  00000c01 R_386_32          00000000   .debug_str
00000011  00000c01 R_386_32          00000000   .debug_str
00000015  00000c01 R_386_32          00000000   .debug_str
00000019  00000201 R_386_32          00000000   .text
0000001d  00000201 R_386_32          00000000   .text
00000021  00000701 R_386_32          00000000   .debug_line
00000027  00000c01 R_386_32          00000000   .debug_str
00000031  00000201 R_386_32          00000000   .text
00000035  00000201 R_386_32          00000000   .text
00000039  00000901 R_386_32          00000000   .debug_loc
00000065  00001101 R_386_32          00000000   buf

Relocation section '.rel.debug_line' at offset 0x8bc contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
0000002a  00000201 R_386_32          00000000   .text

Relocation section '.rel.debug_frame' at offset 0x8c4 contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000018  00000801 R_386_32          00000000   .debug_frame
0000001c  00000201 R_386_32          00000000   .text

Relocation section '.rel.debug_pubnames' at offset 0x8d4 contains 1 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000006  00000601 R_386_32          00000000   .debug_info

Relocation section '.rel.debug_aranges' at offset 0x8dc contains 2 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000006  00000601 R_386_32          00000000   .debug_info
00000010  00000201 R_386_32          00000000   .text

There are no unwind sections in this file.</pre>
<p>这一段描述了需要重定位的符号或者代码总览。其中每一个记录都描述了需要重定位的对象存在于哪一节，以及它相对于节开始位置的偏移量。</p>
<pre class="source-code">Symbol table '.symtab' contains 18 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1
     3: 00000000     0 SECTION LOCAL  DEFAULT    3
     4: 00000000     0 SECTION LOCAL  DEFAULT    4
     5: 00000000     0 SECTION LOCAL  DEFAULT    5
     6: 00000000     0 SECTION LOCAL  DEFAULT    6
     7: 00000000     0 SECTION LOCAL  DEFAULT    8
     8: 00000000     0 SECTION LOCAL  DEFAULT   10
     9: 00000000     0 SECTION LOCAL  DEFAULT   12
    10: 00000000     0 SECTION LOCAL  DEFAULT   13
    11: 00000000     0 SECTION LOCAL  DEFAULT   15
    12: 00000000     0 SECTION LOCAL  DEFAULT   17
    13: 00000000     0 SECTION LOCAL  DEFAULT   19
    14: 00000000     0 SECTION LOCAL  DEFAULT   18
    ^^^---符号表的前14项无须特别关心，因为他们都是编译器自己加的默认值或者调试信息。

    15: 00000000    33 FUNC    GLOBAL DEFAULT    1 main
    16: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND swap
    17: 00000000     8 OBJECT  GLOBAL DEFAULT    3 buf
    ^^^---在main.o文件中真正存在的符号。其中，第一列描述了符号的地址相对于“节”开始的位置的偏移量。
          第二列说明的是这个对象的大小，第三列是对象类型，Ndx指明了对象存在在哪个节中，其中"1"是
          .text节，"3"是.data节。如果是"UND"，它指的是没有在这个ObjectFile中定义的符号，是需
          要重定位的对象。

No version information found in this file.</pre>
<p>谢谢各位看官的耐心，这玩意看一遍赶紧忘了吧，如果记住就是把大脑当硬盘用了。之所以细节到这个地步，只为了说明一个问题“万事万物都是有原因的”，只要仔细一点都能弄清楚事情到底怎么了，而这种仔细和耐心正是现在国内很多程序员缺少的。我参加过的与中国工程师打交道的软件项目大都让我感慨万千，中国人做事的态度和日本人做事的态度简直没有办法相提并论，不知道什么原因让很多程序员都浮躁的烧到不行&#8230;&#8230;最近参加了一些中文的邮件列表，发现这个风气尤为盛行，难道真的是民族性格问题？话又说回来，同样的工作，日本工程师拿着不止2倍于中国工程师的工资，好歹做事情的态度也应该对得起这份钱。</p>
<h3>符号和符号表</h3>
<p>上面提到的“符号”，就是“<strong>链接</strong>”阶段需要解决的重点问题的作业对象。链接需要解决被链接在一起的各个模块之间的符号和代码之间的联系，并重定位这些信息，以便生成的机器代码能够正确的跳转，或者正确的引用到某个变量的值。从链接器的角度看，符号可以分为三类 1.在本模块中定义，被其他模块引用的符号，这可能包括非静态(static)函数和非静态变量； 2.在其他模块中定义，本模块引用的符号，这种符号被称为外部符号(external symbol)； 3.在本模块中定义并只在本模块中引用的符号，这种符号成为本地符号(local symbol)，可能主要包含static函数和static全局变量。值得一提的是，本地符号并不等于程序的本地变量，因为众所周知，本地程序变量是在<strong>运行时栈</strong>中管理的，他们的生存周期很短，通过pop和push就能瞬间产生和消灭，无需符号表管理。</p>
<h3>链接器的符号解析规则</h3>
<p>本地符号的定义在链接过程中不会有大问题，每个本地符号都有唯一的定义；包括Java或者C++中的重载函数等，编译器会运用规则位有同名不同参数的函数各自产生一个唯一的“内藏”函数名。</p>
<p>全局符号比较麻烦，首先是要按照强弱分类，1.函数，已经初始化的全局变量是“<strong>强符号</strong>”；2.未初始化的全局变量是“<strong>弱符号</strong>”。然后是取舍规则，1.同名两强必出错，链接器报错； 2.同名强弱，肯定是选择强者； 3.同名两弱就随便取一个。注意啦，如果这个时候你还没有意识到明明规则存在的重要性的话，真是后知后觉了。此外，还有一个问题，就是为什么链接大批动态链接库时会有莫名其妙的错误？原因就是这些“潜规则”导致的错误了。比如，下面的例子：</p>
<p>文中有注释，c&amp;p注意。</p>
<pre class="source-code">
/* foo2.c */

void bar(void);

int x = 12345;
        ^^^---已经初始化的全局变量，“强”符号

int main()
{
	bar();
	printf("x = %d\n", x);
	return 0;
}</pre>
<pre class="source-code">/* bar2.c */
int x;
    ^^^---未初始化的全局变量，“弱”符号

void bar()
{
	x = 54321;
}</pre>
<p>这个程序被链接的并运行的话，其结果让程序员大跌眼睛的，x的输出居然还是12345。而实际现实中的问题比这个不知道要复杂多少倍，往往非常难于发现和排除，所以好的命名习惯真的是在体现一个程序员和一个软件开发团队的素养，而不是为了符合CodeStyle做的面子工程。如果你使用gcc作编译器的话，开启-warn-common选项，将帮助你查找重定义的错误。微软的编译器肯定也有这个选项，但是我有n多年都没给Windows写过程序了，实在是不知道。</p>
<p>未完待续。<br />
&#8212;-<br />
文中例子来自《深入理解计算机系统》</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/308/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Linux内核源码阅读系列(4)-链接1</title>
		<link>http://www.adamjiang.com/archives/298</link>
		<comments>http://www.adamjiang.com/archives/298#comments</comments>
		<pubDate>Sun, 18 Jan 2009 13:10:11 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux kernel]]></category>
		<category><![CDATA[kernel]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[内核]]></category>
		<category><![CDATA[源代码]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=298</guid>
		<description><![CDATA[上周工作实在太忙，写blog的事情是一拖再拖&#8230;&#8230;本来准备好继续写Linux内核构成的，但是因为看书看的比较快，刚好看到一个比较关键的地方——程序的链接和载入，而这个部分有牵扯代码生成，机器语言编程，程序实例化和虚拟存储器很多内容，可以说是从程序员的角度理解Linux内核如何执行用户应用程序行为的核心。况且，这个部分牵扯很多不容忽视的“细节”，我害怕如果日子久了，就再也不记得这些内容，于是算是插入一篇，给自己记录下来。 什么是链接 大概教科书上都念过这样的经——源代码变成可执行文件一般要经过 1.编译预处理；2.编译；3.汇编；4.链接 这些过程。从使用TurboC那天起，我就被告知程序是这样产生的，但是直到我大学毕业以后的几年里，才真正知道这些事情都是怎么做的。而且，还是从一本卡耐基梅隆大学的本科生前导课程中学到的，由此可见中国所谓的大学误人子弟的老师的确不在少数。除了继续被愚弄几年以外，上那个大学又有什么意思？先忘掉将中国国内的计算机教育搞到本末倒置的Windows吧，看看Linux中的典型编译过程是怎样完成的。每个操作系统都提供一种编译驱动程序(compile driver)，最典型的例子就是gcc。gcc事实上不是一个单独的程序，而是一组程序的组合。Unix世界的逻辑就是将事情分解和简化然后分发给各个可以相互协作的部分完成，在这个设计思想下，Unix世界产成了很多专注做好一件事情的小程序，比如最简单的yes。然而，又为了解决各个小程序之间的协同工作，Unix工具集中又添加进很多tools driver，比如gcc，这种tools driver的设计思想有点像设计模式中提到的facade（这个词似乎是法语，注意发音），他把一些难以把握细节的小工具进行整合从而为用户提供一个简便的接口。gcc在编译程序的过程中实际上需要调用cpp,cc1,as和ld这些工具来帮助它完成工作，于是编译过程就是个RPG游戏，当然这里需要重点看看角色变换和他们的输入与产出。下面游戏开始： 故事背景 一个简单的Swap程序： /* main.c */ void swap(); int buf[2] = {1,2}; int main() { swap(); return 0; } /* swap.c */ extern int buf[]; int *bufp0 = &#038;buf[0]; int *bufp1; void swap() { int temp; bufp1 = &#038;buf[1]; temp = *bufp0; *bufp0 = *bufp1; *bufp1 = temp; [...]]]></description>
			<content:encoded><![CDATA[<p>上周工作实在太忙，写blog的事情是一拖再拖&#8230;&#8230;本来准备好继续写Linux内核构成的，但是因为看书看的比较快，刚好看到一个比较关键的地方——程序的链接和载入，而这个部分有牵扯代码生成，机器语言编程，程序实例化和虚拟存储器很多内容，可以说是从程序员的角度理解Linux内核如何执行用户应用程序行为的核心。况且，这个部分牵扯很多不容忽视的“细节”，我害怕如果日子久了，就再也不记得这些内容，于是算是插入一篇，给自己记录下来。</p>
<h3>什么是链接</h3>
<p>大概教科书上都念过这样的经——源代码变成可执行文件一般要经过 1.编译预处理；2.编译；3.汇编；4.链接 这些过程。从使用TurboC那天起，我就被告知程序是这样产生的，但是直到我大学毕业以后的几年里，才真正知道这些事情都是怎么做的。而且，还是从一本卡耐基梅隆大学的本科生前导课程中学到的，由此可见中国所谓的大学误人子弟的老师的确不在少数。除了继续被愚弄几年以外，上那个大学又有什么意思？先忘掉将中国国内的计算机教育搞到本末倒置的Windows吧，看看Linux中的典型编译过程是怎样完成的。每个操作系统都提供一种编译驱动程序(compile driver)，最典型的例子就是gcc。gcc事实上不是一个单独的程序，而是一组程序的组合。Unix世界的逻辑就是将事情分解和简化然后分发给各个可以相互协作的部分完成，在这个设计思想下，Unix世界产成了很多专注做好一件事情的小程序，比如最简单的yes。然而，又为了解决各个小程序之间的协同工作，Unix工具集中又添加进很多tools driver，比如gcc，这种tools driver的设计思想有点像设计模式中提到的facade（这个词似乎是法语，注意发音），他把一些难以把握细节的小工具进行整合从而为用户提供一个简便的接口。gcc在编译程序的过程中实际上需要调用cpp,cc1,as和ld这些工具来帮助它完成工作，于是编译过程就是个RPG游戏，当然这里需要重点看看角色变换和他们的输入与产出。下面游戏开始：</p>
<h4>故事背景</h4>
<p>一个简单的Swap程序：</p>
<pre class="source-code">
/* main.c */
void swap();

int buf[2] = {1,2};

int main()
{
	swap();
	return 0;
}
</pre>
<pre class="source-code">
/* swap.c */
extern int buf[];

int *bufp0 = &#038;buf[0];
int *bufp1;

void swap()
{
	int temp;

	bufp1 = &#038;buf[1];
	temp = *bufp0;
	*bufp0 = *bufp1;
	*bufp1 = temp;
}
</pre>
<h4>出场人物</h4>
<ol>
<li>cpp，“预处理器”</li>
<li>cc1，“c语言编译器”</li>
<li>as，“汇编器”</li>
<li>ld，本集主角，江湖人称“链接器”</li>
</ol>
<p>这些程序有的是不能被直接调用的，但有的是可以的。为了将问题简化，还是给gcc添加不同的选项作为驱动来观察比他们之间都发生了什么吧。具体什么选项呢，<code>man gcc</code>看看吧。</p>
<p><code><br />
gcc [-c|-S|-E] ... infile ...<br />
</code></p>
<p>gcc手册的第一行就告诉我们“他不是一个人”。这三个选项从后往前分别指明的就是 -E 预处理； -S 编译； -c 汇编；如果不加参数就直接将输入的源文件做到链接，默认情况是一条龙服务。下面的表格简单的列出了各个步骤的命令，输入和输出。</p>
<table>
<thead>
<tr>
<td>命令</td>
<td>角色</td>
<td>输出</td>
<td>注释</td>
</tr>
</thead>
<tbody>
<tr>
<td><code>gcc -E x</code></td>
<td><code>cpp</code></td>
<td>main.i</td>
<td>这里产生一个经过预处理的中间文件</td>
</tr>
<tr>
<td><code>gcc -S x</code></td>
<td><code>cc1</code></td>
<td>main.S</td>
<td>产生汇编语言文件</td>
</tr>
<tr>
<td><code>gcc -c x</code></td>
<td><code>as</code></td>
<td>main.o</td>
<td>产生可重定位的ObjectFile</td>
</tr>
</tbody>
</table>
<p>此外，<code>swap.c</code>的代码也可以按照上面的步骤按部就班的生成一个<code>swap.o</code>。接下来的工作就是用ld对已经生成的<code>.o</code>文件进行“链接”产生可执行文件。由此可见，</p>
<blockquote><p>
链接就是将不同部分的代码和数据收集和组合成一个单一文件的过程。这个文件可以被加载（或者被拷贝）到存储器中执行。（来自《深入理解计算机系统》）
</p></blockquote>
<p>但如果事情做到这个步，算是可以告一个段落，因为最重要的内容之一“可重定位的目标文件”(relocatable object file)已经生成了，也就是这里出现的<code>.o</code>文件。从技术上说<code>.o</code>与普通的二进制文件相比并没有什么特别之处，它就是一个在磁盘文件中的“字节序列”。但是，这个文件的重要之处在于他是类UNIX操作系统的ABI(application binary interface)的核心。</p>
<h3>ELF</h3>
<p>ELF的名字不错，elf似乎是德国神话传说中的一种精灵，恰恰也说明了ELF文件在系统中的执行就像是变魔法。跑题了。其实他是Executable and Linkable Format的缩写形式，<a href="http://en.wikipedia.org/wiki/Executable_and_Linkable_Format">wikipedia上关于elf的解释</a>包括英文在内都不甚详细，但是还是值得一读的。这种二进制文件格式广泛使用于各种计算机平台。最早的ObjectFile的格式是诞生于贝尔实验室的a.out，知道现在仍然有很多应用程序采用a.out形式运行。</p>
<blockquote><p>
这里插播一下历史消息。贝尔实验室的UNIX系统中使用a.out作为可执行文件的形式，而后在很多UNIX版本中这个二进制文件格式被大量的采用。UNIX系统发展的重要里程碑System V在诞生的时候采用COFF(common object file format)——微软这个偷学狂人在其Windows系统发展的过程中采用了COFF的一个变体作为自己的可执行文件的形式至今，称为PE(portable executable)——现代UNIX版本中大多采用了ELF代替此前比较原始的二进制形式。
</p></blockquote>
<p>伟大的系统在诞生和发展过程中总能产生一些伟大的部件，甚至有些系统本身已经不存在了，但它的思想或者某些精妙的实现却依然在其他系统中以某种形式存在。这个例子数不胜数，比如上面说到的ELF，再比如研发Plan 9操作系统(现在依然存在)的过程中诞生的unicode和procfs。</p>
<p>ObjectFile有三种形式，1.可重定位目标文件(relocatable object file)； 2.可执行目标文件(executable object file)； 3.共享目标文件(shared object file)。<br />
其中可重定位目标文件就是指<code>.o</code>文件。下面这个图展示的是一个典型的<code>.o</code>的文件组成。</p>
<pre class="source-code">
 +----------------------+
 |    ELF header        | <--帮助链接器解析ObjectFile的信息
 +----------------------+
 |       .text          | <--已编译程序的机器代码
 +----------------------+
 |       .rodata        | <--只读数据，比如pirntf的格式化字串等
 +----------------------+
 |       .data          | <--<strong>已经初始化</strong>的<strong>全局变量</strong>
 +----------------------+
 |        .bss          | <--<strong>未初始化</strong>的<strong>全局变量</strong>
 +----------------------+
 |        .symtab       | <--符号表，这个表是提供给链接器使用的，每个OjectFile
 +----------------------+
 |      .rel.text       | <--可重定位的代码
 +----------------------+
 |      .rel.data       | <--可重定位的数据
 +----------------------+
 |       .debug         | <--调试符号表
 +----------------------+
 |       .line          | <--.text节中机器指令于源程序行号之间的映射表
 +----------------------+
 |       .strtab        | <--字符串
 +----------------------+
 | section header table | <--节头表(section header table)
 +----------------------+
</pre>
<p>更加详细的图表可以在"<a href="http://www.iecc.com/linker/">Linkers and Loaders</a>"一书中看到，点<a href="http://www.iecc.com/linker/linker03-14.jpg" title="ELF file layout">这里</a>。</p>
<p>这些分段被称为“节”(section)，并且，在<code>.o</code>文件中为这些保留了一张表，称作“节头表”(section header table)。节头表描述了不同节的位置和大小，其作用有点像各个节的检索索引。这些节之中，<code>.debug</code>和<code>.line</code>节包含的是调试信息，只有gcc在使用"-g"选项时才能得到。而<code>.symtab</code>这个符号表节是每一个ObjectFile都会包含的，一些程序员错误的认为只有在使用"-g"选项时才能在ObjectFile中得到符号表。而这个<code>.symtab</code>节正是<strong>链接</strong>操作的核心。</p>
<p>预知后事如何，且听下回分解吧。下午约了师兄去游泳，4点从图书馆出来背着两块砖头一样的书就去了，被水一泡想说的东西全忘了。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/298/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

