<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
 
 <title>fleuria lee</title>
 <updated>2012-05-18T01:40:58-07:00</updated>
 <id>http://fleurer.github.com</id>
 <author>
   <name>fleuria</name>
   <email>me.ssword@gmail.com</email>
 </author>

 
 <entry>
   <title>something new</title>
   <link href="http://fleurer.github.com//life/2012/01/01/new-year.html"/>
   <updated>2012-01-01T00:00:00-08:00</updated>
   <id>http://fleurer.github.com//life/2012/01/01/new-year</id>
   <content type="html">&lt;p&gt;test&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>Hello world</title>
   <link href="http://fleurer.github.com//2011/09/02/hello-world.html"/>
   <updated>2011-09-02T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2011/09/02/hello-world</id>
   <content type="html">&lt;p&gt;Another jekyll site，顺手就好。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>lua指令执行相关的几个数据结构笔记</title>
   <link href="http://fleurer.github.com//2011/08/31/luazhi-ling-zhi-xing-xiang-guan-de-ji-ge-shu-ju-jie-gou-bi-ji.html"/>
   <updated>2011-08-31T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2011/08/31/luazhi-ling-zhi-xing-xiang-guan-de-ji-ge-shu-ju-jie-gou-bi-ji</id>
   <content type="html">&lt;p&gt;对lua的upvalue机制一直很好奇，但两三年下来对它的了解只限于paper中的那张插图，于细节则毫不知情。直到最近几天晚上下班无聊看lua源码，也算是回头来补补这个小坑了。&lt;/p&gt;

&lt;p&gt;某某机制肯定是针对某问题而发，对upvalue机制而言，问题不外乎就是闭包的实现。对lua来讲，又可以分成三个子问题：&lt;/p&gt;

&lt;p&gt;1.怎样处理外部变量的生存周期（funargs问题）
2.将闭包的实现得简洁（Common Lisp的闭包实现就比整个lua还长）
3.尽量高效（深层嵌套的外部变量，该怎样做到高效地查找）&lt;/p&gt;

&lt;p&gt;&lt;i&gt;本来打算在一篇里写完，发现基础的内容比较容易喧宾夺主，分两篇好了。在这里先记录一下lua VM执行相关的几个数据结构，不一定全，足够接下来理解upvalue机制即可。&lt;/i&gt;&lt;/p&gt;

&lt;!--more--&gt;




&lt;h3&gt;预备&lt;/h3&gt;


&lt;p&gt;lua中值的表示先掠过。没有什么黑魔法，过一遍refman就足够了。&lt;/p&gt;

&lt;p&gt;先看闭包的表示：&lt;/p&gt;

&lt;pre lang=&quot;c&quot;&gt;
#define ClosureHeader \
    CommonHeader; lu_byte isC; lu_byte nupvalues; GCObject *gclist; \
    struct Table *env

typedef struct CClosure {
  ClosureHeader;
  lua_CFunction f;
  TValue upvalue[1];
} CClosure;


typedef struct LClosure {
  ClosureHeader;
  struct Proto *p;
  UpVal *upvals[1];
} LClosure;
&lt;/pre&gt;


&lt;p&gt;其中CClosure是为C接口提供(在此掠过)，LClosure就是一般的lua闭包的内部表示了。人肉把ClosureHeader展开，这样好看些：&lt;/p&gt;

&lt;pre lang=&quot;c&quot;&gt;
typedef struct LClosure {
  CommonHeader;
  lu_byte isC; //表示它是C函数还是lua函数
  lu_byte nupvalues; //表示upvalue(也就是外部变量)的数目。至于普通的lua函数，就是nupvalues为0的闭包；
  struct Table *env; //指向全局变量的表，以数字(而不是字符串形式的名字)为索引。
  GCObject *gclist; //为lua GC中的链表，链起lua中所有可回收的对象；
  struct Proto *p; //函数原型，这个留后面说
  UpVal *upvals[1]; //这里有点黑魔法，这里貌似是长度为1的数组，实际上在为LClosure申请内存时，lua会根据upvalue的数目来调整LClosure的长度，见luaF_newLclosure(lfunc.c, 33行)。而*upvals[]这个数组的真正长度，就与nupvalues是相等的。
} LClosure;
&lt;/pre&gt;


&lt;p&gt;如果精力有限，可以只留意两个地方：其一是&lt;em&gt;p，函数原型，就是闭包中各种“静态的”“万年不变”的东西，比如opcode，变量的个数、名字等等信息；其二，就是&lt;/em&gt;env和*upvals，分别表示着闭包中的全局变量和外部变量，那局部变量呢？在栈上。&lt;/p&gt;

&lt;p&gt;对FP感兴趣的人们喜欢说“OO is Poor Man's Closure”，实际上闭包也确实有着对象的特征的。想想，一个类放在那里，我们不能直接拿来用，想用它就必须得创建这个类的实例。同样，一个函数原型放在那里，我们也不能直接调用它，要调用它，也得先有一个它的“实例”。“实例”比“原型”多了什么？全局变量、局部变量和外部变量。程序中的指令都是“死”的，“活”的是数据。数据可能在堆里，可能在栈上，这些数据的集合，就是程序当前的状态。&lt;/p&gt;

&lt;h3&gt;lua_State&lt;/h3&gt;


&lt;p&gt;看lua源码的话一定会注意到，几乎所有函数的开头都是一个lua_State &lt;em&gt;L。在最早的lua实现中，lua_State是没有的，栈啦全局变量表啦都是全局的变量，写起来可以少打些字，问题是不可重入，不能支持多线程。引入lua_State之后，曾经的全局变量都放到了这里面，每个线程(也就是coroutine)便对应着一个lua_State&lt;/em&gt;。&lt;/p&gt;

&lt;pre lang=&quot;c&quot;&gt;
/*
** `per thread' state
*/
struct lua_State {
  CommonHeader;
  lu_byte status;
  StkId top;  /* first free slot in the stack */
  StkId base;  /* base of current function */
  global_State *l_G;
  CallInfo *ci;  /* call info for current function */
  const Instruction *savedpc;  /* `savedpc' of current function */
  StkId stack_last;  /* last free slot in the stack */
  StkId stack;  /* stack base */
  CallInfo *end_ci;  /* points after end of ci array*/
  CallInfo *base_ci;  /* array of CallInfo's */
  int stacksize;
  int size_ci;  /* size of array `base_ci' */
  unsigned short nCcalls;  /* number of nested C calls */
  unsigned short baseCcalls;  /* nested C calls when resuming coroutine */
  lu_byte hookmask;
  lu_byte allowhook;
  int basehookcount;
  int hookcount;
  lua_Hook hook;
  TValue l_gt;  /* table of globals */
  TValue env;  /* temporary place for environments */
  GCObject *openupval;  /* list of open upvalues in this stack */
  GCObject *gclist;
  struct lua_longjmp *errorJmp;  /* current error recover point */
  ptrdiff_t errfunc;  /* current error handling function (stack index) */
};
&lt;/pre&gt;


&lt;p&gt;StkId top; StkId base; StkId stack_last; StkId stack;都是指向求值栈的不同位置。lua5不是寄存器机了，那求值栈里有什么？我还没有调试，不过通过ChunkSpy来看，栈底是函数中的常量，其余都用来存放局部变量和临时变量。那么，函数的返回地址之类放在哪里？&lt;/p&gt;

&lt;p&gt;在CallInfo &lt;em&gt;base_ci; 这边，lua将函数的返回地址、调用者的栈指针以及函数返回值的个数都存在CallInfo这个结构里。可以把CallInfo &lt;/em&gt;base_ci也看作是一个栈，深度是nexeccalls(luaV_execute()中定义的一个局部变量)。不妨留意lvm.c中OP_CALL和OP_RETURN两个指令中luaD_precall()和luaD_poscall()两个函数对它的处理。&lt;/p&gt;

&lt;p&gt;lua5是寄存器机，但对C的接口依然是老样的堆栈机，这点挺有意思。一开始不理解lua为什么改到寄存器机，lua的设计原则不是简单么？寄存器机又是公认比堆栈机更难实现。其实仔细想想，多出来的难点无非是寄存器分配算法，给编译器实现寄存器分配算法自然很难，但是lua这里有一点天生省力的地方，那就是它的“寄存器”可以是认为是无限的(255个?)，而像图着色这样复杂到坑爹的算法，都是针对及其有限寄存器的分配而言，此“寄存器分配”非彼“寄存器分配”。至于C接口依然是堆栈机的样子，则可以这样想：压栈就是分配了一个寄存器，弹出就是释放了。&lt;/p&gt;

&lt;h3&gt;global_State&lt;/h3&gt;


&lt;p&gt;struct lua_State上边还有一个struct global_State，其中五分之四的字段都是针对GC而设，GC部分还没仔细看，在这里先掠过。不过你可以把lua的GC看作是一个可以暂停的状态机。&lt;/p&gt;

&lt;h3&gt;指令执行&lt;/h3&gt;


