Feedburner and Feedsky 的点击统计

我发现我真傻。

由于文章的原始链接被 Feedburner 修改,而 Feedburner 的子域名被封,以前一直无法从 Google Reader 里点击文章标题进入我的博客网站,我也一直以为这是件没办法的事情。今天无意中看别人的文章才发现,这是因为在 Feedburner 中启用了 Item link clicks 功能的缘故,我郁闷啊!

所以今天我把 Feedburner 和 FeedSky 的文章点击统计功能都关闭了,啊哈,链接干净了!现在总算可以直接点击订阅的文章标题进入我的博客文章页面了!

我的北京照片5

播放器看来还是 Amarok 更好,至少它能显示一部分中文歌曲文件名,不像 XMMS,怎么设置都看不到中文字(注意到没有,Ubuntu 8.04 的 Notification Area 现在带天气播报):

Amarok 播放器

写代码累了,拿起飞镖休息一下吧!虽然最近家乐福可能人少点儿,这个小玩意儿我还是在沃尔玛买的,才 11 块:

镖盘

无意间在卓越网看到下面这个木头小镜子,很喜欢,就买了下来准备送给女友:

很精致的小镜子

上周末班里组织春游去北京植物园玩,下面是同志们的合影:

植物园之大合照

又想起去年暑期的一张照片,小百合上版友在北京混的几个南大人,北航的兄弟们看了可能比较熟悉,这就是在北航西南门丽晶酒店门口照的。从左到右:我, baosheng,proline, daoming, ufx222, xum84(均为百合ID):

南大的北漂们

昨天随手搜了一下,发现 Google 搜索我的名字“杨文博”,我已经占到第一位了。用某人的话说:”做网络的人把自己做到搜索引擎第一位是基本要求。“因此,纪念我刚刚达到基本要求 ^_^:

Google 我的名字

在 Dell Latitude D630 上硬盘安装 Ubuntu Linux 8.04

最后更新时间:2008年4月18日

摘要

这份文档主要描述了我在自己的 Dell Latitude D630 上安装 Ubuntu Linux 8.04 的过程。

目录

1. 介绍
2. 安装
3. 效果截图
4. 总结

1. 介绍

虽然一直想忍着等 Ubuntu Linux 8.04(Hardy Heron) 正式发布了再安装它,但最后还是受不了诱惑,在还有 6 天就发布时候当了一次小白。这次 Ubuntu 真的是让我相当相当满意,安装过程什么问题都没有出,所有硬件均一步识别,太爽了!这次写的安装介绍,要比上次那篇简单多了,我真心希望以后的 Ubuntu 版本安装的时候再也不用看别人的安装介绍了。

而且这次 Ubuntu Studio 的 theme 也比 7.10 漂亮许多,Compiz-fusion 的 3D 桌面特效也更好了,真酷!兄弟姐妹们,来玩 Ubuntu 吧!

2. 安装

[安装基本操作系统]
由于我电脑上本来就有 Ubuntu 7.10 和 Cent OS 5.1,所以我的 Ubuntu 8.04 是通过修改 grub 的 menu.lst 来进行硬盘安装的。

首先,下载安装光盘镜像文件:
$ wget -c http://cdimage.ubuntu.com/daily/current/hardy-alternate-i386.iso
由于现在 Ubuntu 8.04 还没有正式发布,所以我是从 daily build 的地址下的,当看到这篇文章时可能它已经发布了,那就请到官方公布的地址下吧。

其次,下载硬盘安装文件:
$ wget http://archive.ubuntu.com/ubuntu/dists/hardy/main/installer-i386/current/images/hd-media/vmlinuz
$ wget http://archive.ubuntu.com/ubuntu/dists/hardy/main/installer-i386/current/images/hd-media/initrd.gz

将这三个文件放在某个分区根目录下,再修改 /boot/grub/menu.lst,在列表最后添加上以下几行:
title Ubuntu 8.04 Install Entry
root (hd0,3)
kernel /vmlinuz root=/dev/ram ramdisk_size=100000 devfs=mount,dall
initrd /initrd.gz
请注意第二行的(hd0,3)是指放镜像文件那个分区编号,请根据自己情况自行修改。一般情况下如果放到了 /dev/sdan 下的话,这个就应该是 (hd0,n-1)。

然后重启电脑,在 grub 中选择引导项 Ubuntu 8.04 Install Entry 进行安装,安装过程不赘述。我一般选择系统语言为英语,地域为中国。

[修改更新源列表]
选择速度最快的源,将 /etc/apt/sources.list 中默认的官方源替换掉,比如我的 sources.list 文件内容就是
deb http://debian.ustc.edu.cn/ubuntu/ hardy main restricted universe multiverse
deb http://debian.ustc.edu.cn/ubuntu/ hardy-updates main restricted universe multiverse
deb http://debian.ustc.edu.cn/ubuntu/ hardy-security main restricted multiverse universe
deb http://debian.ustc.edu.cn/ubuntu/ hardy-backports main restricted universe multiverse

[更新操作系统]
$ sudo apt-get update
$ sudo apt-get upgrade

[安装受限驱动]
在更新完操作系统后,System Notifacation Area(桌面右上角) 中会出现提示安装受限驱动的气泡,点击安装即可。

[安装中文输入法]
在 System->Administration->Language Support 中选择安装中文支持,并在下方勾选上 Enable support to enter complex characters. 系统会自动下载并安装中文支持和中文输入法,安装完后系统会要求重启,重启以后就可以在软件中直接用 Ctrl+Space 调出输入法了。^_^ 比以前的版本配置中文输入法简单太多了!!!

[安装 Windows XP 中文字体]
这是我写的一个小脚本,可以将双系统的 XP 字体拷贝到 Linux 下并使用它们。注意,这将涉及到版权问题,如果不确信的话,请跳过这一步。
$ wget http://share.solrex.org/scripts/install_win_CN_fonts_to_linux.sh
$ sudo sh install_win_CN_fonts_to_linux.sh
然后重新登录 X window

[安装 Ubuntu Studio 主题]
Ubuntu Studio 的黑色桌面主题是我非常喜欢的桌面主题,再配上诺贝尔和平奖得主戈尔的获奥斯卡最佳纪录片奖的电影 An Inconvenient Truth 的灰色背景海报,简直太配了。如果不喜欢,请跳过这一步。
$ sudo apt-get install ubuntustudio-theme
然后在 System->Preference->Appearance 中选择使用 Ubuntu Studio Theme。

[安装 Compiz-Fusion 3D 桌面特效引擎]
$ sudo apt-get install compiz-fusion-plugins-* compizconfig-settings-manager emerald
然后在 System->Preference->Advanced Desktop Effect Settings 中配置喜欢的桌面特效。

