Blog

ext4引入了Extents

Content #

ext4 做了一定的改变。它引入了一个新的概念,叫做 Extents。

比方说,一个文件大小为 128M,如果使用 4k 大小的块进行存储,需要 32k 个块。如果按照 ext2 或者 ext3 那样散着放,数量太大了。但是 Extents 可以用于存放连续的块,也就是说,我们可以把 128M 放在一个 Extents 里面。这样的话,对大文件的读写性能提高了,文件碎片也减少了。

Exents 如何来存储呢?它其实会保存成一棵树。

树有一个个的节点,有叶子节点,也有分支节点。每个节点都有一个头, ext4_extent_header 可以用来描述某个节点。

struct ext4_extent_header {
  __le16  eh_magic;  /* probably will support different formats */
  __le16  eh_entries;  /* number of valid entries */
  __le16  eh_max;    /* capacity of store in entries */
  __le16  eh_depth;  /* has tree real underlying blocks? */
  __le32  eh_generation;  /* generation of the tree */
};

eh_entries 表示这个节点里面有多少项。这里的项分两种: 如果是叶子节点,这一项会直接指向硬盘上的连续块的地址,我们称为数据节点 ext4_extent;如果是分支节点,这一项会指向下一层的分支节点或者叶子节点,我们称为索引节点 ext4_extent_idx。这两种类型的项的大小都是 12 个 byte。

...

inode与iblock

Content #

inode数据结构

struct ext4_inode {
  __le16  i_mode;    /* File mode */
  __le16  i_uid;    /* Low 16 bits of Owner Uid */
  __le32  i_size_lo;  /* Size in bytes */
  __le32  i_atime;  /* Access time */
  __le32  i_ctime;  /* Inode Change time */
  __le32  i_mtime;  /* Modification time */
  __le32  i_dtime;  /* Deletion Time */
  __le16  i_gid;    /* Low 16 bits of Group Id */
  __le16  i_links_count;  /* Links count */
  __le32  i_blocks_lo;  /* Blocks count */
  __le32  i_flags;  /* File flags */
  //......
  __le32  i_block[EXT4_N_BLOCKS];/* Pointers to blocks */
  __le32  i_generation;  /* File version (for NFS) */
  __le32  i_file_acl_lo;  /* File ACL */
  __le32  i_size_high;
......
};

i_atime 是 access time,是最近一次访问文件的时间; i_ctime 是 change time,是最近一次更改 inode 的时间; i_mtime 是 modify time,是最近一次更改文件的时间。

...

PLT(Procedure Linkage Table)

Content #

假设liba.so需要调用libc.so中的bar()函数,那么当liba.so中第一次调用 bar()时,这时候就需要调用动态链接器中的某个函数来完成地址绑定工作,我们假设这个函数叫做lookup(),那么lookup()至少需要知道这个地址绑定发生在哪个模块,哪个函数?那么我们可以假设lookup的原型为lookup(module, function),这两个参数的值在我们这个例子中分别为liba.so和bar()。在 Glibc中,我们这里的lookup()函数真正的名字叫_dl_runtime_resolve()。

当我们调用某个外部模块的函数时,如果按照通常的做法应该是通过GOT中相应的项进行间接跳转。PLT为了实现延迟绑定,在这个过程中间又增加了一层间接跳转。调用函数并不直接通过GOT跳转,而是通过一个叫作PLT项的结构来进行跳转。每个外部函数在PLT中都有一个相应的项,比如bar()函数在PLT中的项的地址我们称之为bar@plt。让我们来看看bar@plt的实现:

bar@plt:
jmp *(bar@GOT)
push n
push moduleID
jump _dl_runtime_resolve

bar@plt的第一条指令是一条通过GOT间接跳转的指令。bar@GOT表示GOT中保存 bar()这个函数相应的项。如果链接器在初始化阶段已经初始化该项,并且将 bar()的地址填入该项,那么这个跳转指令的结果就是我们所期望的,跳转到 bar(),实现函数正确调用。