&lt;p&gt;lua虚拟机中的指令很少，执行都在lvm.c中的luaV_execute(lua_State *L, int nexeccalls)中。可以认为调用它之前，lua虚拟机已经初始化好了求值栈和一个初始的函数。&lt;/p&gt;

&lt;p&gt;先记这些，感兴趣的还是upvalue机制，下篇再记。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>rails3中使用facebox</title>
   <link href="http://fleurer.github.com//2011/07/26/rails3zhong-shi-yong-facebox.html"/>
   <updated>2011-07-26T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2011/07/26/rails3zhong-shi-yong-facebox</id>
   <content type="html">&lt;p&gt;(有两个月没有更新了？这又是例行工事的凑数文。)&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;真正接触rails的时间并不长，先前几个心不在焉的小东西烂尾之后，就一直觉得跟不上rails中各种约定的思路，也一直没有学好。直到最近在家没事搞个小项目，权当实习之前的预习，才发现上手还是很快的。所谓“约定”，就是“同一种问题使用同一种解决方法”，而且往往也正是“最好的方法”。用过一遍之后如果能留下个好印象，就该差不多了。&lt;/p&gt;

&lt;p&gt;ajax的相关内容在Rails Guide中似乎并未提及，因此在这里记一下。假定已经实现了一个非ajax表单，就像豆瓣收藏一本书的表单一样：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://i.min.us/iefmog.png&quot;&gt;&lt;/img&gt;&lt;/p&gt;

&lt;p&gt;需要做的是，将这个表单放到&lt;a href=&quot;http://defunkt.io/facebox/&quot;&gt;facebox&lt;/a&gt;里：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;http://i.min.us/ibKvIy.png&quot;&gt;&lt;/img&gt;&lt;/p&gt;

&lt;h2&gt;jquery-ujs&lt;/h2&gt;


&lt;p&gt;先将rails默认的js框架改为jquery。&lt;/p&gt;

&lt;p&gt;在Gemfile中加入：&lt;/p&gt;

&lt;pre lang=&quot;ruby&quot;&gt;
gem 'jquery-rails', '&gt;= 1.0.12'&lt;/pre&gt;


&lt;p&gt;然后&lt;/p&gt;

&lt;pre lang=&quot;shell&quot;&gt;
bundle install
rails generate jquery:install&lt;/pre&gt;


&lt;p&gt;换用jquery之后，把各种js都生成到html里的link_to_remote就不能使用了。可行的做法是在link_to中加上一个:remote =&gt; true，生成的代码会像是这样：&lt;/p&gt;

&lt;pre lang=&quot;html&quot;&gt;
&amp;lt;a href=&quot;/favorites/4e2e7a331c78b4288b000005/edit&quot; data-remote=&quot;true&quot;&amp;gt; 想读 &amp;lt;/a&amp;gt;
&lt;/pre&gt;


&lt;p&gt;仅仅多了一个data-remote=&quot;true&quot;，专门给jquery看的一个属性。如果用户点击这个链接，触发一个js事件弹出facebox；如果用户要从新标签中打开这个链接，就可以见到原先非ajax的那个表单。&lt;/p&gt;

&lt;p&gt;(ps: data-remote这种自定义属性似乎是html5加入标准的？如果是&amp;lt;a href=&quot;&quot; remote=&quot;&quot;&amp;gt;&amp;lt;/a&amp;gt;这样写，在浏览器生成DOM时会被省略掉，jquery也就读不出来，解决方案就是加一个data-前缀。)&lt;/p&gt;

&lt;h2&gt;约定&lt;/h2&gt;


&lt;p&gt;只要是:remote =&gt; true的链接，在用户点击时就会通过ajax获取一段动态生成的js代码并执行，不同的:action对应的js代码不同。这些js代码的模板都放在views里，比如&quot;/favorites/4e2e7a331c78b4288b000005/edit&quot;这个RESTful的链接，对应的:controller是Favorites，:action是edit，这段js代码也就对应着edit.erb.js。&lt;/p&gt;

&lt;p&gt;原先的Controller:&lt;/p&gt;

&lt;pre lang=&quot;ruby&quot;&gt;
class Favorites
...
  def edit
    @favorite = Favorite.find params[:id]
    @favorite.state = params[:state] if params[:state]
  end
 ...
 end&lt;/pre&gt;


&lt;p&gt;修改后：&lt;/p&gt;

&lt;pre lang=&quot;ruby&quot;&gt;
class Favorites
...
  def edit
    @favorite = Favorite.find params[:id]
    @favorite.state = params[:state] if params[:state]
    respond_to do |format|
      format.html 
      format.js { render :ajax_edit, :layout =&gt; false }
    end
  end
..
end&lt;/pre&gt;


&lt;p&gt;其中respond_to是根据mime类型分派render的内容。获取的是js代码，mime类型肯定就是text/javascript了。在这段js里面打开facebox即可。&lt;/p&gt;

&lt;p&gt;表单的内容呢？嵌到js代码里面。重构下代码，把edit.haml中的内容移动到&lt;em&gt;form_edit.haml中，在edit.haml里只留一行=render :template =&gt; 'favorites/&lt;/em&gt;form_edit.haml'&lt;/p&gt;

&lt;p&gt;ajax_edit.erb.js&lt;/p&gt;

&lt;pre lang=&quot;js&quot;&gt;
var form_html = '&lt;%= escape_javascript(render :template =&gt; 'favorites/_form_edit') %&gt;';
$.facebox(form_html);
&lt;/pre&gt;


&lt;p&gt;javascript并无heredoc那种多行字符串的语法，escape_javascript可以将多行的字符串转义成单行。&lt;/p&gt;

&lt;p&gt;这样就好了。提交表单时还会刷新下页面，不过若要修改成不需刷新的ajax表单，也就是按这路数再走一遍的功夫。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>翻译的错觉</title>
   <link href="http://fleurer.github.com//2011/05/11/fan-yi-de-cuo-jue.html"/>
   <updated>2011-05-11T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2011/05/11/fan-yi-de-cuo-jue</id>
   <content type="html">&lt;p&gt;前两月与熊叔和Elephas童鞋合译《Learning Android》，很简单的书，语言也无甚华丽之处，但是遣词造句的时候仍然颇感力不从心。甚至觉得越是平常的句子反而越不好翻，既然是“平常的句子”，遣词造句就越像是不经意的样子，但实际上依然是免不了推敲的。翻译不是随便的事情，困难不少，至今无解的问题也不少。还需要多下些功夫。&lt;/p&gt;

&lt;p&gt;“信达雅”三难，以“信”为先。但是对译者来讲，自认为最易把握的也正是这个“信”。文学功底弱一些，难以发挥出原文的滋味，但是仍可以退而求其次，搞搞直译——既然滋味难求，姑且先做到原料不失吧。但是现实情况呢，读者第一口吃着不好吃就直接扔掉筷子骂句娘走了人，一盘菜自然就全浪费掉了。“原料不失”这算是做到了吗？做到了，是译者的错觉。可翻译终究还是跟自己的错觉做斗争的一行。&lt;/p&gt;

&lt;p&gt;那么就在这里记一下平时能感觉出来的“错觉“。诚然只是冰山的表面，不过聊胜于无。权当自警吧。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;试图&quot;完整的&quot;表达原意&lt;/strong&gt;。
试图把每个单词都在译文中表达出来。但是汉语所贵意在言外，英文里的许多修饰性质的单词省略掉并不失原意，都表达出来反而显得罗嗦。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;主语泛滥。&lt;/strong&gt;
英文句子里面&quot;I&quot;,&quot;We&quot;,&quot;You&quot;作主语，说着很随意就不觉的多。但中文不这样，在英文里是无所谓的“带着主语”，同样的句式到中文里就成了“强调主语”。一个自然段要是每句话开头必然“我们”“你”“它”，读起来是很别扭的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;词汇单一。&lt;/strong&gt;
尤其是一些形容词，very必然&quot;非常&quot;，many必然&quot;许多&quot;，-able必然&quot;可XX&quot;，a必然“一个”。字典为了通俗易懂，也往往取最简单的词语做解释。这样形成的一个现象就是全文上下除了行业术语，剩下的都是基础词汇。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;逐句翻译。&lt;/strong&gt;
这要比“逐词翻译”好一些，但实际上段落才是文意的基本单位。译者翻译一个段落可能需要左思右想花些时间，但读者阅读段落的速度是很快的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&quot;其&quot;&quot;之&quot;之类的文言。&lt;/strong&gt;
文言用的好，可以让文章有风骨。但是白话做到通达流畅已属不易，何况文言。用的不好，就是不中不西的怪调调。在翻译中出现这种文言，多半是以为可以凭此让文字更简洁，这也是错觉的一种。文字的简洁在于句式，而不在单词。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;俚语&lt;/strong&gt;
除了查词典没别的办法，如果按字面意思翻译出来是要闹笑话的。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;“的”&lt;/strong&gt;
“的”多了自然罗嗦无比，也显得行文不自信。但是自己反过来有点矫往过正的意思，就是在译文中不大敢用“的”字。这样也不好，慢慢体会吧。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>折腾分区表小记</title>
   <link href="http://fleurer.github.com//2011/05/01/zhe-teng-fen-qu-biao-xiao-ji.html"/>
   <updated>2011-05-01T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2011/05/01/zhe-teng-fen-qu-biao-xiao-ji</id>
   <content type="html">&lt;p&gt;就在五一放假回家前的两个小时把分区表给弄没了，囧。&lt;/p&gt;

