2009/3

__umoddi3()的问题

在编译内核时有人遇到下面这个问题:

kernel/built-in.o: In function `getnstimeofday':
(.text+0xb6ae): undefined reference to `__umoddi3'
kernel/built-in.o: In function `getnstimeofday':
(.text+0xb6ce): undefined reference to `__udivdi3'

这个问题可以在用户空间重现,不过不是很容易,我实验了一下,在i386上,并不是所有的64位整数操作都会被转化成调用__umoddi3,gcc bugzilla上有演示程序,如下:

[c]

define NSEC_PER_SEC 1000000000UL

int rmg(void);

int main(void)
{
/ int sec; /
return rmg();
}

int rmg(void)
{
static unsigned long long nsec = 0;
static int sec = 0;
while (sec = NSEC_PER_SEC, 0)) {
nsec -= NSEC_PER_SEC;
++sec;
}
}
return sec;
}
[/c]

这样编译它:% gcc -nostdlib -O2 -o umoddi3 umoddi3.c,就会得到:

/tmp/ccycM684.o: In function `rmg':
umoddi3.c:(.text+0x87): undefined reference to `__udivdi3'
collect2: ld returned 1 exit status

问题重现了。这里的问题是,对于nsec来说,内层的循环其实等价于求模运算,gcc在优化时发现了这一点,而且硬件本身也不支持对64位整数直接进行算术运算,所以gcc会把这一步优化成调用内部函数udivdi3()和umoddi3(),这两个函数在libgcc中(见gcc源代码gcc/libgcc2.c),libgcc默认和libc一样是要被加载的,但如果我们加了-nostdlib(Linux内核是更好的例子),这个问题就会出现了。

知道原因了,怎么解决?网上有两种方法,一种是像这个补丁那样,在循环中插入下面这条内联汇编:


asm(“” : “+r”(ns));

这句是告诉gcc把ns这个变量放到寄存器中,并且既有读操作也有写操作,所以后面再用它时必须重新读取,这样就消除了上面的优化。

