很久之前在知乎看到了那个“一个初学者应该选择学python 2.7还是直接学python 3.*” 的问题,最近几天又推到了时间线上了,这个问题是2011年就提出来了的,没想到这么久了还这么有生命力。

关于技术选型,从编程语言诞生的那天起就成为了编程界的三大难题之一。选择开发语言,选择系统平台,选择开发框架,选择开发工具,简直随便拎一条出来都能撕逼三天三夜。每一个梦想成为软件开发大师的少年都经历过这一段痛苦的撕逼过程。看到自己选择的技术路线被其他人踩在脚下被嘲笑被唾弃,真是心如刀割,冲上去就开始撕逼了,撕了半天竟然自己都开始动摇了:”是不是这个东西真的很垃圾,是不是我也应该去尝试着用一下他说的东西?”。

阅读全文 »

现代系统通过控制流发生突变来对系统状态变化做出反应,我们把这些突变称为异常控制流(exception control flow, ECF)。异常控制流可以发生在硬件层,操作系统层和应用层。这里的异常与高级编程语言,和C++,JAVA等的异常概念不一样。

异常的类型

异常可以分为4类:中断(interrupt),陷阱/陷入(trap),故障(fault),终止(abort)。其区别如下:

类别 原因 异步/同步 返回行为
中断 来自 IO设备的信号 异步 总是返回下一条指令
陷阱 有意识的异常 同步 总是返回到下一条指令
故障 潜在可恢复的错误 同步 可能返回当前指令
终止 不可恢复的错误 同步 不会返回
阅读全文 »

之前将一台CentOS 7的机器使用samba共享家目录到Windows系统,方便使用。升级到Windows 10之后一直没有使用,昨天尝试连接samba怎么都连不上。

中间倒腾了很久,找了很多资料,改了很多配置,还是不行,总是报权限不足,请联系管理员什么的,如下图:

总结一下查到的几种可能存在的问题,虽然它们对我的情况好像都没有什么帮助:

  1. windows 10会尝试使用samba 3_11链接,而现在samba server一般安装的是4.*的,所以需要disable掉Windows 10 samba 客户端的SMB 2/3协议,来自samba邮件列表 https://lists.samba.org/archive/samba/2015-September/193886.html ,禁用方法有微软官方方法 https://support.microsoft.com/en-us/kb/2696547。这个很重要
  2. 对于无密码的samba共享,还需要设置一个注册表,找到 HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\LanmanWorkstation\Parameters,新建一个 DWORD(32-bit)Value,设置名字为AllowInsecureGuestAuth,值为1。有的人说这个有用。
  3. 防火墙的问题,这个好解决,禁用掉iptables或者firewalld试试就知道了,要在iptables打开相关的端口,可以参考 这篇文章
  4. seLinux导致没有权限。这是我遇到的问题,我的server之前是setenfoce=0了的,后来机器断电重启就忘了这事了,结果昨天晚上倒腾好久都没想起来这茬,如果出现上面截图的情况请一定要
sudo setenfoce 0

这样就可以了。如果以前可以用现在不可以连接了,一般不会是samba配置的问题,考虑server防火墙和seLinux比较靠谱。

前一篇讲了链接的基础,这里继续链接过程的重定位实现机制以及可执行目标文件的结构和后面的一些内容。

重定位

前面讲过了链接器的符号解析过程,在符号解析完成之后,代码中的每个符号引用和确定的一个符号定义联系起来了。此时,链接器就知道它的输入目标模块中的代码和数据节的确定大小。此时开始进行重定位,重定位将合并输入模块,并为每个符号分配运行时地址。重定位分两步进行:

  1. 重定位节和符号定义。 链接器将所有相同类型的节进行合并,形成一个新的聚合节。例如将所有输入模块的 .data 节合并到一个节,这个节成为输出的可执行目标文件的 .data 节。然后链接器将新的存储器地址赋给新的聚合节。此时程序中每个指令和全局变量都有了一个运行时存储器地址了。
  2. 重定位节中的符号引用。 对指令和全局变量(定义)重编码后,需要将输入模块的节(代码和数据)中的符号引用进行重定位,使它们指向第一步重定位后的运行时地址。这一步依赖于一个称为 relocation entry(重定位条目)的数据结构。这个条目由汇编器生成,链接器根据这个结构的内容确定符号引用的运行时地址。