&lt;p&gt;侥幸心理，装Ubuntu11.04时候用了老版本的initrd.gz与vmlinuz，引导alternate的iso没问题，但是安装到一半的时候报错，重启就发现grub已经没了。&quot;恢复下分区表就行了吧...&quot;于是万恶的侥幸心理再次得势，拿一张xp安装盘的分区表修复工具扫描并修复了下，进windows下载来&lt;a href=&quot;http://tw.archive.ubuntu.com/ubuntu/dists/natty/main/installer-i386/current/images/hd-media/&quot;&gt;正确的initrd.gz与vmlinuz&lt;/a&gt;，回去继续装11.04。到分区的时候发现几个linux分区都让不认ext4的分区表修复工具给搞没了，整个成了一大块不可用的剩余空间。想起来/home里面几个星期的翻译稿和代码竟都忘了commit，于是冷汗...&lt;/p&gt;

&lt;p&gt;万幸还没有格式化。经weizhong大哥指点，宽了些心，该是可以找回来的。&lt;/p&gt;

&lt;p&gt;尝试了一些工具，最后还是使用了&lt;a href=&quot;http://www.cgsecurity.org/wiki/TestDisk&quot;&gt;testdisk&lt;/a&gt;，它可以扫描整个硬盘，分析出来分区信息。也能在windows下使用。&lt;/p&gt;

&lt;p&gt;一开始没仔细看&lt;a href=&quot;http://www.cgsecurity.org/wiki/TestDisk_Step_By_Step&quot;&gt;教程&lt;/a&gt;，反复扫描了好几遍，期间又把分区表清了n回， ＞﹏＜。现在想想还能找回来，真是够侥幸的。留意的地方就是，扫描完毕的时候它会列个表出来，需要自己按左右键设置分区的信息(主分区/用来引导的主分区/还是逻辑分区)，都&lt;a href=&quot;http://www.cgsecurity.org/mw/images/Set_partition_to_recover.gif&quot;&gt;绿了&lt;/a&gt;才好。如果扫描到的分区不全，就选择Deeper Search，它会扫描出分区表的几种可能情况，自己需要选择出正确的分区，确保无误之后再Write。&lt;/p&gt;

&lt;p&gt;折腾了一天，最后终于把/home找了回来。代价是把windows的D盘搞丢了，不过估计该比较容易找回。&lt;/p&gt;

&lt;p&gt;下次就不一定能这么幸运了，以后装系统什么的千万得对数据上心才是。越来越怕折腾了 &amp;gt;&amp;lt;&lt;/p&gt;

&lt;hr/&gt;&lt;!--more--&gt;


&lt;p&gt;&lt;strong&gt;后记：&lt;/strong&gt;
刚刚看了下&lt;a href=&quot;http://www.fleurer-lee.com/2010/10/25/%E7%AE%80%E4%BB%8B%E7%A1%AC%E7%9B%98%E5%88%86%E5%8C%BA/&quot;&gt;以前的译文&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
每个分区通常以一个柱面为边界：C,H,S=??,0,1。MBR和扩展分区中的首个分区则通常都是以一个磁头为界：C,H,S=??,1,1。

扩展分区的首个扇区的布局与MBR类似，只不过只有两个分区表项：第一项即其中的第一个分区，可选的第二项指向第二个分区，构成一个链表。
&lt;/blockquote&gt;


&lt;p&gt;估计这就是testdisk的原理了吧。扫描整个硬盘，在磁头的边界处看看是不是有扩展分区，然后顺着扩展分区的链表，遍历其下的逻辑分区，看看superblock是否正确。当然实际的情况要复杂的多。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>页面上色机制以及CPU高速缓存的工作方式</title>
   <link href="http://fleurer.github.com//2011/04/05/ye-mian-shang-se-ji-zhi-yi-ji-cpugao-su-huan-cun-de-gong-zuo-fang-shi.html"/>
   <updated>2011-04-05T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2011/04/05/ye-mian-shang-se-ji-zhi-yi-ji-cpugao-su-huan-cun-de-gong-zuo-fang-shi</id>
   <content type="html">&lt;p&gt;摘译自：&lt;a href=&quot;http://www.freebsd.org/doc/en_US.ISO8859-1/articles/vm-design/article.html&quot;&gt;Design elements of the FreeBSD VM system&lt;/a&gt;
作者： Matthew Dillon
翻译：fleurer&lt;/p&gt;

&lt;hr/&gt;


&lt;p&gt;Q: Finally, in the page coloring section, it might help to have a little more description of what you mean here. I did not quite follow it.&lt;/p&gt;

&lt;p&gt;A: 对L1高速缓存的工作方式有了解不？我解释一下。假如一台机器有16MB的物理内存，同时只有128kb的L1高速缓存。它通常也就对应了一整块128k的内存。你要先访问了地址0，再接着访问地址128k的话，也就把刚才访问地址0留下的缓存都刷没了！&lt;/p&gt;

&lt;p&gt;这样就把问题简化多了。刚才我们提到的缓存方式称为“direct mapped”的高速缓存，而现代的缓存技术多数采用2-way-set-associative或者4-way-set-associative技术。所谓&quot;N-way-set-associative&quot;，也就是允许你缓存N块不同的内存区域而不至于将原先的缓存完全覆盖。但只有N个，不多。&lt;/p&gt;

&lt;p&gt;假如我有个4-way set associative的高速缓存，先访问地址0，然后访问地址128k,256k,384k再回来访问地址0仍然可以命中高速缓存。不过再访问512k，前面留下的四块缓存之一就得刷掉。&lt;/p&gt;

&lt;p&gt;这很重要...尤其是CPU的内存访问几乎都是经过L1高速缓存，作为CPU周期的一部分。要是缓存命中失败，就不得不访问L2缓存甚至访问内存，CPU也就只能坐着眼巴巴干等这几百个指令时间的内存访问了。跟现代处理器的速度相比，内存访问无疑慢的很。&lt;/p&gt;

&lt;p&gt;好，回正题，页面上色：现代的高速缓存都是物理缓存。即针对物理地址的缓存，而不是针对虚拟地址。这一来高速缓存即可无视进程的上下文切换，减少不必要的缓存失效。（译者注：如果是针对虚拟地址的缓存，那么每次上下文切换之后地址的映射即全部改变，所有的缓存也就都无效了。进程切换十分频繁，这样显然不靠谱。）&lt;/p&gt;

&lt;p&gt;但是，在Unix世界里虚拟地址才是王道。我们写的任何程序都在虚拟地址空间中执行，连续的虚拟页面对应的物理页面不必连续。实际上，在虚拟地址空间里隔着十万八千里的两个虚拟页面，其对应的两个物理页面没准还是靠着的。&lt;/p&gt;

&lt;p&gt;程序通常假定相邻的两个页面是理想地缓存的。也就是说你分别访问两个页面中的对象，不会影响到彼此的缓存(译者注：这两个对象在高速缓存中的位置相互重合)。但是这样，(目前看来)只有连续的虚拟页面对应的物理页面也是连续的才有可能。&lt;/p&gt;

&lt;p&gt;都是随机申请来的物理页面，靠它们填出来的虚拟地址空间就可能会对缓存不友好。这就是页面上色所做的，为连续的虚拟页面提供“貌似”连续的物理页面。这样在高速缓存看来，就抹平了虚拟地址与物理地址的区别。&lt;/p&gt;

&lt;p&gt;留意前面我说是“貌似”的连续。对一个128kb大小direct mapped的高速缓存来说，物理地址0与物理地址128k是没有区别的。因此两个连续虚拟页面对应的物理页面若分别为128k与132k(页面大小4k的话)，在高速缓存看来就是连续的。所以说，页面上色机制带来的不是真正连续的物理页面，而是“在高速缓存看来”连续的物理页面。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;译者注：&lt;/strong&gt;&lt;/p&gt;

&lt;hr/&gt;