[安装 Mplayer/SMplayer 媒体播放器以及解码器]
$ sudo apt-get install mplayer smplayer
下载最新解码器:
$ wget -c http://www.mplayerhq.hu/MPlayer/releases/codecs/all-20071007.tar.bz2
将最新解码器库扔到 /usr/lib/win32 目录下:
$ tar -xjvf all-20071007.tar.bz2
$ sudo mkdir /usr/lib/win32
$ sudo mv all-20071007/* /usr/lib/win32
这样就可以使用 Mplayer/SMplayer 播放几乎任何格式的影片了。

[安装 星际译王(StarDict) 和辞典包]
$ sudo apt-get install stardict
然后到星际译王官方网站下载安装需要的辞典包

[安装 MP3 播放器 Amarok/xmms]
$ sudo apt-get install amarok

[安装 BBS 登录软件 Qterm]
$ sudo apt-get install qterm

[安装聊天工具 Pidgin, Eva]
$ sudo apt-get install pidgin eva

[安装邮件客户端 ThunderMail]
$ sudo apt-get install mozilla-thunderbird

[安装系统启动项管理软件 sysv-rc-conf]
$ sudo apt-get install sysv-rc-conf

[安装 vim 完全版(gvim)]
$ sudo apt-get install vim-full

[安装编译工具库, g++ 和 javac]
$ sudo apt-get install libc6-dev g++ g++-4.2 sun-java6-jdk

[安装压缩解压缩工具 unzip, unrar, 7zip]
$ sudo apt-get install unzip unrar p7zip-full

[安装 Internet 时间同步工具 ntp]
$ sudo apt-get install ntp

[安装程序开发工具 kscope, eclipse, SVN tool, 十六进制文本编辑器, 网络文件系统]
$ sudo apt-get install kscope eclipsesubversion hexedit nfs-client

[安装工程图画图工具]
$ sudo apt-get install dia kivio

[安装摄像头查看工具]
$ sudo apt-get install cheese

[安装 CHM 文件查看工具]
$ sudo apt-get install chmsee kchmviewer

[安装 tex 编辑编译工具]
$ sudo apt-get install texlive cjk-latex kile

[安装 dos unix 文件转换工具]
$ sudo apt-get install tofrodos

[安装 Adobe Reader]
$ sudo apt-get install acroread
如果上面命令不管用,用下面方法下载安装:
$ wget -c http://debian.cn99.com/ubuntu-cn/dists/gutsy/main/binary-i386/adobe/AdobeReader_chs-8.1.1-1.i386.deb
$ sudo dpkg -i AdobeReader_chs-8.1.1-1.i386.deb

3. 效果截图

Amarok 播放器

4. 总结

Ubuntu 8.04 对电脑硬件的支持又进了一步,至少在我以前曾经有问题的显卡和声卡上都没有出现问题,让人很开心。而更好看的桌面主题,更酷的 3D 桌面效果,更简化的安装过程,更完善的多语言支持,真的很赞,我相信 Ubuntu 将借此进一步巩固和扩大它在 Linux 桌面市场占有率。大家都来用 Ubuntu 吧,它可 Windows 酷多了!

同一个世界,同一个论坛!

今天在我本科导师的博客里看到了这个:英文试译:卡弗蒂的话及某网页的跟贴,又在豆瓣看到这个:看看河南人的处事原则吧,又想起我师兄大四时做的一件事情在百合上引起的那次名为“南大第一牛人现身”的讨论(讨论已经找不到了)。

忽然觉得,这个世界的论坛怎么那么像呢!

而且,这个世界真的有点儿可笑,看到卡弗蒂的话:

嗯--,我不知道中国是不是有什么不同,但是我们和中国的关系当然已经不同了。因为伊拉克战争,我们连眼球都已经被中国吊住了。一个原因,他们手中握有数万亿计的我们印的钞票。不仅如此,我们对华存在着数以万亿计贸易赤字,还依然不断进口着他们的含铅的玩具和有毒的宠物食品等垃圾,却在向他们出口工作。(我们)在他们那一个月工资只需付一美元的地方,生产出我们在沃尔玛买到的玩意儿。所以我想我们和中国的关系当然是改变了。我想,他们基本上五十年来是一样的,同样的一群匪类和暴徒。

难道诸位不感到熟悉吗?想想前几年(包括现在)某些人在论坛上对河南人的谩骂和妖魔化形容,比如这个,比如这个,比如这个,还有豆瓣这个,大概不比卡弗蒂更礼貌吧。我现在好奇的是,那些曾经对河南人恶语相向的人,现在怎么看卡弗蒂呢?

如果你连尊重和善意地对待自己的同胞兄妹都不能,有什么权利要求外国人对你以礼相待呢?(这句话只针对那些对河南人保持深刻偏见的中国人!)

陈良宇和陈寿福

中共中央政治局原委员、中共上海市市委原书记陈良宇被天津市第二中级人民法院认定犯受贿罪,判处有期徒刑十四年,没收个人财产人民币30万元;犯滥用职权罪,判处有期徒刑七年,两罪并罚,决定执行有期徒刑十八年,没收个人财产人民币30万元。

珊瑚虫版QQ作者,北京理工大学计算中心老师陈寿福被深圳南山区法院认定犯侵犯著作权罪,判处有期徒刑三年,并处罚金人民币120万元。

两则新闻充分显示了中国政府重视知识产权保护,打击软件侵权盗版行为,维护著作权人利益的决心!!!

PS: 看不懂的同学请做一道成语填空:窃钩者_,窃_者侯

牙龈出血

大约从大三后期我就开始在刷牙的时候牙龈出血,然后断断续续一直延续到现在。刚开始还去校医院看看,医生给拿些碘甘油什么的抹抹,后来看没效果也就懒得去了,就好像我的慢性鼻炎一样,觉得忍着得了。其实一直想到医院洗洗牙什么的,看看有什么效果,可是因为一直忙,就没有机会去。

前一段回南京那几天忽然牙龈出血变得严重了些,这心里也有点儿害怕,因为长期的牙龈出血可能和血液病有联系。虽然回来之后减轻了许多,还是决定到医院去看看。只有玉泉路本部校医院能看口腔科,今天正好找导师领报销的书费,顺便到校医院看一下牙龈。

中科院研究生院的校医院特别小,看了一下,貌似跑到那里的都是要看口腔科的,而且口腔科只有下午上班,每天只看六个病人。幸亏我去的比较早,挂到了第二个号,基本上十五分钟后六个人的名额就满了。两个是看智齿的,一个是门牙后面长了个小牙,剩下两个是本部的同学,挂了号直接回宿舍等去了。

大夫是个女的,说话特别温柔,看了下我的牙龈,说是因为牙石太多引起的牙龈发炎造成的,建议我洗一次牙。那既然来了,也懒得再进医院了,就顺便在校医院洗了一下。洗牙是自费,不算公费医疗里的,一百块钱,和南大校医院的要价差不多。其实我觉得设备可简陋了,好像就一个超声波的洗牙头,加上冲水装置,把那个东西放到嘴里七刮八刮的,刮一会儿吐一下积在嘴里的血水,大概花了二十分钟左右,流了不少血。刮完了一舔牙齿,还真不一样,尤其觉得下面几颗门牙细了许多,大概是附着在上面的牙石被刮掉了。

有意思的是那医生看了我的牙,问我有没有做过牙齿矫正,说我的牙长得真是特别整齐,让我小小自豪了一把。还专门让她检查了一下有没有蛀牙,因为我从小就爱吃而且吃过许多甜食,我一直以为我肯定有蛀牙,结果居然没有。以后可是要好好的保护了,不然牙痛可是真的很受罪。

唉,也不知道洗洗牙有没有效果,但愿牙龈别再出血了!

收到 Google Adsense PIN Letter

今天总算收到了 Google Adsense 寄给我的 PIN 信封,距离它公元 2008 年 3 月 24 日给我的电子邮件通知正好两周。现在貌似 Google 改政策了,以前都是 Adsense 帐户赚到 $50 以上才会寄 PIN 的,现在仅仅 $10 以上就邮寄 PIN 了。我的 Google Adsense 帐户开了两个月零七天,才赚了 $14.09,唉,真慢啊,什么时候才能到 $100 呢?

三星手机 Java 开发-Hello World 篇

摘要:

这篇文章主要介绍了在 Windows 平台上使用 NetBeans 和 Samsung SDK 构建三星手机 Java 开发环境,建立和调试简单 Java 程序的过程。

目录:

1. 工具
2. 安装和配置开发环境
3. Hello World 程序
4. 在三星模拟器(Samsung Emulator)上调试
5. 结论
6. 参考文献

1. 工具

Windows XP: SamsungSDK 不支持 Windows Vista.

JDK version: 1.6.0_03: 这个版本不重要,自己去下载好了。

NetBeans IDE 6.01(Mobility) Chinese Edition:
http://zh-cn.netbeans.org/download/6.0/ml/netbeans-6.0.1-ml-mobility-windows.exe

Samsung Java SDK 1.0.2
http://developer.samsungmobile.com/Developer/resources/SamsungSDK/SamsungSDK_1.0.2.zip
这个版本的 SamsungSDK 支持的三星手机型号为:
D900 (D600, D820, T809)
E200 (E380, E390, E500, E780, E830, P200, T509)
E250
E590
E740
E790 (E490, E530, E570, E620, E720, E880)
E900 (D800, D830, D840, P900)
F500 (F500, P300, X820, Z370)
J600
L760 (Z140, Z150, Z170, Z230, Z240, Z240E, Z300, Z310, Z360, Z500, ZM60, ZX10, ZX20, ZV40, ZV60)
P310
U100
U600 (U300)
U700 (P910, P920, P940, Z400, Z540, Z560, Z720, ZV50)

2. 安装和配置开发环境

我不知道 JDK 是否必须安装的,因为我电脑里老早就安装上了,但是做 JAVA 开发嘛,JDK 总是需要的,所以建议首先安装 JDK。

安装完 JDK 之后,安装 SUN 公司的 JAVA 集成开发环境 NetBeans。为什么是 NetBeans 而不是 Eclipse?因为 SamsungSDK 官方支持 NetBeans。

当 NetBeans 安装完成后,接着安装 SamsungSDK。需要注意的一点是,由于 SamsungSDK 1.0.2 支持的 NetBeans 版本是 5.5,所以在安装过程中要选择是否安装 NetBeans 插件时,请选择否,因为我们使用的 NetBeans 版本是 6.01。

既然无法自动安装 NetBeans 插件,就需要我们在 NetBeans 中手动添加插件了。具体步骤如下:

一,从 NetBeans 菜单中选择“工具->Java平台”,进入 Java 平台管理器;(我们的 NetBeans 是中文版 *_*,请英文版用户自行理解。)
二,点击“添加平台”按钮,进入“选择平台类型”步骤,选择“Java ME MIDP 平台仿真器”,进入下一步;
三,在“选择要在其中搜索平台的目录”对话框中,选择 SamsungSDK 的安装目录,比如我的是 D:\Program\SamsungSDK,点击确定;
四,这时在“平台文件夹”对话框中会出现三个要检测的平台,将三个平台都勾选上,进入下一步;
五,在“已检测到的平台”对话框中,将三个平台都勾选上,选择“完成”,这样我们的开发环境就配置完成了。

3. Hello World 程序

下面介绍在 Samsung Java 开发环境下如何建立并在模拟器上运行一个 Hello World 项目:

一,“文件->新建项目”,在对话框中项目“类别”选择“Mobility”,然后在右侧“项目”中选择“MIDP 应用程序”,点击下一步;
二,将项目名字更改为“HelloWorld”,项目位置可以自行修改,比如我的就是 D:\J2ME,下面两个“设置为主项目”和“创建 Hello MIDlet”保留为默认值,进入下一步;
三,在“选择缺省平台”对话框中,“仿真器平台”选项选择为“SamsungSDK 1.0”,在设备下拉条中,选择目标平台,比如我的手机是 E908,和 E900 是一类,我就选择“SGH-E900”,其它选项保持不变,进入下一步;
四,在更多配置中用户可以自定义更多配置,刚开始使用可以保持不变,点击完成。

这样我们就建立了一个 Samsung Java 的 Hello World 项目,可以查看它的源代码。由于我们在配置时选择了“创建 Hello MIDlet”选项,此时的源程序已经包含了打印一个“Hello World”的功能,我们只需要在菜单中选择“生成->生成主项目”,即可生成 Hello World 项目。

生成项目后,在项目文件夹里可以找到 dist 目录,比如我的就是 D:\J2ME\HelloWorld\dist,这个目录里面包含了可以在 SGH-E908 上安装并运行的 JAVA 程序:HelloWorld.jar 和 HelloWorld.jad。看着很眼熟吧,对,这和普通的 JAVA 游戏形式是一样的,我们可以把这两个文件下载到手机里并安装它。(不同版本安装方法不一样)

SGH-E908 的安装方法是:先将 jar 和 jad 文件下载到手机的 other files 文件夹中,在待机状态下输入 *#9998*5282#,然后选 3,输入密码 235282,选择 jad 文件安装即可。

安装完程序后,我们可以在自己的 Java world 中找到它,运行后,屏幕上会打印出来一行字符:HelloHello, World! 这样,我们的第一个 Samsung Java 程序就成功了。

4. 在三星模拟器(Samsung Emulator)上调试

当然,我们写嵌入式程序不可能一次成功,总会有需要找 BUG 的时候,那么如何进行调试呢?仍然以我们 Hello World 程序为例:

一,先在程序中设置断点,在要设置断点的行前点击边框即可,比如我在 startApp() 函数中的 initialize (); 一行前设置了一个断点;
二,选择“运行->调试主项目”,就可以开始我们的调试了,耐心等待一会儿,会发现弹出了一个 E908 模拟器的窗口,窗口中写着:Select on to launch,因为只有唯一的一个 HelloMIDlet,所以我们直接选择右下角的 Launch,直接点 Launch 是没用的哦,你要点模拟器的手机键盘,作用和按下手机上某个键是一样的。
三,点下 Launch 之后,激活窗口会重新返回到 NetBeans 中, initialize (); 行前有一个右箭头,指代程序已经运行到断点,我们点“运行->继续”,程序就会继续运行,在模拟器中打印出:HelloHello, World!,并在左下显示 Exit 标志,表示程序运行结束。

如果在第三步产生问题,可能是由于在前面介绍中,我们先“生成主项目”造成的调试信息缺失导致的,可修改设置,更直观的方法是重新建立新项目并跳过生成主项目那一步,使用“运行->运行主项目”或者“运行->调试主项目”来生成主项目文件。

5. 结论

三星手机提供的开发环境在一定程度上还是比较易于使用的,这篇文件对三星手机开发环境的建立做了一个简要的介绍,提供了中文入门导引,降低了中文世界初学者学习的难度。

6. 参考文献

[1] Samsung SDK 1.0.0 Documentation

南京的春天

这次回南京,算是真正地感受了一下南京城的春天。大学的前三年,我都在“浦口大学”(南京大学浦口校区)度过,而回归本部的大四,我又跑到北京实习了 6 个月,和石头城的春色擦肩而过。

以前在南京没怎么逛过公园,大学第一年十一假期去了玄武湖公园,在公交车上被挤成鱼罐头了;大学第二年五一去中山陵公园,在孙中山墓室外被挤成鱼苗了;只有两次集体出行玩得还可以,一次去梅花山,光忙着爬紫金山了;一次去古林公园,光忙着烧烤了。

在临毕业的时候和女友一起去过雨花台公园,其实雨花台不仅仅是个烈士陵墓,里面还有一些其它的小景点,比如方孝孺墓等等。整个公园里树木保存得不错,还有几个小小的植物园,蛮值得逛逛的。

这次回去正好赶上南京的春天,太阳好的日子不在室外享受享受就太可惜了。于是我们不是在校园里晒太阳,就是出去逛逛街,也顺便逛了两个公园:燕子矶和绣球公园。

大名鼎鼎的燕子矶,居然那么小,是让我意想不到的。整个公园绕一圈下来连十分钟都不用,所以开发者就将燕子矶和周围三个景点一起捆绑售票,但是其它三个就更可想而知了,仅仅是非常小的两个溶洞和一个道观。我们去的时候还不算旺季,所以公园里几乎没人,在燕子矶公园最好的就是看长江的角度很棒,其它的都是一般啦。

绣球公园其实是个市民公园,免费的,里面好多老爷子老太太下棋,打扑克,唱戏,抖空竹,是一个休闲的好地方。里面有一个景点,据说是朱元璋的马皇后的绣鞋,比较汗的是,那个鞋是铜的...

在校园里晒太阳的时候,居然发现南大也有樱花,大约七八株的样子,开得很茂盛,很多人围着拍照。我也用手机拍了几张,只是效果都不大好。樱花真的很漂亮,尤其是一阵微风吹过,花瓣如雨下的时候,美极了!可想而知以樱花闻名的武汉大学春天的校园该有多好看了!

中科院 IP 网关 Linux 登录客户端版本 1.0 发布

愚人节和大家开了个 小玩笑,不过这次可不是玩笑了。CAS NET 正式发布版本 1.0,官方主页:http://share.solrex.org/casnet/

CAS Net 是中科院内部 IP 控制网关的 Linux 登录客户端,此软件完全使用 Python 语言写成,同时支持命令行和图形界面,使用简单,安装方便,实乃中国科学院 Linux 使用者居家旅行必备之良品 :)。

最新版本(1.0)特性:

1. 客户端同时具有命令行和图形界面,满足不同用户需要。
2. 可设置选项多,拥有较高扩展性。
3. 可保存用户设置,登录简单快捷。
4. 纯 Python 编程,修改简单,扩展性强,可移植到不同操作系统平台。
5. 开放源代码,确保程序无后门。

软件效果截图:

Ubuntu 7.10 系统下截图
Ubuntu 7.10 系统下截图

中科院 IP 网关 Linux 登录客户端版本 1pre 发布-注意日期

更多请访问官方主页:http://share.solrex.org/casnet/

CAS Net 是中科院内部 IP 控制网关的 Linux 登录客户端,此软件完全使用 Python 语言写成,同时支持命令行和图形界面,使用简单,安装方便,实乃中国科学院 Linux 使用者居家旅行必备之良品 :)。

最新版本(1 pre)特性:

  1. 客户端同时具有命令行和图形界面,满足不同用户需要。
  2. 可设置选项多,拥有较高扩展性。
  3. 可保存用户设置,登录简单快捷。
  4. 纯 Python 编程,修改简单,可移植到不同 Linux 平台。

感谢列表:

  • giv<goldolphin[at]163.com>: 命令行客户端脚本的原型作者

版权声明:

Copyright (C) 2008 Wenbo Yang<http://solrex.org> 祝大家节日快乐!

本软件遵从 GPL 协议<http://www.gnu.org/licenses/gpl.txt>,在此协议保护之下,您可以自由地使用、修改或分发本软件。

Ubuntu 7.10 系统下截图CentOS 5.1 系统下截图

solrex.cn 的新子域名

由于对某些服务感到新鲜,所以为我的域名添加了几个子域名:

1. http://m.solrex.cnhttp://wap.solrex.cn

我的手机版博客,使用 WireNode 提供的服务,上面两个域名均重定向到 http://solrex.wirenode.mobi,我用自己的手机访问测试正常。

2. http://feed.solrex.cnhttp://feeds.solrex.cn/solrex

将博客 FeedSkyFeedBurner 烧录绑定到了自己的域名,不影响原有的 feed 地址。

其中 http://feed.solrex.cnFeedSky 提供的域名绑定服务,http://feeds.solrex.cn/solrexFeedBurner 提供的 MyBrand 服务,这两个域名均为直接 CNAME 绑定。所以,FeedBurner 那个在国内仍然无法正常访问,不过可以通过阅读器订阅。

悲惨的日子

看着早晨天上弥漫的沙尘,我本来期望着今天会有一场沙尘暴,来加深一下北京奥运这一年给我留下的美好回忆。不过不幸的是,老天似乎不乐意做点什么让这一年显得更与众不同。

是的,这一年没那么美好。现在我面前放着一个圆形的不锈钢饭盒,里面还留着一些晚饭吃方便面留下的残渣。桌子下面扔着一个曾经装满零食现在只剩下豆奶粉和榨菜的大塑料袋,柜子里堆满了超市小票银行签单等乱七八糟的东西。一堆没洗的袜子,脱下来的厚衣服也只是挂了起来,唉,看起来一团糟。

有个朋友在小百合上问我,最近干嘛呢?是啊,最近干嘛呢?我说我主要在看 Desperate Housewives,顺便听听课,看看书,写写博客。这的确是实话,在不到一个星期的时间里,我看完了 3 个 season(有人评论说这不像是男人的作风)。不过还没有提某天玩三国 11 一直到早上五点(虽然再看到它都有生理反应了),还有一堆从 FTP down 下来的电影。

我总是在别人面前试图表现出来积极向上的样子,让别人说,啊哈,看这家伙!但其实这家伙很虚伪和愚蠢。Gosh,我怎么能做那种浪费时间浪费生命挥霍健康的事呢?不过我确实做了!

没有朋友,没有运动,没有活动,没有美食,我大概体验到了研究生院的美好生活,这还不包括即将到来的论文的压力。当我做决定要读研究生时,我真的憧憬这段时间将是 happy learning time,轻松地看看书,做做自己感兴趣的研究,像所有期待美好生活的人一样,我失望了。这真是一段糟糕的生活,各种压力,各种沮丧!

这时我居然听到笔记本电脑发出“唧唧”的声音,或许它也想说我有压力?

在一堆糟糕的事情中寻找点能让人舒服的东西其实也不难,比如南京移动的这个活动,每答对一题送我 0.5 元话费,我想,或许我应该为某个体育盛会在北京召开感到开心。

每个人都会做一些事情来摆脱困扰,我觉得,我应该多花些时间去南京陪陪我的 xixi,南京的三月应该比北京更生机勃勃吧!

POSIX 线程取消点的 Linux 实现

摘要:

这篇文章主要从一个 Linux 下一个 pthread_cancel 函数引起的多线程死锁小例子出发来说明 Linux 系统对 POSIX 线程取消点的实现方式,以及如何避免因此产生的线程死锁。

目录:

1. 一个 pthread_cancel 引起的线程死锁小例子
2. 取消点(Cancellation Point)
3. 取消类型(Cancellation Type)
4. Linux 的取消点实现
5. 对示例函数进入死锁的解释
6. 如何避免因此产生的死锁
7. 结论
8. 参考文献

1. 一个 pthread_cancel 引起的线程死锁小例子

下面是一段在 Linux 平台下能引起线程死锁的小例子。这个实例程序仅仅是使用了条件变量和互斥量进行一个简单的线程同步,thread0 首先启动,锁住互斥量 mutex,然后调用 pthread_cond_wait,它将线程 tid[0] 放在等待条件的线程列表上后,对 mutex 解锁。thread1 启动后等待 10 秒钟,此时 pthread_cond_wait 应该已经将 mutex 解锁,这时 tid[1] 线程锁住 mutex,然后广播信号唤醒 cond 等待条件的所有等待线程,之后解锁 mutex。当 mutex 解锁后,tid[0] 线程的 pthread_cond_wait 函数重新锁住 mutex 并返回,最后 tid[0] 再对 mutex 进行解锁。

1  #include <pthread.h>
2
3  pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
4  pthread_cond_t   cond = PTHREAD_COND_INITIALIZER;
5
6  void* thread0(void* arg)
7  {
8    pthread_mutex_lock(&amp;mutex);
9    pthread_cond_wait(&amp;cond, &amp;mutex);
10   pthread_mutex_unlock(&amp;mutex);
11   pthread_exit(NULL);
12 }
13
14 void* thread1(void* arg)
15 {
16   sleep(10);
17   pthread_mutex_lock(&amp;mutex);
18   pthread_cond_broadcast(&amp;cond);
19   pthread_mutex_unlock(&amp;mutex);
20   pthread_exit(NULL);
21 }

22 int main()
23 {
24   pthread_t tid[2];
25   if (pthread_create(&amp;tid[0], NULL, &amp;thread0, NULL) != 0) {
26     exit(1);
27   }
28   if (pthread_create(&amp;tid[1], NULL, &amp;thread1, NULL) != 0) {
29     exit(1);
30   }
31   sleep(5);
32   pthread_cancel(tid[0]);
33
34   pthread_join(tid[0], NULL);
35   pthread_join(tid[1], NULL);
36
37   pthread_mutex_destroy(&amp;mutex);
38   pthread_cond_destroy(&amp;cond);
39   return 0;
40 }

看起来似乎没有什么问题,但是 main 函数调用了一个 pthread_cancel 来取消 tid[0] 线程。上面程序编译后运行时会发生无法终止情况,看起来像是 pthread_cancel 将 tid[0] 取消时没有执行 pthread_mutex_unlock 函数,这样 mutex 就被永远锁住,线程 tid[1] 也陷入无休止的等待中。事实是这样吗?

2. 取消点(Cancellation Point)

要注意的是 pthread_cancel 调用并不等待线程终止,它只提出请求。线程在取消请求(pthread_cancel)发出后会继续运行,直到到达 某个取消点(Cancellation Point)。取消点是线程检查是否被取消并按照请求进行动作的一个位置。pthread_cancel manual 说以下几个 POSIX 线程函数是取消点:

pthread_join(3)
pthread_cond_wait(3)
pthread_cond_timedwait(3)
pthread_testcancel(3)
sem_wait(3)
sigwait(3)

在中间我们可以找到 pthread_cond_wait 就是取消点之一。

但是,令人迷惑不解的是,所有介绍 Cancellation Points 的文章都仅仅说,当线程被取消后,将继续运行到取消点并发生取消动作。但我们注意到上面例子中 pthread_cancel 前面 main 函数已经 sleep 了 5 秒,那么在 pthread_cancel 被调用时,thread0 到底运行到 pthread_cond_wait 没有?

如果 thread0 运行到了 pthread_cond_wait,那么照上面的说法,它应该继续运行到下一个取消点并发生取消动作,而后面并没有取消点,所以 thread0 应该运行到 pthread_exit 并结束,这时 mutex 就会被解锁,这样就不应该发生死锁啊。

3. 取消类型(Cancellation Type)

我们会发现,通常的说法:某某函数是 Cancellation Points,这种方法是容易令人混淆的。因为函数的执行是一个时间过程,而不是一个时间点。其实真正的 Cancellation Points 只是在这些函数中 Cancellation Type 被修改为 PHREAD_CANCEL_ASYNCHRONOUS 和修改回 PTHREAD_CANCEL_DEFERRED 中间的一段时间。

POSIX 的取消类型有两种,一种是延迟取消(PTHREAD_CANCEL_DEFERRED),这是系统默认的取消类型,即在线程到达取消点之前,不会出现真正的取消;另外一种是异步取消(PHREAD_CANCEL_ASYNCHRONOUS),使用异步取消时,线程可以在任意时间取消。

4. Linux 的取消点实现

下面我们看 Linux 是如何实现取消点的。(其实这个准确点儿应该说是 GNU 取消点实现,因为 pthread 库是实现在 glibc 中的。) 我们现在在 Linux 下使用的 pthread 库其实被替换成了 NPTL,被包含在 glibc 库中。

以 pthread_cond_wait 为例,glibc-2.6/nptl/pthread_cond_wait.c 中:

145      /* Enable asynchronous cancellation.  Required by the standard.  */
146      cbuffer.oldtype = __pthread_enable_asynccancel ();
147
148      /* Wait until woken by signal or broadcast.  */
149      lll_futex_wait (&amp;cond-&gt;__data.__futex, futex_val);
150
151      /* Disable asynchronous cancellation.  */
152      __pthread_disable_asynccancel (cbuffer.oldtype);

