<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>notes on liang2kl's Blog</title><link>https://yeshe.ng/blog/tags/notes/</link><description>Recent content in notes on liang2kl's Blog</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><managingEditor>liang2kl@outlook.com (Liang Yesheng)</managingEditor><webMaster>liang2kl@outlook.com (Liang Yesheng)</webMaster><copyright>from 2021, to infinity and beyond...</copyright><lastBuildDate>Wed, 25 May 2022 11:44:11 +0800</lastBuildDate><atom:link href="https://yeshe.ng/blog/tags/notes/index.xml" rel="self" type="application/rss+xml"/><item><title>Operating Systems: Three Easy Pieces 学习笔记（五）</title><link>https://yeshe.ng/blog/posts/2022/05/ostep-concurrency/</link><pubDate>Wed, 25 May 2022 11:44:11 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/05/ostep-concurrency/</guid><description>Concurrency</description><content>&lt;h2 id="26-concurrency-an-introduction">26 Concurrency: An Introduction&lt;/h2>
&lt;p>Each thread is very much like a separate process, except for one difference: they share the same address space and thus can access the same data.&lt;/p>
&lt;p>State of a single thread:&lt;/p>
&lt;ul>
&lt;li>program counter&lt;/li>
&lt;li>private set of registers (context switch is needed)&lt;/li>
&lt;/ul>
&lt;p>which are stored in &lt;strong>process control block&lt;/strong>.&lt;/p>
&lt;p>Any stack-allocated variables, parameters, return values, and other things that we put on the stack will be placed in what is sometimes called &lt;strong>thread-local&lt;/strong> storage.&lt;/p>
&lt;h3 id="261-why-use-threads">26.1 Why Use Threads?&lt;/h3>
&lt;ul>
&lt;li>parallelism: speed up using multi-core processors&lt;/li>
&lt;li>avoid blocking during I/O&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Computers are hard enough to understand without concurrency. Unfortunately, with concurrency, it simply gets worse. Much worse.&lt;/p>
&lt;/blockquote>
&lt;h3 id="264-the-heart-of-the-problem-uncontrolled-scheduling">26.4 The Heart Of The Problem: Uncontrolled Scheduling&lt;/h3>
&lt;p>&lt;strong>Race condition&lt;/strong>: the results depend on the timing execution of the code.&lt;/p>
&lt;p>&lt;strong>Critical section&lt;/strong>: a piece of code that accesses a shared variable (or more generally, a shared resource) and must not be concurrently executed by more than one thread.&lt;/p>
&lt;p>&lt;strong>Mutal exclusion&lt;/strong>: if one thread is executing within the critical section, the others will be prevented from doing so.&lt;/p>
&lt;h2 id="27-interlude-thread-api">27 Interlude: Thread API&lt;/h2>
&lt;h3 id="274-condition-variables">27.4 Condition Variables&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">pthread_cond_wait&lt;/span>(pthread_cond_t &lt;span style="color:#f92672">*&lt;/span>cond, pthread_mutex_t &lt;span style="color:#f92672">*&lt;/span>mutex);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">pthread_cond_signal&lt;/span>(pthread_cond_t &lt;span style="color:#f92672">*&lt;/span>cond);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To wait for a signal:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>pthread_mutex_lock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">while&lt;/span> (ready &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pthread_cond_wait(&lt;span style="color:#f92672">&amp;amp;&lt;/span>cond, &lt;span style="color:#f92672">&amp;amp;&lt;/span>lock); &lt;span style="color:#75715e">// will release the lock when sleep
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">// and re-acquire before wakeup
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>pthread_mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>To send a signal:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>pthread_mutex_lock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>ready &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pthread_cond_signal(&lt;span style="color:#f92672">&amp;amp;&lt;/span>cond);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pthread_mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="28-locks">28 Locks&lt;/h2>
&lt;h3 id="284-evaluating-locks">28.4 Evaluating Locks&lt;/h3>
&lt;p>Criteria:&lt;/p>
&lt;ul>
&lt;li>mutal exclusion&lt;/li>
&lt;li>fairness&lt;/li>
&lt;li>performance&lt;/li>
&lt;/ul>
&lt;h3 id="286-a-failed-attempt-just-using-loadsstores">28.6 A Failed Attempt: Just Using Loads/Stores&lt;/h3>
&lt;p>Problems:&lt;/p>
&lt;ul>
&lt;li>No mutal exclusion&lt;/li>
&lt;li>Poor performance: spin-waiting&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/lock-flag.webp" alt="">&lt;/p>
&lt;p>The problem: set after test (maybe after an interrupt).&lt;/p>
&lt;h3 id="287-building-working-spin-locks-with-test-and-set">28.7 Building Working Spin Locks with Test-And-Set&lt;/h3>
&lt;p>Test-and-set instruction (&lt;code>xchg&lt;/code> on x86) is something like:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">testAndSet&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">*&lt;/span>old_ptr, &lt;span style="color:#66d9ef">int&lt;/span> new) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> old &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">*&lt;/span>old_ptr; &lt;span style="color:#75715e">// fetch old value at old_ptr
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#f92672">*&lt;/span>old_ptr &lt;span style="color:#f92672">=&lt;/span> new; &lt;span style="color:#75715e">// store ’new’ into old_ptr
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">return&lt;/span> old; &lt;span style="color:#75715e">// return the old value
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lock and unlock:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">lock&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>lock) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (testAndSet(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ; &lt;span style="color:#75715e">// spin-wait (do nothing)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">unlock&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>lock) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="288-evaluating-spin-locks">28.8 Evaluating Spin Locks&lt;/h3>
&lt;ul>
&lt;li>correctness: yes&lt;/li>
&lt;li>fairness: no, maybe spinning forever (starvation)&lt;/li>
&lt;li>performance: bad in single CPU case&lt;/li>
&lt;/ul>
&lt;h3 id="289-compare-and-swap">28.9 Compare-And-Swap&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">compareAndSwap&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">*&lt;/span>ptr, &lt;span style="color:#66d9ef">int&lt;/span> expected, &lt;span style="color:#66d9ef">int&lt;/span> new) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> original &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">*&lt;/span>ptr;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">if&lt;/span> (original &lt;span style="color:#f92672">==&lt;/span> expected)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>ptr &lt;span style="color:#f92672">=&lt;/span> new;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> original;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2810-load-linked-and-store-conditional">28.10 Load-Linked and Store-Conditional&lt;/h3>
&lt;p>&lt;strong>Store-conditional&lt;/strong>: only succeeds (and updates the value stored at the address just load-linked from) if no intervening store to the address has taken place.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">lock&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>lock) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (loadLinked(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span> &lt;span style="color:#f92672">||&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> storeConditional(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="2811-fetch-and-add">28.11 Fetch-And-Add&lt;/h3>
&lt;p>The pseudo C code:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">fetchAndAdd&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#f92672">*&lt;/span>ptr) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> old &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#f92672">*&lt;/span>ptr;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">*&lt;/span>ptr &lt;span style="color:#f92672">=&lt;/span> old &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">return&lt;/span> old;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Lock and unlock:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">lock&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>lock) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> myTurn &lt;span style="color:#f92672">=&lt;/span> fetchAndAdd(&lt;span style="color:#f92672">&amp;amp;&lt;/span>lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>ticket);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>turn &lt;span style="color:#f92672">!=&lt;/span> myTurn)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">unlock&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>lock) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>turn &lt;span style="color:#f92672">=&lt;/span> lock&lt;span style="color:#f92672">-&amp;gt;&lt;/span>turn &lt;span style="color:#f92672">+&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>This ensures progress for all threads.&lt;/p>
&lt;h3 id="2813-a-simple-approach-just-yield-baby">28.13 A Simple Approach: Just Yield, Baby&lt;/h3>
&lt;p>&lt;strong>Yield&lt;/strong> is simply a system call that moves the caller from the &lt;strong>running&lt;/strong> state to the &lt;strong>ready&lt;/strong> state, and thus promotes another thread to running. Thus, the yielding thread essentially &lt;strong>deschedules&lt;/strong> itself.&lt;/p>
&lt;ul>
&lt;li>Still costly: context switches.&lt;/li>
&lt;li>Still unfair: endless yield loop.&lt;/li>
&lt;/ul>
&lt;h3 id="2814-using-queues-sleeping-instead-of-spinning">28.14 Using Queues: Sleeping Instead Of Spinning&lt;/h3>
&lt;blockquote>
&lt;p>The real problem with our previous approaches is that they leave too much to chance.&lt;/p>
&lt;/blockquote>
&lt;p>Solaris provides two calls: &lt;code>park()&lt;/code> to put a calling thread to sleep, and &lt;code>unpark(threadID)&lt;/code> to wake a particular thread as designated by &lt;code>threadID&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">struct&lt;/span> __lock_t {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> flag;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> guard;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> queue_t &lt;span style="color:#f92672">*&lt;/span>q;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} lock_t;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">lock_init&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>m) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>guard &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> queue_init(m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>q);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">lock&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>m) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (testAndSet(&lt;span style="color:#f92672">&amp;amp;&lt;/span>m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>guard, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ; &lt;span style="color:#75715e">// acquire guard lock by spinning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>; &lt;span style="color:#75715e">// lock is acquired
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>guard &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> } &lt;span style="color:#66d9ef">else&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> queue_add(m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>q, gettid());
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> setpark();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>guard &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> park();
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> }
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">unlock&lt;/span>(lock_t &lt;span style="color:#f92672">*&lt;/span>m) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (testAndSet(&lt;span style="color:#f92672">&amp;amp;&lt;/span>m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>guard, &lt;span style="color:#ae81ff">1&lt;/span>) &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> ; &lt;span style="color:#75715e">// acquire guard lock by spinning
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">if&lt;/span> (queue_empty(m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>q))
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>flag &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>; &lt;span style="color:#75715e">// let go of lock; no one wants it
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#66d9ef">else&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> unpark(queue_remove(m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>q)); &lt;span style="color:#75715e">// hold lock
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> &lt;span style="color:#75715e">// (for next thread!)
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span> m&lt;span style="color:#f92672">-&amp;gt;&lt;/span>guard &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="29-lock-based-concurrent-data-structures">29 Lock-based Concurrent Data Structures&lt;/h2>
&lt;h3 id="291-concurrent-counters">29.1 Concurrent Counters&lt;/h3>
&lt;p>Approximate counting: record counter values locally, and periodically transfer them to the global counter.&lt;/p>
&lt;h3 id="292-concurrent-linked-lists">29.2 Concurrent Linked Lists&lt;/h3>
&lt;p>A hand-over-hand linked list may not be faster than the simple single lock approach, as the overhead of acquiring and releasing every node along the traversal path is high.&lt;/p>
&lt;h3 id="293-concurrent-queues">29.3 Concurrent Queues&lt;/h3>
&lt;h3 id="294-concurrent-hash-table">29.4 Concurrent Hash Table&lt;/h3>
&lt;h2 id="30-condition-variables">30 Condition Variables&lt;/h2>
&lt;h3 id="301-definition-and-routines">30.1 Definition and Routines&lt;/h3>
&lt;p>A &lt;strong>condition variable&lt;/strong> is an explicit queue that threads can put themselves on when some state of execution (i.e., some &lt;strong>condition&lt;/strong>) is not as desired (by &lt;strong>waiting&lt;/strong> on the condition); some other thread, when it changes said state, can then wake one (or more) of those waiting threads and thus allow them to continue (by &lt;strong>signaling&lt;/strong> on the condition).&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">pthread_cond_wait&lt;/span>(pthread_cond_t &lt;span style="color:#f92672">*&lt;/span>cond, pthread_mutex_t &lt;span style="color:#f92672">*&lt;/span>mutex);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> &lt;span style="color:#a6e22e">pthread_cond_signal&lt;/span>(pthread_cond_t &lt;span style="color:#f92672">*&lt;/span>cond);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>wait()&lt;/code> unlocks a mutex (assumed to be locked) and put the calling thread to sleep;
it re-acquires the lock before returning.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> done &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pthread_mutex_t m &lt;span style="color:#f92672">=&lt;/span> PTHREAD_MUTEX_INITIALIZER;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>pthread_cond_t c &lt;span style="color:#f92672">=&lt;/span> PTHREAD_COND_INITIALIZER;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">thr_exit&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pthread_mutex_lock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>m);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> done &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#ae81ff">1&lt;/span>;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pthread_cond_signal(&lt;span style="color:#f92672">&amp;amp;&lt;/span>c);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pthread_mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>m);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">void&lt;/span> &lt;span style="color:#a6e22e">thr_join&lt;/span>() {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pthread_mutex_lock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>m);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">while&lt;/span> (done &lt;span style="color:#f92672">==&lt;/span> &lt;span style="color:#ae81ff">0&lt;/span>)
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pthread_cond_wait(&lt;span style="color:#f92672">&amp;amp;&lt;/span>c, &lt;span style="color:#f92672">&amp;amp;&lt;/span>m);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> pthread_mutex_unlock(&lt;span style="color:#f92672">&amp;amp;&lt;/span>m);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Some questions:&lt;/p>
&lt;p>&lt;strong>Why is the &lt;code>done&lt;/code> state needed?&lt;/strong>&lt;/p>
&lt;p>If the child runs immediately and calls &lt;code>thr_exit()&lt;/code> immediately, then although the
child will signal, there is no thread asleep. When the parent calls &lt;code>wait()&lt;/code>, it will
be stuck.&lt;/p>
&lt;p>&lt;strong>Why is the mutex needed?&lt;/strong>&lt;/p>
&lt;p>The child might change the state &lt;em>before&lt;/em> the parent goes to sleep, and the parent will
sleep forever. If there is a mutex, the child can only alter the state after the parent goes
to sleep (and thus releasing the mutex).&lt;/p>
&lt;blockquote>
&lt;p>Always hold the lock while signaling!&lt;/p>
&lt;/blockquote>
&lt;h3 id="302-the-producerconsumer-bounded-buffer-problem">30.2 The Producer/Consumer (Bounded Buffer) Problem&lt;/h3>
&lt;blockquote>
&lt;p>Thanks to Mesa semantics, a simple rule to remember with condition variables is to &lt;strong>always use while loops&lt;/strong>.&lt;/p>
&lt;/blockquote>
&lt;h3 id="303-covering-conditions">30.3 Covering Conditions&lt;/h3>
&lt;p>&lt;code>pthread_cond_broadcast()&lt;/code>: waking up all waiting threads; downside: negative performace impact.&lt;/p></content></item><item><title>Read Phenomena and Isolation Levels</title><link>https://yeshe.ng/blog/posts/2022/05/isolation/</link><pubDate>Thu, 05 May 2022 23:11:19 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/05/isolation/</guid><description>In database systems, isolation determines how transaction integrity is visible to other users and systems.</description><content>&lt;p>Reference: &lt;a href="https://en.wikipedia.org/wiki/Isolation_(database_systems)">https://en.wikipedia.org/wiki/Isolation_(database_systems)&lt;/a>&lt;/p>
&lt;p>Isolation is typically defined at database level as a property that defines how or when the changes made by one operation become visible to others.&lt;/p>
&lt;h2 id="read-phenomena">Read phenomena&lt;/h2>
&lt;h3 id="dirty-reads">Dirty reads&lt;/h3>
&lt;p>A dirty read (aka uncommitted dependency) occurs when a transaction is allowed to read data from a row that has been modified by another running transaction and &lt;em>not yet committed&lt;/em>.&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/dirty-read.webp" alt="">&lt;/p>
&lt;h3 id="non-repetable-reads">Non-repetable reads&lt;/h3>
&lt;p>A non-repeatable read occurs when, during the course of a transaction, a row is &lt;em>retrieved twice&lt;/em> and the values within the row &lt;em>differ&lt;/em> between reads.&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/non-repeatable-read.webp" alt="">&lt;/p>
&lt;h3 id="phantom-reads">Phantom reads&lt;/h3>
&lt;p>A phantom read occurs when, in the course of a transaction, &lt;em>new rows are added or removed&lt;/em> by another transaction to the records being read.&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/phantom-read.webp" alt="">&lt;/p>
&lt;h2 id="isolation-levels">Isolation levels&lt;/h2>
&lt;blockquote>
&lt;p>Of the four ACID properties in a DBMS (Database Management System), the isolation property is the one most often relaxed.&lt;/p>
&lt;p>For many database applications, the majority of database transactions can be constructed to avoid requiring high isolation levels (e.g. SERIALIZABLE level), thus reducing the locking overhead for the system.&lt;/p>
&lt;/blockquote>
&lt;h3 id="serializable">Serializable&lt;/h3>
&lt;p>The execution of concurrent SQL-transactions at isolation level SERIALIZABLE is guaranteed to be serializable.&lt;/p>
&lt;p>When using non-lock based concurrency control&amp;hellip; And also range-locks must be acquired when a SELECT query uses a ranged WHERE clause, especially to avoid the phantom reads phenomenon.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Snapshot isolation&lt;/strong>&lt;/p>
&lt;p>snapshot isolation is a guarantee that all reads made in a transaction will see a consistent snapshot of the database (in practice it reads the last committed values that existed at the time it started), and the transaction itself will successfully commit only if no updates it has made conflict with any concurrent updates made since that snapshot.&lt;/p>
&lt;/blockquote>
&lt;h3 id="repeatable-reads">Repeatable reads&lt;/h3>
&lt;p>Range-locks are not managed, so &lt;em>phantom reads&lt;/em> can occur.&lt;/p>
&lt;h3 id="read-committed">Read committed&lt;/h3>
&lt;p>Read locks are released as soon as the SELECT operation is performed, so the &lt;em>non-repeatable reads&lt;/em> phenomenon can occur in this isolation level.&lt;/p>
&lt;h3 id="read-uncommitted">Read uncommitted&lt;/h3>
&lt;p>No read locks. Dirty reads are allowed, so one transaction may see not-yet-committed changes made by other transactions.&lt;/p></content></item><item><title>Concurrency Control for Database Transactions</title><link>https://yeshe.ng/blog/posts/2022/04/database/</link><pubDate>Mon, 11 Apr 2022 23:27:56 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/04/database/</guid><description>DATABASE SYSTEMS: The Complete Book</description><content>&lt;p>来自 &lt;em>DATABASE SYSTEMS: The Complete Book&lt;/em>&lt;/p>
&lt;h2 id="serial-and-serializable-schedules">Serial and Serializable Schedules&lt;/h2>
&lt;p>&lt;strong>Serial schedule&lt;/strong>: no mixing of actions from different transactions.&lt;/p>
&lt;p>&lt;strong>Serializable schedule&lt;/strong>: if there is a &lt;em>serial&lt;/em> schedule S&amp;rsquo; such that for
every initial database state, the &lt;em>effects&lt;/em> of S and S&amp;rsquo; are the same.&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/serializable.webp" alt="">&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/nonserializable.webp" alt="">&lt;/p>
&lt;p>The details of the transactions do matter, but it is not realistic for the
scheduler to concern itself with the details of computation undertaken by
transactions.&lt;/p>
&lt;p>We only care about the reads $r_T(X)$ and writes $w_T(X)$ performed by the transaction $T$.&lt;/p>
&lt;h2 id="conflict-serializability">Conflict-Serializability&lt;/h2>
&lt;p>Actions that conflict (i.e. their order may not be swapped):&lt;/p>
&lt;ol>
&lt;li>Two actions of the same transaction.&lt;/li>
&lt;li>Two writes of the same database element by different transactions.&lt;/li>
&lt;li>A read and a write of the same database element by different transactions.&lt;/li>
&lt;/ol>
&lt;p>In other words, for actions from &lt;em>different&lt;/em> transactions, they conflict when:&lt;/p>
&lt;ol>
&lt;li>They involve the same database element, and&lt;/li>
&lt;li>At least one is a write.&lt;/li>
&lt;/ol>
&lt;p>&lt;strong>Conflict-equivalent&lt;/strong>: two schedules can be turned one into the other by
a sequence of nonconflicting swaps of adjacent actions.&lt;/p>
&lt;p>&lt;strong>Conflict-serializable&lt;/strong>: the schedule is conflict-equivalent to a &lt;em>serial&lt;/em> schedule&lt;/p>
&lt;p>conflict-serializability $\Rightarrow$ serializability&lt;/p>
&lt;p>&lt;strong>Precedence&lt;/strong>: if there are actions $A_1$ of $T_1$ and $A_2$ of $T_2$, such that:&lt;/p>
&lt;ul>
&lt;li>$A_1$ is ahead of $A_2$ in the schedule $S$,&lt;/li>
&lt;li>Both $A_1$ and $A_2$ involve the same database element, and&lt;/li>
&lt;li>At least one of $A_1$ and $A_2$ is a write action.&lt;/li>
&lt;/ul>
&lt;p>then we say that $T_1$ takes precedence over $T_2$ in $S$ ($T_1&amp;lt;_ST_2$).&lt;/p>
&lt;p>Any conflict-equivalent serial schedule must have $T_1$ before $T_2$.&lt;/p>
&lt;p>If the precedence graph is acyclic, then the schedule is conflict-equivalent.&lt;/p>
&lt;p>Proof: for $n$, arrange the schedule by (Actions of $T_i$)(Actions of the other $n-1$ transactions) where $T_i$ is one node without an in arc.&lt;/p>
&lt;h2 id="enforcing-serializability-by-locks">Enforcing Serializability by Locks&lt;/h2>
&lt;p>The use of lock:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Consistency of Transactions&lt;/strong>: Actions and locks must relate in the expected ways:
&lt;ol>
&lt;li>A transaction can only read or write an element if it previously was granted a lock on that element and hasn’t yet released the lock.&lt;/li>
&lt;li>If a transaction locks an element, it must later unlock that element.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>&lt;strong>Legality of Schedules&lt;/strong>: Locks must have their intended meaning: no two transactions may have locked the same element without one having first released the lock.&lt;/li>
&lt;/ul>
&lt;p>The &lt;strong>two-phase locking&lt;/strong> condition than can ensure conflict-serializability:&lt;/p>
&lt;ul>
&lt;li>In every transaction, all lock actions precede all unlock actions.&lt;/li>
&lt;/ul>
&lt;p>A transaction that obeys the 2PL condition is said to be a &lt;strong>two-phase-locked transaction&lt;/strong>, or &lt;strong>2PL transaction&lt;/strong>.&lt;/p>
&lt;p>Proof: for $n$, arrange the schedule by (Actions of $T_i$)(Actions of the other $n-1$ transactions) where $T_i$ is the transaction with the first &lt;em>unlock&lt;/em> action $u_iX$.&lt;/p>
&lt;p>The risk of deadlock:&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/deadlock.webp" alt="">&lt;/p>
&lt;h2 id="locking-systems-with-several-lock-modes">Locking Systems With Several Lock Modes&lt;/h2>
&lt;p>Lock modes:&lt;/p>
&lt;ul>
&lt;li>shared&lt;/li>
&lt;li>exclusive&lt;/li>
&lt;/ul>
&lt;p>The compatibility matrix:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">&lt;/th>
&lt;th style="text-align:center">Request S&lt;/th>
&lt;th style="text-align:center">Request X&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">Currently S&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">Currently X&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The deadlock problem caused by upgrading locks:&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/upgrading-deadlock.webp" alt="">&lt;/p>
&lt;p>Avoid the problem with a third lock mode: &lt;strong>update locks&lt;/strong>.&lt;/p>
&lt;ul>
&lt;li>An update lock $ul_i(X)$ gives transaction $T_j$ only the privilege to read X, not to write X;&lt;/li>
&lt;li>Only the update lock can be upgraded to a write lock later; a read lock cannot be upgraded.&lt;/li>
&lt;/ul>
&lt;p>The compatibility matrix:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">&lt;/th>
&lt;th style="text-align:center">Request S&lt;/th>
&lt;th style="text-align:center">Request X&lt;/th>
&lt;th style="text-align:center">Request U&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">Currently S&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">Currently X&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">Currently U&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The columns for U and S locks are the same, and the rows for U and X locks are the same.&lt;/p>
&lt;p>And now:&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/upgrading-updatelock.webp" alt="">&lt;/p>
&lt;h2 id="an-architecture-for-a-locking-scheduler">An Architecture for a Locking Scheduler&lt;/h2>
&lt;p>The architecture discussed here: the scheduler insert and release locks.&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/scheduler-insert-lock.webp" alt="">&lt;/p>
&lt;ol>
&lt;li>Part 1: inserts appropriate lock actions ahead of all database-access operations&lt;/li>
&lt;li>Part 2: for received action,
&lt;ol>
&lt;li>if the issuing transcation is already delayed, then delay this action&lt;/li>
&lt;li>otherwise,
&lt;ol>
&lt;li>if the action is db access (&lt;em>the required locks are already been granted&lt;/em>), execute it&lt;/li>
&lt;li>if is lock action
&lt;ol>
&lt;li>if the lock can be granted: modify the lock table&lt;/li>
&lt;li>otherwise, request the lock and delay&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>When a transcation commits or aborts, Part 1 releases all locks held by the transaction; if any transactions are waiting for any of these locks, notify Part 2&lt;/li>
&lt;li>Part 2 checks which actions can be executed now&lt;/li>
&lt;/ol>
&lt;p>The lock table entry:&lt;/p>
&lt;ol>
&lt;li>The &lt;em>group mode&lt;/em>: the most stringent conditions&lt;/li>
&lt;li>The &lt;em>waiting bit&lt;/em>: at least one transaction is waiting&lt;/li>
&lt;li>A list of transactions that hold or waiting for the lock&lt;/li>
&lt;/ol>
&lt;h2 id="hierarchies-of-database-elements">Hierarchies of Database Elements&lt;/h2>
&lt;p>&lt;strong>Warning locks&lt;/strong>: useful for hierarchical structure.&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">&lt;/th>
&lt;th style="text-align:center">IS&lt;/th>
&lt;th style="text-align:center">IX&lt;/th>
&lt;th style="text-align:center">S&lt;/th>
&lt;th style="text-align:center">X&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">IS&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">IX&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">S&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">Yes&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">X&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;td style="text-align:center">No&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>The &lt;strong>phantom tuple&lt;/strong>: one that should have been locked but wasn’t, because it didn’t exist at the time the locks were taken.&lt;/p>
&lt;p>Solution: acquire an X lock on the entire relation.&lt;/p>
&lt;h2 id="the-tree-protocol">The Tree Protocol&lt;/h2>
&lt;p>The problem: only one transaction that is not read-only can access the B-tree at any time, because the root node must be locked (at least with an update lock).&lt;/p>
&lt;p>Solution: as soon as a transaction moves to a child of the root and observes the (quite usual) situation that rules out a rewrite of the root, we would like to release the lock on the root; but it violates 2PL, so a new protocol is needed.&lt;/p>
&lt;p>&lt;strong>The tree protocol&lt;/strong>:&lt;/p>
&lt;ol>
&lt;li>The schedule is legal (i.e. does not violate lock schemes).&lt;/li>
&lt;li>A transaction’s first lock may be at any node of the tree.&lt;/li>
&lt;li>Subsequent locks may only be acquired if the transaction currently has a lock on the parent node.&lt;/li>
&lt;li>Nodes may be unlocked at any time.&lt;/li>
&lt;li>A transaction may not relock a node on which it has released a lock, even if it still holds a lock on the node’s parent.&lt;/li>
&lt;/ol>
&lt;p>Conclusion: the precedence graph must always be acyclic if the tree protocol is obeyed. Proof:&lt;/p>
&lt;ul>
&lt;li>If two transactions lock several elements in common, then they are all locked in the same order.&lt;/li>
&lt;li>If $T_i$ locks the root before $T_j$, then $T_i$ locks every node in common with $T_j$ before $T_j$ does.&lt;/li>
&lt;/ul>
&lt;h2 id="concurrency-control-by-timestamps">Concurrency Control by Timestamps&lt;/h2>
&lt;p>Two approaches to generate timestamp:&lt;/p>
&lt;ol>
&lt;li>use the system clock&lt;/li>
&lt;li>the scheduler maintaining a counter&lt;/li>
&lt;/ol>
&lt;p>For the database element $X$:&lt;/p>
&lt;ol>
&lt;li>$RT(X)$: the read time of $X$&lt;/li>
&lt;li>$WT(X)$: the write time of $X$&lt;/li>
&lt;li>$C(X)$: the commit bit fot $X$, true when the most recent transaction to write $X$ has already commited.&lt;/li>
&lt;/ol>
&lt;p>Two kinds of unrealizable behaviours:&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/read-too-late.webp" alt="">&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/write-too-late.webp" alt="">&lt;/p>
&lt;p>The problem with dirty data:&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/dirty-read.webp" alt="">&lt;/p>
&lt;p>$T$ better delays until $U$ commits.&lt;/p>
&lt;p>The commit bit will be false.&lt;/p>
&lt;p>The &lt;strong>Thomas write rule&lt;/strong>: the writes can be skipped when a write with a later write-time is already in place (read intention for value written by T is too-late, as the value is written by a logically latter transaction).&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/thomas-write-rule.webp" alt="">&lt;/p>
&lt;p>The scheduler, in response to a read or write request from a transaction T has the choice of:&lt;/p>
&lt;ul>
&lt;li>Granting the request,&lt;/li>
&lt;li>Aborting T and restarting T with a new timestamp&lt;/li>
&lt;li>Delaying T and later deciding whether to abort or grant the request.&lt;/li>
&lt;/ul>
&lt;p>The rules for timestamp-based scheduling:&lt;/p>
&lt;ol>
&lt;li>The scheduler receives a request $r_T(X)$.
&lt;ol>
&lt;li>$TS(T)\ge WT(X)$: physically realizable.
&lt;ol>
&lt;li>$C(X)$ is true: grant the request; set $RT(X)$ if $TS(T)&amp;gt;RT(X)$.&lt;/li>
&lt;li>$C(X)$ is false: delay until becomes true, or that transaction aborts (to avoid dirty reads).&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>$TS(T)&amp;lt;WT(X)$: physically unrealizable, rollback $T$.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>The scheduler receives a request $w_T(X)$.
&lt;ol>
&lt;li>$TS(T) \ge RT(X)$ and $TS(T) &amp;gt; WT(X)$: physically realizable and must be performed (?).
&lt;ol>
&lt;li>Write $X$,&lt;/li>
&lt;li>Set $WT(X) := TS(T)$, and&lt;/li>
&lt;li>Set $C(X) := \texttt{false}$.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>$TS(T) \ge RT(X)$ but $TS(T) &amp;lt; WT(X)$: physically realizable, and
&lt;ol>
&lt;li>If $C(X)$ is true, ignore the write by T (the thomas write rule);&lt;/li>
&lt;li>otherwise, delay T until the previous write operation commits or aborts.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>$TS(T) &amp;lt; RT(X)$: physically unrealizable, roll back T.&lt;/li>
&lt;/ol>
&lt;/li>
&lt;li>The scheduler receives a request to &lt;em>commit T&lt;/em>: find all elements written by T, and set $C(X) := \texttt{true}$; the delayed transactions are allowed to proceed.&lt;/li>
&lt;li>The scheduler receives a request to &lt;em>abort T&lt;/em>, or to rollback: check all delaying transcation waiting for any element that T wrote to see if they can proceed.&lt;/li>
&lt;/ol>
&lt;p>The use of &lt;strong>multiversion timestamps&lt;/strong>: to provide an appropriate version for a read action (the version of X written immediately before T theoretically executed), even if the latest version is written after the transcation.&lt;/p>
&lt;p>Comparision of locking and timestamp schemes:&lt;/p>
&lt;p>Generally, timestamping is superior in situations where either most transactions are read-only, or it is rare that concurrent transactions will try to read and write the same element. In high-conflict situations, locking performs better.&lt;/p>
&lt;ul>
&lt;li>Locking will frequently delay transactions as they wait for locks.&lt;/li>
&lt;li>If concurrent transactions frequently read and write elements in common, then &lt;em>rollbacks&lt;/em> will be frequent in a timestamp scheduler.&lt;/li>
&lt;/ul>
&lt;h2 id="concurrency-control-by-validation">Concurrency Control by Validation&lt;/h2>
&lt;p>Validation differs from timestamping principally in that the scheduler maintains a record of what &lt;em>active&lt;/em> transactions are doing, rather than keeping read and write times for all database elements.&lt;/p>
&lt;p>Denote the write set and read set as $WS(T)$ and $RS(T)$. Transactions are executed in three phases:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Read&lt;/strong>. The transaction reads from the database all the elements in its read set, and also computes (locally) the results it is going to write.&lt;/li>
&lt;li>&lt;strong>Validate&lt;/strong>. The transaction compares its read and write sets with those of other transactions; rollback if fails.&lt;/li>
&lt;li>&lt;strong>Write&lt;/strong>. The transaction writes to the database the values for the elements in its write set.&lt;/li>
&lt;/ol>
&lt;blockquote>
&lt;p>Thus, the validation-based scheduler has an assumed serial order of the transactions to work with, and it bases its decision to validate or not on whether the transactions’ behaviors are consistent with this serial order.&lt;/p>
&lt;/blockquote>
&lt;p>The scheduler maintains three sets:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>START&lt;/strong>: the set of transactions that have started, but not yet completed validation.&lt;/li>
&lt;li>&lt;strong>VAL&lt;/strong>: the set of transactions that have been validated but not yet finished the writing in phase 3.&lt;/li>
&lt;li>&lt;strong>FIN&lt;/strong>: the set of transactions that have completed phase 3.&lt;/li>
&lt;/ol>
&lt;p>Some examples:&lt;/p>
&lt;p>A later transaction might read a value before an earlier transaction writes the value.&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/validate-1.webp" alt="">&lt;/p>
&lt;p>A later transaction might write before a validated, earlier transaction, as the write of the later one might occur earlier.&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/04/validate-2.webp" alt="">&lt;/p>
&lt;p>Summarization: to validate a transaction T:&lt;/p>
&lt;ul>
&lt;li>Check that $RS(T) \cap WS(U) = \emptyset$ for any previously validated U that did not finish before T &lt;em>started&lt;/em>, i.e., if $FIN(U) &amp;gt; START(T)$.&lt;/li>
&lt;li>Check that $WS(T) \cap WS(U) = \emptyset$ for any previously validated U that did not finish before T &lt;em>validated&lt;/em>, i.e., if $FIN(U) &amp;gt; VAL(T)$.&lt;/li>
&lt;/ul>
&lt;h2 id="comparison-of-three-concurrency-control-mechanisms">Comparison of Three Concurrency-Control Mechanisms&lt;/h2>
&lt;p>Storage: all schemes (can be optimized to) use storage space proportional to the sum over all active transactions of the number of database elements the transaction accesses.&lt;/p>
&lt;p>Performace:&lt;/p>
&lt;ul>
&lt;li>Locking delays transactions but avoids rollbacks, even when interaction is high.&lt;/li>
&lt;li>Timestamps and validation do not delay transactions, but can cause them to rollback.&lt;/li>
&lt;li>If interference is low, then neither timestamps nor validation will cause many rollbacks, and may be preferable to locking because they generally have lower overhead than a locking scheduler.&lt;/li>
&lt;li>When a rollback is necessary, timestamps catch some problems earlier than validation, which always lets a transaction do all its internal work before considering whether the transaction must rollback.&lt;/li>
&lt;/ul>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>同时执行多个事务时，需要保证每个事务的原子性，同时还必须保持一定的先后次序。&lt;/p>
&lt;p>对 schedule 最严格的要求是串行性（serial），当然这并不能做到并发。退一步，我们不需要严格保证不同事务处理的时间不互相重叠，但需要保证最终的效果和串行执行一致，即“可串行化”（serializable schedule）。当然，这个条件覆盖的范围太大，很难判断 schedule 是否满足可串行化条件。因此，我们需要增加更严格的条件，使得我们可以有简单的方法判断。当然，这样一来，满足条件的 schedule 只是可串行化 schedule 的一个真子集。&lt;/p>
&lt;p>第一大类办法是使用锁，并加上 2PL 条件，使得 schedule 满足“冲突可串行化”（confilict-serializable）。上锁有两个需要解决的问题：锁的类型及其兼容性，以及锁的粒度。&lt;/p>
&lt;p>第二大类办法是“乐观的”（optimistic），即假设不会发生不可串行化的事件，仅当的确出现违反可串行化条件时采取措施。Optimistic 方法包括两种：by timestamp，即通过比较事务开始的时间与元素被读或写的时间，判断某个操作是否可以进行；by validation，即在验证阶段比较与之前事务的读写集合判断是否可以提交，验证成功后再写入数据库。&lt;/p>
&lt;p>上锁的方法避免了重新执行，但事务会受到延迟，在冲突严重时较优；乐观方法由于可以进行一定操作，只在出现问题时取消或重新执行，因此延迟较低，但在冲突严重时会造成大量的重新执行而影响性能。&lt;/p></content></item><item><title>Operating Systems: Three Easy Pieces 学习笔记（四）</title><link>https://yeshe.ng/blog/posts/2022/03/ostep-virtualization-2/</link><pubDate>Sun, 06 Mar 2022 17:24:23 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/03/ostep-virtualization-2/</guid><description>Virtual Memory</description><content>&lt;h2 id="13-the-abstraction-address-spaces">13 The Abstraction: Address Spaces&lt;/h2>
&lt;h3 id="133-the-address-space">13.3 The Address Space&lt;/h3>
&lt;p>The address space of a process contains all of the memory state of the running program (e.g. code, stack, heap&amp;hellip;)&lt;/p>
&lt;h3 id="134-goals">13.4 Goals&lt;/h3>
&lt;p>Goals of the design:&lt;/p>
&lt;ul>
&lt;li>Transparency&lt;/li>
&lt;li>Efficiency&lt;/li>
&lt;li>Protection&lt;/li>
&lt;/ul>
&lt;h2 id="14-interlude-memory-api">14 Interlude: Memory API&lt;/h2>
&lt;h2 id="15-mechanism-address-translation">15 Mechanism: Address Translation&lt;/h2>
&lt;h3 id="153-dynamic-hardware-based-relocation">15.3 Dynamic (Hardware-based) Relocation&lt;/h3>
&lt;p>When a program is loaded, the OS sets &lt;code>base&lt;/code> and &lt;code>bounds&lt;/code> registers.&lt;/p>
&lt;pre tabindex="0">&lt;code>physical address = virtual address + base
&lt;/code>&lt;/pre>&lt;h3 id="154-hardware-support-a-summary">15.4 Hardware Support: A Summary&lt;/h3>
&lt;p>The required hardware support:&lt;/p>
&lt;ul>
&lt;li>privileged mode&lt;/li>
&lt;li>base/bounds registers&lt;/li>
&lt;li>translate virtual address and check if within bounds&lt;/li>
&lt;li>privileged instructions to update base/bounds registers&lt;/li>
&lt;li>privileged instructions to register exception handlers&lt;/li>
&lt;li>ability to raise exceptions&lt;/li>
&lt;/ul>
&lt;h3 id="156-summary">15.6 Summary&lt;/h3>
&lt;p>The problem: all the space between stack and heap is wasted (called &lt;strong>internal fragmentation&lt;/strong>)&lt;/p>
&lt;h2 id="16-segmentation">16 Segmentation&lt;/h2>
&lt;h3 id="161-segmentation-generalized-basebounds">16.1 Segmentation: Generalized Base/Bounds&lt;/h3>
&lt;p>&lt;strong>Segment&lt;/strong>: contiguous portion of the address space of a particular length.&lt;/p>
&lt;p>Segmentation allows the OS to place segments in differnet parts of physical memory.&lt;/p>
&lt;p>The hardware structure in the MMU: three &lt;code>base&lt;/code> and &lt;code>bounds&lt;/code> register pairs.&lt;/p>
&lt;h3 id="162-which-segment-are-we-referring-to">16.2 Which Segment Are We Referring To?&lt;/h3>
&lt;p>The explicit approach: chop up the address space into segments based on the top few bits of the virtual address.&lt;/p>
&lt;p>The implicit approcah: the hardware determins the segment by noticing how the address was formed.&lt;/p>
&lt;h3 id="163-what-about-the-stack">16.3 What About The Stack?&lt;/h3>
&lt;p>The difference: &lt;em>it grows backwords&lt;/em>.&lt;/p>
&lt;p>Use a bit in the hardware to indicate the sign of the offset.&lt;/p>
&lt;h3 id="164-support-for-sharing">16.4 Support for Sharing&lt;/h3>
&lt;h3 id="165-fine-grained-vs-coarse-grained-segmentation">16.5 Fine-grained vs. Coarse-grained Segmentation&lt;/h3>
&lt;h3 id="166-os-support">16.6 OS Support&lt;/h3>
&lt;p>The operation to support segmentation:&lt;/p>
&lt;ul>
&lt;li>On context switch: save and store segment registers&lt;/li>
&lt;li>Support growing heap segment (&lt;code>sbrk()&lt;/code>)&lt;/li>
&lt;li>Handle &lt;strong>external fragmentation&lt;/strong> (physical memory quickly becomes full of little holes of free space, making it difficult to allocate new segments, or to grow existing ones)&lt;/li>
&lt;/ul>
&lt;h2 id="17-free-space-management">17 Free-Space Management&lt;/h2>
&lt;h3 id="171-assumptions">17.1 Assumptions&lt;/h3>
&lt;p>&lt;strong>Internal fragmentation&lt;/strong>: the allocator hands out chunks of memory bigger than that requested&lt;/p>
&lt;h3 id="172-low-level-mechanisms">17.2 Low-level Mechanisms&lt;/h3>
&lt;h4 id="splitting-and-coalescing">Splitting and Coalescing&lt;/h4>
&lt;p>&lt;strong>Coalescing&lt;/strong>: coalesce spliited fragments when they are contiguous&lt;/p>
&lt;h4 id="tracking-the-size-of-allocated-regions">Tracking The Size Of Allocated Regions&lt;/h4>
&lt;p>Most allocators store a little bit of extra information in a header block which is kept in memory to keep track of the size of the allocated region.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">typedef&lt;/span> &lt;span style="color:#66d9ef">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> size;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> magic; &lt;span style="color:#75715e">/* for integrity checking */&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} header_t;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="embedding-a-free-list">Embedding A Free List&lt;/h4>
&lt;h4 id="growing-the-heap">Growing The Heap&lt;/h4>
&lt;h3 id="173-basic-strategies">17.3 Basic Strategies&lt;/h3>
&lt;blockquote>
&lt;p>Unfortunately, because the stream of allocation and free requests can be arbitrary (after all, they are determined by the programmer), any particular strategy can do quite badly given the wrong set of inputs.&lt;/p>
&lt;/blockquote>
&lt;h4 id="best-fit">Best Fit&lt;/h4>
&lt;p>The &lt;strong>best chunk&lt;/strong>: the smallest in candidates.&lt;/p>
&lt;p>Cost: heavy performance overhead&lt;/p>
&lt;h4 id="worst-fit">Worst Fit&lt;/h4>
&lt;p>The opposite to the best fit strategy.&lt;/p>
&lt;h4 id="first-fit">First Fit&lt;/h4>
&lt;p>Returns the first qualified block.&lt;/p>
&lt;p>Problem: pollution at the beginning of the free list; approach: address-based ordering.&lt;/p>
&lt;p>Advantage: speed.&lt;/p>
&lt;h4 id="next-fit">Next Fit&lt;/h4>
&lt;p>Keep an eatra pointer to the last-searched location; the blocks are distributed more evenly than first-fit strategy.&lt;/p>
&lt;h3 id="174-other-approaches">17.4 Other Approaches&lt;/h3>
&lt;h4 id="segregated-lists">Segregated Lists&lt;/h4>
&lt;p>Keep a separate list just to manage objects of some popular sizes; all other requests are forwarded to a more general memory allocator.&lt;/p>
&lt;h4 id="buddy-allocation">Buddy Allocation&lt;/h4>
&lt;p>The &lt;strong>binary buddy allocator&lt;/strong>: free memory is thought of as one big space of $2^N$; the search for free space recursively divides free space by two.&lt;/p>
&lt;p>Suffers from &lt;strong>internal fragmentation&lt;/strong>.&lt;/p>
&lt;p>Free process: recursively coalescing blocks upwards.&lt;/p>
&lt;p>Simple to determine the buddy of a particular block: each buddy pair (at the same level) only differs by a single bit.&lt;/p>
&lt;h2 id="18-paging-introduction">18 Paging: Introduction&lt;/h2>
&lt;h3 id="181-a-simple-example-and-overview">18.1 A Simple Example And Overview&lt;/h3>
&lt;p>&lt;strong>Page table&lt;/strong>: a &lt;em>per-process&lt;/em> data structure to store &lt;strong>address translations&lt;/strong>.&lt;/p>
&lt;h3 id="183-whats-actually-in-the-page-table">18.3 What’s Actually In The Page Table?&lt;/h3>
&lt;p>Some bits:&lt;/p>
&lt;ul>
&lt;li>valid bit &lt;em>(supporting a sparse address space; x86 doesn&amp;rsquo;t have it)&lt;/em>&lt;/li>
&lt;li>protection bit&lt;/li>
&lt;li>present bit (whether a page has been swapped out)&lt;/li>
&lt;li>dirty bit (whether a page has been modified)&lt;/li>
&lt;li>refence bit (whether a page has been accessed)&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Don’t worry: it deﬁnitely gets worse, because the mechanisms we are about to introduce only complicate this already complex machinery. Sorry!&lt;/p>
&lt;/blockquote>
&lt;h2 id="19-paging-faster-translations-tlbs">19 Paging: Faster Translations (TLBs)&lt;/h2>
&lt;blockquote>
&lt;p>When we want to make things fast, the OS usually needs some help. And help often comes from the OS’s old friend: the hardware.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Translation-lookaside buffer&lt;/strong> (or &lt;strong>TLB&lt;/strong>)&lt;/p>
&lt;h3 id="193-who-handles-the-tlb-miss">19.3 Who Handles The TLB Miss?&lt;/h3>
&lt;ul>
&lt;li>CISC: uses &lt;strong>hardware-managed TLBs&lt;/strong>&lt;/li>
&lt;li>RISC: uses &lt;strong>software-managed TLBs&lt;/strong>&lt;/li>
&lt;/ul>
&lt;p>The software approach: more flexible and simple; need to avoid infinite chain of TLB misses.&lt;/p>
&lt;h3 id="194-tlb-contents-whats-in-there">19.4 TLB Contents: What’s In There?&lt;/h3>
&lt;p>The TLB typically has 32, 64 or 128 entries, and is &lt;strong>fully associated&lt;/strong> (any given translation can be anywhere in the TLB).&lt;/p>
&lt;h3 id="195-tlb-issue-context-switches">19.5 TLB Issue: Context Switches&lt;/h3>
&lt;p>Some hardware systems provide an &lt;strong>address space identifier&lt;/strong> (&lt;strong>ASID&lt;/strong>) in the TLB to identify the associated process.&lt;/p>
&lt;h3 id="196-issue-replacement-policy">19.6 Issue: Replacement Policy&lt;/h3>
&lt;p>One common approach is to evict the &lt;strong>least-recently-used&lt;/strong> or &lt;strong>LRU&lt;/strong> entry.&lt;/p>
&lt;p>Another typical approach is to use a &lt;strong>random&lt;/strong> policy, which evicts a TLB mapping at random.&lt;/p>
&lt;h3 id="197-a-real-tlb-entry">19.7 A Real TLB Entry&lt;/h3>
&lt;p>&lt;a href="https://stackoverflow.com/questions/52813239/how-many-bits-there-are-in-a-tlb-asid-tag-for-intel-processors-and-how-to-handl">How to handle &amp;lsquo;ASID overflow&amp;rsquo;?&lt;/a>&lt;/p>
&lt;p>In MIPS, some TLB slots can be reserved for the OS.&lt;/p>
&lt;h2 id="20-paging-smaller-tables">20 Paging: Smaller Tables&lt;/h2>
&lt;blockquote>
&lt;p>Here, the problem should be clear: simple linear (array-based) page tables are too big.&lt;/p>
&lt;/blockquote>
&lt;h3 id="202-hybrid-approach-paging-and-segments">20.2 Hybrid Approach: Paging and Segments&lt;/h3>
&lt;blockquote>
&lt;p>Whenever you have two reasonable but different approaches to something in life, you should always examine the combination of the two to see if you can obtain the best of both worlds.&lt;/p>
&lt;/blockquote></content></item><item><title>Operating Systems: Three Easy Pieces 学习笔记（三）</title><link>https://yeshe.ng/blog/posts/2022/02/ostep-virtualization/</link><pubDate>Sat, 26 Feb 2022 13:26:41 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/02/ostep-virtualization/</guid><description>Process、Scheduling</description><content>&lt;h2 id="4-the-abstraction-the-process">4 The Abstraction: The Process&lt;/h2>
&lt;p>&lt;strong>Process&lt;/strong>: a running program&lt;/p>
&lt;h3 id="42-process-api">4.2 Process API&lt;/h3>
&lt;ul>
&lt;li>create&lt;/li>
&lt;li>destroy&lt;/li>
&lt;li>wait&lt;/li>
&lt;li>miscellaneous control&lt;/li>
&lt;li>status&lt;/li>
&lt;/ul>
&lt;h3 id="44-process-states">4.4 Process States&lt;/h3>
&lt;p>A process can be in one of three states:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>running&lt;/strong>: executing instructions&lt;/li>
&lt;li>&lt;strong>ready&lt;/strong>: ready to run but the OS has chosen not to run at this moment&lt;/li>
&lt;li>&lt;strong>blocked&lt;/strong>: a process has performed some kind of operation that makes it not ready to run until some other event takes place&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/02/ostep-process-state.webp" alt="">&lt;/p>
&lt;h2 id="5-interlude-process-api">5 Interlude: Process API&lt;/h2>
&lt;h3 id="54-why-motivating-the-api">5.4 Why? Motivating The API&lt;/h3>
&lt;p>The separation of &lt;code>fork()&lt;/code> and &lt;code>exec()&lt;/code> is essential in building a UNIX shell, because it lets the shell run code after the call to &lt;code>fork()&lt;/code> but before the call to &lt;code>exec()&lt;/code>; this code can alter the environment of the about-to-be-run program, and thus enables a variety of interesting features to be readily built.&lt;/p>
&lt;h3 id="55-process-control-and-users">5.5 Process Control And Users&lt;/h3>
&lt;p>The &lt;strong>user&lt;/strong>, after entering a password to establish credentials, logs in to gain access to system resources (and as a result, can send &lt;strong>signals&lt;/strong> to programs).&lt;/p>
&lt;h2 id="6-mechanism-limited-direct-execution">6 Mechanism: Limited Direct Execution&lt;/h2>
&lt;p>Use &lt;strong>time sharing&lt;/strong> to achieve virtualization&lt;/p>
&lt;p>Some challenges:&lt;/p>
&lt;ul>
&lt;li>performance&lt;/li>
&lt;li>control&lt;/li>
&lt;/ul>
&lt;h3 id="61-basic-technique-limited-direct-execution">6.1 Basic Technique: Limited Direct Execution&lt;/h3>
&lt;p>Direct execution: run the program directly on the CPU&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/02/ostep-direct-exec.webp" alt="">&lt;/p>
&lt;h3 id="62-problem-1-restricted-operations">6.2 Problem #1: Restricted Operations&lt;/h3>
&lt;p>The approach to restrict certain operations: introducing &lt;strong>user mode&lt;/strong> (and &lt;strong>kernel mode&lt;/strong>)&lt;/p>
&lt;p>&lt;strong>System calls&lt;/strong> allow the kernel to carefully expose certain key pieces of functionality to user programs, setting up a &lt;strong>trap table&lt;/strong> for the hardware to jump to &lt;strong>trap handlers&lt;/strong>&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/02/ostep-direct-exec-limited.webp" alt="">&lt;/p>
&lt;h3 id="63-problem-2-switching-between-processes">6.3 Problem #2: Switching Between Processes&lt;/h3>
&lt;blockquote>
&lt;p>The curx: how to regain control of the CPU?&lt;/p>
&lt;/blockquote>
&lt;h4 id="a-cooperative-approach-wait-for-system-calls">A Cooperative Approach: Wait For System Calls&lt;/h4>
&lt;p>The OS regains control of the CPU by waiting for a system call or an illegal operation of some kind to take place.&lt;/p>
&lt;h4 id="a-non-cooperative-approach-the-os-takes-control">A Non-Cooperative Approach: The OS Takes Control&lt;/h4>
&lt;p>A timer device can be programmed to raise an interrupt (&lt;strong>timer interrupt&lt;/strong>) every so many milliseconds; when the interrupt is raised, the currently running process is halted, and a pre-configured &lt;strong>interrupt handler&lt;/strong> in the OS runs.&lt;/p>
&lt;h4 id="saving-and-restoring-context">Saving and Restoring Context&lt;/h4>
&lt;p>Two types of register saves/restores:&lt;/p>
&lt;ul>
&lt;li>when timer interrupt occurs: the &lt;em>user registers&lt;/em> are implicitly saved by the &lt;em>hardware&lt;/em> to the kernel stack&lt;/li>
&lt;li>when the OS decides to switch to another process: the &lt;em>kernel registers&lt;/em> are explicitly saved by the &lt;em>software&lt;/em> to the memory of the process structure (Still in the kernel mode so should kernel registers be saved/restored; the user registers are restored later during return-from-trap)&lt;/li>
&lt;/ul>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/02/ostep-timer-interrupt.webp" alt="">&lt;/p>
&lt;h3 id="64-worried-about-concurrency">6.4 Worried About Concurrency?&lt;/h3>
&lt;p>OS might disable interrupts during interrupt processing (but can lead to lost of interrupts), and use locking to protect concurrent access to internal data structures.&lt;/p>
&lt;h2 id="7-scheduling-introduction">7 Scheduling: Introduction&lt;/h2>
&lt;h3 id="71-workload-assumptions">7.1 Workload Assumptions&lt;/h3>
&lt;p>&lt;em>Unrealistic&lt;/em> assumptions:&lt;/p>
&lt;ol>
&lt;li>each job runs for the same amount of time&lt;/li>
&lt;li>all jobs arrive at the same time&lt;/li>
&lt;li>once started, each job runs to completion&lt;/li>
&lt;li>all jobs only use the CPU (i.e., they perform no I/O)&lt;/li>
&lt;li>the run-time of each job is known (more unrealistic!)&lt;/li>
&lt;/ol>
&lt;h3 id="72-scheduling-metrics">7.2 Scheduling Metrics&lt;/h3>
&lt;p>Simplify with one single metric: &lt;strong>turnaround time&lt;/strong>&lt;/p>
&lt;p>$$
T_\mathrm{turnaround}=T_\mathrm{completion}-T_\mathrm{arrival}
$$&lt;/p>
&lt;p>In our case:&lt;/p>
&lt;p>$$
T_\mathrm{turnaround}=T_\mathrm{completion}
$$&lt;/p>
&lt;h3 id="73-first-in-first-out-fifo">7.3 First In, First Out (FIFO)&lt;/h3>
&lt;p>The &lt;strong>First In, First Out&lt;/strong> scheduling, or &lt;strong>First Come, First Served&lt;/strong>&lt;/p>
&lt;p>&lt;strong>Convoy effect&lt;/strong>: a number of relatively-short potential consumers of a resource get queued behind a heavyweight resource consumer.&lt;/p>
&lt;h3 id="74-shortest-job-first-sjf">7.4 Shortest Job First (SJF)&lt;/h3>
&lt;p>&lt;strong>Shortest Job First&lt;/strong>: runs the shortest job first, then the next shortest, and so on; optimal.&lt;/p>
&lt;p>Bad without assumption 2 (all jobs arrive at the same time)&lt;/p>
&lt;h3 id="75-shortest-time-to-completion-first-stcf">7.5 Shortest Time-to-Completion First (STCF)&lt;/h3>
&lt;p>Relax assumption 3 (each job runs to completion)&lt;/p>
&lt;p>The scheduler can &lt;strong>preempt&lt;/strong> the current job; adding this to SJF, we get the &lt;strong>Shortest Time-to-Completion First&lt;/strong> (&lt;strong>STCF&lt;/strong>) or &lt;strong>Preemptive Shortest Job First&lt;/strong> (&lt;strong>PSJF&lt;/strong>) scheduler&lt;/p>
&lt;h3 id="76-a-new-metric-response-time">7.6 A New Metric: Response Time&lt;/h3>
&lt;p>The &lt;strong>response time&lt;/strong>:&lt;/p>
&lt;p>$$
T_\mathrm{response}=T_\mathrm{firstrun}-T_\mathrm{arrival}
$$&lt;/p>
&lt;h3 id="77-round-robin">7.7 Round Robin&lt;/h3>
&lt;p>&lt;strong>Round-Robin&lt;/strong>: instead of running jobs to completion, RR runs a job for a &lt;strong>time slice&lt;/strong> (sometimes called a &lt;strong>scheduling quantum&lt;/strong>) and then switches to the next job in the run queue&lt;/p>
&lt;p>The length of the time slice is critical for RR; a trade-off between response time and the cost of context switching&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>The cost of context switch&lt;/strong>&lt;/p>
&lt;p>When programs run, they build up a great deal of state in CPU caches, TLBs, branch predictors, and other on-chip hardware. Switching to another job causes this state to be flushed and new state relevant to the currently-running job to be brought in, which may exact a noticeable performance cost.&lt;/p>
&lt;/blockquote>
&lt;p>RR is pretty awful for turnaround time&lt;/p>
&lt;blockquote>
&lt;p>More generally, any policy (such as RR) that is fair, i.e., that evenly divides the CPU among active processes on a small time scale, will perform poorly on metrics such as turnaround time.&lt;/p>
&lt;/blockquote>
&lt;h3 id="78-incorporating-io">7.8 Incorporating I/O&lt;/h3>
&lt;p>Relax assumption 4 (all jobs only use the CPU)&lt;/p>
&lt;p>Overlap will occur if using STCF&lt;/p>
&lt;h3 id="79-no-more-oracle">7.9 No More Oracle&lt;/h3>
&lt;p>Relax our final assumption (the run-time of each job is known)&lt;/p>
&lt;h2 id="8-scheduling-the-multi-level-feedback-queue">8 Scheduling: The Multi-Level Feedback Queue&lt;/h2>
&lt;p>The problem we try to address: given that we in general do not know anything about a process, how to&lt;/p>
&lt;ul>
&lt;li>optimize turnaround time&lt;/li>
&lt;li>minimize response time&lt;/li>
&lt;/ul>
&lt;h3 id="81-mlfq-basic-rules">8.1 MLFQ: Basic Rules&lt;/h3>
&lt;p>The MLFQ has a number of distinct &lt;strong>queues&lt;/strong>, each assigned a different &lt;strong>priority level&lt;/strong>. At any given time, a job that is ready to run is on a single queue.&lt;/p>
&lt;p>A job with higher priority (i.e., a job on a higher queue) is chosen to run; use round-robin scheduling among jobs with the same priority.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>rule 1&lt;/strong>: if Priority(A) &amp;gt; Priority(B), A runs (B doesn’t)&lt;/li>
&lt;li>&lt;strong>rule 2&lt;/strong>: if Priority(A) = Priority(B), A &amp;amp; B run in RR&lt;/li>
&lt;/ul>
&lt;p>To set the priority: MLFQ will try to learn about processes as they run, and thus use the history (&lt;em>long-running&lt;/em> or &lt;em>interactive&lt;/em>) of the job to predict its future behavior.&lt;/p>
&lt;h3 id="82-attempt-1-how-to-change-priority">8.2 Attempt #1: How To Change Priority&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>rule 3&lt;/strong>: when a job enters the system, it is placed at the highest priority&lt;/li>
&lt;li>&lt;strong>rule 4&lt;/strong>
&lt;ul>
&lt;li>a: if a job uses up an entire time slice while running, its priority is reduced&lt;/li>
&lt;li>b: if a job gives up the CPU before the time slice is up (&lt;em>therefore jobs with lower priority may have a chance to run&lt;/em>), it stays at the same priority level&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h4 id="example-1-a-single-long-running-job">Example 1: A Single Long-Running Job&lt;/h4>
&lt;h4 id="example-2-along-came-a-short-job">Example 2: Along Came A Short Job&lt;/h4>
&lt;p>The idea: first assumes a job to be short, otherwise move it down; this is an approximization of SJF&lt;/p>
&lt;h4 id="example-3-what-about-io">Example 3: What About I/O?&lt;/h4>
&lt;h4 id="problems-with-our-current-mlfq">Problems With Our Current MLFQ&lt;/h4>
&lt;ul>
&lt;li>&lt;strong>starvation&lt;/strong>: too many interactive jobs will consume all CPU time, thus long-running jobs will never receive any CPU time&lt;/li>
&lt;li>can be attacked by first using most of the time and then perform an I/O&lt;/li>
&lt;li>a program might change its behaviour&lt;/li>
&lt;/ul>
&lt;h3 id="83-attempt-2-the-priority-boost">8.3 Attempt #2: The Priority Boost&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>rule 5&lt;/strong>: after some time period $S$, move all the jobs in the system to the topmost queue&lt;/li>
&lt;/ul>
&lt;p>If $S$ is set too high, long-running jobs could starve; too low, and interactive jobs may not get a proper share of the CPU.&lt;/p>
&lt;h3 id="84-attempt-3-better-accounting">8.4 Attempt #3: Better Accounting&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>rule 4&lt;/strong>: once a job uses up its time allotment (分配) at a given level (regardless of how many times it has given up the CPU), its priority is reduced (i.e., it moves down one queue)&lt;/li>
&lt;/ul>
&lt;h3 id="85-tuning-mlfq-and-other-issues">8.5 Tuning MLFQ And Other Issues&lt;/h3>
&lt;h2 id="9-scheduling-proportional-share">9 Scheduling: Proportional Share&lt;/h2>
&lt;p>The concept: instead of optimizing for turnaround or response time, a scheduler might instead try to guarantee that each job obtain a certain percentage of CPU time.&lt;/p>
&lt;h3 id="91-basic-concept-tickets-represent-your-share">9.1 Basic Concept: Tickets Represent Your Share&lt;/h3>
&lt;p>&lt;strong>Tickets&lt;/strong>: the share of a resource that a process (or user or whatever) should receive.&lt;/p>
&lt;p>The system picks a ticket (randomly) whose owner process can run at this point&lt;/p>
&lt;blockquote>
&lt;p>If you are ever in need of a mechanism to represent a proportion of ownership, this concept just might be &amp;hellip; (wait for it) &amp;hellip; the ticket.&lt;/p>
&lt;/blockquote>
&lt;h3 id="92-ticket-mechanisms">9.2 Ticket Mechanisms&lt;/h3>
&lt;ul>
&lt;li>ticket currency&lt;/li>
&lt;li>ticket transfer&lt;/li>
&lt;li>ticket inflation (通货膨胀)&lt;/li>
&lt;/ul>
&lt;h3 id="93-implementation">9.3 Implementation&lt;/h3>
&lt;p>Best practice: order the list from the highest number of tickets to the lowest&lt;/p>
&lt;h3 id="94-an-example">9.4 An Example&lt;/h3>
&lt;p>Overall fairness is higher with higher job length&lt;/p>
&lt;h3 id="95-how-to-assign-tickets">9.5 How To Assign Tickets?&lt;/h3>
&lt;p>Given a set of jobs, the “ticket-assignment problem” remains open.&lt;/p>
&lt;h3 id="96-stride-scheduling">9.6 Stride Scheduling&lt;/h3>
&lt;p>Stride: inverse in proportion to the number of tickets a job has&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>curr &lt;span style="color:#f92672">=&lt;/span> remove_min(queue); &lt;span style="color:#75715e">// pick client with min pass
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>schedule(curr); &lt;span style="color:#75715e">// run for quantum
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>curr&lt;span style="color:#f92672">-&amp;gt;&lt;/span>pass &lt;span style="color:#f92672">+=&lt;/span> curr&lt;span style="color:#f92672">-&amp;gt;&lt;/span>stride; &lt;span style="color:#75715e">// update pass using stride
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&lt;/span>insert(queue, curr); &lt;span style="color:#75715e">// return curr to queue
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Stride scheduling is a deterministic fair-share scheduler.&lt;/p>
&lt;p>Lottery scheduling has no global state, so that new processes are easier to be incorporated&lt;/p>
&lt;h3 id="97-the-linux-completely-fair-scheduler-cfs">9.7 The Linux Completely Fair Scheduler (CFS)&lt;/h3>
&lt;blockquote>
&lt;p>Recent studies have shown that scheduler efficiency is surprisingly important; specifically, in a study of Google datacenters, Kanev et al. show that even after aggressive optimization, scheduling uses about 5% of overall datacenter CPU time.&lt;/p>
&lt;/blockquote>
&lt;h4 id="basic-operation">Basic Operation&lt;/h4>
&lt;p>Goal: to fairly divide a CPU evenly among all competing processes&lt;/p>
&lt;p>In the basic case, each process&amp;rsquo;s &lt;code>vruntime&lt;/code> increases at the same rate, propotional to the physical time. Pick the process with the lowest &lt;code>vruntime&lt;/code> to run next.&lt;/p>
&lt;p>Divide &lt;code>sched_latency&lt;/code> by process number to calculate the time slice; use &lt;code>min_granularity&lt;/code> to constrain the minimum possible time slice.&lt;/p>
&lt;h4 id="weighting-niceness">Weighting (Niceness)&lt;/h4>
&lt;p>Use &lt;strong>nice&lt;/strong> level to control the priority of a process. Positive nice values imply lower priority and negative values imply higher priority&lt;/p>
&lt;blockquote>
&lt;p>When you’re too nice, you just don’t get as much (scheduling) attention, alas.&lt;/p>
&lt;/blockquote>
&lt;p>Calculation of the time slice:&lt;/p>
&lt;p>$$
\texttt{time_slice}_k=\cfrac{\texttt{weight}_k}{\sum_{i=0}^{n-1}\texttt{weight}_i}\cdot\texttt{sched_latency}
$$&lt;/p>
&lt;p>Also adjusting the calculation of virtual runtime:&lt;/p>
&lt;p>$$
\texttt{vruntime}_i=\texttt{vruntime}_i+\cfrac{\texttt{weight}_0}{\texttt{weight}_i}\cdot\texttt{runtime}_i
$$&lt;/p>
&lt;p>The table preserves CPU proportionality ratios when the difference in nice values is constant&lt;/p>
&lt;h4 id="using-red-black-trees">Using Red-Black Trees&lt;/h4>
&lt;p>Use a red-black tree to maintain running (or runnable) processes; if a process goes to sleep (say, waiting fot an I/O to complete), it is removed.&lt;/p>
&lt;h4 id="dealing-with-io-and-sleeping-processes">Dealing With I/O And Sleeping Processes&lt;/h4>
&lt;p>A just-awaken process will monopolize the CPU; solution: set the &lt;code>vruntime&lt;/code> of that job to the min value found in the tree (which only keeps track of &lt;em>running&lt;/em> jobs!).&lt;/p>
&lt;h2 id="10-multiprocessor-scheduling-advanced">10 Multiprocessor Scheduling (Advanced)&lt;/h2>
&lt;h3 id="101-background-multiprocessor-architecture">10.1 Background: Multiprocessor Architecture&lt;/h3>
&lt;p>Two kinds of locality:&lt;/p>
&lt;ul>
&lt;li>temporal locality&lt;/li>
&lt;li>spatial locality&lt;/li>
&lt;/ul>
&lt;p>Problem with multi-code processors: &lt;strong>cache coherence&lt;/strong>; the hardware uses &lt;strong>bus snooping&lt;/strong> to update/invalidate outdated cache&lt;/p>
&lt;h3 id="102-dont-forget-synchronization">10.2 Don’t Forget Synchronization&lt;/h3>
&lt;h3 id="103-one-final-issue-cache-affinity">10.3 One Final Issue: Cache Affinity&lt;/h3>
&lt;p>A multiprocessor scheduler should consider cache affinity when making its scheduling decisions, perhaps preferring to keep a process on the same CPU if at all possible.&lt;/p>
&lt;h3 id="104-single-queue-scheduling">10.4 Single-Queue Scheduling&lt;/h3>
&lt;p>&lt;strong>Singlequeue multiprocessor scheduling&lt;/strong> (or &lt;strong>SQMS&lt;/strong>): take an existing policy that picks the best job to run next and adapt it to work on more than one CPU&lt;/p>
&lt;p>Problems:&lt;/p>
&lt;ul>
&lt;li>performance: the overhead of locking is high&lt;/li>
&lt;li>cache affinity: each job ends up bouncing around from CPU to CPU&lt;/li>
&lt;/ul>
&lt;h3 id="105-multi-queue-scheduling">10.5 Multi-Queue Scheduling&lt;/h3>
&lt;p>Each CPU has a queue of tasks to be performed.&lt;/p>
&lt;p>To solve &lt;strong>load imbalance&lt;/strong>: using &lt;strong>migration&lt;/strong>; a basic approach is to use &lt;strong>work stealing&lt;/strong>&lt;/p></content></item><item><title>Operating Systems: Three Easy Pieces 学习笔记（二）</title><link>https://yeshe.ng/blog/posts/2022/02/ostep-persistence-2/</link><pubDate>Wed, 23 Feb 2022 21:37:07 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/02/ostep-persistence-2/</guid><description>FSCK and Journaling、LFS、Flash-based SSD</description><content>&lt;h2 id="42-crash-consistency-fsck-and-journaling">42 Crash Consistency: FSCK and Journaling&lt;/h2>
&lt;blockquote>
&lt;p>One major challenge faced by a file system is how to update persistent data structures despite the presence of a power loss or system crash.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Crash-consistency problem&lt;/strong>: On-disk state may only partially get updated on system crash or power loss&lt;/p>
&lt;h3 id="421-a-detailed-example">42.1 A Detailed Example&lt;/h3>
&lt;h4 id="crash-scenarios">Crash Scenarios&lt;/h4>
&lt;p>Three possible outcomes if an error occured when a data block is allocated and written for a file (which should allocate a data block (and write data to it), update the data bitmap, and update the file inode):&lt;/p>
&lt;ul>
&lt;li>&lt;strong>only the data block&lt;/strong> is written: no problem at all (from the perspective of file-system crash consistency)&lt;/li>
&lt;li>&lt;strong>only the updated inode&lt;/strong> is written: garbage data will be read; file-system inconsistency: the allocated status of the data block is different in the bitmap and the inode&lt;/li>
&lt;li>&lt;strong>only the bitmap&lt;/strong> is written: space leak&lt;/li>
&lt;li>&lt;strong>only the inode and the bitmap&lt;/strong> are written: garbage data&lt;/li>
&lt;li>&lt;strong>only the inode and the data block&lt;/strong>: inconsistency between the inode and the bitmap&lt;/li>
&lt;li>&lt;strong>only the bitmap and the data block&lt;/strong>: space leak again&lt;/li>
&lt;/ul>
&lt;h4 id="the-crash-consistency-problem">The Crash Consistency Problem&lt;/h4>
&lt;p>&lt;strong>Crash-consistency problem&lt;/strong> (or &lt;strong>consistent-update problem&lt;/strong>): the disk can only commit one write at a time, so that an update cannot be done atomically&lt;/p>
&lt;h3 id="422-solution-1-the-file-system-checker">42.2 Solution #1: The File System Checker&lt;/h3>
&lt;p>Early systems&amp;rsquo; approach: fix inconsistencies later (when rebooting)&lt;/p>
&lt;p>&lt;code>fsck&lt;/code>&amp;rsquo;s phases:&lt;/p>
&lt;ul>
&lt;li>check superblock&lt;/li>
&lt;li>scans inodes, indirect blocks, etc. to make consistency between inodes and bitmaps (by trusting inodes)&lt;/li>
&lt;li>check inodes for corruption and other problems&lt;/li>
&lt;li>verify link count&lt;/li>
&lt;li>check for duplicate pointers (two different inodes refer to the same block) (clear some inodes or make copies of the duplicate data)&lt;/li>
&lt;li>check for bad block pointers (and remove them)&lt;/li>
&lt;li>check directories&lt;/li>
&lt;/ul>
&lt;p>Fundamental problems of &lt;code>fsck&lt;/code>: too slow&lt;/p>
&lt;blockquote>
&lt;p>At a higher level, the basic premise of &lt;code>fsck&lt;/code> seems just a tad irrational. Consider our example above, where just three blocks are written to the disk; it is incredibly expensive to scan the entire disk to fix problems that occurred during an update of just three blocks.&lt;/p>
&lt;/blockquote>
&lt;h3 id="423-solution-2-journaling-or-write-ahead-logging">42.3 Solution #2: Journaling (or Write-Ahead Logging)&lt;/h3>
&lt;p>&lt;strong>Write-ahead logging&lt;/strong> (or &lt;strong>journaling&lt;/strong>): write logs before overwriting the structures to be able to recover from errors&lt;/p>
&lt;h4 id="data-journaling">Data Journaling&lt;/h4>
&lt;p>&lt;strong>Journal write&lt;/strong>:&lt;/p>
&lt;ul>
&lt;li>transaction begin block&lt;/li>
&lt;li>blocks containing the exact contents of the updating blocks (physical logging) or some more compact forms (logical logging)&lt;/li>
&lt;li>transaction end block&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Checkpointing&lt;/strong>: override the old structures in the file system&lt;/p>
&lt;p>Problems if a crach occurs during the write to the journal: if writing all the log blocks at once, as the disk might perform scheduling, they might not complete in order, and thus some blocks might not be correct (e.g. invalid data blocks within valid TxB and TxE blocks).&lt;/p>
&lt;p>Solution: write all blocks except the TxE at once, and write TxE block after these writes. &lt;strong>With atomicity guatantee!&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;p>Performance problem: the transaction-end block can only be written after previous blocks are written.&lt;/p>
&lt;p>Optimizing log writes: using checksum&lt;/p>
&lt;/blockquote>
&lt;p>Three phases:&lt;/p>
&lt;ul>
&lt;li>journal write (TxB, metadata and data)&lt;/li>
&lt;li>journal commit (TxE)&lt;/li>
&lt;li>checkpoint (write the contents to final on-disk locations)&lt;/li>
&lt;/ul>
&lt;h4 id="recovery">Recovery&lt;/h4>
&lt;p>Replay the transactions that are commited to the disk (&lt;strong>redo logging&lt;/strong>)&lt;/p>
&lt;h4 id="batching-log-updates">Batching Log Updates&lt;/h4>
&lt;p>Buffer all updates into a global transaction to avoid writing some same blocks many times.&lt;/p>
&lt;h4 id="making-the-log-finite">Making The Log Finite&lt;/h4>
&lt;p>Journaling file systems treat the log as a circular data structure (the journal is referred to as &lt;strong>circular log&lt;/strong>).&lt;/p>
&lt;p>Mark the transaction free in the journal by updating the journal superblock some time after checkpoint.&lt;/p>
&lt;p>Now the four phases:&lt;/p>
&lt;ul>
&lt;li>journal write&lt;/li>
&lt;li>journal commit&lt;/li>
&lt;li>checkpoint&lt;/li>
&lt;li>free&lt;/li>
&lt;/ul>
&lt;h4 id="metadata-journaling">Metadata journaling&lt;/h4>
&lt;p>&lt;strong>Ordered journaling&lt;/strong> (or &lt;strong>metadata journaling&lt;/strong>): the user data is not written to the journal&lt;/p>
&lt;p>One possible order (to avoid garbage data):&lt;/p>
&lt;ul>
&lt;li>data write (1)&lt;/li>
&lt;li>journal metadata write (2)&lt;/li>
&lt;li>journal commit&lt;/li>
&lt;li>checkpoint metadata&lt;/li>
&lt;li>free&lt;/li>
&lt;/ul>
&lt;p>(1) and (2) can be written concurrently&lt;/p>
&lt;h2 id="43-log-structured-file-systems">43 Log-structured File Systems&lt;/h2>
&lt;blockquote>
&lt;p>&lt;strong>The motivation&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>system memories are growing so that more data can be cached, and disk traffic increasingly consists of writes&lt;/li>
&lt;li>a large gap between random and sequential I/O performace&lt;/li>
&lt;li>exising file systems perform poorly on many common workloads&lt;/li>
&lt;li>file systems are not RAID-aware&lt;/li>
&lt;/ul>
&lt;p>An ideal file system would thus focus on write performance, and try to make use of the sequential bandwidth of the disk. Further, it would perform well on common workloads that not only write out data but also update on-disk metadata structures frequently. Finally, it would work well on RAIDs as well as single disks.&lt;/p>
&lt;/blockquote>
&lt;h3 id="431-writing-to-disk-sequentially">43.1 Writing To Disk Sequentially&lt;/h3>
&lt;p>The basic idea: writing all updates (such as data blocks, inodes, etc.) to the disk sequentially&lt;/p>
&lt;h3 id="432-writing-sequentially-and-effectively">43.2 Writing Sequentially And Effectively&lt;/h3>
&lt;p>Sequential writes is not enough; to achieve good performance we must issue contiguous writes (or one large write)&lt;/p>
&lt;p>&lt;strong>Write buffering&lt;/strong>: keeps track of updates &lt;em>in memory&lt;/em>, and write the buffered data at once when gets sufficient updates (called a &lt;strong>segment&lt;/strong>)&lt;/p>
&lt;h3 id="433-how-much-to-buffer">43.3 How Much To Buffer?&lt;/h3>
&lt;p>$$
D=\cfrac{F}{1-F}\times R_\mathrm{peak}\times T_\mathrm{position}
$$&lt;/p>
&lt;h3 id="434-problem-finding-inodes">43.4 Problem: Finding Inodes&lt;/h3>
&lt;h3 id="435-solution-through-indirection-the-inode-map">43.5 Solution Through Indirection: The Inode Map&lt;/h3>
&lt;p>The &lt;strong>inode map&lt;/strong>: takes an inode number as input and returns the disk address of the &lt;em>most recent&lt;/em> version of the inode&lt;/p>
&lt;p>LFS places chunks of the inode map right &lt;em>next to&lt;/em> where it is writing all of the other new information.&lt;/p>
&lt;blockquote>
&lt;p>Inode maps are compact enough to keep the active portions cached in main memory: inode map lookups rarely require disk accesses.&lt;/p>
&lt;p>(From &lt;em>The Design and Implementation of a Log-Structured File System&lt;/em>)&lt;/p>
&lt;/blockquote>
&lt;h3 id="436-completing-the-solution-the-checkpoint-region">43.6 Completing The Solution: The Checkpoint Region&lt;/h3>
&lt;p>The &lt;strong>checkpoint region&lt;/strong> (or &lt;strong>CR&lt;/strong>) contains pointers to the latest pieces of the inode map; it is only updated periodically for performance&lt;/p>
&lt;h3 id="437-reading-a-file-from-disk-a-recap">43.7 Reading A File From Disk: A Recap&lt;/h3>
&lt;p>The steps of reading a file (from start):&lt;/p>
&lt;ul>
&lt;li>read the CR and cache it in memory&lt;/li>
&lt;li>lookup the CR to find the address of a given inode&lt;/li>
&lt;li>(the rest is pretty much the same as a typical FS)&lt;/li>
&lt;/ul>
&lt;h3 id="438-what-about-directories">43.8 What About Directories?&lt;/h3>
&lt;p>Steps:&lt;/p>
&lt;ul>
&lt;li>look in the imap for the location of the inode of the directory&lt;/li>
&lt;li>find inode number of the file in the inode of the directory&lt;/li>
&lt;li>look in the imap for the location of the inode of the file&lt;/li>
&lt;/ul>
&lt;p>The imap also solves the &lt;strong>recursive update problem&lt;/strong> through indirection.&lt;/p>
&lt;h3 id="439-a-new-problem-garbage-collection">43.9 A New Problem: Garbage Collection&lt;/h3>
&lt;p>The cleaner assures to preserve contiguous free regions by compacting existing segments to new, contiguous segments&lt;/p>
&lt;h3 id="4310-determining-block-liveness">43.10 Determining Block Liveness&lt;/h3>
&lt;p>Use &lt;strong>segment summary block&lt;/strong> at the head of the segment to record the inode number and offset (which block of the file) for each data block&lt;/p>
&lt;p>To determine whether a block is dead:&lt;/p>
&lt;ul>
&lt;li>look in the segment summary block and find its inode number and offset&lt;/li>
&lt;li>look in the imap to find and read the inode&lt;/li>
&lt;li>look in the inode using the offset to compare the disk location&lt;/li>
&lt;/ul>
&lt;p>LFS can also use &lt;strong>version number&lt;/strong> to determine this, avoiding extra reads.&lt;/p>
&lt;h3 id="4311-a-policy-question-which-blocks-to-clean-and-when">43.11 A Policy Question: Which Blocks To Clean, And When?&lt;/h3>
&lt;p>The time to clean can be:&lt;/p>
&lt;ul>
&lt;li>perioducally&lt;/li>
&lt;li>during idle time&lt;/li>
&lt;li>when the disk is full&lt;/li>
&lt;/ul>
&lt;h3 id="4312-crash-recovery-and-the-log">43.12 Crash Recovery And The Log&lt;/h3>
&lt;p>Use two CRs containing update timestamps to recover from crash during CR updates&lt;/p>
&lt;p>The last consistent snapshot of the file system may be quite old as LFS only writes the CR every 30 seconds or so; use &lt;strong>roll forward&lt;/strong> to read through the next segments of the end of the log to see if there are any valid updates, and updates the file system accordingly.&lt;/p>
&lt;h2 id="44-flash-based-ssds">44 Flash-based SSDs&lt;/h2>
&lt;p>&lt;strong>Solid-state storage&lt;/strong>: random access, retains information on power loss&lt;/p>
&lt;h3 id="441-storing-a-single-bit">44.1 Storing a Single Bit&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>single-level cell&lt;/strong> (&lt;strong>SLC&lt;/strong>) flash: a single bit per transistor&lt;/li>
&lt;li>&lt;strong>multi-level cell&lt;/strong> (&lt;strong>MLC&lt;/strong>) flash: two bits per transistor (00, 01, 10, 11 represented by different levels of charge)&lt;/li>
&lt;li>&lt;strong>triple-level cell&lt;/strong> (&lt;strong>TLC&lt;/strong>) flash: three bits per cell&lt;/li>
&lt;/ul>
&lt;p>SLC chips achieve higher performance and are more expensive.&lt;/p>
&lt;h3 id="442-from-bits-to-banksplanes">44.2 From Bits to Banks/Planes&lt;/h3>
&lt;p>Flash chips are organized into &lt;strong>banks&lt;/strong> or &lt;strong>planes&lt;/strong> which consist of a large number of cells.&lt;/p>
&lt;p>A bank is accessed in two different sized units:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>block&lt;/strong> (or &lt;strong>erase block&lt;/strong>): typically of size 128KB or 256KB&lt;/li>
&lt;li>&lt;strong>page&lt;/strong>: a few KB in size (e.g. 4KB)&lt;/li>
&lt;/ul>
&lt;h3 id="443-basic-flash-operations">44.3 Basic Flash Operations&lt;/h3>
&lt;p>Three low-level operations:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>read&lt;/strong> (a &lt;strong>page&lt;/strong>): random access; fast, 10s of $\mathrm{\mu s}$ or so&lt;/li>
&lt;li>&lt;strong>erase&lt;/strong> (a &lt;strong>block&lt;/strong>): setting each bit to 1; quite expensive, takes a few ms; need to copy data of care somewhere else&lt;/li>
&lt;li>&lt;strong>program&lt;/strong> (a &lt;strong>page&lt;/strong>): After a block is erased, change some of the 1&amp;rsquo;s within a page to 0&amp;rsquo;s; usually taking around $100\mathrm{\mu s}$&lt;/li>
&lt;/ul>
&lt;h3 id="444-flash-performance-and-reliability">44.4 Flash Performance And Reliability&lt;/h3>
&lt;p>&lt;strong>Wear out&lt;/strong>: erasing and programming accure a little bit of extra charge, 0 and 1 are increasingly difficult to be differentiated.&lt;/p>
&lt;p>The typical lifetime:&lt;/p>
&lt;ul>
&lt;li>MLC: 10,000 P/E (Program/Erase) cycles&lt;/li>
&lt;li>SLC: 100,000 P/E cycles&lt;/li>
&lt;li>actual lifetimes are much longer&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Disturbance&lt;/strong>: some bits get flipped in neighboring pages when accessing (read/program) a particular page&lt;/p>
&lt;h3 id="445-from-raw-flash-to-flash-based-ssds">44.5 From Raw Flash to Flash-Based SSDs&lt;/h3>
&lt;p>&lt;strong>Flash translation layer&lt;/strong> (or &lt;strong>FTL&lt;/strong>): translates read and write requests on &lt;em>logical blocks&lt;/em> into low-level read, erase and program commands on underlying &lt;em>physical blocks&lt;/em> and &lt;em>physical pages&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>The FTL should accomplish this task with the goal of delivering excellent performance and high reliability.&lt;/p>
&lt;/blockquote>
&lt;p>To achieve performance:&lt;/p>
&lt;ul>
&lt;li>parallel&lt;/li>
&lt;li>reduce write amplification&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Write amplification&lt;/strong>: total write traffic issued to the flash chips (by the FTL) divided by the total write traffic issued to the SSD (by the client)&lt;/p>
&lt;p>To achieve high reliability:&lt;/p>
&lt;ul>
&lt;li>for wear out: &lt;strong>wear leveling&lt;/strong>&lt;/li>
&lt;li>for program disturbance: a sequential-programming approach&lt;/li>
&lt;/ul>
&lt;h3 id="446-ftl-organization-a-bad-approach">44.6 FTL Organization: A Bad Approach&lt;/h3>
&lt;p>&lt;strong>Direct mapped organization&lt;/strong>: a read to a logical page is mapped directly to the physical page with the same page number&lt;/p>
&lt;p>The problems with the direct-mapped FTL:&lt;/p>
&lt;ul>
&lt;li>performance: each write needs to
&lt;ul>
&lt;li>read in the entire block (costly)&lt;/li>
&lt;li>erase the entire block (quite costly)&lt;/li>
&lt;li>program the block (costly)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>reliability: the same block can be erased and programmed over and over (e.g., file system metadata is overritten repeatedly)&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>The direct mapped approach simply &lt;strong>gives too much control over wear out to the client workload&lt;/strong>; if the workload does not spread write load evenly across its logical blocks, the underlying physical blocks containing popular data will quickly wear out.&lt;/p>
&lt;/blockquote>
&lt;h3 id="447-a-log-structured-ftl">44.7 A Log-Structured FTL&lt;/h3>
&lt;p>Use a &lt;strong>mapping table&lt;/strong> to map the logical block to the physical address&lt;/p>
&lt;p>The log-based approach improves performance in:&lt;/p>
&lt;ul>
&lt;li>erases being required once in a while&lt;/li>
&lt;li>spread writes across pages (&lt;strong>wear leveling&lt;/strong>)&lt;/li>
&lt;/ul>
&lt;p>Downsides:&lt;/p>
&lt;ul>
&lt;li>garbage collection may drives up write amplification&lt;/li>
&lt;li>in-memory mapping tables might be large&lt;/li>
&lt;/ul>
&lt;h3 id="448-garbage-collection">44.8 Garbage Collection&lt;/h3>
&lt;p>To reduce GC costs, &lt;strong>overprovisioning&lt;/strong> can be performed: adding extra flash capacity and delay cleaning, which will be performed in background.&lt;/p>
&lt;h3 id="449-mapping-table-size">44.9 Mapping Table Size&lt;/h3>
&lt;h4 id="block-based-mapping">Block-Based Mapping&lt;/h4>
&lt;h4 id="hybrid-mapping">Hybrid Mapping&lt;/h4>
&lt;p>Two types of mapping table:&lt;/p>
&lt;ul>
&lt;li>log table (per-page mappings)&lt;/li>
&lt;li>data table (per-block mappings)&lt;/li>
&lt;/ul>
&lt;p>To read:&lt;/p>
&lt;ul>
&lt;li>first find in the log table&lt;/li>
&lt;li>if not found, find in the data table&lt;/li>
&lt;/ul>
&lt;p>The key to the hybrid mapping strategy is keeping the number of log blocks small. To do that, the FTL has to periodically examine log blocks and switch them into blocks that &lt;em>can&lt;/em> be pointed to by only a single block pointer.&lt;/p>
&lt;p>Three types of merge:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>switch merge&lt;/strong>: all pointers of a block in the log table can be switched into a pointer pointing to this block&lt;/li>
&lt;li>&lt;strong>partial merge&lt;/strong>&lt;/li>
&lt;li>&lt;strong>full merge&lt;/strong>&lt;/li>
&lt;/ul>
&lt;h4 id="page-mapping-plus-caching">Page Mapping Plus Caching&lt;/h4>
&lt;h3 id="4410-wear-leveling">44.10 Wear Leveling&lt;/h3>
&lt;p>One problem of the log-structured approach: blocks filled with long-lived data do not get overwritten; solution: periodically read and rewrite elsewhere&lt;/p></content></item><item><title>Operating Systems: Three Easy Pieces 学习笔记（一）</title><link>https://yeshe.ng/blog/posts/2022/01/ostep-persistence/</link><pubDate>Thu, 13 Jan 2022 20:38:28 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/01/ostep-persistence/</guid><description>I/O Devices、Hard Disk Drives、RAID、Files and Directories、File System Implementation、FFS</description><content>&lt;h2 id="36-io-devices">36 I/O Devices&lt;/h2>
&lt;h3 id="361-system-architecture">36.1 System Architecture&lt;/h3>
&lt;p>Devices are connected to the system via:&lt;/p>
&lt;ul>
&lt;li>memory bus (CPU - main memory)&lt;/li>
&lt;li>general I/O bus: PCI (graphics, high performance devices)&lt;/li>
&lt;li>peripheral bus: SCSI, SATA, USB (slow devices)&lt;/li>
&lt;/ul>
&lt;p>In modern systems:&lt;/p>
&lt;ul>
&lt;li>PCIe Graphics&lt;/li>
&lt;li>memory interconnect&lt;/li>
&lt;li>DMI (Direct Media Interface)&lt;/li>
&lt;li>PCIe&lt;/li>
&lt;li>eSATA&lt;/li>
&lt;/ul>
&lt;h3 id="362-a-canonical-device">36.2 A Canonical Device&lt;/h3>
&lt;p>A device has two important components:&lt;/p>
&lt;ul>
&lt;li>interface
&lt;ul>
&lt;li>status&lt;/li>
&lt;li>command&lt;/li>
&lt;li>data&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>internal structure&lt;/li>
&lt;/ul>
&lt;h3 id="363-the-canonical-protocol">36.3 The Canonical Protocol&lt;/h3>
&lt;p>&lt;strong>Programmed I/O&lt;/strong> (&lt;strong>PIO&lt;/strong>): the main CPU is involved with the data movement&lt;/p>
&lt;h3 id="364-lowering-cpu-overhead-with-interrupts">36.4 Lowering CPU Overhead With Interrupts&lt;/h3>
&lt;p>Utilize CPU:&lt;/p>
&lt;ul>
&lt;li>interrupt&lt;/li>
&lt;li>a two-phased approach: polls first, using interrupts if not yet finished&lt;/li>
&lt;li>coalescing&lt;/li>
&lt;/ul>
&lt;h3 id="365-more-efficient-data-movement-with-dma">36.5 More Efficient Data Movement With DMA&lt;/h3>
&lt;p>&lt;strong>Direct Memory Access&lt;/strong> (DMA): transfer data between devices and main memory &lt;strong>without much CPU intervention&lt;/strong>&lt;/p>
&lt;h3 id="366-methods-of-device-interaction">36.6 Methods Of Device Interaction&lt;/h3>
&lt;ul>
&lt;li>explicit I/O instructions (on x86, the in and out instructions), usually privileged&lt;/li>
&lt;li>memory-mapped I/O (no new instructions are needed)&lt;/li>
&lt;/ul>
&lt;h3 id="367-fitting-into-the-os-the-device-driver">36.7 Fitting Into The OS: The Device Driver&lt;/h3>
&lt;p>Read/write blocks:&lt;/p>
&lt;ul>
&lt;li>file system&lt;/li>
&lt;li>raw interface (support low-level storage management)&lt;/li>
&lt;/ul>
&lt;h3 id="368-case-study-a-simple-ide-disk-driver">36.8 Case Study: A Simple IDE Disk Driver&lt;/h3>
&lt;p>Registers are available by reading or writing to specific “I/O addresses” using (on x86) the &lt;code>in&lt;/code> and &lt;code>out&lt;/code> I/O instructions.&lt;/p>
&lt;ul>
&lt;li>wait for drive to be ready&lt;/li>
&lt;li>write parameters to command registers&lt;/li>
&lt;li>start the I/O: issuing read/write to command register&lt;/li>
&lt;li>data transfer (for writes): write data to data port&lt;/li>
&lt;li>handle interrupts&lt;/li>
&lt;/ul>
&lt;h2 id="37-hard-disk-drives">37 Hard Disk Drives&lt;/h2>
&lt;h3 id="371-the-interface">37.1 The Interface&lt;/h3>
&lt;p>The drive consists of a large number of sectors (512-byte blocks), each of which can be read or written&lt;/p>
&lt;p>View the disk as an array of sectors (扇区)&lt;/p>
&lt;ul>
&lt;li>multi-sector operations are possible&lt;/li>
&lt;li>only guarantee a single 512-byte write is atomic&lt;/li>
&lt;/ul>
&lt;p>Efficiency:&lt;/p>
&lt;ul>
&lt;li>access closer blocks are faster than farther ones&lt;/li>
&lt;li>sequential read or write are faster than the random access manner&lt;/li>
&lt;/ul>
&lt;h3 id="372-basic-geometry">37.2 Basic Geometry&lt;/h3>
&lt;ul>
&lt;li>platter: 磁盘&lt;/li>
&lt;li>surface: the 2 sides of a platter&lt;/li>
&lt;li>spindle: 轴&lt;/li>
&lt;li>track: 道, concentric circles of sectors&lt;/li>
&lt;li>disk head&lt;/li>
&lt;li>disk arm&lt;/li>
&lt;/ul>
&lt;h3 id="373-a-simple-disk-drive">37.3 A Simple Disk Drive&lt;/h3>
&lt;p>I/O time:&lt;/p>
&lt;ul>
&lt;li>rotational delay&lt;/li>
&lt;li>seek time
&lt;ul>
&lt;li>acceleration&lt;/li>
&lt;li>coasting&lt;/li>
&lt;li>deceleration&lt;/li>
&lt;li>settling (significant)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>transfer&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Track skew&lt;/strong>: sequential reads can be properly serviced when crossing track boundaries&lt;/p>
&lt;p>&lt;strong>Multi-zoned&lt;/strong> disk drives:&lt;/p>
&lt;ul>
&lt;li>a zone is a consecutive set of tracks on a surface&lt;/li>
&lt;li>each zone has the same number of sectors per track&lt;/li>
&lt;li>outer zones have more sectors&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Cache&lt;/strong> (track buffer):&lt;/p>
&lt;ul>
&lt;li>read: to read in all of the sectors on that track and cache them in its memory&lt;/li>
&lt;li>write
&lt;ul>
&lt;li>write back: acknowledge when puts into memory&lt;/li>
&lt;li>write through: acknowledge after actual writes&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="374-io-time-doing-the-math">37.4 I/O Time: Doing The Math&lt;/h3>
&lt;p>The &lt;strong>rate of I/O&lt;/strong>:&lt;/p>
&lt;p>$$
T_\mathrm{I/O} = T_\mathrm{seek}+T_\mathrm{rotation}+T_\mathrm{transfer}
$$&lt;/p>
&lt;p>$$
R_\mathrm{I/O}=\cfrac{Size_\mathrm{transfer}}{T_\mathrm{I/O}}
$$&lt;/p>
&lt;ul>
&lt;li>a huge gap in drive performance between random and sequential workloads&lt;/li>
&lt;li>a large difference in performance between high-end “performance” drives and low-end “capacity” drives&lt;/li>
&lt;/ul>
&lt;h3 id="375-disk-scheduling">37.5 Disk Scheduling&lt;/h3>
&lt;p>Principle: &lt;strong>shortest job first&lt;/strong>, &lt;strong>SJF&lt;/strong>&lt;/p>
&lt;p>&lt;strong>Shortest-seek-time-first&lt;/strong> (&lt;strong>SSTF&lt;/strong>, also shortest-seek-first or &lt;strong>SSF&lt;/strong>): picking requests on nearest track to complete first&lt;/p>
&lt;p>problems:&lt;/p>
&lt;ul>
&lt;li>the drive geometry is not available to the OS (fix: nearest-block-first, NBF)&lt;/li>
&lt;li>starvation: a steady stream to the closest track will block the access to other tracks&lt;/li>
&lt;/ul>
&lt;h4 id="elevator-algorithm">Elevator algorithm&lt;/h4>
&lt;p>&lt;strong>SCAN&lt;/strong>: simply moves back and forth across the disks&lt;/p>
&lt;p>&lt;strong>F-SCAN&lt;/strong>: freezes the queue when doing a sweep (avoid starvation of far-away requests)&lt;/p>
&lt;p>&lt;strong>C-SCAN (Circular SCAN)&lt;/strong>: only sweeps from outer-to-inner (more fair to the inner and outer tracks)&lt;/p>
&lt;blockquote>
&lt;p>Prevent fights from taking place on elevators!&lt;/p>
&lt;/blockquote>
&lt;p>SCAN and SSTF does not adhere to SJF closly: they ignore rotation&lt;/p>
&lt;h4 id="sptf-shortest-positioning-time-first">SPTF: Shortest positioning time first&lt;/h4>
&lt;p>Also called &lt;strong>shortest access time first&lt;/strong>, &lt;strong>SATF&lt;/strong>&lt;/p>
&lt;blockquote>
&lt;p>In engineering, it turns out “it depends” is almost always the answer, reflecting that trade-offs are part of the life of the engineer.&lt;/p>
&lt;/blockquote>
&lt;p>On modern drives both seek and rotation are roughly equivalent&lt;/p>
&lt;p>SPTF is usually performed inside a drive&lt;/p>
&lt;h4 id="other-scheduling-issues">Other scheduling issues&lt;/h4>
&lt;p>&lt;strong>Where to perform scheduling&lt;/strong>: The OS scheduler usually picks (what it thinks) some best few requests (say 16) and issues them all to disk; the disk then uses its internal knowledge to service said requests in the best possible (SPTF) order.&lt;/p>
&lt;p>&lt;strong>I/O merging&lt;/strong>: merge I/O requests by the OS&lt;/p>
&lt;p>&lt;strong>Non-work-conserving&lt;/strong>: waiting for some time for a possible better request&lt;/p>
&lt;h2 id="38-redundant-arrays-of-inexpensive-disks-raids">38 Redundant Arrays of Inexpensive Disks (RAIDs)&lt;/h2>
&lt;p>&lt;strong>RAID&lt;/strong>: a technique to use multiple disks in concert to build a faster, bigger, and more reliable disk system&lt;/p>
&lt;ul>
&lt;li>externally looks like a disk&lt;/li>
&lt;li>internally consisting multiple disks, memory and processor(s)&lt;/li>
&lt;/ul>
&lt;h3 id="381-interface-and-raid-internals">38.1 Interface And RAID Internals&lt;/h3>
&lt;p>To perform a logical I/O:&lt;/p>
&lt;ul>
&lt;li>calculate the actual disk(s)&lt;/li>
&lt;li>perform one or more physical I/Os&lt;/li>
&lt;/ul>
&lt;h3 id="382-fault-model">38.2 Fault Model&lt;/h3>
&lt;blockquote>
&lt;p>RAIDs are designed to detect and recover from certain kinds of disk faults.&lt;/p>
&lt;/blockquote>
&lt;p>The &lt;strong>fail-stop&lt;/strong> fault model: a not-working disk is assumed permanently lost&lt;/p>
&lt;h3 id="383-how-to-evaluate-a-raid">38.3 How To Evaluate A RAID&lt;/h3>
&lt;p>Characteristics:&lt;/p>
&lt;ul>
&lt;li>capacity&lt;/li>
&lt;li>reliability&lt;/li>
&lt;li>performance&lt;/li>
&lt;/ul>
&lt;h3 id="384-raid-level-0-striping">38.4 RAID Level 0: Striping&lt;/h3>
&lt;blockquote>
&lt;p>RAID level 0, or striping as it is better known, serves as an excellent upper-bound on performance and capacity.&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Striping&lt;/strong>: no redundancy; spread the blocks of the array across the disks in a round-robin fashion to get the most parrallelism.&lt;/p>
&lt;p>Simple striping:&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">Disk 0&lt;/th>
&lt;th style="text-align:center">Disk 1&lt;/th>
&lt;th style="text-align:center">Disk 2&lt;/th>
&lt;th style="text-align:center">Disk 3&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;td style="text-align:center">6&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">8&lt;/td>
&lt;td style="text-align:center">9&lt;/td>
&lt;td style="text-align:center">10&lt;/td>
&lt;td style="text-align:center">11&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">12&lt;/td>
&lt;td style="text-align:center">13&lt;/td>
&lt;td style="text-align:center">14&lt;/td>
&lt;td style="text-align:center">15&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>&lt;strong>Stripe&lt;/strong>: blocks in the same row (0, 1, 2, 3)&lt;/p>
&lt;p>Striping with a bigger chunk size (8KB, 2 blocks, a stripe consists of 32KB of data):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">Disk 0&lt;/th>
&lt;th style="text-align:center">Disk 1&lt;/th>
&lt;th style="text-align:center">Disk 2&lt;/th>
&lt;th style="text-align:center">Disk 3&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">&lt;strong>6&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;td style="text-align:center">&lt;strong>7&lt;/strong>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">8&lt;/td>
&lt;td style="text-align:center">10&lt;/td>
&lt;td style="text-align:center">12&lt;/td>
&lt;td style="text-align:center">14&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">9&lt;/td>
&lt;td style="text-align:center">11&lt;/td>
&lt;td style="text-align:center">13&lt;/td>
&lt;td style="text-align:center">15&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="chunk-sizes">Chunk sizes&lt;/h4>
&lt;ul>
&lt;li>small chunk size: on large files, positioning more on a single disk&lt;/li>
&lt;li>large chunk size: less parallelism, but also less posisioning time&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Thus, determining the “best” chunk size is hard to do, as it requires a great deal of knowledge about the workload presented to the disk system.&lt;/p>
&lt;/blockquote>
&lt;h4 id="raid-0-analysis">RAID-0 Analysis&lt;/h4>
&lt;ul>
&lt;li>capacity: perfect&lt;/li>
&lt;li>reliability: any disk failure will lead to data loss&lt;/li>
&lt;li>performance: all disks are utilized, often in parallel&lt;/li>
&lt;/ul>
&lt;h4 id="evaluating-raid-performances">Evaluating RAID Performances&lt;/h4>
&lt;p>Two performance metrics:&lt;/p>
&lt;ul>
&lt;li>single-request latency&lt;/li>
&lt;li>steady-state throughput (critical in high-perfromance environments)&lt;/li>
&lt;/ul>
&lt;p>Two types of workloads (considered here):&lt;/p>
&lt;ul>
&lt;li>sequential: large contiguous chunks
&lt;ul>
&lt;li>searching through a large file for a keyword&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>random: samll requests, to different random locations on disk
&lt;ul>
&lt;li>transactional workloads on a database management system&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Denote the sequential and random workload&amp;rsquo;s throughput as:&lt;/p>
&lt;ul>
&lt;li>$S$ for sequential&lt;/li>
&lt;li>$R$ for random&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;h3 id="385-raid-level-1-mirroring">38.5 RAID Level 1: Mirroring&lt;/h3>
&lt;p>&lt;strong>RAID-10&lt;/strong> (or &lt;strong>RAID 1+0&lt;/strong>, &lt;strong>stripe of mirrors&lt;/strong>):&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">Disk 0&lt;/th>
&lt;th style="text-align:center">Disk 1&lt;/th>
&lt;th style="text-align:center">Disk 2&lt;/th>
&lt;th style="text-align:center">Disk 3&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">6&lt;/td>
&lt;td style="text-align:center">6&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;blockquote>
&lt;p>RAID-01&lt;/p>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">Disk 0&lt;/th>
&lt;th style="text-align:center">Disk 1&lt;/th>
&lt;th style="text-align:center">Disk 2&lt;/th>
&lt;th style="text-align:center">Disk 3&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">6&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;td style="text-align:center">6&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;/blockquote>
&lt;ul>
&lt;li>read: either copy&lt;/li>
&lt;li>write: update both&lt;/li>
&lt;/ul>
&lt;h4 id="analysis">Analysis&lt;/h4>
&lt;blockquote>
&lt;p>&lt;strong>Consistent-update problem&lt;/strong>&lt;/p>
&lt;p>untimely failures on write: inconsistent copies&lt;/p>
&lt;p>solution: &lt;strong>write-ahead log&lt;/strong> and &lt;strong>recovery&lt;/strong> procedure&lt;/p>
&lt;/blockquote>
&lt;ul>
&lt;li>capacity: expensive, $N\cdot B/2$ for mirroring level = 2&lt;/li>
&lt;li>reliability: tolerate failure on any disk, up to $N/2$ disks can fail&lt;/li>
&lt;li>single request performance
&lt;ul>
&lt;li>read: sames as on a single disk ($R$, writes to two blocks)&lt;/li>
&lt;li>write: slightly higher than writing to a single disk (worst-case seek and rotational delay)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>steady-state throughput
&lt;ul>
&lt;li>write: ($\frac{N}{2}\cdot S$, writes to $N$ blocks)&lt;/li>
&lt;li>read: same as write ($\frac{N}{2}\cdot S$, each disk receives a request for every other block)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>random reads/writes
&lt;ul>
&lt;li>read: $N\cdot R$&lt;/li>
&lt;li>write: $\frac{N}{2}\cdot R$&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="386-raid-level-4-saving-space-with-parity">38.6 RAID Level 4: Saving Space With Parity&lt;/h3>
&lt;blockquote>
&lt;p>Parity-based approaches attempt to use less capacity and thus overcome the huge space penalty paid by mirrored systems. They do so at a cost, however: performance.&lt;/p>
&lt;/blockquote>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">Disk 0&lt;/th>
&lt;th style="text-align:center">Disk 1&lt;/th>
&lt;th style="text-align:center">Disk 2&lt;/th>
&lt;th style="text-align:center">Disk 3&lt;/th>
&lt;th style="text-align:center">Disk 4&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P0&lt;/strong>&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">4&lt;/td>
&lt;td style="text-align:center">5&lt;/td>
&lt;td style="text-align:center">6&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P1&lt;/strong>&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">8&lt;/td>
&lt;td style="text-align:center">9&lt;/td>
&lt;td style="text-align:center">10&lt;/td>
&lt;td style="text-align:center">11&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P2&lt;/strong>&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">12&lt;/td>
&lt;td style="text-align:center">13&lt;/td>
&lt;td style="text-align:center">14&lt;/td>
&lt;td style="text-align:center">15&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P3&lt;/strong>&lt;/em>&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>Simple function: &lt;strong>XOR&lt;/strong>, which makes a row having even number of 1&lt;/p>
&lt;h4 id="analysis-1">Analysis&lt;/h4>
&lt;ul>
&lt;li>capacity: $(N-1)\cdot B$&lt;/li>
&lt;li>reliability: tolerates 1 disk failure&lt;/li>
&lt;li>performance
&lt;ul>
&lt;li>steady-state: $(N-1)\cdot S$ (full-stripe write: calculate parity bits first then write all disks in parallel)&lt;/li>
&lt;li>random
&lt;ul>
&lt;li>read: $(N-1)\cdot R$&lt;/li>
&lt;li>write: $R/2$ (the parity disk is the bottleneck)
&lt;ul>
&lt;li>additive parity: read in parallel, calculate XOR, and write in parallel (larger RAIDs require a high number of reads)&lt;/li>
&lt;li>subtractive parity: $P_\mathrm{new}=(C_\mathrm{old}\oplus C_\mathrm{new})\oplus P_\mathrm{old}$ (&lt;em>Why not always better?&amp;ndash; because it has to read the parity disk?&lt;/em>)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Small-write problem&lt;/strong>: the parity disk is a bottleneck under small writes which prevents parallelism&lt;/p>
&lt;h3 id="387-raid-level-5-rotating-parity">38.7 RAID Level 5: Rotating Parity&lt;/h3>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th style="text-align:center">Disk 0&lt;/th>
&lt;th style="text-align:center">Disk 1&lt;/th>
&lt;th style="text-align:center">Disk 2&lt;/th>
&lt;th style="text-align:center">Disk 3&lt;/th>
&lt;th style="text-align:center">Disk 4&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td style="text-align:center">0&lt;/td>
&lt;td style="text-align:center">1&lt;/td>
&lt;td style="text-align:center">2&lt;/td>
&lt;td style="text-align:center">3&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P0&lt;/strong>&lt;/em>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">5&lt;/td>
&lt;td style="text-align:center">6&lt;/td>
&lt;td style="text-align:center">7&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P1&lt;/strong>&lt;/em>&lt;/td>
&lt;td style="text-align:center">4&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">10&lt;/td>
&lt;td style="text-align:center">11&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P2&lt;/strong>&lt;/em>&lt;/td>
&lt;td style="text-align:center">8&lt;/td>
&lt;td style="text-align:center">9&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">15&lt;/td>
&lt;td style="text-align:center">&lt;em>&lt;strong>P3&lt;/strong>&lt;/em>&lt;/td>
&lt;td style="text-align:center">12&lt;/td>
&lt;td style="text-align:center">13&lt;/td>
&lt;td style="text-align:center">14&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td style="text-align:center">&lt;em>&lt;strong>P4&lt;/strong>&lt;/em>&lt;/td>
&lt;td style="text-align:center">16&lt;/td>
&lt;td style="text-align:center">17&lt;/td>
&lt;td style="text-align:center">18&lt;/td>
&lt;td style="text-align:center">19&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;h4 id="analysis-2">Analysis&lt;/h4>
&lt;p>random write: $\frac{N}{4}\cdot R$ (four I/O operations) (&lt;em>aren&amp;rsquo;t the reads/writes parallel?&amp;ndash;the data disk itself serves as parity disk as well&lt;/em>)&lt;/p>
&lt;h2 id="39-interlude-files-and-directories">39 Interlude: Files and Directories&lt;/h2>
&lt;blockquote>
&lt;p>Thus, the OS must take extra care with such a device: this is where users keep data that they really care about.&lt;/p>
&lt;/blockquote>
&lt;h3 id="391-files-and-directories">39.1 Files And Directories&lt;/h3>
&lt;ul>
&lt;li>&lt;strong>file&lt;/strong>: linear array of bytes
&lt;ul>
&lt;li>low-level name (inode number)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;strong>directory&lt;/strong>
&lt;ul>
&lt;li>low-level name (inode number)&lt;/li>
&lt;li>a list of &lt;code>(user-readable name, low-level name)&lt;/code> pairs&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="393-creating-files">39.3 Creating Files&lt;/h3>
&lt;p>Create a new file: call &lt;code>open()&lt;/code> and pass &lt;code>O_CREAT&lt;/code> flag:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> fd &lt;span style="color:#f92672">=&lt;/span> open(&lt;span style="color:#e6db74">&amp;#34;foo&amp;#34;&lt;/span>, O_CREAT&lt;span style="color:#f92672">|&lt;/span>O_WRONLY&lt;span style="color:#f92672">|&lt;/span>O_TRUNC, S_IRUSR&lt;span style="color:#f92672">|&lt;/span>S_IWUSR);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>O_CREAT&lt;/code>: create the file if it does not exist&lt;/li>
&lt;li>&lt;code>O_WRONLY&lt;/code>: read only&lt;/li>
&lt;li>&lt;code>O_TRUNC&lt;/code>: if exists already, truncates it to a size of zero bytes&lt;/li>
&lt;/ul>
&lt;h3 id="395-reading-and-writing-but-not-sequentially">39.5 Reading And Writing, But Not Sequentially&lt;/h3>
&lt;p>Reading some random offsets within the document:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>off_t &lt;span style="color:#a6e22e">lseek&lt;/span>(&lt;span style="color:#66d9ef">int&lt;/span> fildes, off_t offset, &lt;span style="color:#66d9ef">int&lt;/span> whence);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;ul>
&lt;li>&lt;code>fildes&lt;/code>: file descriptor&lt;/li>
&lt;li>&lt;code>offset&lt;/code>: the file offset to a particular location&lt;/li>
&lt;li>&lt;code>whence&lt;/code>
&lt;ul>
&lt;li>&lt;code>SEEK_SET&lt;/code>: the offset is set to offset bytes&lt;/li>
&lt;li>&lt;code>SEEK_CUR&lt;/code>: the offset is set to its current location plus offset bytes&lt;/li>
&lt;li>&lt;code>SEEK_END&lt;/code>: the offset is set to the size of the file plus offset bytes&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Calling &lt;code>lseek()&lt;/code> does not perform a disk seek&lt;/p>
&lt;/blockquote>
&lt;p>The &amp;ldquo;current offset&amp;rdquo; tracked by the OS:&lt;/p>
&lt;ul>
&lt;li>implicitly updates when read or write&lt;/li>
&lt;li>explicitly updates with &lt;code>lseek&lt;/code>&lt;/li>
&lt;/ul>
&lt;p>The file structure:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> file {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">int&lt;/span> ref;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">char&lt;/span> readable;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">char&lt;/span> writable;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> inode &lt;span style="color:#f92672">*&lt;/span> ip;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> uint off;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>};
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>The &lt;strong>open file table&lt;/strong>:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> spinlock lock;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">struct&lt;/span> file file[NFILE];
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>} ftable;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="396-shared-file-table-entries-fork-and-dup">39.6 Shared File Table Entries: &lt;code>fork()&lt;/code> And &lt;code>dup()&lt;/code>&lt;/h3>
&lt;p>The open file table entries are shared by 1. the parent and child processes created by &lt;code>fork()&lt;/code>; 2. one file descriptor and its duplicates created by &lt;code>dup()&lt;/code>, with the reference count incremented.&lt;/p>
&lt;h3 id="397-writing-immediately-with-fsync">39.7 Writing Immediately With &lt;code>fsync()&lt;/code>&lt;/h3>
&lt;p>Calls &lt;code>fsync()&lt;/code> to force all dirty (not yet written) data to disk.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> rc &lt;span style="color:#f92672">=&lt;/span> write(fd, buffer, size); assert(rc &lt;span style="color:#f92672">==&lt;/span> size);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>In some cases, it is also needed to &lt;code>fsync()&lt;/code> the containing directory to ensure that the file is part of the directory.&lt;/p>
&lt;blockquote>
&lt;p>Not surprisingly, this type of detail is often overlooked, leading to many application-level bugs.&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;strong>&lt;code>mmap()&lt;/code> and persistent memory&lt;/strong>&lt;/p>
&lt;p>By combining the persistence of files with the access semantics of memory, file-backed memory mappings support a software abstraction called persistent memory.&lt;/p>
&lt;/blockquote>
&lt;h3 id="398-renaming-files">39.8 Renaming Files&lt;/h3>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>rename(&lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>old, &lt;span style="color:#66d9ef">char&lt;/span> &lt;span style="color:#f92672">*&lt;/span>new)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>rename()&lt;/code> call is usually implemented as an atomic call.&lt;/p>
&lt;p>An example to use this kind of guarantee:&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">int&lt;/span> fd &lt;span style="color:#f92672">=&lt;/span> open(&lt;span style="color:#e6db74">&amp;#34;foo.txt.tmp&amp;#34;&lt;/span>, O_WRONLY&lt;span style="color:#f92672">|&lt;/span>O_CREAT&lt;span style="color:#f92672">|&lt;/span>O_TRUNC, S_IRUSR&lt;span style="color:#f92672">|&lt;/span>S_IWUSR);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>write(fd, buffer, size);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>fsync(fd);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>close(fd);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>rename(&lt;span style="color:#e6db74">&amp;#34;foo.txt.tmp&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;foo.txt&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="399-getting-information-about-files">39.9 Getting Information About Files&lt;/h3>
&lt;p>Use &lt;code>stat()&lt;/code> or &lt;code>fstat()&lt;/code> to access metadata of files or directories.&lt;/p>
&lt;h3 id="3910-removing-files">39.10 Removing Files&lt;/h3>
&lt;p>Use &lt;code>unlink()&lt;/code> to remove files.&lt;/p>
&lt;h3 id="3911-making-directories">39.11 Making Directories&lt;/h3>
&lt;p>Use &lt;code>mkdir()&lt;/code> to create a directory.&lt;/p>
&lt;p>An empty directory has two entries:&lt;/p>
&lt;ul>
&lt;li>refer to itself&lt;/li>
&lt;li>refer to its parent&lt;/li>
&lt;/ul>
&lt;h3 id="3912-reading-directories">39.12 Reading Directories&lt;/h3>
&lt;p>Use &lt;code>opendir()&lt;/code>, &lt;code>readdir()&lt;/code> and &lt;code>closedir()&lt;/code> to read contents of a directory.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-c" data-lang="c">&lt;span style="display:flex;">&lt;span>DIR &lt;span style="color:#f92672">*&lt;/span> dp &lt;span style="color:#f92672">=&lt;/span> opendir(&lt;span style="color:#e6db74">&amp;#34;.&amp;#34;&lt;/span>);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>assert(dp &lt;span style="color:#f92672">!=&lt;/span> NULL);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">struct&lt;/span> dirent &lt;span style="color:#f92672">*&lt;/span> d;
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">while&lt;/span> ((d &lt;span style="color:#f92672">=&lt;/span> readdir(dp)) &lt;span style="color:#f92672">!=&lt;/span> NULL) {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> printf(&lt;span style="color:#e6db74">&amp;#34;%lu %s&lt;/span>&lt;span style="color:#ae81ff">\n&lt;/span>&lt;span style="color:#e6db74">&amp;#34;&lt;/span>, (&lt;span style="color:#66d9ef">unsigned&lt;/span> &lt;span style="color:#66d9ef">long&lt;/span>) d&lt;span style="color:#f92672">-&amp;gt;&lt;/span>d_ino, d&lt;span style="color:#f92672">-&amp;gt;&lt;/span>d_name);
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>}
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>closedir(dp);
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h3 id="3913-deleting-directories">39.13 Deleting Directories&lt;/h3>
&lt;p>Call &lt;code>rmdir()&lt;/code> to delete a directory.&lt;/p>
&lt;h3 id="3914-hard-links">39.14 Hard Links&lt;/h3>
&lt;p>Call &lt;code>link()&lt;/code> to make an entry in the file system tree, simply creates another name in the directory and refers it to the &lt;strong>same inode number&lt;/strong>.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ln file file2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>When creating a file, two things happened:&lt;/p>
&lt;ol>
&lt;li>making a structure (the inode) that tracks relevent information&lt;/li>
&lt;li>linking a human-readable name to that file, and put that link into a directory&lt;/li>
&lt;/ol>
&lt;/blockquote>
&lt;p>When the file system unlinks a file, it checks the reference count (link count) within the inode number, and only free the node and related data blocks when it reaches zero.&lt;/p>
&lt;h3 id="3915-symbolic-links">39.15 Symbolic Links&lt;/h3>
&lt;p>Call &lt;code>symlink()&lt;/code> to make an symbolic link (soft link) to a file.&lt;/p>
&lt;div class="highlight">&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;">&lt;code class="language-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>ln -s file file2
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>Differences from hard links:&lt;/p>
&lt;ul>
&lt;li>symbolic links is actually a file itself of a different type&lt;/li>
&lt;li>possibility for dangling reference&lt;/li>
&lt;/ul>
&lt;h3 id="3916-permission-bits-and-access-control-lists">39.16 Permission Bits And Access Control Lists&lt;/h3>
&lt;p>&lt;strong>Permission bits&lt;/strong> consist of three groupings:&lt;/p>
&lt;ul>
&lt;li>owner&lt;/li>
&lt;li>group&lt;/li>
&lt;li>other (anyone)&lt;/li>
&lt;/ul>
&lt;p>The owner of the file can change the permissions.&lt;/p>
&lt;blockquote>
&lt;p>But now you have learned an even more important lesson: there can be no secrets in a good marriage, even within the file system.&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>&lt;strong>The &lt;em>Time Of Check To Time Of Use (TOCTTOU)&lt;/em> problem&lt;/strong>&lt;/p>
&lt;p>The problem is caused by the gap between the check and the update.&lt;/p>
&lt;/blockquote>
&lt;h3 id="3917-making-and-mounting-a-file-system">39.17 Making And Mounting A File System&lt;/h3>
&lt;blockquote>
&lt;p>And mkfs said, let there be a file system!&lt;/p>
&lt;/blockquote>
&lt;p>&lt;strong>Mount&lt;/strong>: to take an existing directory as a target &lt;strong>mount point&lt;/strong> and paste a new file system onto the directory tree at that point.&lt;/p>
&lt;blockquote>
&lt;p>Mount unfies all file systems into one tree, making naming uniform and convenient.&lt;/p>
&lt;/blockquote>
&lt;h2 id="40-file-system-implementation">40 File System Implementation&lt;/h2>
&lt;p>&lt;strong>vsfs&lt;/strong> (the &lt;strong>Very Simple File System&lt;/strong>)&lt;/p>
&lt;blockquote>
&lt;p>The file system is pure software; unlike our development of CPU and memory virtualization, we will not be adding hardware features to make some aspect of the file system work better (though we will want to pay attention to device characteristics to make sure the file system works well).&lt;/p>
&lt;/blockquote>
&lt;h3 id="402-overall-organization">40.2 Overall Organization&lt;/h3>
&lt;p>The disk is divided into:&lt;/p>
&lt;ul>
&lt;li>data blocks: user data&lt;/li>
&lt;li>inodes&lt;/li>
&lt;li>allocation structures: to track whether inodes or data blocks are free or allocated&lt;/li>
&lt;li>superblock: contains information about the fie system&lt;/li>
&lt;/ul>
&lt;h3 id="403-file-organization-the-inode">40.3 File Organization: The Inode&lt;/h3>
&lt;p>&lt;strong>Inode&lt;/strong> (&lt;strong>index node&lt;/strong>): the data structure that holds the metadata of a given file&lt;/p>
&lt;p>&lt;strong>i-number&lt;/strong> (&lt;strong>low-level name&lt;/strong>): the number that the inode is implicitly referred to; disk position can be calculated by the number&lt;/p>
&lt;p>Calculation:&lt;/p>
&lt;p>$$
block = (inumber\times sizeof(inode)) / blockSize
$$&lt;/p>
&lt;p>$$
sector = ((block\times blockSize) + inodeStartAddr) / sectorSize
$$&lt;/p>
&lt;p>Metadata in a inode:&lt;/p>
&lt;ul>
&lt;li>type: directory or file (kept in the directory entry and thus is not found in the inode)&lt;/li>
&lt;li>size&lt;/li>
&lt;li>number of blocks&lt;/li>
&lt;li>protection information&lt;/li>
&lt;li>time&lt;/li>
&lt;li>data block position on disk&lt;/li>
&lt;/ul>
&lt;h4 id="the-multi-level-index">The Multi-Level Index&lt;/h4>
&lt;p>&lt;strong>Indirect pointer&lt;/strong>: points to a block that contains pointers which each points to user data&lt;/p>
&lt;p>An inode may have some fixed number of direct pointers and a single indirect pointer.&lt;/p>
&lt;p>If a file grows large enough, an indirect block is allocated (from the data block) and the inode&amp;rsquo;s indirect pointer is set to point to it.&lt;/p>
&lt;p>&lt;strong>Extent-based approaches&lt;/strong>: a disk pointer plus a length; allow for more than one extent&lt;/p>
&lt;p>&lt;strong>Linked-based approaches&lt;/strong>: use a linked list to keep track all blocks of a file&lt;/p>
&lt;p>&lt;strong>Double indirect pointer&lt;/strong>: refer to a block that contains indirect pointers&lt;/p>
&lt;blockquote>
&lt;p>Most files are small: a small number of direct pointers is sufficient&lt;/p>
&lt;/blockquote>
&lt;h3 id="404-directory-organization">40.4 Directory Organization&lt;/h3>
&lt;p>Deleting a file can leave an empty space in the middle of the directory; a new entry may reuse an old, bigger, deleted entry.&lt;/p>
&lt;p>File systems often streat directories as a special type of file, and thus they have inodes in the inode table.&lt;/p>
&lt;h3 id="405-free-space-management">40.5 Free Space Management&lt;/h3>
&lt;p>To track free inode/data blocks:&lt;/p>
&lt;ul>
&lt;li>use bitmap&lt;/li>
&lt;li>use free list&lt;/li>
&lt;li>use more soplisticated ds, e.g. B-tree&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>Pre-allocation&lt;/strong> policy: reserve a sequence of blocks for a newly-created file, to guarantee that a portion of the file is contiguous on the disk.&lt;/p>
&lt;h3 id="406-access-paths-reading-and-writing">40.6 Access Paths: Reading and Writing&lt;/h3>
&lt;h4 id="reading-a-file-from-disk">Reading A File From Disk&lt;/h4>
&lt;p>The timeline of reading a file &lt;code>/foo/bar&lt;/code>:&lt;/p>
&lt;p>&lt;strong>&lt;code>open(&amp;quot;/foo/bar&amp;quot;, O_RDONLY)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>read the root inode (&lt;code>/&lt;/code>) (whose i-number is well known; &lt;code>2&lt;/code> in most UNIX FSs)&lt;/li>
&lt;li>read &lt;code>/&lt;/code> data (to look for an entry for &lt;code>foo&lt;/code>)&lt;/li>
&lt;li>read &lt;code>foo&lt;/code> inode&lt;/li>
&lt;li>read &lt;code>foo&lt;/code> data (to look for an entry for &lt;code>bar&lt;/code>)&lt;/li>
&lt;li>read &lt;code>bar&lt;/code> inode&lt;/li>
&lt;/ul>
&lt;p>&lt;strong>&lt;code>read(...)&lt;/code>&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>read &lt;code>bar&lt;/code> inode&lt;/li>
&lt;li>read specific block of &lt;code>bar&lt;/code>&amp;rsquo;s data&lt;/li>
&lt;li>write &lt;code>bar&lt;/code> inode for last access time&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Yes, life can get pretty bad when reading a file; as you’re about to find out, writing out a file (and especially, creating a new one) is even worse.&lt;/p>
&lt;/blockquote>
&lt;blockquote>
&lt;p>Reads don&amp;rsquo;t access allocation structures (e.g. bitmaps); they are only accessed when allocation is needed. There is no need to make sure a block is allocated when the inode already points to it&lt;/p>
&lt;/blockquote>
&lt;h4 id="writing-a-file-to-disk">Writing A File To Disk&lt;/h4>
&lt;p>Each write to a file logically generates 5 I/Os:&lt;/p>
&lt;ul>
&lt;li>read file inode&lt;/li>
&lt;li>read data bitmap&lt;/li>
&lt;li>write data bitmap (to mark the newly-allocated block as used)&lt;/li>
&lt;li>write file data&lt;/li>
&lt;li>write file inode&lt;/li>
&lt;/ul>
&lt;p>For file creation:&lt;/p>
&lt;ul>
&lt;li>read inode and inode data traversly to find the containing directory&lt;/li>
&lt;li>read containing directory data&lt;/li>
&lt;li>read inode bitmap (find a free inode)&lt;/li>
&lt;li>write inode bitmap (to mark the inode allocated)&lt;/li>
&lt;li>write directory data (link)&lt;/li>
&lt;li>read/write directory inode&lt;/li>
&lt;li>read/write inode of the new file (initialize)&lt;/li>
&lt;li>additional I/O might be needed if the directory needs to grow&lt;/li>
&lt;/ul>
&lt;h3 id="407-caching-and-buffering">40.7 Caching and Buffering&lt;/h3>
&lt;blockquote>
&lt;p>Most file systems aggressively use system memory (DRAM) to cache important blocks.&lt;/p>
&lt;/blockquote>
&lt;p>Early file systems use a fixed-size cache to hold popular blocks; this &lt;strong>static partitioning&lt;/strong> method can be wasteful.&lt;/p>
&lt;p>Modern systems use a &lt;strong>dynamic partitioning&lt;/strong> approach.&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>Static vs. dynamic partitioning&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>static: predictable performance&lt;/li>
&lt;li>dynamic: better utilization; more complex to implement&lt;/li>
&lt;/ul>
&lt;/blockquote>
&lt;p>&lt;strong>Buffering&lt;/strong>: delay writes to reduce I/O&lt;/p>
&lt;blockquote>
&lt;p>In this case, laziness (in writing blocks to disk) is a virtue.&lt;/p>
&lt;/blockquote>
&lt;h2 id="41-locality-and-the-fast-file-system">41 Locality and The Fast File System&lt;/h2>
&lt;h3 id="411-the-problem-poor-performance">41.1 The Problem: Poor Performance&lt;/h3>
&lt;p>Main issue of the old UNIX file system: data was spread all over the place, thus had expensive positioning costs&lt;/p>
&lt;p>Some problems:&lt;/p>
&lt;ul>
&lt;li>the inode and the data block are often far away&lt;/li>
&lt;li>the file system is getting more and more fragmented&lt;/li>
&lt;li>the block size is too small, so that transferring data is inefficient&lt;/li>
&lt;/ul>
&lt;h3 id="413-organizing-structure-the-cylinder-group">41.3 Organizing Structure: The Cylinder Group&lt;/h3>
&lt;p>&lt;strong>Cylinder&lt;/strong> (柱面): a set of tracks on different surfaces that are the same distance from the center&lt;/p>
&lt;p>FFS divides the disk into a number of cylinder groups.&lt;/p>
&lt;p>As modern drives only export a logical address space of blocks, modern file systems instead organize the drive into &lt;strong>block groups&lt;/strong>, each of which is &lt;strong>a consecutive portion of the disk&amp;rsquo;s address space&lt;/strong>. By placing two files within the same group, FFS can ensure that accessing one after the other will not result in long seek time.&lt;/p>
&lt;p>FFS needs to have the ability to place files and directories into a groups and track relevant information, thus all the structures that a file system is expected to have is included in &lt;strong>each group&lt;/strong>.&lt;/p>
&lt;p>The components of a single cylinder group:&lt;/p>
&lt;ul>
&lt;li>a copy of the super block&lt;/li>
&lt;li>a per-group inode bitmap and data bitmap&lt;/li>
&lt;li>inode region&lt;/li>
&lt;li>data block region&lt;/li>
&lt;/ul>
&lt;h3 id="414-policies-how-to-allocate-files-and-directories">41.4 Policies: How To Allocate Files and Directories&lt;/h3>
&lt;blockquote>
&lt;p>The basic mantra is simple: keep related stuff together (and its corollary, keep unrelated stuff far apart).&lt;/p>
&lt;/blockquote>
&lt;p>A few placement heuristics:&lt;/p>
&lt;ul>
&lt;li>directories: find the group
&lt;ul>
&lt;li>with a low number of allocated directories (to balance directories across groups)&lt;/li>
&lt;li>with a high number of free inodes (to be able to allocate a bunch of files)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>files:
&lt;ul>
&lt;li>allocate the data blocks in the same group as its inode&lt;/li>
&lt;li>places all files that are in the same directory in the cylinder group of the directory they are in&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;h3 id="416-the-large-file-exception">41.6 The Large-File Exception&lt;/h3>
&lt;p>Filling an entire block group with large files will prevents related files from being placed within the same group.&lt;/p>
&lt;p>FFS&amp;rsquo; policy for large files: after some number of blocks are allocated into the first block group, it places the next large chunk of the file in another block group.&lt;/p>
&lt;p>To address the performance problem: choosing chunk size carefully. By &lt;strong>amotization&lt;/strong>, if the chunk size is large enough, the seek time will be relatively little.&lt;/p>
&lt;p>&lt;strong>FFS&amp;rsquo;s approach&lt;/strong>&lt;/p>
&lt;ul>
&lt;li>the first 12 blocks are placed in the same group as the inode&lt;/li>
&lt;li>each subsquent indirect block and the blocks it points to are placed in a different group&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Note that the trend in disk drives is that transfer rate improves fairly rapidly, as disk manufacturers are good at cramming more bits into the same surface, but the mechanical aspects of drives related to seeks (disk arm speed and the rate of rotation) improve rather slowly.&lt;/p>
&lt;/blockquote>
&lt;h3 id="417-a-few-other-things-about-ffs">41.7 A Few Other Things About FFS&lt;/h3>
&lt;p>&lt;strong>Internal fragmentation&lt;/strong>: 4KB blocks are used by many 2KB files&lt;/p>
&lt;p>&lt;strong>Sub-blocks&lt;/strong>: 512-byte blocks can be allocated to files; copy to a 4KB block when the size grows beyond 4KB&lt;/p>
&lt;p>To address the performance issue caused by copies: buffer writes, to avoid this in most cases.&lt;/p>
&lt;p>A refined disk layout (&lt;strong>parameterization&lt;/strong>): skipping a block when issuing a sequential read (e.g. 0 6 1 7 2 8 3 9 4 10 5 11); to issue the performance problem: using track buffer.&lt;/p>
&lt;p>FFS also supports:&lt;/p>
&lt;ul>
&lt;li>long file names&lt;/li>
&lt;li>symbolic links&lt;/li>
&lt;/ul>
&lt;blockquote>
&lt;p>Certainly all modern systems account for the main lesson of FFS: treat the disk like it’s a disk.&lt;/p>
&lt;/blockquote></content></item></channel></rss>