&lt;ul&gt;
&lt;li&gt; 文里没说，高速缓存对系统程序员是&lt;b&gt;完全透明&lt;/b&gt;的，没有任何接口。做优化只能是迁就着它的性子。
&lt;li&gt; 文里也没说，L1高速缓存也是按照地址存东西的。&lt;i&gt;L1高速缓存地址 = 物理页面地址 % L1高速缓存大小&lt;/i&gt;。单位是缓存行(cache line)，行可以构成组(set)，组中的行没有顺序。
&lt;li&gt; 个人感觉不一定说不连续的物理页面就一定有问题，而是有个概率。假如是直接映射的高速缓存，有两个连续的虚拟页面1和2，其对应的物理页面地址正好同余(比如132k和260k, &lt;i&gt;132k%128k==260k%128k&lt;/i&gt;))，它们在高速缓存中就可能会占据同一个位置，访问页面1，这块高速缓存就是页面1的内容，访问页面2，这块高速缓存就是页面2的内容。先访问页面1再访问页面2再访问页面1，这样高速缓存就miss了两次。再加上引用局部性原理，这样一来一去是很折腾的。回过来想想，问题就出在“&lt;em&gt;两个连续的虚拟页面对应的物理页面正好同余&lt;/em&gt;”这里，随机分配物理页面的话，这样的概率恐怕也不见得多高。不过回避开总是好的(通过页面上色)。
&lt;li&gt; N-way-set-associative式高速缓存的话，发生不利情况的概率就更小了。但这样会使高速缓存的设计更加复杂。
&lt;li&gt; 所谓“颜色”差不多就是一个余数，&lt;i&gt;物理页面地址 % L1高速缓存大小&lt;/i&gt;，每个“颜色”对应一个空闲物理页面的freelist。没有页面上色之前，只需要一个freelist。
&lt;li&gt; 增加了内存管理的难度，得到的好处是抹平了(在高速缓存看来)虚拟地址与物理地址的区别。
&lt;li&gt; Linux喜欢连续的物理页面，因此似乎没必要页面上色机制。
&lt;li&gt; 虽说都跟高速缓存有关，这里的“页面上色”这跟slab的“上色区”不是一回事。就事论事，胡乱联系定律要不得。
&lt;/ul&gt;

</content>
 </entry>
 
 <entry>
   <title>另一个unix-like内核, Fleurix</title>
   <link href="http://fleurer.github.com//2011/03/18/ling-%5B%3F%5D-ge-unix-likenei-he-fleurix.html"/>
   <updated>2011-03-18T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2011/03/18/ling-[?]-ge-unix-likenei-he-fleurix</id>
   <content type="html">&lt;p&gt;该是在去年五月份，照着网上的教程写了几行helloworld，简简单单的int 13h放在virtualbox里打印出来一段红色的“screw you guys all fucked up~”。随后从这段不得体的汇编开始，慢慢地文件系统、内存管理、输入输出、进程管理等等初具轮廓，到现在一个相对完整的内核，不觉已过了九个月。时间就是个见证成长的东西 :)&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/fleurer/fleurix&quot;&gt;https://github.com/fleurer/fleurix&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;37个系统调用，七千行C，二百多行汇编。没有管道，没有swap，也不是基于POSIX，各种特性是能删即删，能简即简。不过也算完成了它的设计目标，那就是跑起来。Fleurix已经有了：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt; minix v1的文件系统。原理简单，而且可以利用linux下的mkfs.minix，fsck.minix等工具。
&lt;li&gt; fork()/exec()/exit()等等。a.out的可执行格式，实现了写时复制与请求调页。
&lt;li&gt; 信号。
&lt;li&gt; 一个纯分页的内存管理系统，每个进程4gb的地址空间，共享128mb的内核地址空间。至少比Linux0.11中的段页式内存管理方式更加灵活。
&lt;li&gt; 一个简单的kmalloc()(可惜没大用上)。
&lt;li&gt; 一个简单的终端。
&lt;/ul&gt;


&lt;p&gt;&lt;img src=&quot;http://i.min.us/im2snS.jpg&quot;&gt;&lt;/img&gt;
&lt;img src=&quot;http://i.min.us/ikheqK.jpg&quot;&gt;&lt;/img&gt;&lt;/p&gt;

&lt;p&gt;硬伤就是，没有硬盘分区，内存也写死了128mb，恐怕无法在真机上运行。&lt;/p&gt;

&lt;hr /&gt;


&lt;p&gt;编译环境: ubuntu
工具: rake, binutils(gcc, ld), nasm, bochs, mkfs.minix&lt;/p&gt;

&lt;pre code=&quot;bash&quot;&gt;
git clone git@github.com:Fleurer/fleurix.git
cd fleurix
rake
&lt;/pre&gt;




&lt;hr /&gt;


&lt;p&gt;坦白地说，现在编写内核比起Linus的时代已经容易太多了。有模拟器，有工具链，网上也有相当多的资料可以参考，更有Unix世界的先贤更留下的宝库。但是现在又有谁肯再花十年时间让内核从婴儿长大成人呢？就是有人肯做，也只是重复造轮子罢了，非为智者所取。&lt;/p&gt;

&lt;p&gt;那么，Fleurix的意义在哪里呢？&lt;/p&gt;

&lt;p&gt;实际应用不合适；说可供大家学习又未免太自以为是。但是对我自己而言，如果不写Fleurix，在这九个月里总不至于去四处搜集Unix的老书，打印论文，理解每个函数的细节，写下这些代码，甚至耽误勾搭妹子。要总是功利地看待得与失的话，又何曾不是得不偿失呢。&lt;/p&gt;

&lt;p&gt;因此，何必把一些嘴上喊的事情看的太重。把结果看作是过程的一步分，体会这个过程就够了。尤其是在查阅资料中，居然能够找到跟Unix世界的先贤交谈的感觉，就此，我已心满意足。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>文件与内存的桥梁：Page Cache</title>
   <link href="http://fleurer.github.com//2011/01/30/wen-jian-yu-nei-cun-de-qiao-liang-%3Apage-cache.html"/>
   <updated>2011-01-30T00:00:00-08:00</updated>
   <id>http://fleurer.github.com//2011/01/30/wen-jian-yu-nei-cun-de-qiao-liang-:page-cache</id>
   <content type="html">&lt;ul&gt;
&lt;li&gt;作者：Gustavo Duarte&lt;/li&gt;
&lt;li&gt;翻译：fleurer&lt;/li&gt;
&lt;li&gt;原文：&lt;a href=&quot;http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files&quot;&gt;http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;hr&gt;




&lt;!--more--&gt;


&lt;p&gt;前面我们观察了内核为用户进程管理虚拟内存的方法，简单起见，一时忽略了文件和IO。本文则着重讨论下这块，说说文件和内存之间的暧昧关系，及其对性能的影响。&lt;/p&gt;

&lt;p&gt;关于文件，有两个严肃的问题需要考虑。首先是与内存相比，硬件设备往往是发指的慢，其寻址尤然；其次是某文件只应装入物理内存一次，其内容可为不同程序所共享。比如用Process Explorer观查Windows进程的话可以发现，有大约15MB的公用DLL在所有进程中都有装载。想下，我的Windows现在有100个进程，要没共享机制光这些公共DLL就得占去1.5GB的物理内存，显然不靠谱。同样Linux下也是几乎每个程序也都得用到ld.so和libc，一些常用的共享库也是不可或缺的。&lt;/p&gt;

&lt;p&gt;幸甚，上述两个问题能够一举解决：Page Cache，即内核以页为单位缓存文件的机制。拿例子说话，我们编写一个Linux程序render，它打开scene.dat文件，每次读取512字节，将其储存于堆里。第一次读取大约即这样：&lt;/p&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;http://min.us/mvnIBEJ#4&quot;&gt;&lt;img src=&quot;http://i.min.us/je6iVs.png&quot; border=&quot;0&quot;/&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;512kb读取完毕，它的堆和相关的页框大约这样：&lt;/p&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;http://min.us/mvnIBEJ#3&quot;&gt;&lt;img src=&quot;http://i.min.us/je1KiM.png&quot; border=&quot;0&quot;/&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;比较明显了，不过还有些地方待挖掘。首先这个程序普通的一个read调用，即已有三个4kb的页作为Page Cache来存放scene.dat。可能难以置信，但事实如此：任何普通文件的读写都必经Page Cache。x86体系结构的Linux将文件看作是n个4kb的块相连而成的序列，即使仅仅读取一个字节，也不得不读入整个4kb大小的快作为Page Cache。文件的读写往往不是几个字节就罢，这样设定有助于提升磁盘的吞吐量。每个Page Cache对应文件中的一个4kb块，并一个唯一的编号。Windows中的等价物是256kb的视图（view）。&lt;/p&gt;

&lt;p&gt;然而杯具是，普通的一个read之后，内核还要把Page Cache里的内容额外拷贝到用户态的缓冲区，既费CPU时间又污染CPU缓存，还浪费物理内存。如上图所示，scene.dat里同样的内容被储存在了两个地方，而且每个该程序的实例都会如此重复，进一步白白浪费时间浪费空间。我们缓解了磁盘延时这一重镇的危险，却尽失其它城池。于是下一个方案呼之欲出，即文件映射(Memory-mapped files)。&lt;/p&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;http://min.us/mvnIBEJ#2&quot;&gt;&lt;img src=&quot;http://i.min.us/je6ayM.png&quot; border=&quot;0&quot;/&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;使用文件映射时，内核会把程序的虚拟页面直接映射到Page Cache上。这一来性能就燃了：《Windows System Programming》有提及说这样比起普通的read方式，性能提升30%。《Unix环境高级编程》里对Linux和Solaris的性能测试的结果也与之相似。情景合适的话，还可以为程序节省大量的物理内存。&lt;/p&gt;