我们可以看到,在线程进入等待之前,pthread_cond_wait 先将线程取消类型设置为异步取消(__pthread_enable_asynccancel),当线程被唤醒时,线程取消类型被修改回延迟取消 __pthread_disable_asynccancel 。

这就意味着,所有在 __pthread_enable_asynccancel 之前接收到的取消请求都会等待 __pthread_enable_asynccancel 执行之后进行处理,所有在 __pthread_disable_asynccancel 之前接收到的请求都会在 __pthread_disable_asynccancel 之前被处理,所以真正的 Cancellation Point 是在这两点之间的一段时间。

5. 对示例函数进入死锁的解释

当 main 函数中调用 pthread_cancel 前,thread0 已经进入了 pthread_cond_wait 函数并将自己列入等待条件的线程列表中(lll_futex_wait)。这个可以通过 GDB 在各个函数上设置断点来验证。

当 pthread_cancel 被调用时,tid[0] 线程仍在等待,取消请求发生在 __pthread_disable_asynccancel 前,所以会被立即响应。但是 pthread_cond_wait 为注册了一个线程清理程序(glibc-2.6/nptl/pthread_cond_wait.c):

126  /* Before we block we enable cancellation.  Therefore we have to
127     install a cancellation handler.  */
128  __pthread_cleanup_push (&amp;buffer, __condvar_cleanup, &amp;cbuffer);