另一种解决方法是添加新的编译选项:-fno-tree-scev-cprop,这个选项似乎没有文档,至少我没找到。说说它的大体意思。scev应该是SCalar EVolutions,什么意思不知道。:( cprop应该是Copy PROPagation,这个应该很容易理解,就是赋值的传播,比如:

i = 10;
a = i;
b = i;

其实就是:

a = 10;
b = 10;

可见,编译优化是门大学问,写个编译器丝毫不比写个内核容易。:-P

switch_to中的汇编

switch_to()的代码在arch/x86/include/asm/system.h中,如下:
[c]

define switch_to(prev, next, last)

do {
/*

 * Context-switching clobbers all registers, so we clobber    
 * them explicitly, via unused output variables.        
 * (EAX and EBP is not listed because EBP is saved/restored    
 * explicitly for wchan access and EAX is the return value of    
 * __switch_to())                        
 */                                
unsigned long ebx, ecx, edx, esi, edi;                

asm volatile("pushflnt"        /* save    flags */    
         "pushl %%ebpnt"        /* save    EBP   */    
         "movl %%esp,%[prev_sp]nt"    /* save    ESP   */ 
         "movl %[next_sp],%%espnt"    /* restore ESP   */ 
         "movl $1f,%[prev_ip]nt"    /* save    EIP   */    
         "pushl %[next_ip]nt"    /* restore EIP   */    
         "jmp __switch_ton"    /* regparm call  */    
         "1:t"                        
         "popl %%ebpnt"        /* restore EBP   */    
         "popfln"            /* restore flags */    

         /* output parameters */                
         : [prev_sp] "=m" (prev->thread.sp),        
           [prev_ip] "=m" (prev->thread.ip),        
           "=a" (last),                    

           /* clobbered output registers: */        
           "=b" (ebx), "=c" (ecx), "=d" (edx),        
           "=S" (esi), "=D" (edi)                

           /* input parameters: */                
         : [next_sp]  "m" (next->thread.sp),        
           [next_ip]  "m" (next->thread.ip),        

           /* regparm parameters for __switch_to(): */    
           [prev]     "a" (prev),                
           [next]     "d" (next)                

         : /* reloaded segment registers */            
        "memory");                    

} while (0)
[/c]

根据ABI约定和内联汇编,ebx, ecx, edx, esi, edi这几个寄存器是由编译器自动保存和恢复的。这一点可能不太好理解,举个例子,看下面的代码中的ecx:
[c]

include

void modify_ecx(void) {
unsigned long ecx;

asm (
“movl $1, %%ecxnt”
:”=c”(ecx)
:
);
}

void test(void) {
unsigned long ecx;

asm volatile(
“nopnt”
“call modify_ecxnt”
: “=c” (ecx)
:
: “memory”
);
printf(“ecx=%ldn”, ecx);
}

int main(void) {
test();
return 0;
}
[/c]
这里的test()就相当于内核源代码中“调用”switch_to()的context_switch(),我们来查看其对应的汇编代码(注意要加-O0):


main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ecx
subl $8, %esp
call test
movl $0, %eax
addl $8, %esp
popl %ecx
leal -4(%ecx), %esp
ret

可见,在调用test()之前,编译器已经自动完成了保存ecx的操作,而调用之后它又会恢复ecx的值。

而ebp,eflags是手工压入堆栈,并在switch回来后恢复出来的。esp和eip保存在相应的task_struct结构体里。

需要额外说明的是那个jmp,因为这里的参数传递是通过寄存器完成的,具体说是用了eax和edx这个两个寄存器,所以再jmp其实就和call一样了,不过call是要把ebp入栈的,而jmp不需要,这里也不需要。

另外一个可能的问题是:为什么switch_to()有三个参数?我们切换的进程明明是两个啊!这里问题的所在是进程切换时堆栈的切换,如果不使用三个参数,切换的堆栈中仍然保存的是切换前的参数,再切换回来时prev很可能不对了,所以需要一个参数来修正,这个参数又正好是__switch_to的返回值。这样问题就解决了。

尾部调用优化

gcc实在是太聪明了,有时候就会聪明过头,尾部调用优化就是一个很好的例子。:)

尾部调用其实很好理解,就是在函数的最后调用另外一个函数,一种大的可能就是这个函数和调用函数参数差别很小,那么这时候,gcc会对这个尾部调用进行优化,如果可能,完全可以把调用那个函数时的入栈操作给直接优化掉。这就是尾部调用的优化。我们可以看看下面的例子:

[c]
extern int callee(int a, int b);

static int c;

int call(int a, int b)
{
int ret = callee(c, b);
return ret;
}
[/c]

用”gcc -fomit-frame-pointer -fno-inline -O2 -S tail_call.c”编译它,得到的汇编代码截取如下:

call:
        movl    c, %eax
        movl    %eax, 4(%esp)
        jmp     callee

很明显,入栈操作被优化掉了,成了直接操作call()的参数!这会有问题,如果call()的调用函数不想看到堆栈被改变的话。

在Linux内核中有个很好的例子,那就是系统调用!系统调用的入口(arch/x86/kernel/entry_32.S)是用汇编写成的,在进入一个系统调用前存放参数的寄存器会被入栈,系统调用完毕后又会被恢复,所以,如果系统调用也被做了尾部调用优化的话,那么系统调用前后寄存器的值就会发生变化!这就可能会破坏用户空间的代码!

Linux内核采取了相应的办法来解决这个问题,那就是宏asmlinkage_protect(),它的定义在arch/x86/include/asm/linkage.h。我们通过个具体的例子来看,比如open(2),其定义是这样的:

[c]
SYSCALL_DEFINE3(open, const char __user *, filename, int, flags, int, mode)
{
long ret;

    if (force_o_largefile())
            flags |= O_LARGEFILE;

    ret = do_sys_open(AT_FDCWD, filename, flags, mode);
    /* avoid REGPARM breakage on x86: */
    asmlinkage_protect(3, ret, filename, flags, mode);
    return ret;

}
[/c]

