2010/4

glibc定义的系统调用

最近有人问道,glibc 中对我们常见的那些系统调用的定义在哪里?比如write(2)recv(2)

这个问题我以前在看glibc的代码时注意到了。我们通常可以直接找到的所谓定义,比如下面这个:

[c]
ssize_t
libc_write (int fd, const void *buf, size_t nbytes)
{
if (nbytes == 0)
return 0;
if (fd < 0)
{
set_errno (EBADF);
return -1;
}
if (buf == NULL)
{
__set_errno (EINVAL);
return -1;
}

set_errno (ENOSYS);
return -1;
}
libc_hidden_def (
libc_write)
stub_warning (write)

weak_alias (libc_write, write)
libc_hidden_weak (write)
weak_alias (
libc_write, write)
[/c]

其实很明显,这并不是真正的定义,至少并不是你想找的那个。这个是什么呢?这个其实是write(2)的一个alias,而且还是weak alias,换句话说也就是,如果一个平台上没有定义自己的write(2),那么就用一个。而且从上面的代码也可以看得出来,这个函数仅仅是处理了一下errno,别的什么都不做。

那真正的定义究竟在哪里?说实话,我当初找到费了一番周折,找到它们并不容易,因为它们是编译时生成的!!可以从下面三个文件中看出来:

sysdeps/unix/make-syscalls.sh
sysdeps/unix/syscalls.list(sysdeps/unix/inet/syscalls.list)
sysdeps/unix/syscall-template.S

syscall-template.S顾名思义是个定义的模板,每个生成的系统调用都要参考这个模板,但是怎么用模板来“刻画”每一个系统调用呢?于是就有了syscalls.list,而make-syscalls.sh就是用模板和那个列表来构建生成系统调用定义的makefile,该makefile最终生成最后的定义。有兴趣的朋友应该仔细看看这几个文件。

现在再想想,这么做其实是有道理的,在Linux下,系统调用的真正定义有很多相似的地方,确实可以通过“模板”来生成对应的汇编,但是否真值得花时间去构建那么抽象的一个模板和框架?我说不清楚,本着“懒惰”的原则确实应该如此,不过看看模板本身似乎原因不仅仅是“懒惰”。

从这里我们也可以看出glibc的代码难读啊,比起Linux内核来,不仅仅是风格的问题,还有就是使用了太多的tricks,导致的结果也很显而易见,参与glibc开发的和参与linux内核开发的人明显不是一个数量级的。

Kimian Self Reproduction

看《GEB》时看到了这个东西,感觉很有意思。《GEB》在第 16 章中提到了这个东西,中文版的把它翻译成了“凯姆式自复制”。其实它类似于Quine)(如果你还不了解这个东西,可以在我的主页上找到我写的Quine),也是打印其本身,不过它是无意义的,也就是说编译器/解释器会报错,而错误信息本身就是它的内容!

在网上搜了搜,发现就这么一个页面介绍Kimian,里面给出的例子除了Perl的那个别的在Linux上都不行。所以我又自己特意写了几个:

zsh版的Kimian:

% zsh: command not found: zsh:
zsh: command not found: zsh:

Python版的Kimian:

% cat kimian.py
File “kimian.py”, line 1
File “kimian.py”, line 1
^
IndentationError: unexpected indent
% python kimian.py
File “kimian.py”, line 1
File “kimian.py”, line 1
^
IndentationError: unexpected indent

另一个Perl版的:

% cat kimian.pl
kimian.pl syntax OK
% perl -X -c kimian.pl
kimian.pl syntax OK

C版的Kimian:

% cat kimian.c
kimian.c:1: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘attribute‘ before ‘.’ token
% gcc -w -c kimian.c
kimian.c:1: error: expected ‘=’, ‘,’, ‘;’, ‘asm’ or ‘attribute’ before ‘.’ token

有兴趣的朋友可以自己尝试去写写。;)

ELF Extended Numbering

ELF格式天生就有个不小的缺陷—— segment 个数最多 65535 个,这是由e_phnum来决定的,其类型是16位的无符号整数。对于一些程序来说,65535是不够的,如果它使用了很多mmap(2)的话。

要解决这个问题并不容易,e_phnum是用了多年早已定型了的东西,不能随意修改,只能在别的地方想办法去补救,于是下面这个方案就出来乐。这个方案在Solaris上已经使用了长时间了,但 Linux 上一直还没有。其原理是:e_phnum保持不动, 如果 segment 的个数大于等于0xffff时,e_phnum设为0xffff ,同时下标为0的section header中的sh_info被设为真实的 segment 数。而 sh_info 是32位无符号类型,所以它最多能支持4294967295个。使用这个 sh_info 是合理的,因为下标为0的section header平时是没用的,其类型是 SHT_NULL。随之相应改变的还有 sh_size 和 sh_link,它们分别被设为 e_shnum和 e_shstrndx(见该文档P215)。

Daisuke HATAYAMA 提交了Linux上的补丁,主要分为三部分:内核,gdb,和binutils。内核部分补丁不少,但真正起作用的补丁只有一个,其它的都是refactor,见下:

8d9032bbe4671dc481261ccd4e161cd96e54b118 elf coredump: add extended numbering support
93eb211e6c9ff6054fcf9c5b9e344d8d9ad29175 elf coredump: make offset calculation process and writing process explicit
1fcccbac89f5bbc5e41aa72086960059fce372da elf coredump: replace ELF_CORE_EXTRA_* macros by functions
088e7af73a962fcc8883b7a6392544d8342553d6 coredump: move dump_write() and dump_seek() into a header file
05f47fda9fc5b17bfab189e9d54228025befc996 coredump: unify dump_seek()