但是为了实现延迟绑定,链接器在初始化阶段并没有将bar()的地址填入到该项,而是将上面代码中第二条指令“push n”的地址填入到bar@GOT中,这个步骤不需要查找任何符号,所以代价很低。很明显,第一条指令的效果是跳转到第二条指令,相当于没有进行任何操作。

第二条指令将一个数字n压入堆栈中,这个数字是bar这个符号引用在重定位表“.rel.plt”中的下标。

接着又是一条push指令将模块的ID压入到堆栈,然后跳转到_dl_runtime_resolve。这实际上就是在实现我们前面提到的lookup(module, function)这个函数的调用:先将所需要决议符号的下标压入堆栈,再将模块ID压入堆栈,然后调用动态链接器的_dl_runtime_resolve()函数来完成符号解析和重定位工作。_dl_runtime_resolve()在进行一系列工作以后将bar()的真正地址填入到 bar@GOT中。

一旦bar()这个函数被解析完毕,当我们再次调用bar@plt时,第一条jmp指令就能够跳转到真正的bar()函数中,bar()函数返回的时候会根据堆栈里面保存的 EIP直接返回到调用者,而不会再继续执行bar@plt中第二条指令开始的那段代码,那段代码只会在符号未被解析时执行一次。

From #

程序员的自我修养

如何区分一个DSO是否为PIC

Content #

readelf –d foo.so | grep TEXTREL

如果上面的命令有任何输出,那么foo.so就不是PIC的,否则就是PIC的。PIC的 DSO是不会包含任何代码段重定位表的,TEXTREL表示代码段重定位表地址。

From #

程序员的自我修养

动态共享对象(DSO,Dynamic Shared Objects)

Content #

在Linux系统中,ELF动态链接文件被称为动态共享对象(DSO,Dynamic Shared Objects),简称共享对象,它们一般都是以“.so”为扩展名的一些文件;

From #

程序员的自我修养

三种排位稀缺

Content #

排位稀缺可以分为三种。我们称之为“优越感”(Prestige)、“进入权”(Access)和“引导力”(Curation)。

优越感,代表能让你彰显比别人更高的“地位”的东西。比如满大街都是汽车,你要想突出出来,可能需要一辆高档的汽车。奢侈品的价值不在于使用,而在于发出正确的信号:我有钱,我不是一般人。所以奢侈品必须通过“限量”来保证自己的稀缺地位,有时候卖包的商家不是有钱就卖给你。

进入权,则是能在熙熙攘攘的人群之中给你某种特权的东西。比如你因为拿着头等舱的机票,或者因为是金卡会员,可以在普通乘客之前优先登机,这就是“特权”。再比如说有一条繁忙的公路,在常规的车道边上,专门开辟出一条收费通道,因为愿意花钱的人少,别人都堵车的时候这条通道的速度却很快,这也是特权。

搜索引擎的广告竞价排名,本质上就是在卖进入权。不管获取信息再怎么方便,搜索结果页面排第一的那个位置,永远都是稀缺的。

如果说很多优越感和进入权都是可以花钱买到的,那么引导力,则是必须自己经营,才能得到的一种宝贵的稀缺力量。

引导力,是给别人推荐什么东西,别人心悦诚服地接受的能力。中国新近流行的“网红带货”就是引导力的代表。传统上的引导力还包括什么购物指南类杂志、推荐引擎、音乐歌单定制、汽车或者各种东西的评测之类。

引导力能帮助人们做选择。物质越丰富,商品越多,人们越需要帮助选择。

更厉害的东西,则是这三种排位稀缺两两结合的产物。而这一结合,就可能让社会产生更多的不公平。

From #

和这个世界讲讲道理

排位稀缺

Content #

有些东西,就算整个社会的物质再怎么丰富,也会一直是稀缺的。而且可能物质越丰富,它就越稀缺。