那么这个线程清理程序 __condvar_cleanup 干了什么事情呢?我们可以注意到在它的实现最后(glibc-2.6/nptl/pthread_cond_wait.c):

85  /* Get the mutex before returning unless asynchronous cancellation
86     is in effect.  */
87  __pthread_mutex_cond_lock (cbuffer-&gt;mutex);
88}

哦,__condvar_cleanup 在最后将 mutex 重新锁上了。而这时候 thread1 还在休眠(sleep(10)),等它醒来时,mutex 将会永远被锁住,这就是为什么 thread1 陷入无休止的阻塞中。

6. 如何避免因此产生的死锁

由于线程清理函数 pthread_cleanup_push 使用的策略是先进后出(FILO),那么我们可以在 pthread_cond_wait 函数前先注册一个线程处理函数:

void cleanup(void *arg)
{
  pthread_mutex_unlock(&amp;mutex);
}
void* thread0(void* arg)
{
  pthread_cleanup_push(cleanup, NULL);  // thread cleanup handler
  pthread_mutex_lock(&amp;mutex);
  pthread_cond_wait(&amp;cond, &amp;mutex);
  pthread_mutex_unlock(&amp;mutex);
  pthread_cleanup_pop(0);
  pthread_exit(NULL);
}

这样,当线程被取消时,先执行 pthread_cond_wait 中注册的线程清理函数 __condvar_cleanup,将 mutex 锁上,再执行 thread0 中注册的线程处理函数 cleanup,将 mutex 解锁。这样就避免了死锁的发生。