&lt;p&gt;谈及性能，评测至上。而内存映射凭其优良的性能，值得为每个程序员所了解。API也很漂亮，一字节一字节的读内存即访问文件，甚至不用纠结可读性与性能的trade off。*nix上有mmap，windows有CreateFileMapping，还有其它高级语言的各种封装，都不妨一试，留意下你的地址空间。在映射文件的时候，其内容并不是一次性装入内存，而是基于Page Fault的请求调页。取一个物理页存放其内容，然后fault handler将这个虚拟页映射到Page Cache。这是缓存之前的第一次读取。&lt;/p&gt;

&lt;p&gt;提个问题：在最后一个例子程序在执行结束后，Page Cache里的内容会不会释放？可能直觉该如此，但实际上这样不好。想想，我们经常会在某程序里打开某个文件，它退出了之后第二个程序还得用它——Page Cache必须对此有所考虑。比如需要跑那个例子程序一星期，一直缓存着scene.dat不就赚大了。既如此，那Page Cache的内容该在什么时候释放呢？永远记着磁盘的读取速度得比内存慢五个量级，能命中Page Cache自是多多益善。所以只要还有空闲的物理内存，内核就总是拿来做缓存使。Page Cache不是某个进程的私有财产，它是为整个系统所共享的资源。这就是为啥内核缓存总是不到极限不休——绝不是因为系统烂吃内存，毕竟物理内存闲着也是闲着，缓存不嫌多。这是个很好的做法。&lt;/p&gt;

&lt;p&gt;Page Cache架构下的write()调用就只是将数据写入Page Cache再把它标记为dirty，而磁盘IO通常并不立即执行，程序也就无需为磁盘而阻塞。这样的不足就是机器一旦意外崩溃，就可能会丢失部分数据。因此对完整性要求高的文件（比如数据库事务的log）通常会在写入后调用fsync()（唔，还有磁盘驱动器缓存需要纠结）。read通常是阻塞等待数据读取就绪。为减少这里的阻塞，内核会一次性多读几个页，预先缓存起来，即“贪婪读取”(Eager Loading)。我们可以调整贪婪读取的参数(参见madvise(),readahead(),或windows的cache hints)，告诉内核我们读取起来是顺序还是随机。Linux会为内存映射的文件执行预读取(read-ahead)，Windows则不清楚。跳过Page Cache也是可以的，数据库经常需要这样：Linux可以O_DIRECT，Windows可以NO_BUFFERING。&lt;/p&gt;

&lt;p&gt;文件的映射也可以设为私有，即私有映射中的内存读写不会影响到磁盘中文件的内容，也不会对影响到其它进程中的数据，而不像共享映射那样二者皆同步其变化。内核在私有映射的实现上应用了写时复制机制。如下面的例子里，render和render3d两个程序都私有映射了同一个文件scene.dat，随后render修改了一下文件映射的虚拟内存：&lt;/p&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;http://min.us/mvnIBEJ#1&quot;&gt;&lt;img src=&quot;http://i.min.us/je6c6U.png&quot; border=&quot;0&quot;/&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;如上这个页表项是“只读”并不意味着这个映射是只读的。这就是内核用以实现写时复制的小trick，共享物理页，不到万不得已决不复制——这个“万不得已”由x86把关而不是内核。搞明白所谓“私有映射”仅仅是针对“更改“就好理解了。这样设计的一个结果是：在对私有映射来的页面进行修改前，其他程序对它的修改都是可见的；一旦经过写时复制，其他程序对它的修改就不可见了。与之相对，共享映射仅仅把page cache映射到位即可，对它的修改对其它进程皆可见，文件在磁盘中也一并修改。若是只读映射就免了写时复制，Page Fault时直接一个segmentation fault。&lt;/p&gt;

&lt;p&gt;动态库也是通过文件映射装入程序的地址空间，就是普通的私有文件映射，并无特殊之处。如下是同一例子程序的两个实例，其地址空间和物理内存的样子足已囊括本文出现的很多概念：&lt;/p&gt;

&lt;p&gt;&lt;a target=&quot;_blank&quot; href=&quot;http://min.us/mvnIBEJ#5&quot;&gt;&lt;img src=&quot;http://i.min.us/je6ld0.png&quot; border=&quot;0&quot;/&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;以上，内存三部曲已告一段落。希望对大家有帮助，对操作系统的相关概念有个感性认识就好。下周再一篇post说说内存的分配图，也该换换话题了，比如web2.0八卦什么的 :)&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>2011</title>
   <link href="http://fleurer.github.com//2010/12/31/2011.html"/>
   <updated>2010-12-31T00:00:00-08:00</updated>
   <id>http://fleurer.github.com//2010/12/31/2011</id>
   <content type="html">&lt;p&gt;“没有付出就什么也得不到，想得到什么就必须得付出同等的代价，这就是炼金术的等价交换原则。那时的我们以为这就是世界的真理。”&lt;/p&gt;

&lt;p&gt;再熟悉不能的片头音，开始重温钢炼03。刚开始看钢炼03的时候还是年初，转眼即至年末，现在把电脑搬回宿舍打算写总结。当时的情景、感受如何？记不清了。虽说无关紧要，但是这一来总结还怎么写呢？&lt;/p&gt;

&lt;p&gt;再比如，床头的这本《挪威的森林》是什么时候买的？也是想不起来了。一直放在枕头边，睡不着就看两页，意志薄弱的时候总能想起来永泽那句“不要同情你自己！同情自己是卑劣懦夫干的勾当”。现在《挪威》已经拍成了电影，不可思议。&lt;/p&gt;

&lt;p&gt;想...上半年还在青春在线，为一些懒惰的新成员杀病毒吧；暑假在学校折腾了一个多月，结结实实的见识了什么叫不靠谱；下半年带着不舍退出了所属于的团体，开始独自懒懒散散的看书写东西。如此如此，同21岁一样遥远的2011年悄然而至，得失自知。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>随记</title>
   <link href="http://fleurer.github.com//2010/12/24/sui-ji-2.html"/>
   <updated>2010-12-24T00:00:00-08:00</updated>
   <id>http://fleurer.github.com//2010/12/24/sui-ji-2</id>
   <content type="html">&lt;p&gt;&lt;strong&gt;调度&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;发现这个词很暧昧，教科书上说的“调度”几乎就是&quot;进程调度&quot;的同义词，可是看下各种源代码的schedule()函数里面除了进程调度还总是有很多怪怪的东西。搞明白还有个&quot;页面调度&quot;就好理解了。Unix V6与进程的优先级调度相关的属性只有p_cpu和p_pri，linux0.11里面对应的也只是counter和priority两个属性，至于utime,stime,cutime,cstime,start_time神马都是页面调度用的。在这里把“优先级调度”的这个“调度”说成”cpu调度“或许更准确些。&lt;/p&gt;

&lt;p&gt;需要留意一个历史背景，那就是Unix V6的内核是swap式的内存管理，内存里只有有数的几个进程，换出则换出整个进程。进程切换时候发生swap的概率还是很高的，所以页面调度和进程调度几乎是一体。当然很低效了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;写时复制&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;似乎很容易就可以实现写时复制，不过有点细节需要注意。&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;需要把父进程和子进程的所有页面都标记为只读。
（linux0.11的copy_mem()函数对进程0特殊处理不标记为只读就是因为这个。内核地址空间里的页要都只读了还了得）&lt;/li&gt;
&lt;li&gt;需要为每个物理页留一个引用计数。
（这也就是3BSD没搞写时复制的原因(其设计目标之一即对原系统做最少的修改)，为fork性能做的trade off是一个新的系统调用vfork。顺便一提，3BSD是第一个应用页式内存管理的Unix系统，先前移植到VAX机的Unix/32V还是swap的机制，很蹩脚）。&lt;/li&gt;
&lt;li&gt;出现写保护错误的时候，就复制这个页面。若引用计数为1，就把最后那个页面的写保护去掉。&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;&lt;strong&gt;K.I.S.S&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;有点感觉到前面&lt;a href=&quot;http://www.fleurer-lee.com/2010/10/11/%E5%8E%9F%E5%A7%8Bunix/&quot;&gt;那篇post&lt;/a&gt;的幼稚了。早期Unix的内核确实简洁明快，不过要用它肯定是自虐吧...最近经常在想，问题本身的复杂是一个KISS可以盖的过去么？fork()+exec()接口比CreateProcess()好看一万倍，但Unix引入线程的时候也是阵痛了好一阵。fork()本身并不是适合多线程的设计，这是自己承认的。而引入线程是像《unix编程艺术》里说的那样仅仅是因为愚蠢么？不知道...可是线程确实比进程更轻量，至于怎么同步那就是考量程序员的事了。&lt;/p&gt;

&lt;p&gt;像Unix早期的抽象机制，&quot;一切皆文件&quot;。进程通信依然是文件系统的范畴，管道就是硬盘上一个文件，两个进程可以一个读一个写。后来出现了n多复杂的IPC机制，到BSD时候管道改由socket实现的时候，Unix”一切皆文件“的抽象就已经不是再适用了。大神看囊肿了不爽了，搞Plan9，网络、驱动全部回归成文件，内核的代码量恢复成十来万行，然后...就没有然后了....&lt;/p&gt;

