Blog

glibc程序入口解析

Content #

glibc的程序入口为_start(这个入口是由ld链接器默认的链接脚本所指定的,我们也可以通过相关参数设定自己的入口)。_start由汇编实现,并且和平台相关,下面可以单独看i386的_start实现:

libc\sysdeps\i386\elf\Start.S:
_start:
    xorl %ebp, %ebp
    popl %esi
    movl %esp, %ecx

    pushl %esp
    pushl %edx
    pushl $__libc_csu_fini
    pushl $__libc_csu_init
    pushl %ecx
    pushl %esi
    pushl main
    call __libc_start_main

    hlt

_start函数最终调用了名为__lib_start_main的函数。

xor %ebp, %ebp

这其实是让ebp寄存器清零。ebp设为0正好可以体现出这个最外层函数的尊贵地位。

pop %esi
mov %esp, %ecx

在调用_start前,装载器会把用户的参数和环境变量压入栈中,按照其压栈的方法,实际上栈顶的元素是argc,而接着其下就是argv和环境变量的数组。下图为此时的栈布局,其中虚线箭头是执行pop %esi之前的栈顶(%esp),而实线箭头是执行之后的栈顶(%esp)。 pop %esi将argc存入了esi,而mov %esp、%ecx将栈顶地址(此时就是argv和环境变量(env)数组的起始地址)传给%ecx。现在%esi指向argc,%ecx指向argv 及环境变量数组。

综合以上分析,我们可以把_start改写为一段更具有可读性的伪代码:

void _start()
{
    %ebp = 0;
    int argc = pop from stack
    char** argv = top of stack;
    __libc_start_main( main, argc, argv, __libc_csu_init, __libc_csu_fini,
                       edx, top of stack );
}

其中argv除了指向参数表外,还隐含紧接着环境变量表。这个环境变量表要在__libc_start_main里从argv内提取出来。

...

并行编程任务的四个类别

Content #

我们需要周密考虑并行程序编写者的任务,这些任务对于串行编程来说是不需要的。由此,我们可以评估任意一种语言或者环境对开发者的支持程度。这些任务分解成4个类别:

  1. 分割任务

虽然分割任务能极大地提升性能和扩展性,但是也能增加复杂性。允许线程并发执行会大量增加程序的状态集,让程序难以理解和调试,会降低生产率。

  1. 并行访问控制

给定一个单线程的线性程序,这个线程对程序所有的资源都有访问权。这些资源主要是内存数据结构,但也可能是CPU、内存(包括高速缓存)、I/O设备、计算加速器、文件等。并行访问控制的第一个问题是访问特定的资源是否受限于资源的位置。比如,在许多消息传递环境中,本地变量的访问是通过表达式和赋值,但是远程变量的访问是通过一套完全不同的语法,经常要用到消息传递机制。另一个并行访问控制的问题是线程如何协调对资源的访问。这种协调是由不同的并行语言和环境通过大量同步机制来实现的,包括消息传递、加锁、事务、引用计数、显式计时、共享原子变量,以及数据所有权。传统并行编程关注的死锁、活锁和事务回滚都来源于此。

  1. 资源分割和复制

最有效的并行算法和系统非常善于对资源进行并行化,所以并行程序的编写最好从分割写密集型资源和复制经常访问的读密集型资源开始。这里提到的频繁访问的数据,可能会在计算机系统、海量存储设备、NUMA节点、CPU(核或者硬件线程)、页面、高速缓存行(cache line)、同步原语的实例或者代码临界区等不同的层面进行分割。

  1. 与硬件的交互

当需要榨于系统的最后一滴性能时,也需要直接工作于硬件之上。在这时,开发者需要根据目标硬件的高速缓存分布、系统的拓扑结构或者内部互联协议来对应用程序进行量体裁衣。

From #

深入理解并行编程

应用优化好于并行优化的情形

Content #

并行编程是另外一种性能优化,来自并行计算的速度提升与CPU个数大约成正比。然而对软件进行优化,带来的性能提升是指数级的。

还有一点,不同的程序会有不同的性能瓶颈。比如,假设你的程序花费最多的时间在等待磁盘驱动的数据。在这种情况下,让你的程序在多CPU下运行并不大可能会改善性能。实际上,如果进程正在读取一个旋转的磁盘上的大型顺序文件,并行设计程序也许会使它变得更慢。相反,你应该添加更多的磁盘、优化数据以使这个文件能变得更小(因此读得更快),或者,如果可能的话,避免读取如此多的数据。