7. 结论

多线程下的线程同步一直是一个让人很头痛的问题。POSIX 为了避免立即取消程序引起的资源占用问题而引入的 Cancellation Points 概念是一个非常好的设计,但是不合适的使用 pthread_cancel 仍然会引起线程同步的问题。了解 POSIX 线程取消点在 Linux 下的实现更有助于理解它的机制和有利于更好的应用这个机制。

8. 参考文献

[1] W. Richard Stevens, Stephen A. Rago: Advanced Programming in the UNIX Environment, 2nd Edition.
[2] Linux Manpage

飞信充分表明了中国移动是一个闷骚的弱智

因为平时短信通信量很大,所以最近在尝试中国移动的飞信业务,然后发现了一些很有趣的事情。

1. 不允许下载飞信手机客户端到电脑。

在飞信的官方网站上,手机客户端只能用手机上网下载。(飞信给你发条包含 URL 的消息,你点击下载)

为什么用户不能在电脑上下载了然后自己安装到手机上呢?嘿嘿,就算只有几十K,GPRS 下载也是有钱赚的(0.01元/KB那也是将近一块钱那)!要是大家都在电脑上下了,中国移动不少赚了很多吗?

其实这是典型的中国移动拿用户当弱智耍的例子(当然,这也不是第一次了)。不过大家要明白一个道理,几乎所有 WAP 网站都是可以通过 WEB 访问的,那么移动给的链接自然可以用电脑下载的。拿我的 Samsung E908 来说,移动给的链接是:
http://nav.m161.com.cn/drops/clientdownload.aspx?category=j2me&vendor=sam&model=e908
在这里链接下载下来的是一个 Amigo_sam_e908.jad,和下载 JAVA 游戏时一样,.jad 文件其实只是一个文本文件,用文本编辑器打开后就能发现真正的 JAVA 程序 .jar 文件的下载地址是:
http://nav.m161.com.cn/drops/clientdownload.aspx?category=j2me&vendor=sam&model=e908_jar
在这个链接下载下来的是一个 Amigo_sam_e908.jar 文件,然后只需要把那个 .jad 文件中的 MIDlet-Jar-URL: 后面换成 .jar 文件的文件名 Amigo_sam_e908.jar,然后将两个文件传输到手机里,按照一般安装手机 JAVA 游戏的步骤安装即可。