&lt;p&gt;Mach则是一切皆进程。port是第一公民（我怀疑socket在设计时有参考过port，名字都像，但是没找到资料），连系统调用都是port连接起来的进程通信(Message)。设计的动机和Plan9也差不多(这说法不恰当，Mach比plan9早好像)，就是通过合理的抽象减少内核的体积，同时为可移植性再设计。一段时间看来这显然是现代系统高科技的典范，GNU那个杯具系统的内核就是GNU Mach，而HURD仅仅是内核以外的一组核心的进程。然后微内核杯具了，喜欢微内核的老教授Tanebaum还让毛头的Linus带人围观了一通(参考人家的minix碍不着linus骂他)。&lt;/p&gt;

&lt;p&gt;单靠一套抽象机制独当一面就比较容易捉襟见肘吧。水至清则无鱼，逮上某某主义一而贯之，往往不如具体问题具体分析来的实在。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>补遗 - 早期unix同步机制的简单笔记</title>
   <link href="http://fleurer.github.com//2010/12/10/bu-yi-zao-qi-unixtong-bu-ji-zhi-de-jian-dan-bi-ji.html"/>
   <updated>2010-12-10T00:00:00-08:00</updated>
   <id>http://fleurer.github.com//2010/12/10/bu-yi-zao-qi-unixtong-bu-ji-zhi-de-jian-dan-bi-ji</id>
   <content type="html">&lt;p&gt;Unix的历史就像杨振宁的家谱一样，是个图。不管是大公司的程序员、学术界的老教授还是开源hacker，意识形态的不同并没有妨碍他们相互抄袭并为之进一步发展。仅仅把linux的成功归功于开源、仿佛一切统统来自新信仰者发起的一场革命(revolution)的看法自是不恰当的。若忽略无数人在背后所花费的时间，而把功劳统统归功于出台出面的英雄，那历史就不成为历史，我们现在所拥有的一切也皆出于偶然。平铺直叙今天linux中的各种机制策略只能一览其天才精巧，可是若要感受背后无数人们的心血付出或者解惑“为什么这样”，Unix背后几十年的演变（evolution）历程就不能不有所了解。&lt;/p&gt;

&lt;p&gt;上一篇post里说到早期Unix的同步机制，可是漏了些东西，在这里补一下。&lt;/p&gt;

&lt;p&gt;漏掉了早期Unix的同步机制是基于这样的假设，这点很重要：&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;只有一个CPU&lt;/li&gt;
&lt;li&gt;内核是非抢占的&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;于是&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;内核操作某对象时不会被其他进程打断&lt;/li&gt;
&lt;li&gt;但期间可能会发生中断&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;从而允许把同步机制做的很简单。&lt;/p&gt;

&lt;p&gt;还忽略了一个地方，那就是sleep+X_LOCK/X_WANTE机制仅仅是用在“比较长时间”的同步中。比如说一个buffer等待读取一个磁盘块，可能得分两次进入内核才能得到处理。这里需要注意的地方就是sleep/wakeup一定会引起进程切换，很吃性能。在这一点上，大约跟信号量差不多。&lt;/p&gt;

&lt;p&gt;拜前面简单的假设所赐，“短时间”内的同步通过简单开关中断即可形成一个临界区，比如：&lt;/p&gt;

&lt;pre lang=&quot;c&quot;&gt;
bflush(dev)
{
    register struct buf *bp;

loop:
    spl6(); //关中断，相当于x86下的cli()
    for (bp = bfreelist.av_forw; bp != &amp;bfreelist; bp = bp-&gt;av_forw) {
        if (bp-&gt;b_flags&amp;B_DELWRI &amp;&amp; (dev == NODEV||dev==bp-&gt;b_dev)) {
            bp-&gt;b_flags =| B_ASYNC;
            notavail(bp);
            bwrite(bp);
            goto loop;
        }
    }
    spl0(); //开中断，相当于sti()
}
&lt;/pre&gt;


&lt;p&gt;这里开关中断的应用，差不多就相当于自旋锁(spin lock，俗称simple lock)。但是更加简单，也没有纠结死锁的必要。&lt;/p&gt;

&lt;p&gt;== Drawbacks&lt;/p&gt;

&lt;p&gt;机制做的简单，往往正是因为事先对外部情况做了假设。问题当然不少：
 * 扩展性差。有多少种对象就得写出来多少遍类似的同步代码，没有形成某种具体的锁。早期Unix代码之短令人印象深刻，但是不能无视这个事实：后来（仅仅是几年间）Unix代码的爆炸式增长。
 * 伸缩性不好，一放到多核下边就瞬间杯具。&lt;/p&gt;

&lt;p&gt;后来Unix移植到了多核心的机器上，这样的机制即已不再适用，很快就被信号量和自旋锁所取代了。至于信号量和自旋锁带来的问题还不是很明白，有空再写好了。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>早期unix同步机制的简单笔记</title>
   <link href="http://fleurer.github.com//2010/12/01/zao-qi-unixde-tong-bu-ji-zhi-bi-ji.html"/>
   <updated>2010-12-01T00:00:00-08:00</updated>
   <id>http://fleurer.github.com//2010/12/01/zao-qi-unixde-tong-bu-ji-zhi-bi-ji</id>
   <content type="html">&lt;p&gt;在写那篇关于buffer cache的post时还没有意识到，B_BUSY+B_WANTED+sleep/wakeup，即为早期unix中一套通用的同步机制了。只要存在在不同进程间共享的数据，不管并行还是并发，就都免不了得考虑竞态条件和同步问题。在处理某个对象之前都先给它上锁，防止其他进程碰它。比方前面说的B_BUSY，改叫B_LOCK没准更直白。&lt;/p&gt;

&lt;p&gt;早期Unix内核中没有通用的内存分配器，不同类型的对象单独静态分配一个固定长度的数组，inode，super block，file，buf乃至proc等等皆如此。所以有关对象分配释放的代码都是十分相似，而且分配即缓存，统一到一类简单的机制之下。可是反过来看就是把功能相近的代码重复了n遍，也是模块化和重用性不好的体现吧。这也正是现代unix所努力的方向，其结果之一即solaris/linux中的伙伴系统（用于申请内存）和slab（用于对象的缓存）。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;sleep/wakeup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;早期的unix一般都是非抢占的内核，内核态的进程不可以被其它进程打断，不过可以自愿放出控制权（swtch）。一个常见的情景就是，在申请资源的时若该资源的空间已经用尽，就让这个等资源的进程睡眠（sleep）把控制权交给其它进程，等某进程释放了资源则唤醒所有等待该资源的进程（wakeup）。拿偏理论的操作系统书上的说法，sleep/wakeup即早期unix的同步原语了。&lt;/p&gt;

&lt;p&gt;其函数原型如下：
void sleep(uint chan, int pri)；
void wakeup(uint chan)；&lt;/p&gt;

&lt;p&gt;chan是事件通道（event channel）的缩写，而pri用来指定进程在苏醒那一刻的优先级。像刚才说的比如等待一个空闲的inode，就直接sleep(&amp;amp;ino, PRIINO);把inode对象的地址当作chan即可。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;锁&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;有了sleep/wakeup当作同步原语，再给相应的对象加上两个flag就相当于实现了一个简单的锁。上锁即为了防止其它进程碰它，所以获取对象时即上锁，比如iget,namei,getfs,getblk等等。若获取对象时它正在被其它进程使用，就sleep等它释放。这里需要留意一下，就是同一个资源若在释放前在同一进程里重复申请，就会睡眠而永远都不会被唤醒，比如：&lt;/p&gt;

&lt;p&gt;bp = getblk(rootdev, 1);
bp = getblk(rootdev, 1); // hangs forever&lt;/p&gt;

&lt;p&gt;所以就得小心，资源用完一定记得释放或unlock。不像用户态的程序malloc不free、open不close似乎也没什么大碍，在这里忘记释放的下场就是华丽丽一个死锁。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>简介硬盘分区</title>
   <link href="http://fleurer.github.com//2010/10/25/jian-jie-ying-pan-fen-qu.html"/>
   <updated>2010-10-25T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2010/10/25/jian-jie-ying-pan-fen-qu</id>
   <content type="html">&lt;p&gt;作者：Denis A Nicole
翻译：fleurer
原文：http://www.hpcc.ecs.soton.ac.uk/~dan/filesystems/partition.html&lt;/p&gt;

&lt;p&gt;(99年的文章，好像有点老了)&lt;/p&gt;

&lt;!--more--&gt;


&lt;p&gt;== Introduction ==&lt;/p&gt;

&lt;p&gt;本教程无耻的参考了这个文档http://www.diskwarez.com/articles/par.htm，新引入的任何错误由我本人负责（译者注：翻译引入的错误由我负责 =&quot;=）。本文假定你会使用任意一个raw sector editor，比如diskedit.com。&lt;/p&gt;

&lt;p&gt;传统的硬盘依据其物理结构直接按cylinder,head,sector（CHS）进行寻址。到现代的IDE/ATA设备中每个磁道（track）不再是固定数目的扇区(sector)，其寻址机制随之变更：直接按照一个从0开始的扇区号即可，更简单了。为向前兼容，设备增加了一个将CHS转为绝对扇区号的翻译单元，依然允许虚拟CHS方式进行寻址。其翻译过程可以通过HD-MBOOT以及IDEATA等工具进行修改或检查。&lt;/p&gt;