From #

深入理解并行编程

并行编程的替代方案

Content #

并行编程只是提高性能的方案之一。其他熟知的方案按实现难度递增的顺序罗列如下。

  1. 运行多个串行应用实例。
  2. 利用现有的并行软件构建应用。
  3. 对串行应用进行逻辑优化。

From #

深入理解并行编程

并行编程的目标

Content #

并行编程(在单线程编程的目标之上)有如下三个主要目标。 1.性能。2.生产率。3.通用性。

越往上层,生产率变得越来越重要;然而越往下层,性能和通用性变得越来越重要。一方面,大量的开发工作消耗在上层,并且必须考虑通用性以降低成本;另一方面,下层的性能损失很不容易在上层得到恢复。在靠近堆栈的顶端,也许只有少数的用户工作于特定的应用。在这种情况下,生产率是最重要的。这解释了这样一种趋势,越往上层,采用额外的硬件通常比额外的开发者更划算。

From #

深入理解并行编程

对自身缺陷的忽视

Content #

讽刺的是,能力不足反而会促进过度自信倾向。克鲁格和邓宁 (Kruger&Dunning,1999)指出,对能力的认识也是需要能力的,那些在语法、幽默以及逻辑测验中得分最低的学生反而最有可能高估他们在这些方面的才能。那些不知道何谓好的语法和逻辑的人通常也不知道自己缺乏这些东西。如果让你写出由“psychology”这个单词中所包含的字母组成的所有单词,你可能会自我感觉良好。但是当你的一个朋友提醒你忘记了某个单词时,你就会感觉自己有些愚蠢。卡普托和邓宁(Caputo&Dunning,2005)在实验中再次考察了这一现象,用以说明我们通过对自身缺陷的忽视来支撑着我们的自信心。下面的实验提示我们,“对自身不足的忽视”易发生于那些看起来容易的任务中,比如上面提到的用“psychology”这个单词中所包含的字母构成单词。在那些困难的任务中,差的表现通常被归结为自身能力的不足(Burson&others,2006)。

这种对自身缺陷的忽视可以解释戴维·邓宁(Dunning,2005)的员工评估实验所得到的惊人结论:别人眼中的我们比我们自己眼中的自己要更加接近客观现实。在一项实验中,被试观察某个人走进屋子、坐下、读一段天气预报,然后走出去 (Borkenau&Liebler,1993)。仅仅依据这点信息,被试对这个人的智商的估计与这个人真实的智力测验得分的相关就达到0.30,而那个人对自己智商的估计与实际分数的相关也不过为0.32!假如无知会产生虚假的自信,那么我们可能会问:呀!我们无法察觉的缺陷到底有哪些?

From #

社会心理学(戴维·迈尔斯)

过度自信现象(overconfidence phenomenon)

Content #

为了探讨这种过度自信现象(overconfidence phenomenon),卡尼曼和特韦尔斯基(Kahneman&Tversky,1979)向被试呈现一些事实描述,要求他们填写在下面的空白处:

“我有98%的把握确信新德里到北京的空中航线距离要大于__公里但是小于__公里。”

大部分被试都显得过度自信:大约30%的次数正确答案都在他们98%的自信正确判断区间之外。

From #

社会心理学(戴维·迈尔斯)

情绪反应通常是即刻的

Content #

情绪反应通常是即刻的,在我们进行审慎的思考之前就已表现了出来。神经通路的捷径是将从眼睛和耳朵那里收集到的信息传送到脑区的感觉交换台(丘脑),并且下传至它的情绪控制中心(杏仁核),而这些过程都在与思维活动有关的皮层以任何形式进行介入之前发生(LeDoux,2002)。

我们的祖先会不自觉地被灌木丛中莫名的声响吓一跳,而实际上并没有什么可怕的。但是与那些更偏向审慎思考的族人相比,如果这个声音是由危险的动物发出的,那么我们的祖先倒更有可能生存下来并将这种基因传递给后代。

From #

社会心理学(戴维·迈尔斯)

受控制加工和自动化加工

Content #

我们的思维只有一部分是受控制加工(controlled processing)(反应性的、深思熟虑的和有意识的),而还有一部分则是自动化加工(automatic processing) (冲动的、无需努力的、无意识的),这超出了心理学家的想象。自动化的直觉思维不是发生在屏幕上,而是发生在屏幕外,我们的视线外,在那里没有理性。

From #

社会心理学(戴维·迈尔斯)