上面的asmlinkage_protect会被展开成:


asm volatile (“” : “=r” (ret) : “0” (ret), “g” (filename),
“g” (flags), “g” (mode));

这句不会直接生成任何汇编代码,但是它会强迫gcc把参数放到寄存器中,也就避免了上面展示的直接对栈进行操作。

为了完整,我们把最开始的那段代码修复一下:
[c]
extern int callee(int a, int b);

static int c;

int call(int a, int b)
{
int ret = callee(c, b);
asm(“”:”=r”(ret):”0”(ret), “g”(a), “g”(b));
return ret;
}
[/c]
再汇编就可以看到堆栈操作了:


call:
pushl %ebx
subl $8, %esp
movl c, %eax
movl 20(%esp), %ebx
movl %eax, (%esp)
movl %ebx, 4(%esp)
call callee
addl $8, %esp
popl %ebx
ret

这里展示的代码都很简单,实际中往往很复杂,情况不同gcc的优化操作也不同,比如,当上面的callee()是static的时候虽然也会被优化但不会直接操作堆栈(而是通过使用寄存器),因为gcc可以完整地看到两个函数,可以在“全局”进行优化。实际问题实际分析,但这个尾部调用优化带来的问题我们必须得小心才是。

Serenity

这其实不仅仅是一部电影,还包括一部14集的电视剧。从故事情节上来看,电视剧的故事发生在电影之前,而电影又恰好起了画龙点睛的作用,给整个故事画上了一个完美的句号。

从科幻的角度来讲,这部电影的故事情节并不是那么精彩,肯定没有《基地》系列好看。片中号称加入了中国元素,但我怎么看都是不伦不类,十分憋足!

如果它仅仅是这样的话我是没必要写评论的,但豆瓣上一篇对它的精彩评论吸引了我的注意,它让我从另一种角度去重新看待这部电影。

我坚持认为,“政府”和“国家”这两个东西的存在说明了人类依旧是那么愚蠢。于是,我们可能会幻想建立一个不需要政府的乌托邦,很不幸的是,那真的只是个乌托邦,残酷的现实是我们依旧还需要有政府,还需要有国家,说白了,我们还需要秩序,因为我们自己还不能自觉去维持秩序。“面对愚昧,连神们自己都束手无策。”又何况是我们人呢?无政府主义者注定无法在这个愚蠢的世界上活下去……

我认为人类的未来会有两种命运(如果人类还没灭绝的话):要么是乌托邦,要么是如同《1984》中的世界一样。很不幸,我看的越多我越发现前者是那么地渺茫,后者又是那么地可能…… F**K!!

=================================================

附:《Serenity》片头曲

Take my love, take my land
Take me where I cannot stand
I don’t care, I’m still free
You can’t take the sky from me
Take me out to the black
Tell them I ain’t comin’ back
Burn the land and boil the sea
You can’t take the sky from me
There’s no place I can be
Since I found Serenity
But you can’t take the sky from me…

语言学术语解释

(注:以下内容搜集整理于互联网。)

article:定语。像英文中的a, an, the,葡语中的um, uma, o(s), a(s),德语中的der, die, das, ein, eine等。

verb: 动词。

conjugation: 变位。动词的某种形式,典型的像葡语,法语都有很多种变位,英语中的动词变化也可以叫变位(?)。

noun: 名词。

gender: 名词,形容词的性,葡语中把名词分为阳性(masculine)和阴性(feminine),所以其前面的形容词也要做相应的变化。

singular/plural:单数/复数。汉语的名词并没有单复数形式的变化。

adjective: 形容词。

degree: 形容词或副词的度,比如:good, better, best。用于比较(comparison)。

adverb: 副词。

pronoun: 代词。

synonym/antonym: 同义词/反义词。

auxiliary verb:助动词。协助主要动词构成谓语动词词组。

比如英语需要借助助动词来完成否定和疑问:

He does not like English. 中的”does”就是助动词,其本身并无意义。
Do you like it? 中的”Do”也是如此。