&lt;p&gt;注意，Cylinder，Head，Sector的下标分别以0,0,1为起始。扇区号是个例外。&lt;/p&gt;

&lt;p&gt;过去最后一个物理柱面通常是保留用于IBM的磁盘诊断，不过可能已经过时。&lt;/p&gt;

&lt;p&gt;有个杯具，BIOS接口中Cylinder,Head,Sector的上限分别是1024,256和63，然而IDE/ATA接口的上限是65536,16和256。这一来要不在中间加个转换，就只能出现木桶原理各取最小的一环1024,16,63，每扇区512字节，每张硬盘最大也就504Mb了。为周旋这一限制，现代的BIOS会对CHS进行转换，让设备看起来像是磁头数加倍，且柱面数减半（1024为止）。&lt;/p&gt;

&lt;p&gt;(译者注：上面说的磁头数*2同时柱面数/2的变换机制，即LARGE模式。在BIOS中好像可以设置)&lt;/p&gt;

&lt;p&gt;还有杯具，那就是DOS不能处理超过255的磁头数，对于超过4Gb的设备，BIOS最多只能把磁头数转换到255。这就把磁盘的大小限制为8Gb。&lt;/p&gt;

&lt;p&gt;对于大于8Gb的磁盘，其C,H,S返回值为16383,16,63，表示这个CHS值不正确。&lt;/p&gt;

&lt;p&gt;超过8Gb就只能通过首地址为0的逻辑扇区号（LBA）进行寻址。Windows NT4 SP4和Linux等系统都能直接按这种方式寻址IDE/ATA硬盘，不过Windows 95和Windows 98这样依赖BIOS中Int 13h扩展的系统就不那么靠谱了。&lt;/p&gt;

&lt;p&gt;==主引导记录(MBR)==&lt;/p&gt;

&lt;p&gt;主引导记录即硬盘的第一个扇区（C,H,S=0,0,1）。它为引导代码提供了446kb的空间，后跟4个分区表项（每个16字节），最后以一个0xAA55（小端序）做魔数。这四个表项相对于扇区的偏移分别是0x1be, 0x1ce, 0x1de, 0x1ee。&lt;/p&gt;

&lt;pre&gt;
             +--------------------------------+
             |                                |
             |                                |
            /\/             code             /\/
             |                                |
             |                          +-----|
             |                          |     |
             +--------------------------------+
             |table 1    |    table 2   |     |
             +--------------------------------+
             |table 3    |    table 4   |55|aa|
             +--------------------------------+

                       figure 1 - MBR
&lt;/pre&gt;


&lt;p&gt;每个分区表项包含了分区的起始及结束地址（CHS格式）、开始的扇区号（LBA格式）、扇区数。以及分区类型和一个可引导标志。&lt;/p&gt;

&lt;p&gt;CHS地址是BIOS INT 13h的格式，柱面号在扇区号的那个字节里占着两个高位。其中的bit与int 13h时几个寄存器内的值相同。&lt;/p&gt;

&lt;pre&gt;

  BYTES    0      1 - 3      4      5 - 7     8 - 11     12 - 15
       +----------------------------------------------------------+
       |      |           |      |         |           |          |
       | BOOT | START-CHS | TYPE | END-CHS | ABS-START | NUM-SECS |
       |      |           |      |         |           |          |
       +----------------------------------------------------------+

                       Figure 2 - Partition Table

The CHS format is:
       +-----------------+-----------------+-----------------+
       | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 | 7 6 5 4 3 2 1 0 |
       +-----------------+-----------------+-----------------+
       | H H H H H H H H | C C S S S S S S | C C C C C C C C |
       +-----------------+-----------------+-----------------+
       | DH              | CL              |CH               |
       +-----------------+-----------------+-----------------+

                        Figure 3 - CHS Format
&lt;/pre&gt;


&lt;p&gt;第一行是每字节的Bit位，第二行表示CHS值（C=cylinder,H=head,S=sector），最后一行是BIOS调用相关的几个寄存器。cylinder在CL中占有高二位，可得如下公式：&lt;/p&gt;

&lt;p&gt;C,H,S = ((CL&amp;amp;0xC0)&amp;lt;&amp;lt;2+CH),DH,(CL&amp;amp;0x3F).&lt;/p&gt;

&lt;p&gt;ABS-START和NUM-SECS都是四字节的小端数据。&lt;/p&gt;

&lt;p&gt;不用的分区表项最好全部清零，将TYPE项设为0也可以。&lt;/p&gt;

&lt;p&gt;引导分区的BOOT项为0x80且唯一，其它一律为0。&lt;/p&gt;

&lt;p&gt;起始或结束地址若是超过CHS的限制，就设为1023,255,63表示它是错误的。&lt;/p&gt;

&lt;p&gt;MBR中只允许一个FAT文件系统。若需要更多，抑或需要四个以上的分区，只能通过一个扩展分区（DOS EXTended partition）实现。&lt;/p&gt;

&lt;p&gt;每个分区通常以一个柱面为边界：C,H,S=??,0,1。MBR和扩展分区中的首个分区则通常都是以一个磁头为界：C,H,S=??,1,1。&lt;/p&gt;

&lt;p&gt;扩展分区的首个扇区的布局与MBR类似，只不过只有两个分区表项：第一项即其中的第一个分区，可选的第二项指向第二个分区，构成一个链表。&lt;/p&gt;

&lt;p&gt;(译者注：某种意义上说，扩展分区并不是真正的分区，其存在只能通过逻辑分区体现。逻辑分区的第一个扇区叫做EBR, 即Extended Boot Record。与MBR中固定的四个分区表项不同，EBR中只有两个分区表项，EBR之间构成一个链表，所以理论上可以分无限的逻辑分区。)&lt;/p&gt;

&lt;pre&gt;
Partition Types

   Partition  Fdisk                                          Starting in
   Type       Reports      Size                  FAT Type    version
   ---------------------------------------------------------------------
   01         PRI DOS      0-15 MB               12-Bit      MS-DOS 2.0
   04         PRI DOS      16-32 MB              16-Bit      MS-DOS 3.0
   05         EXT DOS      0-2 GB                n/a         MS-DOS 3.3
   06         PRI DOS      32 MB-2 GB            16-bit      MS-DOS 4.0
   07         ----  Windows NT NTFS [if Boot partition then &lt; 4Gb] ----
   0E         PRI DOS      32 MB-2 GB            16-bit      Windows 95  *
   0F         EXT DOS      0-2 GB                n/a         Windows 95  *
   0B         PRI DOS      512 MB - 2 terabytes  32-bit      OSR2        *
   0C         EXT DOS      512 MB - 2 terabytes  32-bit      OSR2        *
   82         ---------------- Linux Swap ------------------------
   83         ---------------- Linux EXT2 (native) ---------------           
                               [in part from Microsoft KB article Q69912]

        * Types 0C..0F Use the ABS-START and NUM-SECS fields and thus
                  can extend beyond the 8Gb boundary.
&lt;/pre&gt;


&lt;p&gt;==引导扇区==&lt;/p&gt;

&lt;p&gt;引导的代码位于MBR，LILO什么的就是了。微软提供了一个fdisk /mbr，可以将其标准的引导代码插入mbr。它只用了分区表中START-CHS一项，所以引导分区必须位于硬盘的前8Gb。&lt;/p&gt;

&lt;p&gt;同MBR一样，每个活动分区的首个扇区中也有引导代码；在微软的系统中可以使用format命令插入这段代码。引导扇区的末尾同样也是魔数AA55h(小端序，先55h后AAh)。它们都是被装到0000:7c00，不过一般它们都会把自己挪到别处。&lt;/p&gt;
</content>
 </entry>
 
 <entry>
   <title>unix笔记：the Buffer Cache</title>
   <link href="http://fleurer.github.com//2010/10/15/unixbi-ji-%3Athe-buffer-cache.html"/>
   <updated>2010-10-15T00:00:00-07:00</updated>
   <id>http://fleurer.github.com//2010/10/15/unixbi-ji-:the-buffer-cache</id>
   <content type="html">&lt;p&gt;不知这个该怎么译，缓冲缓存？ = =&lt;/p&gt;

&lt;p&gt;Buffer Cache在Unix中不只一套缓存机制，更是系统访问块设备中间不可或缺的一层。在早期的Unix中大约可以扮演三个角色：中断请求队列、访问缓冲、高速缓存。简单起见，这里拿Unix V6讨论。&lt;/p&gt;

&lt;p&gt;buffer相关的各种信息保存在buffer header中，而其中缓存的数据也就是buffer body分布于内存中固定的一块区域（大约是物理内存的10%），保持一对一的关系。每一块buffer body的大小必须是设备中块的整数倍。&lt;/p&gt;

&lt;!--more--&gt;




&lt;h3&gt;buffer pool的结构&lt;/h3&gt;