relocation entry

汇编器生产一个目标模块时,它并不知道数据和代码最终存放在存储器的什么位置,所以在汇编器遇到最终位置未知的目标引用时,就生成一个重定位条目(relocation entry),告诉链接器如何在合并成可执行文件时修改这个引用。代码的重定位条目保存在 .rel.text 中,数据的重定位条目存放在 .rel.data 中(见上一篇的可重定位目标文件结构)。

relocation entry的数据结构定义如下:

typedef struct {
int offset;
int symbol:24,
type:8;
} ELF32_Rel;

其中offset是需要被修改的引用的节偏移(相对于节的偏移),symbol是标识被修改的引用应该指向的符号(的地址),type是高职链接器如果修改新的引用。

type有11中,我们只需要知道最主要的两种方式,一种是 R_386_PC32,即使用一个32位PC(程序计数器)相对地址的引用。PC的值通常是存储器中下一条指令的地址。另一种方式R_386_32;重定位使用一个32位绝对地址的引用。

重定位符号引用

一种重定位算法的伪代码如下所示:

foreach section s {
foreach relocation entry r {
refptr = s + r.offset;
if (r.type == R_386_PC32) {
refaddr = ADDR(s) + r.offset;
*refptr = (unsigned) (ADDR(r.symbol) + *refptr - refaddr);
}
if (r.type = R_386_32)
*refptr = (unsigned)(ADDR(r.symbol) + *refptr);
}
}

其中ADDR(s)表示s的运行时地址(在节内部符号重定位时,符号和指令的运行时地址已经知道了)。ADDR(r.symbol)是符号的运行时地址。

重定位PC相对引用 对于PC相对位置的重定位。根据上面的代码分析:refptr是一个地址,其值 refaddr 是相对定位的偏移的值(32位长度)。一定要注意第6行的计算, refptr不要用refptr代入计算了,注意它是一个指针。汇编器会将call指令的引用的初始值设置为 -4 (0xfffffffc)(在不同指令大小和编码方式上可能不一样)。这是因为PC总是指向当前指令的下一条指令,指向 -4 允许链接器透明地重定位引用,而不用知道某一台机器的指令编码。这部分使用一个例子更加清晰,CSAPP Page 462的例子可以仔细看看。

重定位绝对引用

可执行文件的结构和加载

链接器将多个目标模块合并成了一个可执行的二进制目标文件。这个二进制文件包含加载程序到存储器并运行它所需的所有信息。一个典型的ELF可执行文件结构如下图所示。

其结构整体和可重定位目标文件差不多,不过因为已经完成了重定位(称作完全链接的),所以没有了 .rel.data 和 .rel.text 节了。多了一个 .init 节,定义了一个_init函数,程序的初始化代码会调用它。

段头部表(segment header table)描述了可执行文件的连续的片(chunk)被映射到连续的存储器段的信息。

shell通过一个驻留在存储器中称为加载器(loader)的操作系统代码来运行生成的可执行文件,任何Unix程序都可以通过调用 execve 函数来调用加载器。

每个Unix程序都一个一个运行时存储器影响,在32位Linux系统中,代码段总是从地址 0x08048000 处(小端存储)开始。数据段是接下来的下一个4KB对齐的地址处,运行时堆在读/写段之后接下来的下一个4KB对其的地址处,并通过调用malloc库往上增长。用户栈总是总最大的合法用户地址开始,向下增长的,从栈的上部开始的段是操作系统驻留存储器的部分(内核)保留的。其表示如下图。

每个C程序中启动例程的伪代码如下:

0x080480c0 <_start>:
call __libc_init_first
call _init
call atexit
call main
call _exit

加载工作的一个概述如下:Unix系统中的每个程序都运行在一个进程上下文中,有自己的虚拟地址空间。当shell运行一个程序时,(父)shell进程生成一个子进程,它是父进程的一个复制,子进程通过execve系统调用启动加载器。加载器删除子进程现有的虚拟存储器段,并创建一组新的代码、数据、堆和栈段。新的栈和堆段被初始化为0.通过将虚拟地址空间中的页映射到可执行文件的页大小的片,新的代码和数据段被初始化为可执行文件的内容。最后加载器跳转到 _start 地址,它最终会调用应用程序的 main 函数。除了一些头部信息,在加载过程中没有任何从磁盘到存储器的数据拷贝。知道CPU引用一个被映射的虚拟页才会进行拷贝,此时操作系统利用它的页面调度机制自动将页面从磁盘传送到存储器。