比如说,世界杯足球赛的冠军。不管有多少人踢球,冠军只有一个。比赛奖金也好,出场费也好,广告代言也好,冠军的价值只会越来越高。冠军这个位置,哪怕从理论上来说,也是不可能变丰富的,它永远都是稀缺的。

这样的东西,才是我们这个富足时代最贵的东西。

所谓排位稀缺,就是能让你在众人之中突出出来,把你的位置往前排的东西。社会越富足,排队的人越多,它只会越稀缺。参与排位的不是什么可以批量生产的实体商品,它只存在于人们的头脑之中,但是它是可经营的,而且常常是可购买的。

From #

和这个世界讲讲道理

内卷与内耗

Content #

内耗是迫在眉睫的危机,内卷是更长期的忧患。内卷给我们的教训是复杂不等于高级,更不等于先进。中国仍然在高速进步,现在并不是一个马尔萨斯陷阱局面,而且就算是,解决方案也不是“入关”。你把鼻烟壶卖到全世界又能挣多少钱?结果只能是整个世界变成一个更大的马尔萨斯陷阱。

不论是内卷还是内耗,真正的解决办法都是创新。你不是要“入关”,而是要“出关”:你得跳出当前这个发展模式。如果到了S形曲线的平台区,你就要寻找第二曲线,你要积极探索蓝海。

From #

和这个世界讲讲道理

辉格史观

Content #

什么是辉格史观呢?

现在英国一个主流政党叫自由党,自由党的前身叫“辉格党”(Whig)。辉格党从 1688年光荣革命时期兴起,长期支配英国政治。辉格党的理念是要自由、要限制王权、要进步等,都是今天的主流意识形态。现代英国之所以是现代英国,辉格党可以说是功劳巨大。

那么到了19世纪的时候,英国历史学家总结英国光荣革命以来的历史,就把它几乎描写成了一部辉格党的党史。在这些人笔下,辉格党是英国进步的力量,英国在辉格党的带领下不断走向进步是历史的必然,而当初那些反对辉格党的力量则都是落后势力,他们的失败是不可避免的。

1931年,英国历史学家赫伯特·巴特菲尔德(Herbert Butterfield)出了一本书,叫《辉格史观》(The Whig Interpretation of History),对这种历史观提出了强烈质疑。简单来说,巴特菲尔德认为辉格史观是作为胜利者的、现代的、我们的,执念。

“辉格史观”这个词就是巴特菲尔德发明的。此前的历史学家一直都在日用而不知,但是巴特菲尔德这么一说,历史学家们立即就意识到了这是个毛病。

现在“辉格史观”是个毫无争议的贬义词,代表一种原始落后的、不够现代化的历史叙事。如果有个历史学家写一本《哈尔滨人民的奋斗史》,你要说他这是辉格史,他肯定会感到强烈的冒犯。

From #

和这个世界讲讲道理

中国高考的确是内卷

Content #

内卷并不一定降低生活水平。内卷的关键不在于有竞争,而在于“向内演化”,是精细化,是低水平的复杂。内耗是危机,内卷却是一种无声的悲哀。陷入内卷的人很可能乐在其中,都不觉得那是悲哀。

中国高考的确是内卷,但这并不是因为它的残酷性。彩票、诺贝尔奖、奥运冠军、电影明星,这些都是“中奖者”极少而“炮灰”极多的项目,但是这些项目并没有内卷化。高考的内卷之处在于考试内容呈现低水平的复杂。

如果人多名额少,选拔优秀人才的直观办法是增加难度。美国名校录取的一个重要项目是在高中开设大学课程——这有点囚徒困境的意思,但是因为优秀人才可以尽量发挥,所以不能叫内卷。然而中国高考受到大纲的限制,题目如果超纲就对不起边远地区的考生,可是又要能把人淘汰掉,结果只能向大纲之“内”发展,把题目出得很怪。

From #

和这个世界讲讲道理