&lt;p&gt;数据结构绝对比算法重要。&lt;/p&gt;

&lt;pre lang=&quot;c&quot;&gt;4520: struct buf
4521: {
4522:        int     b_flags;                    /* see defines below */
4523:        struct  buf *b_forw;            /* headed by devtab of b_dev */
4524:        struct  buf *b_back;            /*  &quot;  */
4525:        struct  buf *av_forw;           /* position on free list, */
4526:        struct  buf *av_back;           /*     if not BUSY*/
4527:        int     b_dev;                  /* major+minor device name */
4528:        int     b_wcount;               /* transfer count (usu. words) */
4529:        char    *b_addr;                /* low order core address */
4530:        char    *b_xmem;                /* high order core address */
4531:        char    *b_blkno;               /* block # on device */
4532:        char    b_error;                /* returned after I/O */
4533:        char    *b_resid;               /* words not transferred after error */
4534:
4535: } buf[NBUF];

4551: struct devtab
4552: {
4553:        char    d_active;               /* busy flag */
4554:        char    d_errcnt;               /* error count (for recovery) */
4555:        struct  buf *b_forw;            /* first buffer for this dev */
4556:        struct  buf *b_back;            /* last buffer for this dev */
4557:        struct  buf *d_actf;            /* head of I/O queue */
4558:        struct  buf *d_actl;            /* tail of I/O queue */
4559: };

4566: struct  buf bfreelist;&lt;/pre&gt;


&lt;p&gt;先无视上面粘贴的代码...&lt;/p&gt;

&lt;p&gt;主要的数据结构是三个链表。它们都是双向的循环链表，好处就是插入删除起来方便：&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Free List：&lt;/strong&gt;
表示当前所有可以分配的buffer，用于其分配。使用Last Recently  Used算法，若是分配一定是在链表的头部取出，若是释放一般都是放到链表的尾部（若错误发生，仍把buffer放回头部）。链表的头部在 bfreelist(4566)，buffer之间由av_forw和av_back连接。若说“Buffer Pool”，这就是了。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;hash queue：&lt;/strong&gt;
每个设备一个，里面装了与该设备相关的Buffer（已经缓存的(B_DONE)、或等待读取(B_BUSY)的）等等，用于查找缓存。在v6时只是一个简单的链表，查找就是穷举；到System V好像改成了一个哈希表。同一个buffer可以同时存在于free list和hash  queue，但是标有B_BUSY的buffer与是否在Free List是互斥的（notavail,4999）。
（前面说我们这里拿v6做讨论，可是hash queue...不知道这里叫啥好，不过system V里是叫hash queue的，功能貌似一样...先这么叫着吧 TvT）&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;I/O queue：&lt;/strong&gt;
也是每个设备一个，表示该设备的请求队列。链表的头部和尾部在devtab的b_actf和b_actl项中指出。buffer若在IO Queue中就肯定是BUSY的，也就肯定不在Free List中，于是使用freelist用的av_forw和av_back作链表的指针。在I/O  queue中的buffer仍在hash queue中。&lt;/p&gt;

&lt;p&gt;《unix操作系统设计》中的图没有考虑IO queue，大概是因为没有打算讨论设备驱动吧。于是画了一个，不知是否恰当。&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;http://www.fleurer-lee.com/wp-content/uploads/2010/10/thebuffercache-hashqueue.png&quot;&gt;&lt;img title=&quot;thebuffercache-hashqueue&quot; src=&quot;http://www.fleurer-lee.com/wp-content/uploads/2010/10/thebuffercache-hashqueue-300x166.png&quot; alt=&quot;&quot; width=&quot;480&quot; height=&quot;250&quot; /&gt;&lt;/a&gt;&lt;br/&gt;&lt;/p&gt;

&lt;h3&gt;buffer的分配/释放&lt;/h3&gt;


&lt;p&gt;早期Unix很少在内核中使用动态分配的内存，而固定长度的数组＋free list是大受欢迎的解决方案。其实开开心心malloc/free的现代人不也是喜欢内存池么...&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;getblk(dev, blkno)&lt;/strong&gt;
这个名字貌似容易给人一个错误印象，其实getblk函数并不会读取设备的块。它用于分配buffer（或许...叫getbuf多好？）：依据设备号和 blkno查找对应的buffer是否存在于hash queue，存在就直接返回这个buffer；不存在，就从freelist中取出一个新的buffer。&lt;/p&gt;

&lt;p&gt;好吧上面一句是大白话，具体起来有五种情景：&lt;/p&gt;

&lt;ol&gt;
    &lt;li&gt;在hash queue找到了对应的buffer，这个buffer也正好free。&lt;/li&gt;
    &lt;li&gt;在hash queue中没有找到这个buffer，于是到freelist中分配buffer。&lt;/li&gt;
    &lt;li&gt;在hash queue中没有找到这个buffer，而从freelist中得到的buffer被标记为“延迟写入”(B_DELWRI)，得先把这个块写入磁盘，再找其它的buffer。&lt;/li&gt;
    &lt;li&gt;在hash queue里没有找到这个buffer，而freelist也是空的。就把bfreelist标记为B_WANTED，让进程睡眠，等待有新的free buffer。&lt;/li&gt;
    &lt;li&gt;在hash queue中找到了对应的buffer，不过这个buffer正在忙（B_BUSY）。跟情景4一样也是让进程睡眠，把这个buffer标记为B_WANTED，等它被free。&lt;/li&gt;
&lt;/ol&gt;


&lt;p&gt;&lt;strong&gt;brelse(buf)&lt;/strong&gt;
buffer release。释放一个buffer，即将其放回freelist。上面getblk的情景4和情景5都是在资源短缺的情况下让进程睡眠等待新资源的释放，而brelse正是唤醒那些睡眠进程的负责人。&lt;/p&gt;

&lt;p&gt;注意，使用brelse释放buffer前必先将其上锁（B_LOCK）。&lt;/p&gt;

&lt;h3&gt;从设备中读/写块&lt;/h3&gt;


&lt;p&gt;bread(dev, blkno)
不知道这是block read还是buffer read的缩写，不过这才是真正用来读取块的函数。先用getblk判断是否缓存，有就返回getblk的结果。没有就发起一个IO请求并让进程睡眠，到IO完成时被唤醒。睡眠、唤醒，这就是阻塞式IO中“阻塞”的由来。&lt;/p&gt;

&lt;p&gt;这个的代码比较短，贴过来。&lt;/p&gt;

&lt;pre lang=&quot;c&quot;&gt;4754: bread(dev, blkno)
4755: {
4756:        register struct buf *rbp;
4757:
4758:        rbp = getblk(dev, blkno);
4759:        if (rbp-&gt;b_flags&amp;B_DONE)  //判断这个buffer是否可用
4760:                return(rbp);
4761:        rbp-&gt;b_flags =| B_READ;    //若不可用，就准备一个IO请求
4762:        rbp-&gt;b_wcount = -256;
4763:        (*bdevsw[dev.d_major].d_strategy)(rbp);  //发起一个IO请求
4764:        iowait(rbp);   //等待IO完毕
4765:        return(rbp);
4766: }&lt;/pre&gt;


&lt;p&gt;写块(bwrite)与读块差不多，略过吧。&lt;/p&gt;

&lt;h3&gt;IO请求队列&lt;/h3&gt;


&lt;p&gt;上面bread的代码中，(*bdevsw[dev.d_major].d_strategy)(rbp)这句就 是添加IO请求的操作。这与调用每个设备的驱动相关，不过都会插入到设备的IO队列。若请求队列为空，就立即把请求交给硬件，然后进入睡眠等待硬件中断的发生。一旦发生中断，中断处理程序就负责把数据读入请求队列头部的buffer，将其移出队列并标记为B_IODONE，唤醒等待这个buffer的进程。若队列中有后继，就继续给硬件交请求。&lt;/p&gt;

&lt;h3&gt;Buffer Cache机制的优点&lt;/h3&gt;


&lt;ul&gt;
    &lt;li&gt;减少了读取磁盘的频率，自不必多说。&lt;/li&gt;
    &lt;li&gt;统一了访问块设备的接口，使得文件系统可以独立于设备。&lt;/li&gt;
&lt;/ul&gt;


&lt;h3&gt;缺点&lt;/h3&gt;


&lt;ul&gt;
    &lt;li&gt;磁盘的内容和内存中的内容不一定同步，也就是我们强制关机导致文件系统杯具的原因。&lt;/li&gt;
    &lt;li&gt;若是连续写入一个大文件，而这个文件可能再也不会访问，却仍占据了大量的缓存。&lt;/li&gt;
    &lt;li&gt;需要复制两次数据：一次是从设备到内核，第二次是从内核到用户空间。诚然第二次复制要比第一次复制快的多，不过文件若比较大，仍会是比较大的开销。&lt;/li&gt;
&lt;/ul&gt;


&lt;p&gt;Note：现代的Unix系统中，缓存的管理好像融入到了Paging子系统中。Buffer Cache仍然保留，不过仅用来缓存superblock, inode之类常用的对象。&lt;/p&gt;
</content>
 </entry>
 
 
</feed>

