见过凌晨4点的杭州吗?醒来作文一篇。
其实本文我想换个题目的,但是就着《闲谈IPv6》系列写吧,反正也是闲谈,就着一个系列,也便于自己以后整理成专栏。
没关系,闲扯吧。
引
近期上演的《倚天屠龙记》,很垃圾,但是我很喜欢这个故事,因为主角张无忌总是很多人yy的对象,当然肯定也包括我。
爹妈牛逼,义夫太师父黑白两道通吃,身边几大天王几大掌门非亲即友,自身武功无人能敌,蛛儿-殷离,周芷若,赵敏,小昭…就不说了,还混入了政界,最后抱得美人归…论财富,论单挑,论群殴,论撩妹,论脾气好,谁敢PK,还TMD能指挥军队…开挂的人生啊!
这部小说引起人们一个疑问,即为什么大家都去抢屠龙刀而没有人去抢倚天剑。某逼乎的答案汗牛充栋,这里我给出我的答案:
绝大多数人喜欢独秀,那是因为没有能力没有资源去运筹。
两本书,一本武功秘籍,一本兵法,你会选哪个?毫无疑问是秘籍!Why?因为秘籍你自己学会就能独秀,而兵法要想去秀,你首先要有一大批军队。是不是这个道理?
很多程序员痴迷于某种编程语言,痴迷于Linux内核源码,却非常不care产品,不care经理总监这种所谓的管理层的技术,也是一样的道理。所以编程语言的书卖相要远远好于软件工程的书,后者大家都认为是空话,太形而上。
漂亮的大厦,大家都对空间有着苛刻的需求,却不care电梯,楼梯,水箱;
编写一个大数据分析引擎,程序员只关注算法却不care计算机是如何工作的;
…
这就是分工协作,它带来了收益,同时也必然意味着某种代价的付出。
管理开销不容忽视! 管理者非常不容易!管理者要做支撑,要做资源协调,你知道你在舞台上嗨歌艳舞的时候,背后那些灯光师音响师,舞台搭建团队的努力吗?当你的嗨歌艳舞嗨到一定程度的时候,就是那些背后力量说不的时候了。
我来反着为那些背后的人说句公道话,你知道你能做某项技术或者主导某个项目的核心技术的背后,其实是那些穿西装皮鞋的经理,销售,售前已经帮你铺好路了么?销售不拿下单子,你做个毛啊!
艳丽的表象背后,总有活动的苦逼。
序言
IPv6替换IPv4,地址空间扩大到了128比特,这意味着我们拥有了更多的可用IP地址,但是代价是什么?
如果有一天,IPv6又不够用了,我们的IPv8是不是可以轻松地将地址空间再次扩大到256比特?IPv10呢?IP地址空间是不是可以无限扩大下去?本文将总结一些我的思考。
如果仅仅是文字说教,那将是没有说服力的,我比较喜欢计算,那本文里或许会有一些值得揣摩的好玩的数据。
大道至简,万物相通!先说点儿别的。
电梯
早在64位系统替换32位系统的时候,我就在担心,如果还是按照32位系统的MMU机制实现方式,我们的Gbit大小数量级的物理内存够不够装下64位系统进程的MMU页表,幸运的是,没有问题,然而并不是说64位系统的页表设计是多么的好,而是根本没有那么大的进程需要映射全部的虚拟地址空间的所有地址。
容我慢慢道来。
如果你没有接触过内存的MMU,那么我再说点别的,说点所有人都懂的一个例子,那就是摩天大楼的电梯系统。
上海陆家嘴是一个摩天大楼聚集的地方,站在黄浦江对岸的外滩远望,非常之震撼,一点不输纽约曼哈顿和香港维港。现在放下震撼,思考一个问题,按照现在上海中心大厦的设计方案,能不能将它堆10倍,造一座6000米的大楼?
杠精们可能会考虑一些边角的问题,比如疏散问题,消防问题,抗震问题,高空氧气问题,烘暖防风问题等等,我现在假设,这些都已经解决了,不考虑这些问题,仅仅从工程可用性的角度来看,能不能造出来?
答案是不能?Why?因为电梯占据的面积将把所有的可用面积挤压殆尽,最终这座大楼完全就是一座电梯塔,毫无用处。让我们简单算一下。
对于200米以下的综合写字楼,我们常见的形式是下面的样子:
请注意中间电梯井,我们假设它是直筒形的,也就是说一楼的电梯井截面积和顶楼的电梯井截面积是相同的,为了简化我们的计算,做以下假设:
- 大楼的面积仅仅分为两个部分:可使用部分和电梯占据的部分(也就是电梯井截面积)
- 所有的电梯必须可以装进去整座大楼所有的人。
现在开始我们的计算:
设可使用面积单位面积可以容纳
m
m
m人,电梯部分单位面积可容纳
n
n
n人,很显然,电梯里要挤一挤:
m
<
n
m<n
m<n
设楼层数量为 f f f,现在计算整座大楼电梯面积占据整体面积的比例。
设单层使用面积为 x x x,电梯面积为 y y y,则所有 f f f层一共可以容纳的人数为:
x × m × f x\times m\times f x×m×f
它等于一次电梯运送的人数:
x × m × f = y × n x\times m\times f=y\times n x×m×f=y×n 【式子1】
按照我们的假设,大楼和电梯井都是直筒的,所以单层的电梯面积和有效使用面积的比值将可以代表整座大楼的对应比值(消去 f f f的结果),那么电梯井截面积和使用面积的比值为:
y x = m × f n \dfrac{y}{x}=\dfrac{m\times f}{n} xy=nm×f 【式子2】
由于电梯里往往挤一些, m < n m<n m<n,那么只要楼层 f f f满足 f > n m f>\dfrac{n}{m} f>mn,则有:
y x > 1 \dfrac{y}{x}>1 xy>1
即电梯的面积超过使用面积,如果楼层 f f f再高一些,这个比值就会更大,也就是说,大楼的有效使用率和楼层的高度是成反比的,楼层高到一定程度,大楼将变得不再可用,看起来仅剩下了电梯空间,这个时候, 必须要修改架构!!
我们看一下如何修改。
考虑到一个现象, 一般而言上楼的时候,电梯越往上人越少。 这是因为低楼层已经将人数给卸载了,也就是说 电梯越往上需要的空间越小!
因此,高低楼层分区的多部电梯并行设计就出来了,简单点说,就是将一部大的从底通到顶的电梯拆分成两部甚至多部更小的,但是只负责独立楼层区间的电梯:
既然高楼层不需要那么大的电梯空间了,那么大楼整体上也就可以缩小一些,如果这座大楼非常高,那就不止需要分高楼层和低楼层两个区间了,而是要分成多个区间,越高电梯就越少:
嗯,我们常见的摩天大楼基本也都是这个样子,越高越细,这不仅仅得益于电梯系统的分区设计,还有一个原因是不得不这么做,不光要越高越细,还要越高的建筑材料越轻,除了电梯因素,地基受力也是要考虑的,因为所有垒上去的高度,其重量都要被地基承载,但这是另外一个工程问题,本文不扯。
不光是摩天大楼,很多塔都是这样的形状,甚至树也是。因为树也需要内部的管道来输送营养物质,也会遇到类似的问题,所以自然选择便如是解决了问题。
那么我们不禁要问,这种分级设计,可以让建筑物(或者树)无限增高吗?
好,为此,我们要算一下这种新型的分区电梯系统,它们有效面积率。
还是上面的假设,我们把楼层 f f f分成了高度相等的 k k k个区间, x x x依然表示单楼层的有效使用面积, y y y虽然代码的数值没变,但它的含义变了,它仅仅表示最低层那个区间的电梯井截面积,按照上面说的原理和图示,次低区间的电梯井截面积将是 k − 1 k × y \dfrac{k-1}{k}\times y kk−1×y,以此类推,最顶区间的电梯井截面积将是:
k − ( k − 1 ) k × y = 1 k × y \dfrac{k-(k-1)}{k}\times y=\dfrac{1}{k}\times y kk−(k−1)×y=k1×y
好了,现在让我们计算整座大楼所有电梯面积和所有有效使用面积的比值:
P = ( k − 1 k × y ) × f k + ( k − 2 k × y ) × f k + . . . + ( 1 k × y ) × f k x × f P=\dfrac{(\dfrac{k-1}{k}\times y)\times\dfrac{f}{k}+(\dfrac{k-2}{k}\times y)\times\dfrac{f}{k}+...+(\dfrac{1}{k}\times y)\times\dfrac{f}{k}}{x\times f} P=x×f(kk−1×y)×kf+(kk−2×y)×kf+...+(k1×y)×kf
分子是所有的电梯面积,分母为所有的有效面积,化简得:
P = y × ( k − 1 ) 2 × x × k P=\dfrac{y\times (k-1)}{2\times x\times k} P=2×x×ky×(k−1)
根据上面的【式子1】和【式子2】的原理,导出一个新的等式, f k \dfrac{f}{k} kf楼层的人要用截面积为 y k \dfrac{y}{k} ky的小电梯一次性运送:
x × m × f k = y k × n x\times m\times \dfrac{f}{k}=\dfrac{y}{k}\times n x×m×kf=ky×n
最终它还是【式子2】,于是:
P = y x × k − 1 2 × k = m × f n × k − 1 2 × k P=\dfrac{y}{x}\times \dfrac{k-1}{2\times k}=\dfrac{m\times f}{n}\times \dfrac{k-1}{2\times k} P=xy×2×kk−1=nm×f×2×kk−1 【式子3】
可以看到,经过电梯分区改造之后,电梯空间的占比增加了一个因子:
F ( k ) = k − 1 2 × k F(k)=\dfrac{k-1}{2\times k} F(k)=2×kk−1
该因子是关于
k
k
k的函数,一眼看得出来,它是趋向于最大值
1
2
\dfrac{1}{2}
21的:
也就是说,如果说不分区的电梯设计能支持的大楼最大楼层是
f
f
f的话,切换到这个分区电梯设计后,它只能缓解
1
2
\dfrac{1}{2}
21,也就是说最大支持到
2
×
f
2\times f
2×f的高度!
这是杯水车薪!!所以说,多分区电梯更多的是为了减小地基压力,而不是增加有效面积比例。
也许下面的措施可以进一步优化:
- 优化电梯调度。
- 让人们更加挤一些。
但都无法从根本上解决问题。此外,如果你把大楼建到1000米以上,地面地基面积将会无比庞大!
因此,除非彻底更改架构,再牛逼的电梯系统都不能支撑无限高的摩天大楼!
如何彻底更改架构呢?本质上是一个 反重力 问题,但这与本文无关,回头再谈。现在的结论是,只要摩天大楼增加到一定高度,这个 有效面积比例降低到不可用的问题 就必然浮上水面,就必须被解决!
总结一下电梯设计,一句话,你不care电梯,错了!
MMU系统
如果你对上面的摩天大楼问题有了认知,下面就说说64位系统寻址。
我们知道,现代操作系统中,为了将进程隔离的虚拟地址转换为物理地址,需要一个叫做MMU的部件,而MMU部件得以工作的基础则是内存中的页表!
嗯,是的, 指导内存转换的元数据,本身也要占据内存! 是不是跟摩天大楼的电梯问题非常类似,简直是一个问题啊!
现在, 和摩天大楼电梯占据多少面积的问题一样,页表需要占据多少内存?
先说32位系统。
一个页面 2 12 = 4096 2^{12}=4096 212=4096字节,即4K的大小,32位地址空间一共需要 2 32 2 12 \dfrac{2^{32}}{2^{12}} 212232也页表项,一个页表项大小为4字节来指示,那么页表项总共需要的内存数量是 2 32 2 12 × 4 \dfrac{2^{32}}{2^{12}}\times 4 212232×4,也就是4M的空间,这对于当代动辄以16GB买入的物理内存而言,太小case了,但是…
一个进程都需要一个页表,如果有1000个进程,那就需要4GB的空间来存储页表,对于Linux这种操作系统而言,更加悲哀,页表是常驻物理内存的!这意味着,1000个进程,光页表就要占4G的空间,如果2000个进程,那就是8G,看吧,遇到了摩天大楼的电梯问题了吧…
回到20年前,那时候,设备的物理内存一般都只有64MB,128MB,而服务器动辄上千个进程妥妥的,怎么办。可见,在内存MMU领域,早早就遇到了摩天大楼的电梯问题类似的问题!
注意分析一个进程,没有哪个进程能映射完整个4G的虚拟地址空间,一般进程映射的地址数量也就几兆到百兆,并且这些内存在编译期就被安排到了连续的地址空间,所以就设计了 分级页表 ,每级的页表也是按需分配,也就是说,只要这个页表对应的虚拟内存被映射了,才会分配页表空间。鉴于大部分的进程都只映射很少的内存,所以说每个进程的页表也就占据几十字节到几K的空间,非常好的解决了问题。
但这不是根本解法,和分区电梯一样,不是根本解法!我们来分析一下为什么不是根本解法,两个例子:
- 遇到吃内存大户,妥妥的就真的映射完了4G的空间怎么办?
- 32位系统的分级页表到了64位怎么办?
32位系统承诺给用户每个进程可以使用32位的地址空间,由MMU来负责映射物理内存,但是如果你真的去映射完32位4G的空间,你就会发现,虽然系统对单个进程兑现了承诺,但是你却创建不了太多这样的进程。
不信,你在Linux上按照分级页表的布局,把全部地址空间按照页面边界完全建立仅仅16字节的映射,把地址空间撸一遍,碰到冲突失败了就继续,不退出,然后启动进程,试试看,看看能创建多少进程。用一次性映射的方法,映射同样多的内存,试试看,是不是可以启动的进程数增加了不少。
总结一句, 分级页表并没有减少页表可能占据的总空间,它只是利用了进程内存的局部性,连续性分配而减少了不必要的页表空间分配。
现在,我们都使用64位系统,我们看看64位系统如果依然按照和32位系统类似的分级页表设计,一个进程需要多少内存来存储页表。如果映射完所有的地址空间,总共需要的内存数量为:
2
64
2
12
×
8
=
2
55
\dfrac{2^{64}}{2^{12}}\times 8=2^{55}
212264×8=255字节
嗯,天文数字!天文数字!
如果说32位系统映射完4G空间需要4M的内存仅仅是影响可用的进程数量(至少还有几百个进程可以启动)的话,那么在64系统,仅仅一个进程映射完所有的地址空间就需要 2 55 2^{55} 255字节,也就是 2 43 2^{43} 243PB的空间!这已经超过了目前内存工艺的极限!
因此,64位系统完全无法映射太多的地址空间,因为页表的存储代价太大!64位系统就是计算机界的超高摩天大楼!
然而,现实却没有这么夸张!真实的64位系统并没有使用完全64位的地址空间。
比如,Intel的处理器只能处理48位的地址空间:
可能对于处理器厂商来讲,也早就注意到了这个管理平面本身的能效问题,不仅仅页表要占据内存空间,处理器的MMU处理逻辑也会相应复杂化,归根结底就是与非门的暴增!
说到硬件,Intel有苦衷,你可以想象CPU芯片就是一座大厦,MMU设施就是它的电梯,这座Intel大厦已经毕竟了最大的高度极限。
其实,软件问题还好,页表占据内存的问题可以通过交换空间来解决,但处理器芯片上的空间利用却是不能解决的,当前处理器工艺已经逼近了热密度极限!
但即便处理器并不真的处理64位空间,而是48位空间,也够喝一壶的,我们算算看一个进程完全映射48位空间会使用多大的页表:
2 48 2 12 × 8 = 2 39 \dfrac{2^{48}}{2^{12}}\times 8=2^{39} 212248×8=239字节
嗯,确实小了很多,也就是 2 30 2^{30} 230GB的空间。需要 2 30 2^{30} 230GB的内存存放一个进程的页表!!
当然,其实考虑细节的话, 2 48 2^{48} 248的空间也夸张了,事实上,可用的地址空间包括两个部分,处于64位的两端:
- 0x0000000000000000 - 0x00007fffffffffff
- 0xffff800000000000 - 0xffffffffffffffff
这么算来,一个进程也就256TB的地址空间。如果全部把这256TB的空间映射满,所需要的页表空间也不是现代内存工艺能hold住的。
我们写一个程序来说明一下一个事实,即 进程页表占据的内存非常可观!
为了以最简单的方式描述这个问题,我们分别以不同的方式映射相同大小的内存空间,这样体现出来的是 页表占用的内存随着内存分布分不同而不同。
代码如下:
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
int main(int argc, char **argv)
{
size_t pagesize = getpagesize();
char * region;
int intv = atoi(argv[1]);
long i, j = (1 << 10);
// 一共映射10240*pagesize字节这么大的内存空间。
for (i = 0; i < 1024*10; i++) {
region = mmap((void*) (pagesize * j), pagesize, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_ANON|MAP_PRIVATE, 0, 0);
if (region == MAP_FAILED) {
perror("Could not mmap");
return 1;
}
// 触发缺页处理,调页!
strcpy(region, "write it to trigger pagefault!");
// 这个很关键,这个影响到映射内存的分布。
j += intv;
printf("%s %p %d\n", region, region, i);
}
sleep(120);
return 0;
}
在执行之前,首先看一下当前的内核内存占用情况,页表就被PageTables统计,此外,页表还被SUnreclaim统计,这意味着它们是常驻内存不可回收的。为了检查单纯的内存占用,我使用 swapoff -a 关闭了swap。
先看执行进程前的结果:
[root@localhost ~]# cat /proc/meminfo|grep PageTables
PageTables: 4064 kB
[root@localhost ~]# free -m
total used free shared buff/cache available
Mem: 992 99 739 6 153 750
Swap: 0 0 0
然后用1作为参数执行后,内核内存占用为:
[root@localhost ~]# cat /proc/meminfo|grep PageTables
PageTables: 4164 kB
[root@localhost ~]# free -m
total used free shared buff/cache available
Mem: 992 139 698 6 154 710
Swap: 0 0 0
几乎没有变。内存used从99增加到139,那是因为我在进程地址空间映射了40M的内存。
那么我用4096作为参数来执行呢?内存映射布局变化了,变得更加离散,这意味着需要离散的很多页表来支持这些个映射了,预期中的页表占用内存会增加。
在我执行后,查看内核内存占用,果然增加了:
[root@localhost ~]# cat /proc/meminfo|grep PageTables
PageTables: 45048 kB
[root@localhost ~]# free -m
total used free shared buff/cache available
Mem: 992 180 655 6 157 666
Swap: 0 0 0
used内存增加了80M的大小!比进程实际映射的内存增加了一倍!
映射 4 K × 1024 × 10 = 40 M 4K\times 1024 \times 10=40M 4K×1024×10=40M的内存,竟然需要40M的页表,我的天啊!一座大楼里电梯和使用面积一比一!哈哈,这种房子如果是商品住宅,你会买吗?
你去映射个400M,4G内存看看!?
可以说,64位系统根本就不支持满空间映射!那么解决方案呢?
- 采用大页面,比如4MB,128M的大页面。
- 废掉分级页表,改为哈希页表,反向页表,用时间换空间。
顺便一说,目前大数据时代,动辄处理数以PB的数据太多了,因此不要再认为 一个进程使用不完满地址空间的内存 这个古老的结论了。 时代变了,基础设施必须改变!
不管怎么说,32位进化到64位,并不仅仅是地址空间扩大了32个比特这么简单,它涉及到了几乎是重构级别的基础设施改造!正如上海中心大厦拓展到6000米并不是10个大厦堆在一起那么简单是一样的道理。
地址空间线性扩展,管理开销指数级暴增!
所以,不要再说我们国家工作人员冗余了,那是因为人口实在太多了。
线性扩展的背后,藏着指数级增长的基础设施改造的需求!
总结一下大数据编程设计,一句话,你不care MMU,错了!
IPv6
本文题目是说要闲谈IPv6,但是到目前却一点儿没提IPv6,而且我要说的是,本文就要结束了。
知道我想说什么了吗?本文用电梯和MMU作为例子,其实是想说,IPv6也是一样,它将地址空间长度扩展了4倍,预示着什么将被彻底重构呢?
很多人都说是路由表或者说是路由表的查表算法,认为IPv6的路由表项或许达到天文数字级别,然而并没有,它在严格聚类分配的约束下,反而更小了。
但是幸亏是有严格的聚类分配约束,不然呢?或者如果说没有聚类分配约束,互联网真的会在IPv6时代崩溃吧。
一个路由查询如果到达秒级,那么整个IP路由系统将不再可用,但是随着路由表项的增加,即便是再高效的查询算法,也会使这个数字到达秒级,所以说,IPv6一定要严控地址分配过程,一定要严格处理BGP通告,否则,后果将不可设想。
算法正在遭遇挑战!
我们推崇很多 O ( l n N ) O(lnN) O(lnN)算法,到目前为止却没有一个真正的 O ( 1 ) O(1) O(1)算法,完美哈希依然在梦里,但是我们知道 f ( x ) = l n x f(x)=lnx f(x)=lnx是单调递增函数,它永远向正无穷大趋近,只是慢了一些罢了,但是我们要清楚的认识到,慢不等于停,事情正在悄悄地起变化!
我们没有高效率处理海量路由表项的算法,所以我们不得不借助于人,我们不介意人的介入!在分配IP地址的时候,是人而不是机器在阻止IP地址被任意切割挖洞分配。
这就好比PKI领域的认证,我们在理论上没有一个完备的体系可以完成一次认证,所以最终我们还是要人为干预,这就有了X.509证书,记住,这张证书永远是人签发的,机器只是帮忙罢了,看看各级CA的大佬,是人还是机器!
我想说的是,如果假设我们现在有一个处理海量路由的 O ( 1 ) O(1) O(1)算法,我们还会介意IP地址怎么分配吗?我的IP地址1234:1:2:3::123在我飞到美国依然还是这个,我需要什么转交地址,家乡地址啊!换个领域想想看,如果我们国家地区发展平衡了,我无论到哪都是一样活着,还办什么居住证户口啊,另外,户口上的那个籍贯,我自己都不知道是什么地方…这背后一样是一个算法问题。
卖洗衣昂膏儿诶卖,阿拉洗衣昂膏儿诶…卖绿豆粉浆儿诶来诶,谁要绿豆粉浆儿诶…谁要火柴诶…
浙江温州皮鞋湿,下雨进水不会胖。
浙江温州皮鞋胖,下雨进水不会湿。