gdb 的补丁如下:
78999a9bcf9d87f72cd67a782e1e859a6a09d9de  * common.h (PN_XNUM): Define.
cce546478afe296ebbb69f208b708a8b3fe5f7e5 * elfcode.h (elf_swap_ehdr_out): Handle e_phnum > 0xffff.

binutils 的补丁:
[http://sourceware.org/ml/binutils/2010-01/msg00393.html](http://sourceware.org/ml/binutils/2010-01/msg00393.html)

man-page 补丁:
[http://permalink.gmane.org/gmane.linux.man/1302](http://permalink.gmane.org/gmane.linux.man/1302)

自动生成内核config

配置内核的人都清楚要选择内核的配置项是一件比较麻烦的事:要是多选了吧,内核体积可能变大,编译时间会变长;要是选少了吧,恐怕系统连启动都启动不了。所以一般我们的做法是拿过系统提供的 config 来直接make oldconfig。

但这么做并不是很好,因为发行版通常为了照顾不同配置的机器而选中了很多的模块,这样一来对本机器没有用的东西也会被编译进来,从而导致编译时间变长,这还是次要,关键是模块数量会增多,内核体积也有可能变大。

理想的情况是我们根据本系统运行时加载的模块来决定到底选中哪些模块。问题就来了,我们怎么才能知道自己的系统需要哪些模块不需要哪些模块呢?

为了解决这个问题,Steven Rostedt写了一个脚本,叫作streamline_config.pl,来解决这个问题。而且他已经把此脚本提交到内核,见scripts/kconfig/streamline_config.pl。这样一来,我们就可以通过 make localmodconfig 或 make localyesconfig 来生成我们真正需要的最小的config了。不同的是,后者会把系统正在使用模块编译进内核,而前者不会,它只会保留系统使用的模块而且继续以模块的形式存在。

这个脚本不复杂,原理也很简单,通过 lsmod 找出正在使用的驱动, 然后通过解析所有的Makefile中的obj-$(CONFIGXXX) += xxx.o模式来找到驱动对应的CONFIG*,不过还要解决依赖性的问题。

2010上半年旅行计划

前一段时间天气比较冷,懒得出去。最近比较宅,不想出门。可是好久不出门旅行浑身难受,每个月总有那么几天……再这么宅下去不行,得出去活动活动了!

于是乎我制定了下面的近期旅行计划,初步计划,具体时间待定。

4月17号——19号,内蒙古,库布其沙漠穿越。已定。

4月底或5月初,苏州,南京,乌镇。本打算去山西平遥,但一想山西那么近,什么时候去都成,没必要在这么好的季节去,还不如趁着春天下江南,更主要的是看看传说中的江南美女!

5月底或6月份,青海,西藏。待详细计划。

6月底,大连。

一直以来都是一个人出门旅行,提前征征旅伴,最起码拍照方便一些。不靠谱人士免谈,极品人士请绕行。

改去西藏了

鉴于地球日益危机,外星人犯我之心不死,不明飞行物频繁光临地球和太阳系,我深刻感觉到2012真的快要来了!同志们,你们的船票买好了吗?我不管了,我的船票已经通过黄牛买到,所以我得赶紧去西藏看看诺亚方舟建得如何了!葡萄牙等到2012过去后我坐诺亚方舟过去!

以上纯属虚构。真实原因如下。

感谢国家,贵国的护照除了去一些我都没听说过的国家不需要签证,去哪都需要!所以说就算你只是为了换个好用的护照你也得移民啊!我发现申请去葡萄牙的旅游签证比我想象中的还麻烦,还要搞什么财产证明之类的,就我这点儿财产还不够证明的呢。钱咋就花得这么快捏?反正葡萄牙今年7月份是去不成了,等我什么时候攒够钱了再去吧。决定改去西藏了,一直想去但没机会去的地方。花钱不多,省下的钱还可以买个单反。而且,我现在积攒了大约10天的假期,加上3个周末一共16天,估计去西藏应该差不多了。

于是从现在开始征旅伴,同去西藏的,估计可能会5月底或者6月去,坐火车。可以见我发起的豆瓣活动

痛定思痛,我深刻反思了一下上面的问题:我的钱到底都花到哪里去了呢?我发现,相当一部分钱交给了国家,交税不说了,还交了根本就没有用的住房公积金(我交一辈子也买不起首都的一个厕所啊!)和基本上是在忽悠人的养老保险金等。剩下拿到手的钱一部分贡献给了贵国的房地产,贵国的水泥盒子租也不便宜啊!其余的钱中,相当一部分花到了旅游、户外以及户外装备上,虽然我在旅途上已经算是很节省的了;一部分钱给了爸妈,这没的说,我又不是富二代,穷二代还倒是差不多;一部分外债,是借给别人的;一部分花在了吃上,穿很少花钱,差不多半年才买一次衣服,而且买的比去动物园买贵不了多少。这么一算心里踏实了,钱虽然花得快但没浪费,也算是持家有道吧。

下一步就是要买个单反,去西藏之前。有人给我推荐佳能550D,不知道到底怎样。哎,玩单反比玩户外烧钱还厉害,入行有风险,出手须谨慎啊!

傻逼们的原理

傻逼们运行的基本原理是:

你永远不能战胜一个纯傻逼,因为他会把你的智商拉到跟他一个水平,然后用丰富的经验打败你!
Do not argue with an idiot. He will drag you down to his level and beat you with experience.

所以,没有足够的时间不要跟傻逼们斗。别把他们当正常人看就是了!

某些人就别对号入座了!