modal auxiliary:情态动词。助动词的一种。比如英文中的can, must, dare, need等。表达建议、要求、可能和意愿等。

infinitive:不定式。不定式是一种非限定性动词。而非限定动词是指那些在句中不能单独充当谓语的动词,可分为不定式,动名词,现在分词和过去分词。

subjunctive:虚拟语气。用来表示说话人的主观愿望或假想,所说的是一个条件,不一定是事实,或与事实相反。

例子:If I were you, I would not do that. 这个句子就是用的虚拟语气,从动词”were”上面可以体现出来。

vowel: 元音。典型的元音字母就是a, e, i, o, u这五个字母,包括我们的汉语拼音,只是有些语言对这些字母做了变化,比如葡语中的á,德语中的ä, ö, ü。

consonant:辅音。元音之外的音节。

tense: 时态。

present tense: 现在时。
present continuous tense:现在进行时。
present perfect tense:现在完成时。
present perfect continuous tense:现在完成进行时。
future tense: 将来时。
past tense:过去时。
past continuous tense:过去进行时。
past perfect tense:过去完成时。

clause:从句。其中的定语从句(descriptive clause )又分为限制性定语从句(restrictive clause)和非限制性定语从句(nonrestrictive clause)。

subject:主语。主语表示句子主要说明的人或事物。

predication: 表语。表语是谓语的一部分,它位于系动词,如”be”,之后(系表结构),说明主语身份,特征,属性或状态。

predicate: 谓语。说明主语的动作,状态或特征。

例子:She is beautiful. 这句中”she”作主语,”is”作系动词,”beautiful”作表语。

subject complement:主语补语。表语就是主语补语,但主语补语不一定是表语。具体地说是:除了主系表结构中的表语这外(其标志是要有系动词be等),其它的主语补足语均不能称为表语。

例子:He waked up frightened. 中的”frightened”是主语补语,但不是表语。

object:宾语。宾语表示动作行为的对象,跟在及物动词之后。

例子:I am learning Portuguese. 中的”Portuguese”作宾语。

attributive:定语。在句中修饰名词或代词的成分叫定语。

例子:She is a beautiful girl. 这句中的”beautiful”作定语。

complement: 补语。用来说明宾语或主语所处的状态或正在进行的动作。

例子:I want to make her happy. 中的”happy”做补语。

adverbial:状语。从英文上很容易看出,它是”adverb”(副词)的形容词变形,所以副词一般在句子中做状语。状语用来修饰、限制、动词或形容词,表示动作的状态、方式、时间、处所或程度等。

例子:They did well.中的副词”well”作状语。

object complement:宾语补足语,或者简称宾补。用来补充说明宾语。

例子:I saw you speaking with him. 中的”speaking with him”作宾语补足语。

注意:通常来说,一个带宾语补足语的句子转化成被动后就会成为一个带主语补语的句子。比如上面的句子可以转化为:

You were seen speaking with him.

direct/indirect object:直接宾语,间接宾语。带双宾语的句子中要区分直接宾语和间接宾语。

例子:I give her an apple. 中的”her”作间接宾语,”an apple”作直接宾语。

注意:要区分双宾语和宾语补足语的区别,前者中两个宾语可以说是没什么联系,而后者中是有明显的修饰关系。

accusative: 宾格。
nominative:主格。

例子:I want to thank him. 中的”I”是主格形式,”him”是”he”的宾格形式。

dative:与格。和宾格类似,只是与格是作间接宾语。

appositive:同位语。

例子:He, a famous writer, wrote this book. 中的”a famous writer”作”He”的同位语,用来解释或进一步说明前面的名词或代词。

注意:不要和定语混了,定语是修饰和被修饰的关系,这里是解释与被解释的关系。

emacs PK vi

Harley Hahn的《Unix Unbound》一书中有介绍vi和emacs的章节。其中介绍vi的一章中提到:

HINT: If you are learning vi and you become temporarily
discouraged, take a break and try a little emacs. emacs
will seem so complex and impossible that you will feel a
lot better about using vi.
介绍emacs的一章中提到:
HINT: If you are learning emacs and you become temporarily
discouraged, take a break and try a little vi. vi
will seem so complex and impossible that you will feel a
lot better about using emacs.

我:囧。。。。。。。。。

丑陋的人类

看到这本书的名字时我以为是描写未来人类消失后的地球,其实不然,它不仅描绘了假设人类消失后地球会如何,而且还大量描写了历史上人类的种种行为,然后运用科学进行分析,得出人类消失以后地球上将会变得更好这个靠谱的结论。

我觉得作者分析历史是很有道理,我一直坚持认为:如果你对历史了解足够多,那么,在某种程度上,你就可以预测未来!通过分析人类历史上各种丑陋的行为,你会发现未来人类是不会有什么好结果的,真是一种悲哀!虽然书中并没有对人类的未来进行任何预测,但是书中分析了没有了人类其它动物将会生活得更好,不得不说这是对人类的一个巨大讽刺!

书中的另外一个亮点是作者并没有光凭歪歪就去预测,而是通过有理有据的科学事实,和一些科学家的分析得出一个个结论。这一点也是我们值得学习的地方。

最后,引用别人的一句话作为本文的结尾:

“越来越丑陋的人类越来越配不上美丽的地球。”

Linux kernel ASCII art

Linux内核源代码里有两个比较好玩儿的ASCII art,一个sparc的panic里(见arch/sparc/kernel/traps_32.c),另一个在parisc的panic里(见arch/parisc/kernel/traps.c)。

我想拿来用用,放到shell里,就把它们改成了shell语句:
[bash]
echo -e ‘ |/ _ |/n “@’”‘“‘/,. `@”n /| \/ |\n __U/n’

echo -e “ \ ^^n \ (xx)___n (__)\ )\/\””
U ||——w |n || ||n”
[/bash]

这样每次启动shell我就可以看到这个图像了,见我的截图:


Linux漫画教程

最近国外出了个linux漫画教程,大体看了看,非常有意思。能把学习linux以漫画的形式来传授,真是一个非常好的主意!现在学linux真是幸福啊~

这个教程现在刚出到第2期,有兴趣的可以去其官方网站看看。另外,第1期已经可以以pdf格式下载了。可惜的是只有英文的。

下面是其中的一幅图片:

从量子物理远望上帝

其实我想看的那本书本来是《寻找薛定谔的猫》,但一开始没找到它,就读了这本。这本书挺薄,我花了不到两个小时就看完了,因为书中很多东西在大学物理课上已经学过了。

量子物理是从一场争论开始的,就是讨论光到底是波还是粒子。虽然这场争论从牛顿那时候就有了,但无疑到了那个时候才发展到高潮。人家上帝说有就有的东西,放到人类这里争得不亦乐乎,热火朝天……最后的结果还是个平手:它既是波又是粒子。

好了,看看我们的物理学吧,简单的说它的终极目的就是要寻找理论去解释一切现象,以前都是一个牛b的理论都能横扫全部相关的现象,但到这里行不通了,好多理论都是只能解释一部分,没有一个能解释全部,费了好大的劲儿怎么都不行。于是,有人开始怀疑人生了,这位老师就是玻尔,这位老大的观点是,既然我们没法解释那就说明它本来就不能解释!一切都是概率,宏观世界因为统计才美。爱因斯坦老师不同意了,他老人家坚持认为不能解释是因为我们笨,没找到合适的理论,上帝是不会玩筛子滴!上帝呢?说不定就在一旁偷偷地笑呢:我设计你们人类时就压根没想让你们搞明白这东西!这一点被另一个人看出了些端倪,那就是海森堡老师!但真相除了上帝本人谁知道呢!

上帝到底有没有我不知道,我知道的是,如果真有,TA肯定是个超一流的数学家,设计师,艺术家!人类不是流行写《xxx艺术》嘛,上帝TA老人家如果也出本书的话就可以叫《设计宇宙的艺术》……