待续

Vimuim是一款很酷的chrome插件,它能让你使用Vim的方式操作你的chrome浏览器,滚动,打开链接,tab跳转,复制,搜索,功能非常强大。

在有鼠标的时候可以使用鼠标手势快速操作浏览器,但是在使用笔记本(我现在的T430)的时候,基本不使用鼠标,使用小红点浏览网页的时候还是有点麻烦。今天使用了一下大名鼎鼎的Vimium插件,简直解放了我的食指。

首先安装插件,地址是 https://chrome.google.com/webstore/detail/vimium/dbepggeogbaibhgnhhndojpepiihcmeb?utm_source=chrome-ntp-icon ,另外还有一些类似的插件,像 cVim 等,不过用的最多是这个 Vimium。

阅读全文 »

链接(linking)是将各种代码和数据部分收集起来并组合成为一个单一文件的过程,这个文件可以被加载到存储器并执行。

链接可以是在编译时执行(静态链接),或者在加载时执行,甚至在运行时执行(后两者均为动态链接)。在现代操作系统中,链接由链接器(linker)自动执行。

链接器使得分离编译成为可能。可以把程序分解为更小、更好管理的模块,可以独立的修改和编译这些模块,只需要简单重新编译它们,然后重新链接应用,而不需要重新编译其他文件。

阅读全文 »

今天下午因为实验室被占用开会就直接没过去了,想在宿舍自己看看书算了。在宿舍果然没法好好看书,看了一会书之后即不想看了,就想着翻一部电影出来看吧。想起来一直都想看的一部《美丽人生》。

之前看过一部日剧也叫《美丽人生》,由木村拓哉和常盘贵子主演的,我很喜欢那部剧。这一部美丽人生的电影则更加有名一些,在IMDB的排行榜是前100,豆瓣评分高达9.5。之前只是知道名字,并不知道讲什么内容。

《美丽人生》的名字很就很有意思,影片一开始讲述了两个很有逗逼的意大利人的一些事情,确实有种意大利人的滑稽与幽默。一开始两人开车被”冒充”国王,碰到女主时自称是王子,开头的半个小时就像一个普通的滑稽戏剧一样。这并没有太多特别的。

如果不是后面的剧情,我只会认为男主圭多就是一个快乐的逗逼,而且是个运气很好的逗逼。总是能逢凶化吉,遇到很多巧合来,制造很多笑料。甚至他对于女主的钟情都有点像是搞笑的。女主像是一个大小姐一样的人,在已经决定要那个市政府的工作人员订婚了,由于和圭多的多次”巧合“而愿意抛弃那个自己并不爱的公务员,和圭多”私奔“了。

意大利人和法国人都有点”民族特色“的浪漫,女主多拉(奥拉)和男主的爱情似乎没有什么理由。圭多很聪明,很幸运,活得很开心,和各色人都关系很好,就是一个自来熟。想干什么就干什么,想见女主了就借各种巧合去了,比如冒充视学官去女主的学校检查工作,上演逗逼的一幕,问到女主要去看话剧就也跑去剧院,并半路把女主从她的未婚夫那”劫走”了。他这种想干就干的性格还是挺讨人喜欢的。两人驾车抛锚之后,直接把车后的红丝绸从阶梯滚下去做红毯,然后用枕头当伞在雨中漫步,男主真是个会玩的主。

喜欢一个人本就是没有什么原因的吧,喜欢了,想见面,越来越喜欢就想在一起,结婚生子。没有什么是注定的,喜欢的人只是因为在这个时间这个地点遇到了,就会喜欢上,然后慢慢的生长,就成了爱,不过或许这也就是注定。

圭多和奥拉在一起后,生活非常幸福(虽然这容易的有点假了)。可是不久纳粹政府就开始掌权了,由于圭多是犹太人,他们的儿子自然也是犹太血统。在他们的儿子生日的那天,圭多父子和他叔叔都被纳粹抓走了。大人们应该都已经知道纳粹的政策了,这次被抓走,可能就再也没有见面的机会了。奥拉并不是犹太人,没有必要去集中营的,但是为了儿子和丈夫义无反顾地上了去往集中营的火车。