2. 手机客户端的常用短语。

在用手机客户端发消息时,飞信内置了一些常用短语,下面大家来看看这些“常用”短语是什么:

1> 你好。
2> 有时间么?可以和你聊聊吗?
3> 你好,很高兴可以和你成为朋友。
4> 你多大了?是男生还是女生?
5> 你是用手机聊吗?
6> 为什么不说话?
7> 你的名字好特别,是什么意思呢?
8> 可以给我你的电话号码吗?
9> 回头再聊,我正开车呢?
10> 这么晚了,还不睡?

看完了这几条,我实在不明白它们有哪条能勉强算上“常用”!特别是第 5 条和第 8 条,用飞信自然能看到对方在线状态和对方手机号码,傻啊,还问?

嘿嘿,如果真的有某个人“常用”这几句话,我只能说你很“中国移动”了!

十字绣

秀一下我给 xixi 绣的十字绣!莫笑我大男人做小女子事,幼时家贫,常着补丁衣,针线活计还是能做一点的。

刚完成平面照

完工立体照

今天从卓越网购得《寂静的春天》一本,原在南大也曾想看过,但图书馆中仅藏两本旧书还都被借走,就有些小遗憾。不过没想到的是买到的这本居然是英文版加中文评注,真好玩,索性就当英文消遣读物好了。

寂静的春天

APUE, A Great Book

这两周是选课试听期,还没有正式开始上课,所以有点空闲就翻了翻 UINX 环境高级编程(Advanced Programming in the UNIX Environment, 2e),看了七八章,发现这本书真的是无愧于“UNIX 编程圣经”的称号。书中对编程中可能遇到的问题讲解得非常系统和详细,尤其当看到自己以前遇到过问题的地方时,简直就有一种顿悟的感觉,就想感叹一句“哦,原来如此!”。

我平常在写程序时,遇到问题总是求助于 Google。对那些讲编程技巧的书向来不怎么感冒(尤其是中国人写的),总觉得那种书根本不适合花时间仔细看一遍。这种问题驱动式的学习方式固然在解决某一特定问题时显得快捷高效,但是也往往受限于一叶障目不见泰山的困境。在解决了某一问题之后,对其它同类问题没有足够关注,导致再遇到类似问题时仍需要去搜索答案。

问题驱动式的学习方式会导致对问题的了解不够系统和深入,但如果仅仅拿本大部头慢慢翻完的话,又会枯燥无味,而且体会也不深。我觉得读编程书的最好方法就是,先有一定量的实践,再去看书,而且要保持对书中习题和代码的练习量。有时候不妨先看实例代码再看正文解释,如果代码看得懂,看作者的解释是否和自己理解一样;如果代码看不懂,就会加深对正文的注意度。而且有时候读那些入门级的教科书,不妨只看代码。

当然,在编程的时候,桌子上应该有几本经典图书当作手册来参考,不时地重读一下某些章节会很有好处。像 APUE,我就觉得非常适合作为案头书,做 Linux/Unix 开发的程序员买一本看看绝对不会失望。

Google Talk 小徽章

无意间发现 Google 推出了一个新功能,Google Talk 小徽章,把一个 iframe 放到网站上,这样网站的访客就能直接通过点击这个小徽章里的链接匿名和发布者聊天,不需要 Google 帐户。

测试了一把,发现当状态标识为 "busy" 的时候,通过小徽章无法聊天;当状态标识为 "available" 时匿名访客才可以点击它来进行聊天。由于是匿名,聊天时发布者方显示的对方名字是 "Guest"。

想看看效果?看我的个人主页,或者博客右侧 widgets。

Hello World 背后的真实故事

* 原作者:Antônio Augusto M. Fröhlich
* 原文链接http://www.lisha.ufsc.br/~guto/teaching/os/exercise/hello.html

* 译者:杨文博 <http://blog.solrex.org>
* 译文链接http://share.solrex.org/os/hello_cn.html
* 最后更新时间: 2008 年 2 月 28 日

我们计算机科学专业的大多数学生至少都接触过一回著名的 "Hello World" 程序。相比一个典型的应用程序——几乎总是有一个带网络连接的图形用户界面,"Hello World" 程序看起来只是一段很简单无趣的代码。不过,许多计算机科学专业的学生其实并不了解它背后的真实故事。这个练习的目的就是利用对 "Hello World" 的生存周期的分析来帮助你揭开它神秘的面纱。

源代码

让我们先看一下 Hello World 的源代码:

1. #include <stdio.h>
2. int main(void)
3. {
4. printf("Hello World!\n");
5. return 0;
6.
7. }

第 1 行指示编译器去包含调用 C 语言库(libc)函数 printf 所需要的头文件声明。

第 3 行声明了 main 函数,看起来好像是我们程序的入口点(在后面我们将看到,其实它不是)。它被声明为一个不带参数(我们这里不准备理会命令行参数)且会返回一个整型值给它的父进程(在我们的例子里是 shell)的函数。顺便说一下,shell 在调用程序时对其返回值有个约定:子进程在结束时必须返回一个 8 比特数来代表它的状态:0 代表正常结束,0~128 中间的数代表进程检测到的异常终止,大于 128 的数值代表由信号引起的终止。

