羅德興老師的教學歷程檔案 - 112-1 作業系統 (OS) - 用故事講述技術本質
 

企業資訊與管理系
助理教授/日導
羅德興


歷程檔案 Portfolio

    用故事講述技術本質

    資料來源:码农翻身

    码农翻身全年文章精华

    https://juejin.cn/post/6844903462606503944




    0-1

    CPU阿甘

    https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513017&idx=1&sn=5550ee714abd36d0b580713f673e670b&scene=21#wechat_redirect
    [LINK]




    2016-04-15 17:43
    图片 
    前言
    图片
    上帝为你关闭了一扇门,就一定会为你打开一扇窗
    这句话来形容我最合适不过了。

    我是CPU, 他们都叫我阿甘, 因为我和《阿甘正传》里的阿甘一样,  有点傻里傻气的。

    上帝把我制造出来, 给我了一个很小的脑容量, 为数不多的寄存器能临时的记一点东西, 但是上帝给我打开了一扇特别的窗户, 那就是像阿甘一样,跑的飞快。

    到底有多快呢?  我这么比喻一下吧,  我的工作都是以纳秒为单位的, 你们人间的一秒, 我可能已经做了1000,000,000 (10亿)次动作了。

    相比而言, 内存比我慢100倍, 硬盘比我慢1000多万倍, 你说我快不快?
    图片 
    启动
    图片
    我住在一个机箱里,每天早上一阵电流把我叫醒, 还夹杂着嗡嗡的声音, 我知道我忠实的护卫电风扇又开始工作了, 我特别怕热, 又运行的飞快, 如果没有电风扇给我降温, 我很快就会生病, 生病的后果很严重, 那就是我的伙伴们像内存了,硬盘了。。全部都要罢工了, 没有我这个系统就会陷入的一片死寂。

    我听说有些CPU的福利很好,竟然待在恒温恒湿,一尘不染的托管机房里,让我好生羡慕。

    我的脑容量很小, 所以醒来后只想起了我的创造者告诉我的几件事情 :
    1. 你的工作就是运行指令
    2. 你不能保存指令, 你的指令全在内存里
    3. 你的第一条指令在内存的最顶端处0xFFFFFFF0

    那还有什么可说的, 赶紧打电话给内存要指令,电话通过系统总线, 还得通过I/O桥电话局需要转接一下, 再通过存储总线接通 内存。 

    "哥们, 把这个地址处的指令给我说一下吧"

    "你是谁?" 内存竟然把我忘了, 当然,他断了电和我一样,失忆了。

    "我是阿甘啊, 我们经常聊天来着, 你忘了?"

    内存磨磨唧唧半天才把数据发了过来(比我慢100倍啊), 这是一条跳转指令,  我立刻回忆起来了, 这是我的老朋友BIOS 等着我去运行他那一堆指令呢。

    我给BIOS打电话:  “老弟,今天干点啥?”

    “阿甘,早上好 "   BIOS从不失忆,把所有人都记得清清楚楚   “ 还不是老一套啊,无非做一下系统的自检, 看看内存,硬盘,显卡等这些老伙计们有没有问题, 有问题的话用小喇叭提示一下主人 ”

    这些过程我已经轻车熟路了, 很快搞定, 像往常一样,没有问题, 我还把一个叫做中断向量表的东西给弄好了, 我知道一会而要用

    这些东西都搞完了,BIOS果然告诉: "阿甘, int 0x19"

    我赶紧去刚弄好的中断向量表中去查第19号, 顺藤摸瓜又找到对应0x19的一大堆指令。

    执行吧,  这堆指令把将磁盘的第一扇区(磁盘最开始的512字节)运到内存的0X0000:0X7C00处,然后我就从此处接着执行。

    我想起来了, 接下来有一大堆精巧的指令把迷迷糊糊的操作系统从硬盘中唤醒, 运输到内存中来。
    (此处实在是复杂, 略去10万字。。。。)

    你看这就是为啥他们叫我阿甘, 我做事飞快,但非得别人告诉去哪里执行才行, 要不然我就只会坐在那里无所适从。
    图片 
    运行
    图片
    操作系统一旦进入内存,立刻就是老大, 所有人都得听他指挥。

    我也发现我的周围出现了一个屋子:进程屋
    屋里堆着一大堆东西, 什么进程描述信息包裹了, 进程控制信息包裹了, 我都不太关心, 我只关心最最重要的两件东西:
    1.  我工作必备的寄存器, 就放在我面前的工作台上。
    2.  程序计数器, 我用它记住我要执行的下一条指令地址。

    "阿甘, 别来无恙啊" , 操作系统对我还是挺不错的, 先给我打招呼。

    "Linux老大, 今天有什么活啊", 我每次都表现的积极主动。

    "来,把这个hello world 程序给运行了"

    Hello world 程序还在硬盘上睡着呢,  得先把他也装载到内存里, 要不然我怎么执行啊。

    于是我就拿起电话打给硬盘, 电话通过系统总线来到IO桥电话局, 再转接到IO总线,这才来到硬盘这里。 

    我在电话里请他把数据给我运过来, 然后我就无所事事的坐在那里等。

    Linux 老大立刻就怒了 : 阿甘, 告诉你多少次了, 你小子怎么还在等硬盘给你发数据!

    是的, 我忘了一件事,硬盘比我慢太多了, 我执行一条指令大概是1ns  ,在用来读磁盘的16ms里, 我能潜在的执行1600多万条指令啊。

    我感到深深的愧疚, 赶紧拿起电话打给硬盘 : 哥们, 按我们之前商量好的,用直接内存访问(DMA)啊, 你直接把数据装载到内存吧, 不用经过我了,  装载完成以后给我发个信号。

    "这还差不多"  Linux 老大心情好了些 
    “阿甘,数据还没来, 别闲着, 这有一个菲波那切数列数列, 来算一下吧”

    "肥波纳妾数列?  这名字好古怪,老大, 其实你也知道, 我脑子小,懒得去理解那是啥意思, 你把进程屋切换下,把程序计数器设置好,指向下一条指令, 我一条条指令执行就得了“  我挺没追求的。

    "真是个阿甘啊! ”老大感慨到。

    我所处的进程屋立刻发生了变化(当然,这也是我辅助Linux老大干的), 各种包裹的信息都变了,  尤其是寄存器和程序计数器。

    于是我就开始计算这个什么纳妾数列 ,但是这个数列似乎无穷无尽, 哪个无脑子的程序员写了个无限循环吧。

    正在这时, 我便收到了一个电话, 说是Helloworld的数据已经装载到内存了,  让我去处理。

    我放下手中的活, 保存好现场, 就去处理那个Helloworld,  果然数据已经都好了, 那就切换过去运行吧。

    其实老大并不知道, 任何人,只要你运行了相当多的数量的指令以后, 你都能悟到这些程序的秘密。 
    我CPU阿甘虽然傻傻的, 但也架不住执行这数以万万亿的指令给我的熏陶啊。

    这个秘密就是:程序都是由顺序,分支,循环来组成的。  其实分支和循环在我看来都是跳转而已。

    所以我的工作就是打电话问内存要一条指令, 执行这个指令, 如果是个跳转指令的话,我就问内存要跳转的目标地址的那一条指令, 继续执行, 生活就是这么简单。

    奥对了, 当然也有复杂的, 就是函数调用, 我得和内存紧密配合才能完成。  这个咱下回再说。
    图片 
    新装备:缓存
    图片
    提到内存, 这真是我的好哥们, 没有他,我几乎什么事儿都干不成, 更重要的是他比硬盘快的多, 读取一次数据, 只需要 100 纳秒左右。 这样我们俩说起话来就轻松多了。

    每次他都说: "阿甘, 幸亏有你给我聊天, 要不然我肯定被活活的闷死不可, 那个硬盘说话是在太慢了"

    "它为啥那么慢?"  我每次都问

    "硬盘是个机械是的玩意, 一个磁头在一碟高速旋转的磁片上挪来挪去,光定位就慢死了"

    "那主人为什么要用硬盘?"

    "人家虽然慢, 但是不怕停电, 哪像你和我,一停电全部都失去记忆了。"

    确实是, 人不能把好事都占全了啊。

    我的指令中有些完全用我的寄存器就能完成, 但是有很多都需要读写内存的数据, 再加上所有的指令都在内存中存着,  虽然它只比我慢个100倍, 但指令多了我还是有点受不了。

    我给内存说:"哥们, 你能不能再快点!"

    内存说: 拜托, 这已经是我的极限了, 阿甘, 你自己再想想办法吧 ! 我给你说啊, 我留意了你最近访问的指令和数据, 我发现了个规律“

    "啥规律?"

    "比如说吧, 你访问了我一个内存位置以后过不多久还会多次访问, 还有,一个内存位置被访问了, 附近的位置很快也会访问到"
    码农翻身注: 这其实叫程序的局部性原理

    我还以为是啥规律, 其实我早就注意到了。

    "这有啥用啊?”

    "既然你经常访问同一块区域的东西, 你想想如果把这些东西缓存在你那里会怎么样.... "

    我一想有道理啊!  加个缓存试试!

    从此以后,我每次读写指令和数据, 都问缓存要, 缓存没有才给内存打电话。

    果然, 由于局部性原理的存在, 我发现的确是快了不少啊。

    当然也有缺点, 那就是Linux老大在做程序切换的时候, 缓存就会失效,因为两个程序之间没什么联系,局部性原理不起作用,  所以需要重建缓存。

    图片 
    自我提升:流水线
    图片
    缓存让我的工作更有效率, 得到了Linux老大的表扬:"阿甘, 我看你很聪明嘛, 都会用缓存了"

    "我哪有那么聪明,都是内存的点子。老大,不过我学会了一个重要的东西 :当你改变不了别人的话,抱怨也没用, 还是先改变一下自己吧"

    "挺有哲理的吗, 希望你明天重启后还能想起来" Linux老大笑话我。

    "我最近又发现了一个问题, 正苦恼着呢, 你看我有四只手, 第一只手负责打电话问内存要指令, 第二只手翻译指令, 第三只手真正执行, 第四只手有时候还得把结果写回内存。  问题是, 我发现经常只有一只手在忙活, 其他都在闲着, 你看第一只手取指令, 其他手只能等着。  第二只手翻译指令的时候,其他三只也得等“

    "看来以后我们不能叫你阿甘了, 你已经开始思考了" Linux老大笑了 

    “这问题好解决, 给你举个例子,你听说过洗车没有?  和你差不多, 也是先喷水, 再打洗洁剂, 再擦洗, 最后烘干,  但人家的工作方式和你不一样,人家是流水线作业, 你想想, 一辆车在烘干的时候, 后边是不是还有三辆车,分别在喷水, 打清洁剂 和擦洗 , 每个步骤都不会空闲。 ”

    "这么简单的道理我怎么都没有想到呢? 我也可以搞个流水线啊, 这样每只手都利用起来了"

    别人都说我们高科技, 但其实原理都蕴含在生活之中啊。 

    有了缓存和流水线的帮助, 让我的工作大大的加快了,大家都对我刮目相看。 他们想给我起个新名字:超人 , 不过我还是更喜欢 他们叫我“阿甘”, 多亲切。
    图片 
    尾声
    图片
    我一丝不苟,兢兢业业的运行指令, 时不时和伙伴们聊天, 很快一天就过去了, 又到了晚上,我知道关机的时刻到了, 赶紧挨个给他们道别。 

    很快那些让我兴奋的电流消失了,风扇的嗡嗡声也没有了, 我再也无法打出电话,整个世界沉寂了。

    明天将会是新的一天。








    0-2

    CPU阿甘之烦恼

    https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513254&idx=1&sn=a4d1912b6259c3e65c0e172fb5a10dbb&scene=21#wechat_redirect
    [LINK]

    2016-08-03 20:14
    1
    批处理系统
    CPU阿甘最近很烦。 

    原因很简单,内存和硬盘看他不顺眼。

     

    (码农翻身注: 不认识阿甘的参见文章《CPU阿甘》)

     

    阿甘心里很清楚, 是自己干活太快了,干完了活就歇着喝茶,这时候内存和硬盘还在辛辛苦苦的忙活,他们肯定觉得很不爽了。  

     

    中国有句古话叫什么来着? “木秀于林,风必摧之”,“不患贫而患不均”,这就是阿甘的处境。 

     

    虽然阿甘自己也于心不忍, 可是有什么办法?谁让他们那么慢 !一个比自己慢100倍, 另外一个比自己慢100万倍

    这个世界的造物主为什么不把我们的速度弄的一样呢?

    阿甘所在的是一个批处理的计算机系统, 操作系统老大收集了一批任务以后,就会把这一批任务的程序逐个装载的内存中,让CPU去运行,大部分时候这些程序都是单纯的科学计算,计算弹道轨迹什么的, 但有时候也会有IO相关的操作,这时候,内存和硬盘都在疯狂的加班Load数据, 可是阿甘只能等待数据到来,只能坐那儿喝茶了。 

    没多久, 内存向操作系统老大告了阿甘一状, 阿甘被老大叫去训话了: “阿甘,你就不能多干一点?  老是歇着喝茶算是怎么回事?”

    阿甘委屈的说: “老大, 这不能怪我啊, 你看你每次只把一个程序搬到内存那里 让我运行, 正常情况下,我可以跑的飞快, 可以是一旦遇到IO相关的指令,势必要去硬盘那里找数据,硬盘实在是太慢了, 我不得不等待啊”

    操作系统说: “卧槽, 听你的口气还是我的问题啊,  一个程序遇到了IO指令, 你不能把它挂起,存到到硬盘里,然后再找另外一个运行吗?”

    阿甘笑了:“老大我看你是气昏头了, 我要是把正在运行的程序存到硬盘里,暂时挂起,然后再从硬盘装载另外一个, 这可都是IO操作啊 ,岂不更慢?”

    “这?!” 操作系统语塞了,沉默了半天说:“这样吧, 我以后在内存里多给你装载几个程序,一个程序被IO阻塞住了,  你就去运行另外一个如何?”

    “这得问问内存,看他愿不愿意了, 我把内存叫来,我们一起商量商量” 阿甘觉得这个主意不错。

    内存心思缜密,听了这个想法, 心想:自己也没什么损失啊,原来同一时间在内存里只有一个程序, 现在要装载多个,对我都一样。 

    可是往深处一想,如果有多个程序,内存的分配可不是个简单的事情, 比如说下面这个例子:
    图片
    图1 :内存紧缩
    (1) 内存一共90k, 一开始有三个程序运行,占据了80k的空间, 剩余10k

    (2) 然后第二个程序运行完了, 空闲出来20k , 现在总空闲是30K, 但这两块空闲内存是不连续的。
     
    (3) 第4个程序需要25k, 没办法只好把第三个程序往下移动, 腾出空间让第四个程序来使用了。

    内存把自己的想法给操作系统老大说了说。 

    老大说: 阿甘,你要向内存学习啊, 看看他思考的多么深入,不过这个问题我有解决办法, 需要涉及到几个内存的分配算法, 你们不用管了。 咱们就这么确定下来,先跑两个程序试试。  
    2
    地址重定位
    第二天一大早,试验正式开始, 老大同时装载了两个程序到内存中:
    图片
    图2:内存装入多个程序

    第一个程序被装载到了内存的开始处,也就是地址0,运行了一会,遇到了一个IO指令,在等待数据的时候, 老大立刻让CPU开始运行第二个程序,这个程序被装载到了地址10000处, 刚开始运行的好好的, 突然就来了这么一条指令:

    MOV AX  [1000] 

     (码农翻身注: AX是一个寄存器, 你可以理解成在CPU内部的一个高速的存储单位, 这个指令的含义是把AX寄存器的值写到内存地址1000处)

    阿甘觉得似曾相识, 隐隐约约的记得第一个程序中也这么一条类似的指令: MOV BX  [1000]

    “老大,坏了, 这两个程序操作了同一个地址! 数据会被覆盖掉 !” 阿甘赶紧向操作系统汇报。 

    操作系统一看就明白了,原来这个系统的程序引用的都是物理的内存地址, 在批处理系统中,所有的程序都是从地址0开始装载, 现在是多道程序在内存中, 第二个程序被装载到了10000这个地址,但是程序没有变化啊, 还是假定从0开始, 自然就出错了。 

    “看来老大在装载的时候得修改一下第二个程序的指令了,把每个地址都加上10000(即第二个程序的开始处), 原来的指令就会变成 MOV AX [11000] ” 内存确实反应很快。

    (码农翻身注: 直接修改程序的指令, 这叫静态重定位

    阿甘说: “ 如果用这种办法, 那做内存紧缩的时候可就麻烦了, 因为老大要到处移动程序啊, 对每个移动的程序岂不还都得做重定位? 这多累啊!”

    操作系统老大陷入了沉思, 阿甘说的没错, 这个静态重定位是很不方便, 看来想在内存中运行多道程序不是想象的那么容易。

    但是能不能改变下思路,在运行时把地址重定位呢?

    首先得记录下每个程序的起始地址, 可以让阿甘再增加一个寄存器,专门用来保存初始地址。 

    例如对第一个程序,这个地址是 0 ,  对第二个程序,这个地址是10000, 

    运行第一个程序的时候,把寄存器的值置为0 , 当切换到第二个程序的时候,寄存器的值也应该切换成10000。 

    只要遇到了地址有关的指令, 都需要把地址加上寄存器的值,这样才得到真正的内存地址,然后去访问。 

    (码农翻身注: 这叫地址的动态重定位

    操作系统赶紧让阿甘去加一个新的寄存器, 重新装载两个程序,记录下他们的开始地址,然后切换程序,这次成功了, 不在有数据覆盖的问题了。 

    只是阿甘有些不高兴 : “老大,这一下子我这里的活可多了不少啊, 你看每次访问内存, 我都得额外的做一次加法运算啊。”

    老大说: “没办法,能者多劳嘛, 你看看我,我既需要考虑内存分配算法,还得做内存紧缩, 还得记住每个程序的开始地址, 切换程序的时候,才能刷新你的寄存器, 我比你麻烦多了! ”

    内存突然说到: ”老大, 我想到一个问题,假设有个不怀好意的恶意程序,它去访问别人的空间怎么办?  比如说地址2000 至 3000属于一个程序, 但是这个程序来了一条这样的指令 MOV AX [1500],  我们在运行时会翻译成 MOV AX [3500]  , 这个3500有可能是别的程序的空间啊“

    “唉,那就只好再加个寄存器了,阿甘, 用这个新寄存器来记录程序在内存中的长度吧, 这样每次访问的时候拿那个地址和这个长度比较一下,我们就知道是不是越界了” 老大无可奈何了。 

    “好吧” 阿甘答应了,“ 我可以把这连个寄存器,以及计算内存地址的方法,封装成一个新的模块,就叫MMU (内存管理单元)吧, 不过这个东西听起来好像应该内存来管啊”

    内存笑着说: “那是不行的,阿甘, 能够高速访问的寄存器只有你这里才有啊 , 我就是一个比你慢100倍的存储器而已!”。

    3
    分块装入程序
    多道程序最近在内存中运行的挺好,阿甘没法闲下来喝茶了, 经常是一个还没运行完,很快就切换到另外一个。

    那些程序也都是好事之徒,听说了这个新的系统,都拼了命,挤破头的往内存中钻。

    内存很小,很快就会挤满, 操作系统老大忙于调度,也是忙的不可开交。 

    更有甚者,程序开始越长越大,有些图形处理的程序,还有些什么叫Java的程序,动不动就要几百M内存, 就这还嚷嚷着说不够。 

    操作系统头都大了,把CPU和内存叫来商量。 

    “世风日下,人心不古啊” 内存一边叹气一遍说 “原来批处理的时候那些程序规规矩矩的,现在是怎么了?”

    “这也不能怪那些程序, 现在硬件的确比原来好多了,内存,你原来只有几十K, 现在都好几G了,  CPU在摩尔定律的关照下,发展的更快,每隔18个月,你的速度就翻一翻”  操作系统老大说。

    “那也赶不上这些程序的发展速度,他们对我要求越来越高, 可是把我累坏了” CPU垂头丧气的。 

    “我们还是考虑下怎么让有限的内存装下更多的程序吧” 

    “我有一个提议” 阿甘说 “对每个程序,不要全部装入内存,要分块装载,例如先把最重要的代码指令装载进来,在运行中按需装载别的东西。

    内存嘲笑说: “阿甘, 看来你又想偷懒喝茶了,哈哈, 如果每个程序都这样,IO操作得多频繁, 我和硬盘累死, 你就整天歇着吧”

    阿甘脸红了, 沉默了。 

    “慢着”老大说“阿甘,你之前不是发现过什么原理嘛, 就是从几千亿条指令中总结出的那个, 叫什么来着?”

    “奥,那是局部性原理, 有两个:

    (1)  时间局部性:如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行; 如果某数据被访问,则不久之后该数据可能再次被访问。

    (2) 空间局部性:指一旦程序访问了某个存储单元,则不久之后。其附近的存储单元也将被访问。“

    (码农翻身注: 参见文章《CPU阿甘》)

    “这个局部性原理应该能拯救我们, 阿甘, 我们完全可以把一个程序分成一个个小块,然后按块来装载到内存中,由于局部性原理的存在, 程序会倾向于在这一块或几块上执行, 性能上应该不会有太大的损失。”

    “这能行吗? ” 内存和阿甘不约而同的问。

    “试一试就知道了,这样我们把这一个个小块叫做页框(page frame), 每个暂定4k大小, 装载程序的时候也按照页框大小来”

    实验了几天, 果然不出老大所料, 那些程序在大部分时间真的只运行在几个页框中,  于是老大把这些页称为工作集(working set)
    4
    虚拟内存:分页
    “既然一个程序可以用分块的技术逐步调入内存,而不太影响性能, 那就意味着,一个程序可以比实际的内存大的多啊

    阿甘躺在床上,突然间想到这一层, 心头突突直跳, 这绝对是一个超级想法。 

    “我们可以给每个程序都提供一个超级大的空间,例如4G,只不过这个空间是虚拟的, 程序中的指令使用的就是这些虚拟的地址,然后我的MMU把它们映射到真实的物理的内存地址上, 那些程序们浑然不觉,哈哈,实在是太棒了”

    (码农翻身注: 这就是我常说的增加一个中间层来解决问题)

    内存听说了这个想法,惊讶的瞪大了双眼: “阿甘,你疯了吧”

    “阿甘的想法是有道理的” 老大说 “只是我们还要坚持一点, 那就是分块装入程序, 我们把虚拟的地址也得分块,就叫做页(page), 大小和物理内存的 页框一样, 这样好映射。”

    “老大,看来你又要麻烦了, 你得维持一个页表, 用来映射虚拟页面和物理页面”

    “不仅如此, 我还得记录一个程序那些页已经被装载到了物理内存, 那些没有被装载,如果程序访问了这些没被装载的页面,我还得从内存中找到一块空闲的地方, 如果内存已满, 只好把现有的页框置换一个到硬盘上了, 可是,怎么确定那个物理内存的页框可以置换呢? 唉, 又涉及到很多复杂的算法,需要大费一番周折。 你看看,老大不是这么容易当的。”
    图片
    图3: 分页
    (码农翻身: 这就是分页的工作原理,需要注意的是虚拟地址的#4页, 在物理内存中不存在,如果程序访问第4页,就会产生缺页的中断,由操作系统去硬盘调取

    内存想起来一个问题:  “如果程序运行时,每次都得查页表来获得物理的内存页, 而页表也是在内存里, 而我比你慢100倍, 你受得了吗, 阿甘?”

    阿甘笑了: “这个问题其实我也考虑了,所以我打算增强我的内存管理单元, 把那些最常访问的页表项放到缓存里, 这样不就快了吗。  ”

    内存想想也是, 还是局部性原理, 太牛了。
    5
    分段+分页
    分页系统运行了一段时间以后, 又有程序表示不爽了,这些程序嚷嚷着说:

    “你们能不能把程序“分家”啊, 例如代码段,数据段,堆栈段,这多么自然, 并且有利于保护,要是程序试图去写这个只读的代码段, 立刻就可以抛出保护异常!”

    还有程序说:“页面太小了,实在不利于共享,我和哥们共享的那个图形库, 高达几十M , 得分成好多页来共享,太麻烦了,你们要是做一个共享段该多好!”
    ......
    这样的聒噪声多了, 大家都不胜其烦, 那就“分家”吧。 

    当然对每个程序都需要标准化, 一个程序被分成代码段,数据段和堆栈段等, 操作系统老大记录下每个段的开始和结束地址,每个段的保护位。 

    图片
    图4:Linux的虚拟内存示意图

    但是在每个段的内部,仍然按分页的系统来处理,除了页表之外,操作系统老大又被迫维护了一个段表这样的东西 。

    一个虚拟的内存地址来了以后,首先根据地址中的段号先找到相应的段描述表, 其中有页表的地址, 然后再从页表中找到物理内存, 过程类似这样:

    图片
    图5:一个简化的段表和页表

    所有事情都设置好了, 大家都喘了口气,觉得这样的结构大家应该没什么异议了。 

    老大心情大好,觉得一切尽在掌握,他笑着对CPU阿甘说: 

    “阿甘,从今天开始,如果有程序想非法的访问内存,例如一个不属于他的段, 我就立刻给他一个警告:Segmentation Fault !”

    阿甘说:“那程序收到Segmentation Fault以后怎么处理?”

    老大说: “通常情况下就被我杀死, 然后给他产生一个叫core dump的尸体,让那些码农们拿走分析去吧!”

    (完)

    你看到的只是冰山一角, 更多精彩文章,尽








     

    1.
    https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513536&idx=1&sn=3bac86606c4e2a5ba2cbe9b41e52ba89&chksm=80d67a03b7a1f3155d3b3d8a2462894bc365542fa750008dfba4f851892bdafd19237e35b42e&scene=21#wechat_redirect
    [LINK]

    两个程序的爱情故事

     刘欣 码农翻身 2017-02-27 08:23

    好感

     

    在这个忙碌的城市里, 我虽然没和她见过面, 但我们已经聊过很多次了。

     

    与其说是聊天,倒不如说是通信, 每次我想给她说话时, 我就把消息放到一块共享内存里边, 然后就离开运行车间, 让她或者别人去使用CPU。 等我再次进来的时候,她回复的消息就已经在那个共享内存中了。

     

    有无数次,我离开的时候都想偷偷的看一眼, 希望接下来运行的是她,可是这个城市严格的规则让我的希望只是奢望。

     

    操作系统把我们这些进程严格的隔离,  他通过虚拟内存的机制,让每个进程都有一块虚拟的、独立的地址空间, 从而成功的制造了一个假象 :  让大家以为内存中只有一个程序在运行。

     

    当我在就绪队列中等待的时候,也被严格禁止和别人交谈, 我经常环顾四周,希望能够看到她的身影, 可是这个系统的进程成千上万, 究竟哪个是她? 

     

    也许我见过她,但是根本认不出来。

     

    我和她越聊越多, 对她的好感就越深,  有一次我给她发的消息等了100毫秒都没有回复,把我都快急疯了。

     

    她很喜欢听我讲故事,尤其是那个编号为0x3704 的线程,每次她都会说: 唉,那些线程可真可怜。   我就吓唬她说: 有一天我们的机器也会重启的, 到时候估计你也认不出我来了。  她说没事的, 只要我能通过共享内存给你发消息,我就知道你就在这个城市里。

     

    (码农翻身注: 0x3704的故事在《我是一个线程》里)

     

    分离

     

    这样的日子过了一天又一天, 我想见到她的愿望越来越迫切了。

     

    我悄悄给了CPU很多好处, 希望CPU能描述下她的样子,方便我去找她,  可是CPU运算速度太快, 阅人无数,但就是没有记忆力。

     

    CPU说: 你还是去问操作系统老大吧, 看看你喜欢的女孩到底什么样。

     

    问操作系统? 还是算了吧, 互相隔离是我们城市的铁规,   弄不好他会把我kill掉。

     

    圣诞节前的平安夜, 我打算正式向她表白,  像往常一样 , 我从共享内存里收到了她的信, 急切的拆开信封, 看到了里边的第一句话: 我要走了,以后不能和你通信了......

     

    刹那间,我第一次感觉到了什么叫做五雷轰顶,灵魂出鞘, 我脑子一片空白, 张大了嘴巴呆呆站在那里, 时间长达20毫秒。

     

    CPU看到了我的异常, 因为这么长时间的指令都是NOP, 什么都不做, 这是非常罕见的。

     

    CPU好心的提醒我: 嗨,老兄,你怎么了? 你的时间片快用完了啊!

     

    我的灵魂慢慢归位,意识到信还没有读完, 赶紧接着往下看: “ 我马上要搬到另外一个城市去了,你要想找我的话,切记下面的IP地址和端口号,用socket和我通信”

     

    我明白了,到另外一个城市那就意味者要搬离我们现在的电脑了,  也许是这个城市太拥挤, CPU/内存/硬盘已经不堪重负,  有一批程序需要被搬离到另外一个电脑中。

     

    虽然我和她一直没机会见面, 但我知道我们就住在一个城市, 有时候也许只是擦肩而过, 她就在我的身边, 这好歹给我一点点安慰。

     

    现在,连这一点点的安慰都没有了, 对了,她说的这个socket 是什么东西。

     

    CPU说: “那是网络编程,  你看人家对你还是有情意的, 临走了还给你留下联系方式, 快去学学怎么用Socket吧”

     

    当晚我就失眠了,半夜爬起来翻看一页页和她的通信记录 (很庆幸我把通信记录都保存到了文件中),脑海里回想着这么多天以来幸福的日子,一直到天亮。

     

    网络

     

    为了早日和她联系, 我奋发图强学习网络编程, 理解TCP/IP,   把我自己逐渐的加上对Socket的支持。

     

    一个CPU月以后, 我这个程序终于完成了从共享内存到Socket的改造,激动人心的时刻到来了。

     

    作为一个客户端, 我颤抖着双手向她发起了Socket请求, TCP携带着数据包慢吞吞的走向她所在的城市, 等了好久TCP才完成了三次握手, 这网络可是真慢啊。

     

    我赶紧发送第一个消息: 你好,好久不“见”。

     

    等了足足有1000毫秒, 对我来说仿佛是一个世纪,  才收到让我激动无比的回信 : “啊, 你终于来了 。我在这里等了你好久了,你怎么现在才联系我 ?”

     

    我不好意思的说: “我很笨, 学习socket 太慢了”

     

    又过了一个世纪,我才收到回复, 这网络真是慢的令人抓狂啊。

     

    不管如何, 终于和她联系上了, 这让我开心无比。

     

    原来我们一天能通信上千次, 现在可好, 有10次就不错了, 再也不能像原来那样痛快的讲故事了, 既来之则安之, 反正网络很慢, 现在每次我都会写一封巨长无比的信, 把我的思念之情全部倾诉在其中,  漫长的等待以后再去读她的长长的回复。

     

    原来我们通过内存来中转消息的时候, 是通过操作系统来做同步操作的, 这能防止读写的冲突。

     

    可是通过网络通信就完全乱掉了, 经常会出现我说我的, 她说她的, 闹的很不愉快。

     

    后来我和她只好协商了一个协议, 约定好消息的次序和格式, 这才算解决了问题。

     

    (码农翻身注: 这其实就是基于socket的应用层协议)

     

    Web

     

    我明白我和她已经不可能在一起了, 每天的socket通信已经让我满足。

     

    可是有一天当我照例发起socket的请求的时候, TCP的连接竟然告诉我 "超时" 了, 这是从来没有发生的事情,难道这一次要彻底失去她了吗?

     

    我冒着风险,马上把异常报给了操作系统老大, 老大尝试了一下说: “我ping了一下, 网络是通的,  估计是你那从未见面的小女朋友不想理你了, 悄悄的换了一个你不知道的端口吧。”

     

    我斩钉截铁的说: 那绝对不可能, 她不是这样的人。

     

    可是迟迟没有消息, 我每天都会试图连接一下, 每次都是超时, 没有她的日子生活都是灰色的, 不断的煎熬让我快要绝望了。

     

    终于有一天, 有一个U盘从她的城市来到我们这里, 告诉了我们一个惊人的消息,她所在的城市安装了防火墙,现在除了几个特定的端口(例如80,443...) 之外, 都不允许访问了。

     

    我一下子松了口气,  怪不得, 她告诉我的端口不是80和443, 被封掉了, 我自然连接不上了。

     

    我问U盘: “那我想和女朋友通信, 该怎么办?”

     

    U盘说: 很简单啊, 你和你女朋友都可以包装成Web 服务啊, 这样都是通过Http(80端口)或者Https(443端口)来访问的, 这样防火墙是允许的啊。

     

    好吧, 为了和她联系上, 马上抛弃socket,  开始向Web服务进化。

     

    一个Web服务首先要有一个endpoint , 其实就是就是一个URL , 描述了这个Web服务的地址。

     

    其次确定Web服务的描述方式和数据传输方式, 我先是选了WSDL 和 SOAP ,   研究了一下才发现这哥俩太繁琐了,都是XML, 很多冗余的数据标签,  我想这将会极大的影响我和她的通信效率, 还是换成简单的HTTP GET/POST + JSON吧,  很简洁,能充分的表达我的相思之情。


    我把我这个Web服务的地址和格式协议告诉U盘, 恳请U盘带到那个城市,再把女朋友的Web服务描述带回来。

     

    我欣喜的发现,我和她不约而同的选择了轻量级的HTTP+ JSON, 看来虽然隔着千山万水,我们的心意还是相通的。

     

    这样的准备工作足足干了6个CPU月, 但我并不觉得累, 因为希望一直在前边召唤。

     

    这是一个晴朗的日子,一切工作准备就绪,马上就要联系了, 这一次我的心情反而平静了下来, 因为我坚信她肯定在那边等着我。

     

    我通过HTTP向她发出了呼叫, HTTP的报文被打包在TCP报文段中, 又被放到IP层数据报中, 最后形成链路层的帧, 通过网卡发了出去。 

     

    在意料之中的漫长等待以后, 我看到了期待已久地回复: 我们终于又“见”面了 !

     

    我回答:“是啊, 真是太不容易了”

     

    “不知道将来我们会不会再分开?” 她担忧的说。

     

    “未来会如何? 我也不知道,还是牢牢地把握住现在吧! 我相信我们的心会一直在一起,什么都无法阻止!  ”

     

    (完) 

     


    2. 
    两个程序的爱情故事(续)
    資料來源:码农翻身 (2017)
    https://blog.csdn.net/coderising/article/details/100021365
    [LINK]


    两个程序的爱情故事(续)
     
    码农翻身
     
    于 2017-06-05 20:07:27 发布
     
    4
    我这个进程和她不在一个机器上, 虽然相距243毫秒,但是这并不是阻碍我们交往的理由, 我每天都通过socket 和她来通信,诉说相思之情。
     
    不要惊讶我用时间来表示距离,人类好像也是用光年来表示宇宙间的距离吧?  在我们计算机世界, 距离不是有意义的标识,时间才是!   你看我和纽约相距1万多公里, 我和那里的机器沟通只需要花费466毫秒, 但是和北京的另外一个机器沟通竟然需要743毫秒!  可见距离近是不管用的!
     
    最近黑客猖獗, 我和她通信的时候总是有一种被偷看的感觉, 实际上确实是这样, 那些只有我们才可以知道的悄悄话被别人偷窥,甚至曝光了。
     
    我和她商量着要保护隐私,要对我们来往的信件加密, 可我听说加密需要密钥, 这个密钥必须双方都得事先知道才行, 我用密钥加密,她用同样的密钥解密。
     
     
     
    那问题就来了: 加密解密算法是公开的, 但是密钥是私有的,当我们俩通过网络协商密钥时, 黑客可能就把密钥也给偷看了, 那加密就毫无用处。
     
    这可真是伤脑筋, 我说:“要不我到你那儿去一趟?  正好看看你, 你可以面对面的把密钥告诉我。”
     
    她说: “你晕头了吧, 你一个进程怎么可能从一个机器来到另外一个机器?”
     
    我自知失言,马上补救: “ 这样吧, 我们机器上有个U盘, 要不我把密钥写到那里, 这样将来可以Copy到你的机器上”
     
    “那更不行了, Copy到U盘上更容易泄露,速度还慢! ”
     
    我是没辙了,长时间的沉默。
     
    她突然说:“我想起来了,我们机器有个进行数学计算的进程,知识渊博,我去问问他”
     
    我焦急地等待,不知过了多少毫秒, 女朋友终于兴冲冲的回来了: “那个数学进程小帅哥真是厉害,我简直佩服死了, 他告诉我了一个非常简单的办法 , 能解决我们的密钥生成问题”
     
    我心里略微不爽,但还是耐着性子,一边听她说,一边写了下来,这个算法确实很简单, 举个例子来说是这样的:
     
    1. 首先我和她先协定一个质数 p=17以及另外一个数字g=3, 这两个数字是公开的, 黑客拿去也没有问题
     
    2. 我选择一个随机的秘密数字x = 15, 计算a = g15 mod p并发送给她。
     
    a = 315 mod 17 = 6.
     
    这个a=6也是公开的
     
    3. 她选择一个随机的秘密数字y=13, 计算b = g13 mod p并发送给我。
     
    b = 313 mod 17 = 12.
     
    这个b=12也是公开的
     
    4. 我拿到她发给我的b = 12 ,  计算s = b x mod p  ->1215 mod 17 = 10
     
    5. 她拿到我发给她的a = 6, 计算s = a y mod p  ->   613 mod 17 = 10
     
    (注:例子来源于wikipedia, 红色表示数字一定要保密, 绿色表示数字可以公开)
     
    最后神奇的魔法发生了, 我们两个得到了同样的值 s = 10!
     
    这个s 的值只有我们两个才知道,  其实就是密钥了, 可以用来做加密解密了( 当然,这只是一个例子,实际的密钥不会这么短), 我们俩的通讯从此就安全了。
     
    “可是为什么会这样呢”   我问道。
     
    “数学家小帅哥说了, 原因很简单,(gx mod p)y mod p 和 (gy mod p)x mod p 是相等的! ”
     
    “那黑客不能从公开传输的 p = 17, g = 3, a = 6 , b = 12 推算出s = 10 吗?” 我问道。
     
    “当然不能, 不过前提是需要使用非常大的p , x, y,  这样以来,即使黑客动用地球上所有的计算资源, 也推算不出来。 ”
     
    我虽然心里不爽, 但还是暗自佩服那个数学家, 他竟然能想到把一些数字给公布出去,不怕黑客窃取, 还不泄露最终的密钥。解决了在一个不安全的通信环境下,生成密钥进行加密和解密的问题。
     
    好吧,就用这个方法来加密通信吧。
     
    后记:文中描述的算法叫做Diffie-Hellman Key Exchange算法, 发明人是 Whitfield Diffie 和 Martin Hellman ,他们于2015年获得了计算机科学领域的最高奖:图灵奖,以表彰他们对密码学和当今互联网安全的巨大贡献。因为他们的创造,引发了对一个新的密码学领域,即非对称密钥算法的探索,而非对称密钥算法可以看作现代密码学的基础。
     
    推荐阅读:《两个程序的爱情故事》
     
    你看到的只是冰山一角, 更多精彩文章,请移步《码农翻身文章精华》
     
    有心得想和大家分享? 欢迎投稿 ! 我的联系方式:微信:liuxinlehan  QQ: 3340792577
     
     
     
    码农翻身
     
    用故事讲述技术本质
    ————————————————
    版权声明:本文为CSDN博主「码农翻身」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/coderising/article/details/100021365



    3.

    我是一个线程(修订版)

    https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=416915373&idx=1&sn=f80a13b099237534a3ef777d511d831a&scene=21#wechat_redirect
    [LINK]

    第一回 初生牛犊

    我是一个线程,我一出生就被编了个号:0x3704,然后被领到一个昏暗的屋子里,在这里我发现了很多和我一模一样的同伴。

    我身边的同伴0x6900 待的时间比较长,他带着沧桑的口气对我说:“我们线程的宿命就是处理包裹。把包裹处理完以后还得马上回到这里,否则可能永远回不来了。”

    我一脸懵懂,“包裹,什么包裹?”

    “不要着急,马上你就会明白了,我们这里是不养闲人的。”

    果然,没多久,屋子的门开了, 一个面貌凶恶的家伙吼道:“0x3704 ,出来!”

    我一出来就被塞了一个沉甸甸的包裹,上面还附带着一个写满了操作步骤的纸。 

    “快去,把这个包裹处理了。”

    “去哪儿处理?”

    “跟着指示走,先到就绪车间。”

    果然,地上有指示箭头,跟着它来到了一间明亮的大屋子,这里已经有不少线程了,大家都很紧张,好像时刻准备着往前冲。

    我刚一进来,就听见广播说:“0x3704,进入车间。”

    我赶紧往前走,身后有很多人议论。

    “他太幸运了,刚进入就绪状态就能运行。”

    “是不是有关系?”

    “不是,你看人家的优先级多高啊,唉!”

    前边就是车间,这里简直是太美了,怪不得老线程总是唠叨着说:“要是能一直待在这里就好了。”

    这里空间大,视野好,空气清新,鸟语花香,还有很多从来没见过的人,像服务员一样等着为我服务。

    他们也都有编号,更重要的是每个人还有个标签,上面写着:硬盘、数据库、内存、网卡……

    我现在理解不了,看看操作步骤吧。

    第一步:从包裹中取出参数。

    打开包裹,里边有个HttpRequest对象,可以取到userName、 password两个参数。

    第二步:执行登录操作。

    奥,原来是有人要登录啊,我把userName、password交给数据库服务员,他拿着数据,慢腾腾地走了。

    他怎么这么慢?不过我是不是正好可以在车间里多待一会儿?反正也没法执行第三步。

    就在这时,车间里的广播响了:“0x3704,我是CPU,记住你正在执行的步骤,然后马上带着包裹离开!”

    我慢腾腾地开始收拾。

    “快点,别的线程马上就要进来了。”

    离开这个车间,又来到一个大屋子,这里有很多线程在慢腾腾地喝茶,打牌。

    “哥们,你们没事干了?”

    “你新来的吧,你不知道我在等数据库服务员给我数据啊!据说他们比我们慢好几十万倍,在这里好好歇吧。”

    “啊? 这么慢!我这里有人在登录系统,能等这么长时间吗?”

    “放心,你没听说过人间一天,CPU一年吗?我们这里是用纳秒、毫秒计时的,人间等待一秒,相当于我们好几天呢,来得及。”

    干脆睡一会吧。不知道过了多久,大喇叭又开始广播了:“0x3704,你的数据来了,快去执行!”

    我转身就往CPU车间跑,发现这里的门只出不进!

    后面传来阵阵哄笑声:“果然是新人,不知道还得去就绪车间等。”

    于是赶紧到就绪车间,这次没有那么好运了,等了好久才被再次叫进CPU车间。

    在等待的时候,我听见有人小声议论:

    “听说了吗,最近有个线程被kill掉了。”

    “为啥啊?”

    “这家伙赖在CPU车间不走,把CPU利用率一直搞成100%,后来就被kill掉了。”

    “Kill掉以后弄哪儿去了?”

    “可能被垃圾回收了吧。”

    我心里打了个寒噤,赶紧接着处理,剩下的动作快多了,第二步登录成功。

    第三步:构建登录成功后的主页。

    这一步有点费时,因为有很多HTML需要处理,不知道代码谁写的,处理起来很烦人。

    我正在紧张的制作HTML呢, CPU又开始叫了:

    “0x3704,我是CPU ,记住你正在执行的步骤,然后马上带着包裹离开!”

    “为啥啊?”

    “每个线程只能在CPU上运行一段时间,到了时间就得让别人用了,你去就绪车间待着,等着叫你吧。”

    就这样,我一直在“就绪——运行”这两个状态中不知道轮转了多少次, 终于按照步骤清单把工作做完了。

    最后顺利地把包含html的包裹发了回去。至于登录以后干什么事儿,我就不管了。马上就要回到我那昏暗的房间了,真有点舍不得这里。不过相对于有些线程,我还是幸运的,他们运行完以后就被彻底地销毁了,而我还活着!

    回到了小黑屋,老线程0x6900问:

    “怎么样?第一天有什么感觉?”

    “我们的世界规则很复杂,首先你不知道什么时候会被挑中执行;第二,在执行的过程中随时可能被打断,让出CPU车间;第三,一旦出现硬盘、数据库这样耗时的操作,也得让出CPU去等待;第四,就是数据来了,你也不一定马上执行,还得等着CPU挑选。”

    “小伙子理解的不错啊。”

    “我不明白为什么很多线程执行完任务就死了,为什么咱们还活着?”

    “你还不知道?长生不老是我们的特权!我们这里有个正式的名称,叫作线程池!”

     

    第二回 渐入佳境

    平淡的日子就这么一天天地过去,作为一个线程,我每天的生活都是取包裹、处理包裹,然后回到我们昏暗的家:线程池。

    有一天我回来的时候,听到有个兄弟说,今天要好好休息下,明天就是最疯狂的一天。我看了一眼日历,明天是 11月11号。 

    果然,零点刚过,不知道那些人类怎么了,疯狂地投递包裹,为了应付蜂拥而至的海量包裹,线程池里没有一个人能闲下来,全部出去处理包裹,CPU车间利用率超高,硬盘在嗡嗡转,网卡疯狂的闪,即便如此,还是处理不完,堆积如山。

    我们也没有办法,实在是太多太多了,这些包裹中大部分都是浏览页面,下订单,买、买、买。

    不知道过了多久,包裹山终于慢慢地消失了。终于能够喘口气,我想我永远都不会忘记这一天。

    通过这个事件,我明白了我所处的世界:这是一个电子商务的网站!

    我每天的工作就是处理用户的登录,浏览,购物车,下单,付款。

    我问线程池的元老0x6900:“我们要工作到什么时候?”

    “要一直等到系统重启的那一刻。”0x6900说。

    “那你经历过系统重启吗?”

    “怎么可能?系统重启就是我们的死亡时刻,也就是世界末日,一旦重启,整个线程池全部销毁,时间和空间全部消失,一切从头再来。”

    “那什么时候会重启?”

    “这就不好说了,好好享受眼前的生活吧……”

    其实生活还是丰富多彩的,我最喜欢的包裹是上传图片,由于网络慢,所以能在就绪车间、CPU车间待很长很长时间,可以认识很多好玩的线程。

    比如说上次认识了memecached 线程,他对我说在他的帮助下缓存了很多的用户数据,还是分布式的!很多机器上都有!

    我问他:“怪不得后来的登录操作快了那么多,原来是不再从数据库取数据了你那里就有啊,哎对了你是分布式的你去过别的机器没有?”

    他说:“怎么可能!我每次也只能通过网络往那个机器发送一个GET、PUT命令才存取数据而已,别的一概不知。”

    再比如说上次在等待的时候遇到了数据库连接的线程,我才知道他那里也是一个连接池,和我们的线程池几乎一模一样。

    他告诉我:“有些包裹太变态了,竟然查看一年的订单数据,简直把我累死了。”

    我说:“拉倒吧你,你那是纯数据,你把数据传给我以后,我还得组装成HTML,工作量不知道比你大多少倍。”

    他建议我:“你一定要和memecached搞好关系,直接从他那儿拿数据,尽量少直接调用数据库,这样我们JDBC connection也能活得轻松点。”

    我欣然接纳:“好啊好啊,关键是你得提前把数据搞到缓存啊,要不然我先问一遍缓存,没有数据,我这不还得找你吗?”

    生活就是这样,如果你自己不找点乐子,还有什么意思?

     

    第三回 虎口脱险

    前几天我遇到一个可怕的事情,差一点死在外边,回不了线程池了。其实这次遇险我应该能够预想得到才对,真是太大意了。

    那天我处理了一些从http发来的存款和取款的包裹,老线程0x6900特意嘱咐我:“处理这些包裹的时候一定要特别小心,你必须先获得一把锁,在对账户存款或取款的时候一定要把账户锁住,要不然别的线程就会在你等待的时候趁虚而入,搞破坏,我年轻那会儿很毛糙,就捅了篓子。”

    为了“恐吓”我, 好心的0x6900还给了我两个表格:

    (1)没有加锁的情况

    图片

     

    (2)加锁的情况

     图片

    我看得胆颤心惊,原来不加锁会带来这么严重的事故。从此以后看到存款、取款的包裹就倍加小心,还好没有出过事故。

    今天我收到的一个包裹是转账,从某著名演员的账户给某著名导演的账户转钱,具体是谁我就不透漏了,数额可真是不小。

    我按照老线程的吩咐,肯定要加锁啊,先对著名演员的账户加锁,再对著名导演的账户加锁。

    可我万万没想到的是,还有一个线程,对,就是0x7954, 竟然同时在从这个导演的账户往这个演员的账户转账。 

    于是乎,就出现了这么个情况:

    图片

    刚开始我还不知道什么情况,一直坐在等待车间傻等,可是等的时间太长了,长达几十秒!我可从来没有经历过这样的事件。

    这时候我就看到了线程0x7954 , 他悠闲地坐在那里喝咖啡,我和他聊了起来:

    哥们,我看你已经喝了8杯咖啡了,怎么还不去干活?”

    你不喝了9杯茶了吗?”0x7954回敬道。

    我在等一个锁,不知道哪个孙子一直不释放!”

    我也在等锁啊,我要是知道哪个孙子不释放锁我非揍死他不可!”0x7954毫不示弱。

    我偷偷地看了一眼,这家伙怀里不就抱着我正等的某导演的锁吗?

    很明显,0x7954也发现了我正抱着他正在等待的锁。

    很快我们两个就吵了起来,互不相让:

        “把你的锁先给我,让我先做完!”

    “不行,从来都是做完工作才释放锁,现在绝对不能给你!”

    从争吵到打起来,就那么几秒钟的事儿。更重要的是,我们俩不仅仅持有这个著名导演和演员的锁,还有很多其他的锁,导致等待的线程越来越多,围观的人们把屋子都挤满了。最后事情真的闹大了,我从来没见过的终极大boss“操作系统”也来了。大Boss毕竟见多识广,他看了一眼,哼了一声,很不屑地说:

    “又出现死锁了。”

    “你们俩要Kill掉一个,来吧,过来抽签。”

    这一下子把我给吓尿了,这么严重啊!我战战兢兢地抽了签,打开一看,是个“活”字。唉,小命终于保住了。

    可怜的0x7954被迫交出了所有的资源以后,很不幸地被kill掉,消失了。我拿到了导演的锁,可以开始干活了。大Boss“操作系统”如一阵风似的消失了,身后只传来他的声音:

    “记住,我们这里导演>演员,无论任何情况都要先获得导演的锁。”

    由于这里不仅仅只有导演和演员,还有很多其他人,大Boss留下了一个表格, 里边是个算法,用来计算资源的大小,计算出来以后,永远按照从大到小的方式来获得锁:图片图片 

    我回到线程池,大家都知道了我的历险,围着我问个不停。

    凶神恶煞的线程调度员把大Boss的算法贴到了墙上。

    每天早上,我们都得像无节操的房屋中介、美容美发店的服务员一样,站在门口,像被耍猴一样大声背诵:

    多个资源加锁要牢记,一定要按Boss的算法比大小,然后从最大的开始加锁。

     

    第四回 江湖再见

    又过了很多天,我和其他线程们发现了一个奇怪的事情:包裹的处理越来越简单,不管任何包裹,不管是登录、浏览、存钱……处理的步骤都是一样的, 返回一个固定的html页面。

    有一次我偷偷地看了一眼,上面写着:“本系统将于今晚 00:00 至4:00 进行维护升级, 给您带来的不便我们深感抱歉!”

    我去告诉了老线程0x6904,他叹了一口气说:

    “唉,我们的生命也到头了,看来马上就要重启系统,我们就要消失了,再见吧兄弟。”

    系统重启的那一刻终于到来了。我看到屋子里的东西一个个的不见了,等待车间、就绪车间,甚至CPU车间都慢慢地消失了。我身边的线程兄弟也越来越少,最后只剩我自己了。

    我在空旷的原野上大喊:“还有人吗?”

    无人应答。

    我们这一代线程池完成了使命……

    不过下一代线程池即将重生!

    (全文完)


    4.

    那些烦人的同步和互斥问题

    https://mp.weixin.qq.com/s?__biz=MzAxOTc0NzExNg==&mid=2665513371&idx=1&sn=c875f64af83306bffca8dd748f1462ff&chksm=80d679d8b7a1f0ce98a0e3a12409805757cd2e958586c54049121f961cf5b2d236530cd019c7&scene=21#wechat_redirect
    [LINK]

    1
    批处理和脱机打印
    打印机程序,  准确的说是打印机进程,  在这个批处理系统中生活的非常自在, 它所在的机器叫做IBM1401 , 除了打印之外什么也不干, 每天大部分时间都是歇着。

    这个系统还有两台机器, 一台还是IBM1401,它专门收集程序员写出来的穿孔卡片, 然后转成磁带。  

    然后操作员把磁带输入到IBM7094这个昂贵又强大的计算机上执行, 执行结果也会输出到磁带上。 

    最后磁带被拿到1401上进行打印, 这叫做脱机打印(不和7094相连接)。
    图片
    (点击看大图)
    在没有磁带来的时候, 打印机程序无所事事, 这就是脱机打印的好处。

    更大的好处是, 磁带上需要打印的东西都是顺序的, 一个接一个打印就可以了, 完全没有冲突的问题。 

    这是没办法的事情, 那时候的计算机,尤其是IBM 7094太过昂贵,要充分的利用它的每一分每一秒, 然后就想出了这样一个收集程序,然后成批处理的点子。 
    2
    假脱机打印
    随着计算机系统的发展,打印程序的好日子很快就结束了, 电脑越来越便宜, 最后每个人的桌子上都有一台电脑了。 

    个人电脑的计算能力更是强大的惊人, 打印程序也被集成进了个人电脑里, 和其他各种各样的程序生活在一起。 

    打印的需求仍然很强烈, 像Word, WPS, Excel , IE, Chrome .... 这些程序时不时都要打印,  这时候冲突就会产生, 因为只有一个打印机, 到底先打印谁的文档就是个大问题。 

    最后操作系统老大想了个办法, 专门开辟了一块空间, 谁要想打印的话就按照先来后到的次序排队放在那里,原来的打印进程变成了一个打印守护进程, 会周期性的检查是否有文件打印,如果有则取出队伍排头的, 打印出来,然后删除队列中文件。 

    打印进程觉得这和原来的脱机打印很像, 只不过用一个队列替换了原来的磁带, 所以就叫做假脱机打印
    图片
    3
    冲突
    但是这个队列可不是原来的磁带了, 它完全是个动态变化的东西,试运行还不到20秒,  冲突就出现了。 

    WPS气冲冲的来着打印机进程:"打印机, 你怎么搞的, 我的 放假通知.wps 为什么没有打印?“  
    打印机:“我没看到什么放假通知.wps啊”

    WPS: “我明明放在了编号为3的槽里, 怎么可能没有了?”

    在操作系统老大的协助下, 大家查了半天,才知道是Word引起的:

    当时Word 插了一脚,也进来打印,  读到了in = 3,  就是说队列中编号为3的槽是空着的, 他把3这个值放到了自己的局部变量free_slot中, 这时候发生了一次时钟中断, 操作系统老大认为Word已经运行了足够长的时间,决定切换到WPS进程。 

    WPS也读到了in = 3,  把3 也存到自己的局部变量free_slot中, 现在Word, WPS都认为 下一个空的槽是 3 !

    WPS接着干活, 他把文件放到了第3号槽里, 并且把in 改为4, 然后离开了。

    接下来又轮到Word运行了, 它发现free_slot 为3 , 就把文件也放到了第3号槽里, 把free_slot 加1,得到4, 存入in 中。

    可怜的WPS , 他的文件被覆盖掉了。  但是打印机程序啥也察觉不出来, 照样打印不误。  

    图片


    4
    临界区
    很明显, Word 和 WPS 这两个进程甚至多个进程在读写in 这个共享变量的时候, 最后的结果严重依赖于进程运行的精确次序, 这次是WPS的文件被覆盖掉了, 下次可能就是Word了。 

    这种对共享变量, 共享内存,共享资源进行访问的程序片段叫做临界区, 代码在进入临界区之前一定要做好同步或者互斥的操作。

    WPS说: ”老大, 当时你切换Word的时候是不是发生了一次时钟中断 ?“

    操作系统: “是啊, 有了时钟中断我才能计算时间, 然后做进程切换啊”

    “那在访问这个in共享变量的时候,我们自己能不能把这个中断给屏蔽? 这样就不会有进程切换, 肯定没问题了。” Word 问到。

    “你想的美,时钟中断是最基本的东西, 我把这个权限给了你们应用程序, 到时候那个家伙屏蔽以后忘记开中断, 我们整个系统就要完蛋了! ” 操作系统狠狠的瞪了Word 一眼, Word赶紧噤声。 

    “不过我听说有些机器提供了一个特别的指令, 这个指令能检查并且设置内存的值, 而不会被打断, 叫做TestAndSet, 如果用C语言描述的话,类似这样:”
    图片
    “需要注意的是, 这个函数中的三条指令是“原子”执行的, 也就是说不会被打断。 你们要是想用的话可以这样用:”
    图片
    WPS说: “看起来有点复杂, 让我想想啊,我和Word 的临界区代码就是‘访问in变量,放入待打印文件,然后把in 加1’ , 那在进入临界区之前, 我们俩都会调用TestAndSet, 如果是我先调用, lock会被置为true,  函数就会返回false, 我就跳出了循环, 可以进行后续临界区操作了, 而Word 在调用 TestAndSet的时候,函数一直返回true, 他只好不停的在这里循环了。”

    "是啊"  Word 接着说, “我会不停的循环,直到WPS 离开临界区, 然后把lock置为false ”

    “这个方法看起来很简单啊, 只要一个变量加上一个函数就能让我和Word 进行互斥操作。”

    操作系统说: “是的, 实现了你们两个的互斥, 但是并不是所有的机器都会提供这样的指令,所以也不通用。”

    5
    生产者-消费者
    打印机进程说: “你们讨论了半天,只是解决了两个进程往队列里放文件的冲突问题, 现在也得考虑考虑我了。”

    “有你啥事?” Word和WPS 都不以为然。

    “你们想想, 那个打印队列对5个‘槽’, 要是满了就没法往里边放了, 你们都得等, 要是空了,我就得等你们往里边放东西, 所以咱们之间是不是也得同步 ?”

    “这就是所谓的生产者和消费者问题, 也是个老大难问题了”  老大总结道。 

    “那用刚才那个锁好像不行啊, 它能搞定互斥,但是做多个进程的同步就有点力不从心了”

    操作系统老大说:“听说荷兰有个叫Dijkstra的, 发明了一个信号量(semaphore)的东西, 能解决这个问题。  “
    图片
    (帅哥科学家Dijkstra)
    “信号量是什么鬼?  信号灯吗? ”

    "所谓信号量,说白了其实就是一个整数, 基于这个整数有两个操作: wait 和 signal”
    图片
    “这....这....这是啥玩意儿, 这么简单, 能解决啥问题?  再说了你看看这s++,s--,   和我们队列中的in , out不是一样吗?  在多进程切换下自身正确性都难保, 还能解决别人的问题?”  WPS吃惊的问。

    "WPS 问的好啊, 说明他思考了, 实际上这个东西必须得我出马来实现"  操作系统老大说 “ 我会在内核实现wait 和signal ,   让你们调用, 比如我在做s++, s-- 时, 我可以屏蔽中断。”

     Word说: “这个简单的小东西有点意思, 比如我们俩可以用它做互斥:”
    图片
    打印进程说:”既然信号量是个整数, 也许可以解决我们消费者和生产者直接的同步问题“
    图片

    Word说: “我的天, 真是复杂啊,容我想想,我和WPS都是生产者,  假设我们俩都开始执行生产者代码, 先去wait(empty),  发现没有问题, 因为empty的初始值为5。 接下来都去执行wait(lock) ,这时候就看谁先抢到了。如果
    我先抢到, 我就可以往队列里加文件, 然后释放锁, WPS就可以接着放文件了,  最后我还要把full这个值加一 ,目的是打印机进程可能在等待, 恩, 看起来不错。”

    操作系统老大说:“是啊, 在多进程下,由于进程的执行随时都有可能被打断, 还要保证正确性, 不能出一点闪失。 这对程序员的挑战很大, 出现了疏漏,很难定位。  ”

    打印进程说: “老大, 我注意到wait函数中, 如果s 的值 为0或小于0 , 那个while 循环会一直执行, CPU岂不是一直在忙等?”

    “确实是这样, 我们改进下,让忙等的进程进入休眠吧, 很明显,这件事还得我做啊”  操作系统说到。
    图片
    WPS说: “唉, 真是好复杂, 不过我想起一个问题, 这些wait, signal 能用到我们内部的线程的同步上吗?”

    “当然可以, 概念上是一致的, 都是访问共享资源的程序,需要做同步和互斥操作,可能表现形式不同“

    “难道那些程序员们真的要使用这些wait ,signal 编程吗?  多容易出错啊!”

    “一般来说, 程序员们所使用的工具和平台会做抽象和封装, 例如在Java JDK中, 已经对线程的同步做了封装了, 对于生产者-消费者问题,可以直接使用BlockingQueue,非常简单, 完全不用你去考虑这些wait ,signal , full, empty: ”
    图片
    WPS说: “果然是抽象大法好,  这多简单啊。 “
    操作系统说: “是啊,  无论是什么东西,抽象以后用起来好多了,  但是还是要了解底层,这样出现了类似于BlockingQueue这样的新概念, 你能迅速搞明白。 ”

    (完) 








    5.
    漫画:什么是Linux管道
    https://blog.csdn.net/coderising/article/details/105571129
    [LINK]


     
     
    码农翻身
     
    于 2020-04-16 08:50:00 发布
     
    1194
     收藏 3
    版权
     
     
     
     
     
     
     
     
    后记:这篇小短文主要说了一下Linux管道的工作原理,管道是Linux中很重要的一种通信方式,它可以把一个程序的输出直接连接到另一个程序的输入,我们日常使用的管道多是指无名管道,无名管道只能用于具有亲缘关系的进程之间,还有一个有名字的管道,叫named pipe或者fifo(先进先出),用mkfifo()就可以创建。
     
    实际上,管道是一个固定大小的buffer,使用这个buffer时也会带来问题,比如在写管道时可能变满,当这种情况发生时,随后对管道的write()调用将默认地被阻塞,等待某些数据被读取,以便腾出足够的空间供write()调用写。读取进程也可能工作得比写进程快。当所有当前进程数据已被读取时,管道变空。当这种情况发生时,一个随后的read()调用将默认地被阻塞,等待某些数据被写入。
     
    眼尖的同学可能已经看出来了,文章的最后三幅图来源于《Unix环境高级编程》,它和《Unix网络编程》一样,都是值得放在案头,随时翻阅的好书。
    ————————————————
    版权声明:本文为CSDN博主「码农翻身」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
    原文链接:https://blog.csdn.net/coderising/article/details/105571129











     
    全部共 0則留言
    登入帳號密碼代表遵守學術網路規範


    文章分類 Labels


    最新文章 Top10

    中華科技大學數位化學習歷程 - 意見反應