即时在残酷的集中营中,圭多用各种办法保护儿子的安全。还和儿子说他们来这里是玩一个游戏的。圭多是如此的乐天而聪明,我一开始都觉得情况并没有那么糟糕嘛。这也许就是快乐是能传染的吧。圭多在儿子面前表现的那种快乐才能欺骗过儿子。即使儿子偶然听到了其他孩子是被送进了毒气室,被烧死了,被用来制作肥皂这样的事情,圭多还是用自己的办法哄骗过去。

圭多用自己的办法为幼小的儿子在黑暗而残酷的集中营搭起了一个快乐的天地,在这里他看不到死亡与折磨,看不到那些惨无人道的事情,而能保持一个孩子的天性。

那一张经典的带着头巾,用毯子做围裙装作妇女,被纳粹军人用枪威胁着大踏步地从儿子所在破铁箱子前走过的剧照,给人留下深刻的印象。即使即将被黑暗的纳粹的力量所撕碎,还是在孩子面前表现得这就是一个游戏,我们即将赢得大奖,让孩子能安全地躲过最后一晚。

最后留给圭多的只有一串枪声,没有一个镜头,从此消失不见。而第二天儿子从破铁箱冲钻出来的时候,正好盟军开着坦克进入了集中营,大兵让他上坦克一起走,在路上终于又找到了妈妈,这圭多祈求的,一直在努力要送给儿子的大奖吧。

如果从常理分析肯定可以说这个电影有很多不合理的地方,但是这部电影应该是有原型故事的,其实现实中发生的事情也总是那么不可思议。有很多事情就是那样神奇的,永远保持一颗乐天,勇敢的心,把人生的残酷作为一个游戏,明天总会是美好的一天。

这部电影,是一部滑稽,浪漫的喜剧,也是一部黑暗而令人窒息的悲剧。希望纳粹这样反人类的事情不要再在这个世界出现。

你能获得的对程序对大的加速比就是当你第一次让它工作起来的时候。
–John K.Ousterhout

优化编译器的能力和局限性

下面的代码1不能优化为代码2:

代码1:

void teiddle1(int *xp, int *yp) {
*xp += *yp;
*xp += *yp
}
阅读全文 »

C语言中最难理解也是最容易出错的的就是指针了,以前学C的时候就最害怕指针,CSAPP中3.10章节理解指针部分,非常有利于加强对指针的理解。

指针是C语言的一个重要特征 。它以一种统一方式,对不同数据结构中的元素产生引用。对于编程新手来说,指针总是会带来很多的困惑,但是基本的概念其实很简单。在此,我们指点介绍一些指针和它们映射到机器代码的关键原则。

  • 每一个指针都对应到一个类型。这个类型表明指针指向哪一类对象。以下面的指针声明为例:
int *ip;
char **cpp;
阅读全文 »

一个方法可以有很多种实现,在a.c种实现func函数,在b.c种也实现func函数,在main中使用了func,可以通过链接时链接到不同的.o文件调用不同的实现。可能描述的不是很清楚,下面用一个例子说明。

现在要实现一个三个整数轮转的函数,就是 int x, y, x要调换他们的值,也就是向右轮转,是y=x, z=y, x=z; 下面有两种实现

a.c

void decode(int *xp, int *yp, int *zp) {
int tp1 = *xp;
*xp = *zp;
int tp2 = *yp;
*yp = tp1;
*zp = tp2;
}

b.c

void decode(int *xp, int *yp, int *zp) {                                                       
int tx = *xp;
int ty = *yp;
int tz = *zp;

*xp = tz;
*yp = tx;
*zp = ty;
}

他们实现了同样的功能,现在在main种调用decode方法。

main.c

#include <stdio.h>

int main() {
int x = 1;
int y = 2;
int z = 3;

decode(&x, &y, &z);
printf("%d, %d, %d\n", x, y, z);
}

下面就是编译了,使用gcc分别链接到a的.o文件和b的.o文件。
先汇编到 .o 文件。

gcc -c a.c
gcc -c b.c

这样会生成 a.ob.o 两个文件,下面编译main.c 生成可执行文件。

gcc -o decode1 a.o main.c
gcc -o decode2 b.o main.c

这样生成两个可执行文件decode1decode2,分别运行他们可以得到其结果是一样的,都输出 3, 1, 2

很久没有用C了,很多东西都忘记了。