从第 4 行到第 8 行构成了 main 函数的实现,即调用 C 语言库函数 printf 输出 "Hello World!\n" 字符串,在结束时返回 0 给它的父进程。

简单,非常简单!

编译

现在让我们看看 "Hello World" 的编译过程。在下面的讨论中,我们将使用非常流行的 GNU 编译器(gcc)和它的二进制辅助工具(binutils)。我们可以使用下面命令来编译我们的程序:

# gcc -Os -c hello.c

这样就生成了目标文件 hello.o,来看一下它的属性:

# file hello.o
hello.o: ELF 32-bit LSB relocatable, Intel 80386, version 1 (SYSV), not stripped

给出的信息告诉我们 hello.o 是个可重定位的目标文件(relocatable),为 IA-32(Intel Architecture 32) 平台编译(在这个练习中我使用了一台标准 PC),保存为 ELF(Executable and Linking Format) 文件格式,并且包含着符号表(not stripped)。

顺便:

# objdump -hrt hello.o
hello.o: file format elf32-i386

Sections:
Idx Name Size VMA LMA File off Algn
0 .text 00000011 00000000 00000000 00000034 2**2
CONTENTS, ALLOC, LOAD, RELOC, READONLY, CODE
1 .data 00000000 00000000 00000000 00000048 2**2
CONTENTS, ALLOC, LOAD, DATA
2 .bss 00000000 00000000 00000000 00000048 2**2
ALLOC
3 .rodata.str1.1 0000000d 00000000 00000000 00000048 2**0
CONTENTS, ALLOC, LOAD, READONLY, DATA
4 .comment 00000033 00000000 00000000 00000055 2**0
CONTENTS, READONLY

SYMBOL TABLE:
00000000 l df *ABS* 00000000 hello.c
00000000 l d .text 00000000
00000000 l d .data 00000000
00000000 l d .bss 00000000
00000000 l d .rodata.str1.1 00000000
00000000 l d .comment 00000000
00000000 g F .text 00000011 main
00000000 *UND* 00000000 puts

RELOCATION RECORDS FOR [.text]:
OFFSET TYPE VALUE
00000004 R_386_32 .rodata.str1.1
00000009 R_386_PC32 puts

这告诉我们 hello.o 有 5 个段:

(译者注:在下面的解释中读者要分清什么是 ELF 文件中的段(section)和进程中的段(segment)。比如 .text 是 ELF 文件中的段名,当程序被加载到内存中之后,.text 段构成了程序的可执行代码段。其实有时候在中文环境下也称 .text 段为代码段,要根据上下文分清它代表的意思。)

1. .text: 这是 "Hello World" 编译生成的可执行代码,也就是说这个程序对应的 IA-32 指令序列。.text 段将被加载程序用来初始化进程的代码段。

2. .data:"Hello World" 的程序里既没有初始化的全局变量也没有初始化的静态局部变量,所以这个段是空的。否则,这个段应该包含变量的初始值,运行前被装载到进程的数据段。

3. .bss: "Hello World" 也没有任何未初始化的全局或者局部变量,所以这个段也是空的。否则,这个段指示的是,在进程的数据段中除了上文的 .data 段内容,还有多少字节应该被分配并赋 0。

4. .rodata: 这个段包含着被标记为只读 "Hello World!\n" 字符串。很多操作系统并不支持进程(运行的程序)有只读数据段,所以 .rodata 段的内容既可以被装载到进程的代码段(因为它是只读的),也可以被装载到进程的数据段(因为它是数据)。因为编译器并不知道你的操作系统所使用的策略,所以它额外生成了一个 ELF 文件段。

5. .comment:这个段包含着 33 字节的注释。因为我们在代码中没有写任何注释,所以我们无法追溯它的来源。不过我们将很快在下面看到它是怎么来的。

它也给我们展示了一个符号表(symbol table),其中符号 main 的地址被设置为 00000000,符号 puts 未定义。此外,重定位表(relocation table)告诉我们怎么样去在 .text 段中去重定位对其它段内容的引用。第一个可重定位的符号对应于 .rodata 中的 "Hello World!\n" 字符串,第二个可重定位符号 puts,代表了使用 printf 所产生的对一个 libc 库函数的调用。为了更好的理解 hello.o 的内容,让我们来看看它的汇编代码:

1. # gcc -Os -S hello.c -o -
2. .file "hello.c"
3. .section .rodata.str1.1,"aMS",@progbits,1
4. .LC0:
5. .string "Hello World!"
6. .text
7. .align 2
8. .globl main
9. .type main,@function
10. main:
11. pushl %ebp
12. movl %esp, %ebp
13. pushl $.LC0
14. call puts
15. xorl %eax, %eax
16. leave
17. ret
18. .Lfe1:
19. .size n,.Lfe1-n
20. .ident "GCC: (GNU) 3.2 20020903 (Red Hat Linux 8.0 3.2-7)"

从汇编代码中我们可以清楚的看到 ELF 段标记是怎么来的。比如,.text 段是 32 位对齐的(第 7 行)。它也揭示了 .comment 段是从哪儿来的(第 20 行)。因为我们使用 printf 来打印一个字符串,并且我们要求我们优秀的编译器对生成的代码进行优化(-Os),编译器用(应该更快的) puts 调用来取代 printf 调用。不幸的是,我们后面将会看到我们的 libc 库的实现会使这种优化变得没什么用。

那么这段汇编代码会生成什么代码呢?没什么意外之处:使用标志字符串地址的标号 .LCO 作为参数的一个对 puts 库函数的简单调用。

连接

下面让我们看一下 hello.o 转化为可执行文件的过程。可能会有人觉得用下面的命令就可以了:

# ld -o hello hello.o -lc
ld: warning: cannot find entry symbol _start; defaulting to 08048184

不过,那个警告是什么意思?尝试运行一下!

是的,hello 程序不工作。让我们回到那个警告:它告诉我们连接器(ld)不能找到我们程序的入口点 _start。不过 main 难道不是入口点吗?简短的来说,从程序员的角度来看 main 可能是一个 C 程序的入口点。但实际上,在调用 main 之前,一个进程已经执行了一大堆代码来“为可执行程序清理房间”。我们通常情况下从编译器或者操作系统提供者那里得到这些外壳程序(surrounding code,译者注:比如 CRT)。

下面让我们试试这个命令:

# ld -static -o hello -L`gcc -print-file-name=` /usr/lib/crt1.o /usr/lib/crti.o hello.o /usr/lib/crtn.o -lc -lgcc

现在我们可以得到一个真正的可执行文件了。使用静态连接(static linking)有两个原因:一,在这里我不想深入去讨论动态连接库(dynamic libraries)是怎么工作的;二,我想让你看看在我们库(libc 和 libgcc)的实现中,有多少不必要的代码将被添加到 "Hello World" 程序中。试一下这个命令:

# find hello.c hello.o hello -printf "%f\t%s\n"
hello.c 84
hello.o 788
hello 445506

你也可以尝试 "nm hello" 和 "objdump -d hello" 命令来得到什么东西被连接到了可执行文件中。

想了解动态连接的更多内容,请参考 Program Library HOWTO

装载和运行

在一个遵循 POSIX(Portable Operating System Interface) 标准的操作系统(OS)上,装载一个程序是由父进程发起 fork 系统调用来复制自己,然后刚生成的子进程发起 execve 系统调用来装载和执行要运行的程序组成的。无论何时你在 shell 中敲入一个外部命令,这个过程都会被实施。你可以使用 truss 或者 trace 命令来验证一下:

# strace -i hello > /dev/null
[????????] execve("./hello", ["hello"], [/* 46 vars */]) = 0
...
[08053d44] write(1, "Hello World!\n", 13) = 13
...
[0804e7ad] _exit(0) = ?

除了 execve 系统调用,上面的输出展示了打印函数 puts 中的 write 系统调用,和用 main 的返回值(0)作为参数的 exit 系统调用。

为了解 execve 实施的装载过程背后的细节,让我们看一下我们的 ELF 可执行文件:

# readelf -l hello
Elf file type is EXEC (Executable file)
Entry point 0x80480e0
There are 3 program headers, starting at offset 52

Program Headers:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
LOAD 0x000000 0x08048000 0x08048000 0x55dac 0x55dac R E 0x1000
LOAD 0x055dc0 0x0809edc0 0x0809edc0 0x01df4 0x03240 RW 0x1000
NOTE 0x000094 0x08048094 0x08048094 0x00020 0x00020 R 0x4

Section to Segment mapping:
Segment Sections...
00 .init .text .fini .rodata __libc_atexit __libc_subfreeres .note.ABI-tag
01 .data .eh_frame .got .bss
02 .note.ABI-tag

输出显示了 hello 的整体结构。第一个程序头对应于进程的代码段,它将从文件偏移 0x000000 处被装载到映射到进程地址空间的 0x08048000 地址的物理内存中(虚拟内存机制)。代码段共有 0x55dac 字节大小而且必须按页对齐(0x1000, page-aligned)。这个段将包含我们前面讨论过的 ELF 文件中的 .text 段和 .rodata 段的内容,再加上在连接过程中生成的附加的段。正如我们预期,它被标志为:只读(R)和可执行(X),不过禁止写(W)。

第二个程序头对应于进程的数据段。装载这个段到内存的方式和上面所提到的一样。不过,需要注意的是,这个段占用的文件大小是 0x01df4 字节,而在内存中它占用了 0x03240 字节。这个差异主要归功于 .bss 段,它在内存中只需要被赋 0,所以不用在文件中出现(译者注:文件中只需要知道它的起始地址和大小即可)。进程的数据段仍然需要按页对齐(0x1000, page-aligned)并且将包含 .data 和 .bss 段。它将被标识为可读写(RW)。第三个程序头是连接阶段产生的,和这里的讨论没有什么关系。

如果你有一个 proc 文件系统,当你得到 "Hello World" 时停止进程(提示: gdb,译者注:用 gdb 设置断点),你可以用下面的命令检查一下是不是如上所说:

# cat /proc/`ps -C hello -o pid=`/maps
08048000-0809e000 r-xp 00000000 03:06 479202 .../hello
0809e000-080a1000 rw-p 00055000 03:06 479202 .../hello
080a1000-080a3000 rwxp 00000000 00:00 0
bffff000-c0000000 rwxp 00000000 00:00 0

第一个映射的区域是这个进程的代码段,第二个和第三个构成了数据段(data + bss + heap),第四个区域在 ELF 文件中没有对应的内容,是程序栈。更多和正在运行的 hello 进程有关的信息可以用 GNU 程序:time, ps 和 /proc/pid/stat 得到。

程序终止

当 "Hello World" 程序运行到 main 函数中的 return 语句时,它向我们在段连接部分讨论过的外壳函数传入了一个参数。这些函数中的某一个发起 exit 系统调用。这个 exit 系统调用将返回值转交给被 wait 系统调用阻塞的父进程。此外,它还要对终止的进程进行清理,将其占用的资源还给操作系统。用下面命令我们可以追踪到部分过程:

# strace -e trace=process -f sh -c "hello; echo $?" > /dev/null
execve("/bin/sh", ["sh", "-c", "hello; echo 0"], [/* 46 vars */]) = 0
fork() = 8321
[pid 8320] wait4(-1, <unfinished ...>
[pid 8321] execve("./hello", ["hello"], [/* 46 vars */]) = 0
[pid 8321] _exit(0) = ?
<... wait4 resumed> [WIFEXITED(s) && WEXITSTATUS(s) == 0], 0, NULL) = 8321
--- SIGCHLD (Child exited) ---
wait4(-1, 0xbffff06c, WNOHANG, NULL) = -1 ECHILD (No child processes)
_exit(0)

结束

这个练习的目的是让计算机专业的新生注意这样一个事实:一个 Java Applet 的运行并不是像魔法一样(无中生有的),即使在最简单的程序背后也有很多系统软件的支撑。如果您觉得这篇文章有用并且想提供建议来改进它,请发电子邮件给我

常见问题

这一节是为了回答学生们的常见问题。

* 什么是 "libgcc"? 为什么它在连接的时候被包含进来?
编译器内部的函数库,比如 libgcc,是用来实现目标平台没有直接实现的语言元素。举个例子,C 语言的模运算符 ("%") 在某个平台上可能无法映射到一条汇编指令。可能用一个函数调用实现比让编译器为其生成内嵌代码更受欢迎(特别是对一些内存受限的计算机来说,比如微控制器)。很多其它的基本运算,包括除法、乘法、字符串处理(比如 memory copy)一般都会在这类函数库中实现。

15 Minutes of Fame

短暂的荣光

——对 免费电子书《使用开源软件-自己动手写操作系统》 的一点点说明

我前几日发布了免费电子书《使用开源软件-自己动手写操作系统》,本以为只是在和我相关的一些圈子,主要是南京大学和南京大学毕业的 Linux 爱好者中得到传播。虽然我预计到了它会慢慢的扩散开来,但是从没有想过它在发布之初就产生了这么大的影响,我的个人主页和博客的点击量在这几日都迅速上升到日近千次访问。

从统计结果来看,JservsolidotLinuxGem 对此消息的分享功不可没。特别是对岸的 Jserv,繁体发布页的一千多次访问几乎都是他带来的(其实这也是我准备繁体发布页的原因,只可惜暂时没有精力发布繁体电子书)。在这里我对他们的无私帮助表示感谢。

再来说说这本电子书,其实我是将其当作对《自己动手写操作系统》一书的读书报告和扩展来写的。之所以将其发布出来,是想让更多人得益于我的共享,从零开始去理解一个操作系统,因为很多讲操作系统的书不会讲这些体验,了解了这些就能得到更完整的体验。

有很多人诟病本书的英文名“Write OS with Free Software”从语法上来讲有错误。我不得不承认我没有认真考虑这个名字,只是随手写了上去,所以可能下次发布的时候本书英文名会更改为“Write Your Own OS with Free and Open Source Software”,不知道各位可有更好提议?

我不敢说自己写这本书就是大公无私,回报开源社区,没有一点私心。私心是有的,不过只是为了出一点小名。现在的杨文博仅仅是无名小卒一个,有一些小小名气之后,于找工作交朋友都有益处。我花费了不少的气力在这本书上面,除了一点点名气并没有要求其它的什么,所以我欢迎中肯的批评和建议,而不是对本书毛病的横加指责。谁不想得到别人对自己工作的尊重呢?

我仍是研究生一年级学生,上个学期周五和周末都会去一间公司兼职来赚点生活费。因为作息和饮食一直不够规律引发了身体的一些零件出现问题,所以这个学期辞去了兼职工作专心学习。我有我的正常生活,网络仅仅是我生活的一小部分,这些小小名气也不能解决我的吃饭问题。我不保证电子书的更新发布速率会满足某些读者的要求,所以我只能说我尽力,非常感谢你们能喜欢这本书,很抱歉但请不要在这一点上指责我。