<?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; opensource</title>
	<atom:link href="http://www.adamjiang.com/archives/tag/opensource/feed" rel="self" type="application/rss+xml" />
	<link>http://www.adamjiang.com</link>
	<description>长脑袋的个人博客</description>
	<lastBuildDate>Wed, 28 Dec 2011 14:18:32 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3</generator>
		<item>
		<title>Emacs——很漂亮很强大</title>
		<link>http://www.adamjiang.com/archives/622</link>
		<comments>http://www.adamjiang.com/archives/622#comments</comments>
		<pubDate>Sat, 11 Jul 2009 13:32:07 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[Linux tips]]></category>
		<category><![CDATA[Emacs]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=622</guid>
		<description><![CDATA[Vim我用了5年有余，最近因为接触的项目非常大，大约10W行代码，而且该项目采用包括C和C++还有python的多种语言开发，突然让我觉得使用Vim有些应付不过来。我的主要任务并不是去查看系统细节，而是要在短时间内获得已有系统的情况——包括结构和功能上的信息，所以我需要一个比较强大的代码浏览程序。公司提供的软件库里面搜索了半天，找到十几款代码浏览工具竟然没有一款是我熟悉的。一狠心，我觉得尝试一下Emacs。或许Vim也能够胜任这个工作，但是我尝试拼凑了很久插件也没能找到符合工作需要的集合。总之，破釜沉舟，试一下Emacs。谁知道上手之后我竟然再也放不下Emacs了，彻底被这款编辑器的扩展性和强大功能征服。在使用了Emacs一个月以后，对于我的“重大发现”，我想还是留下一个记录比较好。 基础软件包 $ sudo apt-get install emacs-snapshot emacs-snapshot-common emacs-snapshot-bin-common emacs-snapshot-el emacs-goodies-el emacs-snapshot-gtk 漂亮的Emacs 我并不是个特别喜欢冒险的人，所以刚刚开始的时候，我选择了Emacs22。但是，后来发现，处理日语和中文的字体显示非常繁琐，索性放弃“保守派”的一贯作风，跃迁到还在开发中的Emacs23。在google上搜索得到Alexandre Vassalotti写的很流行的一篇博文Pretty Emacs，如法炮制，在Ubuntu 9.04上得到了一个让我基本满意的操作界面。这篇博文虽然有用但却非常简略，并没有详细说明CJK字体的反锯齿应该怎样处理。我尝试在~/.Xresource文件中使用包含日语和中文的字体，但是结果并不能让我满意，原因是日语和中文的汉字显示混在了一起，错别字太多。拜google所赐，最终还是找到了解决之法。证据在此。 在.emacs中需要使用字符集来将日语和中文的显示字符区别对待，代码如下： ;;;------------------------------------------------------------;;; ;;; VIEW ;;; No startup message (setq inhibit-startup-message t) ;;; GUI font (set-default-font "Bitstream Vera Sans Mono-12") ;;; JP fonts (set-fontset-font (frame-parameter nil 'font) 'japanese-jisx0208 '("M+2VM+IPAG circle" . "unicode-bmp")) ;;; zh-CN fonts (set-fontset-font (frame-parameter nil 'font) [...]]]></description>
			<content:encoded><![CDATA[<p>Vim我用了5年有余，最近因为接触的项目非常大，大约10W行代码，而且该项目采用包括C和C++还有python的多种语言开发，突然让我觉得使用Vim有些应付不过来。我的主要任务并不是去查看系统细节，而是要在短时间内获得已有系统的情况——包括结构和功能上的信息，所以我需要一个比较强大的代码浏览程序。公司提供的软件库里面搜索了半天，找到十几款代码浏览工具竟然没有一款是我熟悉的。一狠心，我觉得尝试一下Emacs。或许Vim也能够胜任这个工作，但是我尝试拼凑了很久插件也没能找到符合工作需要的集合。总之，破釜沉舟，试一下Emacs。谁知道上手之后我竟然再也放不下Emacs了，彻底被这款编辑器的扩展性和强大功能征服。在使用了Emacs一个月以后，对于我的“重大发现”，我想还是留下一个记录比较好。</p>
<h3>基础软件包</h3>
<p><code><br />
$ sudo apt-get install emacs-snapshot emacs-snapshot-common emacs-snapshot-bin-common emacs-snapshot-el emacs-goodies-el emacs-snapshot-gtk<br />
</code></p>
<h3>漂亮的Emacs</h3>
<p>我并不是个特别喜欢冒险的人，所以刚刚开始的时候，我选择了Emacs22。但是，后来发现，处理日语和中文的字体显示非常繁琐，索性放弃“保守派”的一贯作风，跃迁到还在开发中的Emacs23。在google上搜索得到<a href="http://images.google.co.jp/imgres?imgurl=http://farm4.static.flickr.com/3346/3336195816_817511aa10_o.jpg&amp;imgrefurl=http://flickr.com/photos/sfllaw/3336195816/&amp;usg=__v9gDhwGunQ6e7XAvLyDSFBUp0Ys=&amp;h=4368&amp;w=2912&amp;sz=73&amp;hl=ja&amp;start=9&amp;sig2=c9ZFxGdvK-qzSq3deq9-FQ&amp;um=1&amp;tbnid=3z99E0FRZYeH6M:&amp;tbnh=150&amp;tbnw=100&amp;prev=/images%3Fq%3DAlexandre%2BVassalotti%26hl%3Dja%26client%3Dfirefox-a%26rls%3Dcom.ubuntu:ja:unofficial%26sa%3DN%26um%3D1&amp;ei=lnhYSpfmAobmtgPJ5czIAg">Alexandre Vassalotti</a>写的很流行的一篇博文<a href="http://peadrop.com/blog/2007/01/06/pretty-emacs/">Pretty Emacs</a>，如法炮制，在Ubuntu 9.04上得到了一个让我基本满意的操作界面。这篇博文虽然有用但却非常简略，并没有详细说明CJK字体的反锯齿应该怎样处理。我尝试在~/.Xresource文件中使用包含日语和中文的字体，但是结果并不能让我满意，原因是日语和中文的汉字显示混在了一起，错别字太多。拜google所赐，最终还是找到了解决之法。证据在此。</p>
<p><a href="http://lh6.ggpht.com/_O-FwCW3mN_w/SliQpLvR2-I/AAAAAAAAEPo/bQWHMJ_6SJ8/Screenshot-emacs-3.png"><img title="Emacs screenshot" src="http://lh6.ggpht.com/_O-FwCW3mN_w/SliQpLvR2-I/AAAAAAAAEPo/bQWHMJ_6SJ8/s400/Screenshot-emacs-3.png" alt="" /></a></p>
<p>在.emacs中需要使用字符集来将日语和中文的显示字符区别对待，代码如下：</p>
<pre>;;;------------------------------------------------------------;;;
;;; VIEW
;;; No startup message
(setq inhibit-startup-message t)
;;; GUI font
(set-default-font "Bitstream Vera Sans Mono-12")
;;; JP fonts
(set-fontset-font (frame-parameter nil 'font)
		  'japanese-jisx0208
		  '("M+2VM+IPAG circle" . "unicode-bmp"))
;;; zh-CN fonts
(set-fontset-font (frame-parameter nil 'font)
		  'han
		  '("Vera Sans YuanTi Mono" . "unicode-bmp"))
;;;;===========================================================;;;;
;;; cjk-misc fonts
(set-fontset-font (frame-parameter nil 'font)
		  'cjk-misc
		  '("Vera Sans YuanTi Mono" . "unicode-bmp"))
;;; bopomofo fonts
(set-fontset-font (frame-parameter nil 'font)
		  'bopomofo
		  '("Vera Sans YuanTi Mono" . "unicode-bmp"))
;;; symbol fonts
(set-fontset-font (frame-parameter nil 'font)
		  'symbol
		  '("Vera Sans YuanTi Mono" . "unicode-bmp"))
;;;;=========================================================;;;;</pre>
<p>“Vera Sans YuanTi”字体似乎涉嫌侵权，所以现在在互联网上已经难觅其踪，但是我还是非常喜欢这款合成字体，三四年前私自收藏了一直使用。在Ubuntu Jaunty上，读者可以尝试使用文泉驿等字体替代。或者，如果你拥有Windows的授权，使用微软雅黑也会有不错的效果。这里需要注意的是，Ubuntu 8.04或者更早版本上的emacs-snapshot提供的Emacs版本并不支持CJK字体的反锯齿显示。如果你使用比较早版本的Emacs23，可以尝试使用<a href="http://emacs.orebokech.com/">emacs.orebokech.com</a>提供的WeeklyBuild。但是事先应该确认一下在Ubuntu的backport上有没有提供emacs-snapshot的软件包。下面是中文显示效果的截屏。</p>
<p><a href="http://lh4.ggpht.com/_O-FwCW3mN_w/SliRUu1hspI/AAAAAAAAEPs/URgQ7TazitA/Screenshot-emacs-4.png"><img title="Emacs Screenshot Display Chinese" src="http://lh4.ggpht.com/_O-FwCW3mN_w/SliRUu1hspI/AAAAAAAAEPs/URgQ7TazitA/s400/Screenshot-emacs-4.png" alt="" /></a></p>
<h3>安装CEDET</h3>
<p><a href="http://cedet.sourceforge.net/">CEDET</a>是一整套开发环境的集合工具。包括工程管理，代码浏览和自动补全工具。但是，Ubuntu的软件频道中提供的版本并不能和Emacs23一起工作。详情可以看这个Debian的<a href="http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=525613">Bug#525613</a>。我的解决办法是采用新的CEDET版本。<br />
<code><br />
$ wget http://sourceforge.net/projects/cedet/files/cedet/cedet-1.0pre6.tar.gz/download<br />
$ tar zxf cedet-1.0pre6.tar.gz<br />
$ cd cedet-1.0pre6/<br />
$ make<br />
$ make install-info<br />
$ cd ../ &amp;&amp; cp -a cedet-1.0pre6 /usr/share/emacs-snapshot/site-lisp/cedet<br />
</code><br />
这里需要注意的是，必须将编译好的cedet拷贝到/usr/share/emacs-snapshot/site-lisp中，而不能仅仅将这个文件放到load-path中了事。原因是Emacs在初始化的时候，会利用这个目录中的subdirs.el来保证整个目录都添加进入load-path之中。接下来在.emacs中添加下面这几行就基本可以使用了，但是上手之后应该多查看info的内容找到更多的信息。在Emacs中利用组合键Ctrl-h i可以打开info，按s查找CEDET。</p>
<pre>;;;-----------------------------------------------------------------;;;
;;; CEDET
;;;-----------------------------------------------------------------;;;
;; Load CEDET.
;; See cedet/common/cedet.info for configuration details.
(require 'cedet)

;; Enable EDE (Project Management) features
(global-ede-mode 1)

;; Enable EDE for a pre-existing C++ project
;; (ede-cpp-root-project "NAME" :file "~/myproject/Makefile")

;; Enabling Semantic (code-parsing, smart completion) features
;; Select one of the following:

;; * This enables the database and idle reparse engines
(semantic-load-enable-minimum-features)

;; * This enables some tools useful for coding, such as summary mode
;;   imenu support, and the semantic navigator
(semantic-load-enable-code-helpers)</pre>
<p>CEDET非常强大，以至于很难几句话将这个工具讲清楚，但是经常查看info和多多实践，马上就会用的非常顺手。</p>
<h3>后记</h3>
<p>最近一些日子，我一直在看Emacs相关的内容。Emacs可以说不仅仅是一款编辑器，而是一个集成并且可以扩展的工作环境。很多年轻的程序员，包括我自己，总是在各种各样的工具之间跳来跳去，一方面可以是因为自己不够坚定，但另一方面只能说很多工具都有自己的局限性，当程序员遇到新的情况的时候就可能很自然的抛弃旧工具。Emacs却不同，它更像一个framework，你可以将自己的技巧，使用习惯以及效率提升工具全部集成其上。如果它不够用了，你可以自己动手写lisp对它进行扩展。又想起那句话，人们往往高估自己短期的能力而低估自己长期的积累。Emacs正是给这种积累提供一种可能性。也许这正是对我来说最要命特性。我的.emacs文件在<a href="https://github.com/jcadam/pemacs/blob/master/_emacs">这里</a>。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/622/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>民主实践的感受和思考</title>
		<link>http://www.adamjiang.com/archives/581</link>
		<comments>http://www.adamjiang.com/archives/581#comments</comments>
		<pubDate>Sat, 13 Jun 2009 12:18:58 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[杂谈]]></category>
		<category><![CDATA[opensource]]></category>
		<category><![CDATA[民主]]></category>
		<category><![CDATA[深圳]]></category>
		<category><![CDATA[社区]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=581</guid>
		<description><![CDATA[参与深圳LUG的讨论后，我得到的一些对民主实践的新的感受。或者，这些感受也有可能与“民主”没有什么关系，现在我只是想把他记录下来。 开放源代码社区和一些类似的自治团体，应当是现在中国社会实践小范围民主自由制度的摇篮。特别是开源社区的氛围和一些约定俗称的制度，非常有利于让以民主方式讨论问题的形式逐步展开。但是，真正的将这个想法付诸时间之后，我才发现这其中却显露出来很多问题。 第一，社区组织形成通常来自于一些爱好者的热情支持，因此，社区在发展过程中会有可能走不出广泛民主实践的道路。比如，我比较关注的深圳LINUX爱好者团体，创建伊始到现在一直靠几位创始人和核心活动成员的的热情得以存在。而且这种松散的线上社区一般都没有明确的行动纲领(guideline)和目标，于是有可能将社区活动最终办成“圈内”人士的自娱自乐。这可以说是开源软件社区相比较商业软件形成的联盟存在的先天性不足吧。 第二，文化和政治环境的限制让民主制度的创新受到限制。我不敢说这是社区实践民主缺乏的首要因素，但至少这一点也是关键因素。说的大一点，即是“民主制度究竟应该怎样搞”这个问题还没有得到解决。我想这个问题的形成，正反应的中国政治教育的失败。政治本身不是一件可怕的事情，这个词字面意义上理解，它应当是一门关于个人如何参与群体决策和体现个人价值的问题。我们从小就没有机会去实践如何发表自己的意见，如何用辩论支撑自己的意见，如何让群体接受自己意见，以及如何根据群体作出的决策进行妥协和让步。可以说，谁也没有教给我们这些能力。而这种能力，又是在社会生活中一种必须的能力。如果我们接受的不是关于愚忠愚孝的而是关于如何实践民主的教育的话，那么相信社会阶层之间可以通过对话和协调的方式缓解社会压力。放到一个小团体的范围内来看，又可以通过这种方式来促进成员之间的相互交流和互信关系，从而促进社区发展。 第三，民主实践能力的缺陷，导致我们的辩论技巧只剩下了谩骂。中国国内的各大论坛，无论是技术论坛还是非技术论坛都充斥这谩骂。当一个人实在不知道用什么方式表达自己意愿的时候，就只能生产视听垃圾了。口舌之利又是似乎比生命还重要，这是在是中国人根深蒂固的毛病之一。但是，可恨之人必定有可怜之处，我们需要用仁慈的心去体会一个个贫乏的灵魂。 第四，也许文革还没有结束。看到很多人谈到过“文化大革命”对中国社会的影响，但是谁曾想过，有一种东西已经植根在很多人心中，生长在我们使用的语言里。肆意的无视规则的人身攻击和死不认错的的态度，完全体现了一种人性的恶。那件事情注定贻害万年。 Ubuntu社区是在开源社区中发展非常好的，最具代表性的是它的开发者进阶制度。我觉得从Ubuntu社区得到的很多经验，非常值得学习和参考。]]></description>
			<content:encoded><![CDATA[<p>参与深圳LUG的讨论后，我得到的一些对民主实践的新的感受。或者，这些感受也有可能与“民主”没有什么关系，现在我只是想把他记录下来。</p>
<p>开放源代码社区和一些类似的自治团体，应当是现在中国社会实践小范围民主自由制度的摇篮。特别是开源社区的氛围和一些约定俗称的制度，非常有利于让以民主方式讨论问题的形式逐步展开。但是，真正的将这个想法付诸时间之后，我才发现这其中却显露出来很多问题。</p>
<p>第一，社区组织形成通常来自于一些爱好者的热情支持，因此，社区在发展过程中会有可能走不出广泛民主实践的道路。比如，我比较关注的<a href="http://wiki.szlug.org/w/%E9%A6%96%E9%A1%B5" title="深圳LUG">深圳LINUX爱好者团体</a>，创建伊始到现在一直靠几位创始人和核心活动成员的的热情得以存在。而且这种松散的线上社区一般都没有明确的行动纲领(guideline)和目标，于是有可能将社区活动最终办成“圈内”人士的自娱自乐。这可以说是开源软件社区相比较商业软件形成的联盟存在的先天性不足吧。</p>
<p>第二，文化和政治环境的限制让民主制度的创新受到限制。我不敢说这是社区实践民主缺乏的首要因素，但至少这一点也是关键因素。说的大一点，即是“民主制度究竟应该怎样搞”这个问题还没有得到解决。我想这个问题的形成，正反应的中国政治教育的失败。政治本身不是一件可怕的事情，这个词字面意义上理解，它应当是一门关于个人如何参与群体决策和体现个人价值的问题。我们从小就没有机会去实践如何发表自己的意见，如何用辩论支撑自己的意见，如何让群体接受自己意见，以及如何根据群体作出的决策进行妥协和让步。可以说，谁也没有教给我们这些能力。而这种能力，又是在社会生活中一种必须的能力。如果我们接受的不是关于愚忠愚孝的而是关于如何实践民主的教育的话，那么相信社会阶层之间可以通过对话和协调的方式缓解社会压力。放到一个小团体的范围内来看，又可以通过这种方式来促进成员之间的相互交流和互信关系，从而促进社区发展。</p>
<p>第三，民主实践能力的缺陷，导致我们的辩论技巧只剩下了谩骂。中国国内的各大论坛，无论是技术论坛还是非技术论坛都充斥这谩骂。当一个人实在不知道用什么方式表达自己意愿的时候，就只能生产视听垃圾了。口舌之利又是似乎比生命还重要，这是在是中国人根深蒂固的毛病之一。但是，可恨之人必定有可怜之处，我们需要用仁慈的心去体会一个个贫乏的灵魂。</p>
<p>第四，也许文革还没有结束。看到很多人谈到过“文化大革命”对中国社会的影响，但是谁曾想过，有一种东西已经植根在很多人心中，生长在我们使用的语言里。肆意的无视规则的人身攻击和死不认错的的态度，完全体现了一种人性的恶。那件事情注定贻害万年。</p>
<p>Ubuntu社区是在开源社区中发展非常好的，最具代表性的是它的<a href="https://wiki.ubuntu.com/UbuntuDevelopment" title="Ubuntu-development">开发者进阶制度</a>。我觉得从Ubuntu社区得到的很多经验，非常值得学习和参考。</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/581/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>
		<item>
		<title>Linux内核源码阅读系列(3)-代码阅读的陷阱</title>
		<link>http://www.adamjiang.com/archives/283</link>
		<comments>http://www.adamjiang.com/archives/283#comments</comments>
		<pubDate>Sun, 11 Jan 2009 07:08:05 +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=283</guid>
		<description><![CDATA[在前几天些的博文中大概说了一下应该真怎样索引代码来帮助阅读过程。但是那篇文章实在是粗略的很，粗略到几乎不能用的地步。于是想偷偷补充一下&#8230;&#8230; Cscope不能帮你的事情 Cscope本来是为了检索c语言的代码而设计的。这句话的意义就是，Cscope可以索引和帮助检索大部分内核源代码，但是却是有很多遗漏。被遗漏掉的索引理所当然的不能被检索到。所以，这是阅读源代码是必须注意的问题。Linux内核代码无法分析的部分形成原因在于： 汇编源码包括内迁汇编 汇编代码和C代码之间的调用关系 利用函数指针的函数调用 宏定义的“假函数” 利用宏在编译时动态生成的函数体 比如，第2点所说的情况，利用汇编语言调用c语言定义的函数，在i386结构的“调度器”dispatcher代码面就存在： 18 #define switch_to(prev,next,last) do { \ 19 unsigned long esi,edi; \ 20 asm volatile("pushfl\n\t" /* Save flags */ \ 21 "pushl %%ebp\n\t" \ 22 "movl %%esp,%0\n\t" /* save ESP */ \ 23 "movl %5,%%esp\n\t" /* restore ESP */ \ 24 "movl $1f,%1\n\t" /* save EIP */ [...]]]></description>
			<content:encoded><![CDATA[<p>在前几天些的博文中大概说了一下应该<del datetime="2009-01-13T05:59:37+00:00">真</del>怎样索引代码来帮助阅读过程。但是那篇文章实在是粗略的很，粗略到几乎不能用的地步。于是想偷偷补充一下&#8230;&#8230;</p>
<h3>Cscope不能帮你的事情</h3>
<p>Cscope本来是为了检索c语言的代码而设计的。这句话的意义就是，Cscope可以索引和帮助检索大部分内核源代码，但是却是有很多遗漏。被遗漏掉的索引理所当然的不能被检索到。所以，这是阅读源代码是必须注意的问题。Linux内核代码无法分析的部分形成原因在于：</p>
<ol>
<li>汇编源码包括内迁汇编</li>
<li>汇编代码和C代码之间的调用关系</li>
<li>利用函数指针的函数调用</li>
<li>宏定义的“假函数”</li>
<li>利用宏在编译时动态生成的函数体
</ol>
<p>比如，第2点所说的情况，利用汇编语言调用c语言定义的函数，在i386结构的“调度器”dispatcher代码面就存在：</p>
<pre class="source-code"> 18 #define switch_to(prev,next,last) do {                                  \
 19         unsigned long esi,edi;                                          \
 20         asm volatile("pushfl\n\t"               /* Save flags */        \
 21                      "pushl %%ebp\n\t"                                  \
 22                      "movl %%esp,%0\n\t"        /* save ESP */          \
 23                      "movl %5,%%esp\n\t"        /* restore ESP */       \
 24                      "movl $1f,%1\n\t"          /* save EIP */          \
 25                      "pushl %6\n\t"             /* restore EIP */       \
 26                      "jmp __switch_to\n"                                \
                          ^^^----------<strong>这里利用汇编代码跳转到__switch_to函数</strong>
 27                      "1:\t"                                             \
 28                      "popl %%ebp\n\t"                                   \
 29                      "popfl"                                            \
 30                      :"=m" (prev-&gt;thread.esp),"=m" (prev-&gt;thread.eip),  \
 31                       "=a" (last),"=S" (esi),"=D" (edi)                 \
 32                      :"m" (next-&gt;thread.esp),"m" (next-&gt;thread.eip),    \
 33                       "2" (prev), "d" (next));                          \
 34 } while (0)</pre>
<p>第4点中提到的宏定义函数这种情况也比较常见，比如：</p>
<pre class="source-code">#define page_buffers(page)					\
	({							\
		BUG_ON(!PagePrivate(page));			\
		((struct buffer_head *)page_private(page));	\
	})</pre>
<p>在这里利用宏定义事实上定义了一个函数，而代码索引工具是无法发现这个符号的。<br />
第5点，利用宏定义动态生成的函数体，也是无法被代码索引工具识别的。比如：</p>
<pre class="source-code"> 82 #define BUFFER_FNS(bit, name)                                           \
...
 91 static inline int buffer_##name(const struct buffer_head *bh)           \
 92 {                                                                       \
 93         return test_bit(BH_##bit, &amp;(bh)-&gt;b_state);                      \
 94 }
...
130 BUFFER_FNS(Unwritten, unwritten)</pre>
<p>在编译预处理阶段，##name将被替换为正确的缓冲名字，但是，源代码解析工具却不能看到这种变化。这类函数通常和是一些小的内嵌函数，即使是用kgdb调试的时候也是不容易被发现的，要精确发现它们只能靠你的大脑和字符搜索了。所以，在源代码阅读的之前，你必须很清楚的知道，自己究竟可能漏掉什么东西。</p>
<h3>内核源码中的其他容易被遗漏的重要内容</h3>
<p>除了上面提到的可能被源码索引工具漏掉的内容之外，还必须对下面这些内容保持警惕。<br />
<code><br />
% export LNX=${WHERE YOUR KERNEL SRC LOCATED}<br />
</code></p>
<h4>内核的链接器脚本(linker scripts)</h4>
<p><code><br />
find $LNX -name "*lds"<br />
</code><br />
你可以在<a title="链接器脚本" href="http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gnu-linker/scripts.html">这里</a>找到<a href="http://www.redhat.com/docs/manuals/enterprise/RHEL-4-Manual/gnu-linker/scripts.html">链接器脚本</a>的说明。</p>
<blockquote><p>Every link is controlled by a linker script. This script is written in the linker command language.</p>
<p>The main purpose of the linker script is to describe how the sections in the input files should be mapped into the output file, and to control the memory layout of the output file. Most linker scripts do nothing more than this.</p></blockquote>
<h4>内核中重要的宏定义文件</h4>
<p><code><br />
$LNX/include/linux/moduleparam.h<br />
$LNX/include/linux/init.h<br />
</code><br />
这两个头文件中定义的宏在很多情况吓都可能被用到（特别是驱动程序开发），所以，尽可能把大脑当硬盘用一下。</p>
<h4>内核中的汇编文件</h4>
<p><code><br />
find $LNX -name "*.S"<br />
</code></p>
<h4>内核中的Makefile</h4>
<p><code><br />
find $LNX -name "Makefile"<br />
</code></p>
<h4>内核中的配置文件</h4>
<p><code><br />
find $LNX -name "*config*"<br />
</code></p>
<p>可能妨碍你理解内核行为的另外一个因素是内核设计的思想以及可能被它影响到的代码运行形式——也就是说虽然内核是使用c语言代码写的，但是并不证明她就是简单的结构化的顺序构成。这主要包含两个方面，1. 面向对象的部分实现，比如文件系统和<strong>设备模型</strong>——设备模型是内核2.5开发时的一个重要目标，目的在于统一驱动程序的动作以及分离procfs和sysfs；2. “发布——订阅”模型，比如 notification chain，主要思想类似于Design Pattern中提到的<a title="Observer Pattern" href="http://en.wikipedia.org/wiki/Observer_pattern">Observer Pattern</a>，用于事件消息的传递过程。</p>
<p>OK.源码阅读是件不容易的事情，特别是像Linux内核这样规模庞大的源代码。所以，“长期奋战在”Linxu内核开发“第一线的广大”内核开发者，很难有人说清楚究竟应该怎样去读。但是，注意上面提到的陷阱尽管去读也就是了。 :)</p>
<p>附送<a title="cscope使用的db" href="http://www.adamjiang.com/resources/make_kernel_cscope_db.sh">用来生成cscope使用db的脚本</a>。Happy hacking!</p>
<pre class="source-code">#!/bin/sh
if [ "$1" = "" ]
then
	echo "Usage: `basename $0`(linux-src-dir) (architecture)";
	echo "e.g. `basename $0` ~/linux-src/ i386";
	exit;
fi

if [ "$2" = "" ]
then
	echo "Usage: `basename $0`(linux-src-dir) (architecture)";
	echo "e.g. `basename $0` ~/linux-src/ i386";
	exit;
fi

LNX=$1
ARCH=$2
mkdir -p ${HOME}/.cscope/
cd /
find $LNX 								\
	-path "$LNX/arch/*" ! -path "$LNX/arch/${ARCH}*" -prune -o 	\
	-path "$LNX/include/asm-*" ! -path "$LNX/include/asm-${ARCH}*" -prune -o \
	-path "$LNX/tmp*" -prune -o 					\
	-path "$LNX/Documentation*" -prune -o 				\
	-path "$LNX/scripts*" -prune -o 				\
	-path "$LNX/drivers*" -prune -o 				\
	-name "*.[chxsS]" -print &gt; ${HOME}/.cscope/cscope.files

cd ${HOME}/.cscope/ #the directory with 'cscope.files'
cscope -b -q -k</pre>
</ol>
</li>
</ol>
<p>&#8212;-<br />
参考文献：<br />
<a href="http://wiki.zh-kernel.org/sniper#x86%E8%99%9A%E6%8B%9F%E8%B0%83%E8%AF%95%E7%8E%AF%E5%A2%83%E7%9A%84%E5%BB%BA%E7%AB%8B">x86虚拟调试环境的建立</a></p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/283/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Linux内核源码阅读系列(2)-内核的构成 之 一</title>
		<link>http://www.adamjiang.com/archives/263</link>
		<comments>http://www.adamjiang.com/archives/263#comments</comments>
		<pubDate>Thu, 08 Jan 2009 15:54:48 +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=263</guid>
		<description><![CDATA[“Linux系统”，通常我们都这样叫它。但是，这个说法简便的同时却缺少了一个重要的信息和概念。很多Linux发行版本都有自己特别的名字，但事实上他们都应该有一个共同的名字，那就是“GNU Linux”。这个前缀很重要，它表明了Linux操作系统的构成。一个是Linux内核，另外一个是主要由GNU组织提供的各种基础应用程序。而这个系列的文章里，我的讨论对象仅仅是“Linux内核”。事实上Linux内核几乎就是操作系统，如果再给它添加一个shell用来提供用户操作接口的话。操作系统是提供一个用户概念里的计算机的软件。它最基础的功能是提供用户和计算机硬件之件的抽象——这件事情Linux内核+shell就能够完成。 Linux内核－她的芳名 Linux内核是模仿传统的UNIX内核创建的，整个内核是一个非常大的程序，并非多个程序的组合，所以，Linux内核通常被成为“宏内核”。又因为她的构成方法深受传统的学院派UNIX内核的影响——特别是Minix（注1）——所以，又被称为”Monolothic kernel”。与Linux内核组成方式相对的是GNU Hurd内核，它是GNU组织计划中的尚未长大的婴儿。Hurd中以一个叫做”March”的部件为核心将n个程序组合起来的方式构成内核。以这种方式组成的内核被称为“微内核”。 另一方面，Linux内核采用了模块化的方式组织自身，所有，Linux内核可以通过编译选项方便的进行裁减形成一个体积微小的操作系统内核，某些时候她可能会被叫做“微（小）内核”，总之，这个意义不要搞混阿，这里说的仅仅是她的苗条身段罢了。 微软的Windows操作系统是“微内核”，而由BSD UNIX发展而来的Apple公司的Mac OS则很显然是宏内核。 内核代码－你不可能对她一见钟情 Linux0.0.1的代码不过才6000-8000行，互联网的上的很多说法是说，这个版本的内核代码比较适合初学者阅读。但我的想法却恰恰与此相反，Linux0.0.1代码我记得在大学3年时候曾经翻看过，但是在此以后就没有什么印象了，因为我根本用不着它。反而是工作以后，整天跟Linux2.6内核接触，却一天天的熟悉起来。由此说明，数量的多少并不是决定一个事情难易程度的主要因素，关键看自己努力与否。hoho，大嘴大话。总之，虽然Linux2.6.18内核的代码已经庞大到吓人，但是我还是觉得读这个版本的源代码对实践有真正的意义。虽然整体很大，但是化整为零的方法开始吧。解开她的第一颗钮扣之前，你得先得到她。www.kernel.org提供了一个mirror列表，你可以选择距离你比较近的下载——如果能找到邻家女孩又何必舍近求远呢？如果你在中国国内，直接使用下面的命令就可以了。注意，给wget设置proxy这个事情不在我的博客里。:) $ wget http://www.cn.kernel.org/pub/linux/kernel/v2.6/linux-2.6.18.8.tar.bz2 解压，并将她放入你自己喜欢的目录比如~/workspace/kernel-src/。掀起盖头看一下吧。大概应该是长这个样子： adam@capricorn-x61:~/workspace/kernel-src/linux-2.6.18.8$ ls COPYING MAINTAINERS arch fs kernel scripts CREDITS Makefile block include lib security Documentation README crypto init mm sound Kbuild REPORTING-BUGS drivers ipc net usr kernel Linux内核的动作控制，基本功能 mm 内存管理（物理内存，虚拟内存） fs 虚拟文件系统（VFS），各个子目录下面是各种文件系统 net 网络协议，各个子目录下面是各种协议 ipc System V的进程间通讯（IPC）机制（共享内存，信号量，消息） init 各种Linux启动时需要的代码，初始化代码 [...]]]></description>
			<content:encoded><![CDATA[<p>“Linux系统”，通常我们都这样叫它。但是，这个说法简便的同时却缺少了一个重要的信息和概念。很多Linux发行版本都有自己特别的名字，但事实上他们都应该有一个共同的名字，那就是“GNU Linux”。这个前缀很重要，它表明了Linux操作系统的构成。一个是Linux内核，另外一个是主要由GNU组织提供的各种基础应用程序。而这个系列的文章里，我的讨论对象仅仅是“Linux内核”。事实上Linux内核几乎就是操作系统，如果再给它添加一个shell用来提供用户操作接口的话。操作系统是提供一个用户概念里的计算机的软件。它最基础的功能是提供用户和计算机硬件之件的抽象——这件事情Linux内核+shell就能够完成。</p>
<h3>Linux内核－她的芳名</h3>
<p>Linux内核是模仿传统的UNIX内核创建的，整个内核是一个非常大的程序，并非多个程序的组合，所以，Linux内核通常被成为“宏内核”。又因为她的构成方法深受传统的学院派UNIX内核的影响——特别是<a href="http://www.minix3.org/">Minix</a>（注1）——所以，又被称为”Monolothic kernel”。与Linux内核组成方式相对的是GNU Hurd内核，它是GNU组织计划中的尚未长大的婴儿。Hurd中以一个叫做”March”的部件为核心将n个程序组合起来的方式构成内核。以这种方式组成的内核被称为“微内核”。</p>
<p>另一方面，Linux内核采用了模块化的方式组织自身，所有，Linux内核可以通过编译选项方便的进行裁减形成一个体积微小的操作系统内核，某些时候她可能会被叫做“微（小）内核”，总之，这个意义不要搞混阿，这里说的仅仅是她的苗条身段罢了。</p>
<p>微软的Windows操作系统是“微内核”，而由BSD UNIX发展而来的Apple公司的Mac OS则很显然是宏内核。</p>
<h3>内核代码－你不可能对她一见钟情</h3>
<p>Linux0.0.1的代码不过才6000-8000行，互联网的上的很多说法是说，这个版本的内核代码比较适合初学者阅读。但我的想法却恰恰与此相反，Linux0.0.1代码我记得在大学3年时候曾经翻看过，但是在此以后就没有什么印象了，因为我根本用不着它。反而是工作以后，整天跟Linux2.6内核接触，却一天天的熟悉起来。由此说明，数量的多少并不是决定一个事情难易程度的主要因素，关键看自己努力与否。hoho，大嘴大话。总之，虽然Linux2.6.18内核的代码已经庞大到吓人，但是我还是觉得读这个版本的源代码对实践有真正的意义。虽然整体很大，但是化整为零的方法开始吧。解开她的第一颗钮扣之前，你得先得到她。<a href="http://www.kernel.org">www.kernel.org</a>提供了一个<a href="http://www.kernel.org/mirrors/">mirror列表</a>，你可以选择距离你比较近的下载——如果能找到邻家女孩又何必舍近求远呢？如果你在中国国内，直接使用下面的命令就可以了。注意，给wget设置proxy这个事情不在我的博客里。:)<br />
<code><br />
$ wget http://www.cn.kernel.org/pub/linux/kernel/v2.6/linux-2.6.18.8.tar.bz2<br />
</code><br />
解压，并将她放入你自己喜欢的目录比如~/workspace/kernel-src/。掀起盖头看一下吧。大概应该是长这个样子：</p>
<pre>adam@capricorn-x61:~/workspace/kernel-src/linux-2.6.18.8$ ls
COPYING        MAINTAINERS     arch     fs       kernel  scripts
CREDITS        Makefile        block    include  lib     security
Documentation  README          crypto   init     mm      sound
Kbuild         REPORTING-BUGS  drivers  ipc      net     usr</pre>
<table border="0">
<tbody>
<tr>
<td>kernel</td>
<td>Linux内核的动作控制，基本功能</td>
</tr>
<tr>
<td>mm</td>
<td>内存管理（物理内存，虚拟内存）</td>
</tr>
<tr>
<td>fs</td>
<td>虚拟文件系统（VFS），各个子目录下面是各种文件系统</td>
</tr>
<tr>
<td>net</td>
<td>网络协议，各个子目录下面是各种协议</td>
</tr>
<tr>
<td>ipc</td>
<td>System V的进程间通讯（IPC）机制（共享内存，信号量，消息）</td>
</tr>
<tr>
<td>init</td>
<td>各种Linux启动时需要的代码，初始化代码</td>
</tr>
<tr>
<td>crypto</td>
<td>加密处理的共通部分</td>
</tr>
<tr>
<td>block</td>
<td>块设备的设备控制代码的共通部分</td>
</tr>
<tr>
<td>drviers</td>
<td>各种设备的驱动</td>
</tr>
<tr>
<td>sound</td>
<td>音频设备的驱动等</td>
</tr>
<tr>
<td>arch</td>
<td>各种体系结构相关的代码</td>
</tr>
<tr>
<td>include</td>
<td>Linux内核在编译的时候需要查看的头文件，开发内核模块后者设备驱动时必须参照的编程接口。其中，各个体系结构相关的代码都是以asm开头</td>
</tr>
</tbody>
</table>
<p>从上面这个表你可以看到各个子目录的大致情况，不至于需要寻找代码时无处下手。除此以外，在内核顶层目录里面你看到的各个文件“自我描述”的文件名，这个就不多说了。值得一提的是Documentation目录。Linux内核采取Bazzar方式开发，开发者的组织比较分散，所以为了保证协同工作的顺利，Linux内核的文档应该说是比较详细的，而且其中不乏幽默和智慧。但Documentation目录下的文档还是准备给用户看的，你可以在里面找到与内核开发相关的很多内容。刚刚开始的话，可以读一读<br />
<code><br />
Documentation/CodingStyle<br />
</code><br />
。另外，最近有一本被翻译成中文版就变很火的书”Dreaming in Code”——中文名《<a href="http://www.dreamingincode.cn/">梦断代码</a>》，若你真的有空，并且真的不会对内核代码一见钟情，也可以读一读。另外，若是实在闲的慌可以上<a href="http://groups.google.com/group/pongba/browse_thread/thread/c48f9b8e3e435ae1#">这个邮件列表</a>去打嘴炮。你得知道，在中国，空谈和争论是一种破文化，恩。下面有一张她的X光片。我从小在医院长大，而小时候最要好的玩伴的老爸是放射科的大夫，看胸片那是“耳濡目染”，恩。也希望你喜欢。</p>
<div class="wp-caption alignnone" style="width: 522px"><img title="Linux内核模块概览" src="http://lh5.ggpht.com/_O-FwCW3mN_w/SWRY6suuh9I/AAAAAAAADx0/4fMJ6km2Ijw/s640/Diagram1-2.jpeg" alt="Linux内核模块以及它们之间的关系" width="512" height="388" /><p class="wp-caption-text">Linux内核模块以及它们之间的关系</p></div>
<p>这张图片之中，箭头的意义可能是模块之间调用或者关联关系，但是仅仅表示这个意思，并不严格，不要较真。</p>
<h3>内核的核-她的骨架</h3>
<h4>优秀的进程调度(process scheduling)</h4>
<p>我说Linux是女人你千万别不信，由她的复杂精巧的程度就可以看出性别来。而且，她有一个非常优秀的多任务处理机制——进程调度——这就更加说明她的性别。</p>
<blockquote><p>女性的大脑结构决定了她可以一心多用：她可以同时用手玩4—5个球；她可以一边用电脑，一边接电话，同时还听别人在她身后说话，并且在整个过程中又不断地喝咖啡；她可以在谈话中同时涉及好几个毫不相关的话题，变换五种语调来转移话题或强调重点。而男人只能分辨其中的三种语调，所以男人在听女人谈话的时候总是跟不上她的思路。</p></blockquote>
<p>详细的证明过程可以参照《<a href="http://vip.book.sina.com.cn/book/index_39465.html">为什么男人爱撒谎女人爱哭泣</a>》，-_-!!!。还有就是，如果你仔细看过她的启动消息或者日记，那个verbose的喋喋不休会让你抓狂——特别是新手上路的时候，这又是一个证据。</p>
<p>要搞懂她这种古怪的行为，就必须深刻认识到“在CPU数目限制的情况下，她事实上不可能同时执行很多任务，她只能通过调度来实现看上去同时执行多个任务的假象”这一点。关于这个smart girl怎么做到这一点的，后面我想细细道来。这里需要说的是，她这一部分的故事大概都在源代码的kernel目录下。</p>
<h4>同样优秀的中断处理(interrupt handling)</h4>
<p>中断，顾名思义就是暂时停止某件事情的执行，而后，当然你还是要想办法把它做完。就像我gf下厨房，炒菜的中途发现忘记了买酱油一样，她得先关掉炉火，出去弄瓶酱油回来再继续炒菜一样，其实她大多数时候都美其名曰为了提高效率，所以，给我五毛钱打发我去买，&#8230;&#8230;，这都什么比喻。Linux中断主要有两种，一个是硬件控制产生的需要CPU给予响应的中断，另外一种是时钟中断。此外，Linux内核的中断处理设计跟我gf一样smart。她特别为了1. 提高系统响应的效率；2. 减轻系统负荷；采取了一种叫做软中断(soft interrrupt)的机制——比如，给五毛钱打发出去打酱油的时候都有一些“乌龙”软语——事实上这是一种将中断延迟处理的机制。</p>
<h4>时钟(timer)</h4>
<p>Linux自带生物钟。这个时钟主要负责两个任务，1. 时刻；2. 时限；时刻，这个很容易理解么。比如晚上9点1分才回家，她会清楚的知道，并且问你这一分钟究竟上哪里鬼混了。时限，就是你出去打酱油不能超过10分钟就得返回，因为她在家里看秒表。</p>
<h4>系统调用(system call)</h4>
<p>能够让她帮你做事的唯一交流手段。一般这里的确需要一些鲜花和巧克力，但事实上你得按照和她约定好的方式和她交流，切记“理解万岁”。Linux2.6内核中，内核向普通的用户应用程序提供2种方式的系统调用方式，一种是从Linux诞生以来就有的系统调用方式；另一种是2.6的新特性，在x86结构上可以通过sysenter命令高速执行的系统调用。事实上系统调用在内核中是以一种非常类似于中断处理的方式实现的，甚至可以说系统调用就是用户应用程序（而不是硬件）产生的中断。而系统调用的执行结果，内核通过像普通函数一样的返回值通知用户空间的应用程序。系统调用事实上也是操作系统利用CPU提供的保护模式将内核代码的执行和普通应用程序执行划分开来的结果，由此提高内核的健壮性。受保护的内核代码不会被普通应用程序直接访问，Linux在执行系统调用的时候，她会将改变CPU的执行模式，并在这个模式中执行内核代码。</p>
<h4>排他和同期(mutual exclusive &amp; synchronize)</h4>
<p>Synchronize这个词事实上原本的意义就是时间上相同，因为它含有一个&#8217;chron&#8217;词根，这个词根表示时间的意思。跑题了。Linux需要在某些情况下独占资源，又在某些情况下需要协调和同步工作，所以，内核自身必须准备一些机制以保证自身的正常运行。这些包括，1. 信号量(semaphore)；2. spin lock；3. RCU-Read Copy Update等等等。总之，这是Linux自身具有的特性，并且是基础特性。</p>
<p>基本上，在内核代码的kernel目录中你可以看到上面提到的这些内容了。因为它们组成了Linux的基础功能，所以通常被合在一起被叫做”Kernel Primitive”。装B的翻译方法通常是“内核原语”，但是我很害怕讲错，所以通常不这么叫它，再次言而总之，这个部分是你只要知道内核的核，就一切OK了。</p>
<p>－－<br />
1. Minix却是<strong>微内核</strong>的</p>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/263/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>在Google group开设”嵌入式Linux”新闻组</title>
		<link>http://www.adamjiang.com/archives/244</link>
		<comments>http://www.adamjiang.com/archives/244#comments</comments>
		<pubDate>Tue, 06 Jan 2009 11:34:18 +0000</pubDate>
		<dc:creator>jcadam</dc:creator>
				<category><![CDATA[嵌入式Linux]]></category>
		<category><![CDATA[linux]]></category>
		<category><![CDATA[opensource]]></category>

		<guid isPermaLink="false">http://www.adamjiang.com/?p=244</guid>
		<description><![CDATA[今天突发奇想的在Google group搜索”嵌入式Linux”，发现居然还没有人创建这个新闻组。于是，很自然的我就当仁不让了。 人是很奇怪的动物，即便是在我建立这个新闻组不到6小时，但是已经乐在其中了。这也许就是opensource运动中motivation的核心——人不仅仅因为得到钱才开心。 废话不多说了，如果你对嵌入式软件开发，特别是嵌入式Linux开发有兴趣的话，欢迎加入我们。 订阅 嵌入式Linux 电子邮件： 访问此论坛]]></description>
			<content:encoded><![CDATA[<p>今天突发奇想的在Google group搜索”嵌入式Linux”，发现居然还没有人创建这个新闻组。于是，很自然的我就当仁不让了。</p>
<p>人是很奇怪的动物，即便是在我建立这个新闻组不到6小时，但是已经乐在其中了。这也许就是opensource运动中motivation的核心——人不仅仅因为得到钱才开心。</p>
<p>废话不多说了，如果你对嵌入式软件开发，特别是嵌入式Linux开发有兴趣的话，欢迎加入我们。</p>
<div id="google-group">
  <img src="http://groups.google.com/groups/img/3nb/groups_bar_zh-CN.gif" height=26 width=132 alt="Google 网上论坛"></p>
<p><strong>订阅 嵌入式Linux</strong></p>
<form action="http://groups.google.com/group/embedded-linux-zh-CN/boxsubscribe">
<input type=hidden name="hl" value="zh-CN">
<tr>
<td style="padding-left: 5px;"> 电子邮件：<br />
<input type=text name=email>
<input type=submit name="sub" value="订阅">
  </td>
</tr>
</form>
<p><a href="http://groups.google.com/group/embedded-linux-zh-CN?hl=zh-CN">访问此论坛</a></p>
</div>
]]></content:encoded>
			<wfw:commentRss>http://www.adamjiang.com/archives/244/feed</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
	</channel>
</rss>

