<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>study on liang2kl's Blog</title><link>https://yeshe.ng/blog/tags/study/</link><description>Recent content in study 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>Thu, 28 Jul 2022 23:42:15 +0800</lastBuildDate><atom:link href="https://yeshe.ng/blog/tags/study/index.xml" rel="self" type="application/rss+xml"/><item><title>中国主流媒体视角与游戏产业发展的割裂——以《人民日报》游戏相关报道（1989-2021）为例</title><link>https://yeshe.ng/blog/posts/2022/07/media-game/</link><pubDate>Thu, 28 Jul 2022 23:42:15 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/07/media-game/</guid><description>写作与沟通游戏主题长文终稿</description><content>&lt;p>&lt;em>本文为 2022 年夏季学期程祥钰老师《写作与沟通》课程游戏主题长文终稿&lt;/em>&lt;/p>
&lt;blockquote>
&lt;p>&lt;strong>摘要&lt;/strong> 主流媒体几乎参与了中国游戏产业发展的全过程，但其报道相较于游戏产业的发展而言存在着缺失和偏差，存在着明显的割裂。纵观中国游戏产业发展史，这种割裂长期存在，总体上在缓慢弥合，但最近又有重新加大的趋势。由于主流媒体宣传官方主张、传播主流声音的性质，其与游戏产业的发展存在着产生割裂的必然性，而游戏相关政策与主流舆论对割裂的产生具有重要作用。二者之间割裂的影响具有两面性，游戏产业的健康发展需要主流媒体保持适当的距离。&lt;/p>
&lt;p>&lt;strong>关键词&lt;/strong> 游戏；游戏产业；主流媒体；游戏报道；人民日报&lt;/p>
&lt;/blockquote>
&lt;p>主流媒体对游戏&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>的报道自其进入中国以来就一直存在。《人民日报》对游戏的间接报道最早可追溯到 1981 年&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>；此后，随着中国游戏产业的不断发展，《人民日报》对游戏的直接报道数量基本呈递增趋势，目前游戏已成为其热门话题之一。可见，主流媒体几乎参与了中国游戏产业发展的全过程。&lt;/p>
&lt;p>然而，尽管主流媒体似乎并未缺席中国游戏行业的发展历史，且主流媒体的视角与中国游戏产业的发展大体上步调一致，但其在某些历史阶段的报道忽略了游戏产业发展的重要历程，且立场存在明显的偏向性；主流媒体的视角与游戏产业的发展之间存在着缺失和偏差。例如，对于 20 世纪 80 年代末到 21 世纪初之间的历史时期，在游戏产业发展史的叙事中，“20 世纪 90 年代新出现的游戏杂志群开始频繁讨论游戏中的文化议题，其核心主张便是如何生产作为文化抵抗媒介的国产游戏。”，自此产生了第一批国产游戏&lt;sup id="fnref:3">&lt;a href="#fn:3" class="footnote-ref" role="doc-noteref">3&lt;/a>&lt;/sup>；而在《人民日报》的叙事中，没有一篇文章提及此历史进程，反之在重复讨论“电子游戏热中的负效应”&lt;sup id="fnref:4">&lt;a href="#fn:4" class="footnote-ref" role="doc-noteref">4&lt;/a>&lt;/sup>、“电子游戏的暴力内容对孩子不利”&lt;sup id="fnref:5">&lt;a href="#fn:5" class="footnote-ref" role="doc-noteref">5&lt;/a>&lt;/sup>、“青少年沉迷电子游戏现象”&lt;sup id="fnref:6">&lt;a href="#fn:6" class="footnote-ref" role="doc-noteref">6&lt;/a>&lt;/sup>等游戏的负面形象。主流媒体的视角并不总是与游戏产业的发展相统一，二者之间存在明显的割裂。&lt;/p>
&lt;p>对于媒体报道对游戏的舆论评价所产生的影响，何威等在研究《人民日报》游戏报道的话语变迁时指出，“不论普罗大众、文化精英或政府官员，多少都依赖媒体报道提供素材、主题、框架、观点，来理解和对待游戏及游戏玩家”&lt;sup id="fnref:7">&lt;a href="#fn:7" class="footnote-ref" role="doc-noteref">7&lt;/a>&lt;/sup>。具体到主流媒体，汉娜·沃曼在研究西方媒体对中国游戏文化和商业的报道时指出，“官方媒体成为了一种精英象征，代表社会上‘最有发言权’的群体，也因此可以优先影响‘大众的思想’”&lt;sup id="fnref:8">&lt;a href="#fn:8" class="footnote-ref" role="doc-noteref">8&lt;/a>&lt;/sup>。正因为《人民日报》等主流媒体反映了官方的态度，且有着巨大的舆论影响力，主流媒体的视角与游戏产业发展之间的割裂，以及这种割裂对中国游戏产业带来的深刻影响，值得关注和反思。&lt;/p>
&lt;p>本文将以《人民日报》以游戏为主题的文章&lt;sup id="fnref:9">&lt;a href="#fn:9" class="footnote-ref" role="doc-noteref">9&lt;/a>&lt;/sup>为例，从中国主流媒体游戏报道话语演变及游戏产业发展的历史入手，揭示二者之间的割裂性，分析这种关系背后的影响因素，进而探讨这种关系对中国游戏产业的影响。&lt;/p>
&lt;h2 id="中国游戏产业发展的过程中主流媒体的视角看向何处">中国游戏产业发展的过程中，主流媒体的视角看向何处&lt;/h2>
&lt;p>中国游戏产业发展的历史可以清晰地划分为若干个阶段，每个发展阶段既有标志性的事件作为开端，又有明确的主题贯穿其中。《人民日报》的视角是否能够覆盖中国游戏发展的重大事件，其报道是否能够呈现当时游戏产业发展阶段的现状？我们以中国游戏发展的标志性事件为点、以发展阶段中的主要趋势为线，对《人民日报》游戏报道的视角进行分析。&lt;/p>
&lt;p>1981 年北京第一轻工业研究研制出 YQ-1 型游戏机，标志着当代中国游戏史的开端。虽然同年《人民日报》第一次在报道中提到了电子游戏，但距离《人民日报》第一篇以游戏为主题的文章发表还有 8 年时间。在这段时间里，中国游戏产业尚未开始全面发展；电子游戏以学习机的面目出现，其游戏的本质被压抑，与大众尚有一定距离，游戏并未产生足够的社会影响。因此，游戏产业并不足以得到主流媒体的关注，这导致了主流媒体叙事中这段历史中的缺失。&lt;/p>
&lt;p>1980 年代末，中国东南沿海逐渐具备规模化的任天堂兼容机生产能力，电子游戏开始成为社会现象&lt;sup id="fnref:10">&lt;a href="#fn:10" class="footnote-ref" role="doc-noteref">10&lt;/a>&lt;/sup>，自然也逐渐被主流媒体所关注。《人民日报》对游戏的第一篇直接报道（1989 年）便出现在此时期：这篇报道聚焦上海大量游戏机涌现而导致学生沉迷的问题，呼吁有关部门加强管理&lt;sup id="fnref:11">&lt;a href="#fn:11" class="footnote-ref" role="doc-noteref">11&lt;/a>&lt;/sup>，可见此时电子游戏已具有相当的社会影响。值得注意的是，此后 10 年《人民日报》对于游戏的 15 篇报道均停留在其消极的社会影响上，并未关注电子游戏本身或其背后产业的发展，直到1999 年才出现第一篇介绍电子游戏的文章&lt;sup id="fnref:12">&lt;a href="#fn:12" class="footnote-ref" role="doc-noteref">12&lt;/a>&lt;/sup>。可见，此时期《人民日报》的游戏报道虽然逐渐增多，但其视角极其局限。这一结论在同一发展阶段的另一个标志性事件及其带来的发展趋势中可以得到印证：1994 年 10 月，金盘公司游戏部发表首款国产游戏《神鹰突击队》；一年后，《电子游戏软件》一篇题为《乌鸦·乌鸦·叫：该谈大陆电子游戏业了》的文章&lt;sup id="fnref:13">&lt;a href="#fn:13" class="footnote-ref" role="doc-noteref">13&lt;/a>&lt;/sup>批评了电子游戏中中国文化元素的缺失，引发了游戏界的广泛讨论，从此直到 21 世纪初，游戏国产化成为了中国游戏产业发展的主旋律；然而，如前文所述，这一阶段《人民日报》的视角仍局限在电子游戏产生的影响上，对游戏产业这一关键发展历程完全没有涉及。在强调电子游戏的危害的同时忽视游戏本身及游戏行业的发展现状，主流媒体的报道几乎与游戏产业的发展完全割裂。&lt;/p>
&lt;p>2000 年 7 月到 9 月，全国开展电子游戏经营场所的专项治理，出台了持续 14 年之久的“游戏机禁令”&lt;sup id="fnref:14">&lt;a href="#fn:14" class="footnote-ref" role="doc-noteref">14&lt;/a>&lt;/sup>，从此中国失去游戏软件产业升级的硬件基础，转向代理海外网游的新模式，从此进入长达十几年的网游时代&lt;sup id="fnref:15">&lt;a href="#fn:15" class="footnote-ref" role="doc-noteref">15&lt;/a>&lt;/sup>。与之前的缺位不同，《人民日报》在此次转变中不仅在场，还起到了十分重要的推动作用：从 2003 年 3 月 3 日开始，《人民日报》以前所未有的频率关注游戏，在专项治理开始前的 3 个月连续刊发 8 篇关于游戏厅现状及其危害的文章，从时间上看这些报道导致了政策的制定和实行；随后的 4 个月中，《人民日报》又刊发了 4 篇文章介绍专项治理的效果；这一年，《人民日报》的全部 13 篇游戏报道均与专项治理相关。值得关注的是，虽然主流媒体的报道仍然局限在游戏的负面影响上，其在游戏产业的此次转折中扮演了一个完全不同的角色，在以往的旁观者、批判者之外，短暂地成为了游戏产业发展的推动者，尽管游戏产业本身在其报道中并未在场。&lt;/p>
&lt;p>2001 年开始，中国游戏厂商开始代理国外网络游戏，开启了中国的网游时代。《人民日报》在 2002 年和 2003 年分别对网易推出的国产新网游《大话西游》&lt;sup id="fnref:16">&lt;a href="#fn:16" class="footnote-ref" role="doc-noteref">16&lt;/a>&lt;/sup>和金山推出的新网游《剑侠情缘网络版》&lt;sup id="fnref:17">&lt;a href="#fn:17" class="footnote-ref" role="doc-noteref">17&lt;/a>&lt;/sup>进行的报道，是其最早对具体游戏的主题报道；2003 年，《人民日报》发表了一篇解读网络游戏的文章，详细介绍了网友相关的政策和产业发展情况，并强调辩证地看待游戏的影响&lt;sup id="fnref:18">&lt;a href="#fn:18" class="footnote-ref" role="doc-noteref">18&lt;/a>&lt;/sup>。虽然这只是这个时代开端的几篇报道，但却反映了这个阶段主流媒体与游戏产业关系的明显变化：主流媒体终于主动向游戏产业靠近，游戏产业开始作为主体在主流媒体的报道中出现；主流媒体与游戏产业的对立开始得到缓解，其对游戏产业发展报道的缺失逐渐得到弥补。尽管如此，2009 年之前的主流媒体报道仍然带有对游戏的严重偏见。例如，“网瘾治疗专家”杨永信作为“靠着一颗爱心，挽救深陷泥潭的学子”的“好人”出现在人民日报的报道中&lt;sup id="fnref:19">&lt;a href="#fn:19" class="footnote-ref" role="doc-noteref">19&lt;/a>&lt;/sup>，尽管此人在当时饱受争议。主流媒体报道立场偏向性严重的问题导致其对游戏产业的报道不够全面客观，主流媒体与游戏产业发展之间的割裂继续存在。&lt;/p>
&lt;p>2009 年后，随着“转变经济发展方式”、“科技强国”、“中国文化走出去”等主流叙事的流行，游戏的科技与文化属性成为这一时期《人民日报》报道游戏的主要主题：2009 至 2011 年，“产业经济”框架在游戏报道中占据绝对主流；而 2012 年到 2017 年，“文娱新方式”框架占比陡增，维持在26%至36%之间波动&lt;sup id="fnref:20">&lt;a href="#fn:20" class="footnote-ref" role="doc-noteref">20&lt;/a>&lt;/sup>。可以看到，《人民日报》在这个时期从以往的游戏产业的旁观者变成了参与者，利用自身的舆论影响力服务于国家发展需要塑造舆论话语，有意识地引导游戏产业与国家发展战略接轨。为了达到此目标，《人民日报》对电子游戏危害的讨论相比以往几乎销声匿迹，报道角度也更加多元。以上种种变化，在客观上促进了主流媒体与游戏产业之间的交流，主流舆论与游戏产业之间更加平衡，同时一定程度上缓解了由于立场偏向而导致的割裂。&lt;/p>
&lt;p>2017 年，《王者荣耀》风靡全国，带来了新的一轮网络手游热，由此沉寂已久的游戏成瘾话题又重新成为主流议题；相比之下科技创新、文化输出等报道框架虽仍然存在，但明显有所减少。2018 年世卫组织首次将游戏成瘾列为疾病&lt;sup id="fnref:21">&lt;a href="#fn:21" class="footnote-ref" role="doc-noteref">21&lt;/a>&lt;/sup>后，《人民日报》在游戏成瘾以外的报道框架进一步被压缩，直到 2021 年仍是如此。主流媒体与游戏产业之间的关系在当下又重新变得紧张，二者之间的割裂有重新加大的趋势。&lt;/p>
&lt;h2 id="什么造成了主流媒体与游戏产业发展的割裂">什么造成了主流媒体与游戏产业发展的割裂&lt;/h2>
&lt;p>如前文所述，主流媒体与游戏产业发展之间的关系在40多年的中国游戏史中经历了明显的起伏。主流媒体与游戏产业发展之间的割裂，一方面表现在主流媒体对游戏的报道中游戏产业作为主体的缺失，另一方面表现在主流媒体报道话语的偏向性；这种割裂长期存在，总体上在缓慢地弥合，但有重新加大的趋势。这种关系变化的背后，有什么因素对二者之间的割裂产生影响？&lt;/p>
&lt;p>首先需要指出的是，以《人民日报》为首的官方媒体的报道角度与游戏产业的发展之间存在产生割裂的必然性，所有对主流媒体与游戏产业关系的影响因素的讨论都必须基于这一前提。《人民日报》是党中央的机关报，其作用是“上连党心，下接民心”&lt;sup id="fnref:22">&lt;a href="#fn:22" class="footnote-ref" role="doc-noteref">22&lt;/a>&lt;/sup>；《人民日报》并不是为游戏产业服务的，其目的是宣传国家中央主张、传播社会的主流声音，这就导致一旦游戏产业的发展与中央政策导向或主流舆论发生冲突，《人民日报》必然会在一定程度上站在游戏产业的对立面，其报道与游戏产业自身的发展历程必然会产生割裂，这与持小众、个人观点的游戏媒体有着本质的不同。&lt;/p>
&lt;p>在这一前提下，通过前文对游戏发展过程的归纳，不难发现游戏相关政策对二者关系的重要影响。一个鲜明的例子是《人民日报》对游戏产业在上世纪 90 年代以及 2012 至 2017 年这两段发展历史截然不同的态度：这两段时期中，游戏产业都致力于将中国文化意象融合到游戏中，然而《人民日报》在前一段时期中完全忽视了游戏产业发展进程，将游戏妖魔化，而在后一段时期中却积极地推动着此进程的发展，游戏的负面报道几乎不再存在。难道 90 年代中国游戏产业的探索没有意义，而 2012 年以后游戏的负面影响就完全消失了吗？显然不是。政策的因素在其中起到了至关重要的作用：第一部与促进国产游戏发展的政策“民族网络游戏出版工程”在 2004 年的网游时代才面世，90 年代主流媒体的报道更多基于社会主流舆论产生；而在 2010 年，“讲好中国故事”、“中国文化走出去”已成为中国文化工作的主流导向&lt;sup id="fnref:23">&lt;a href="#fn:23" class="footnote-ref" role="doc-noteref">23&lt;/a>&lt;/sup>，同时文化部指导网游厂商发起“家长监护工程”，意在引导未成年人“玩健康的游戏”和“健康地玩游戏”，游戏产业的发展得到了国家战略与政策的支持。尽管此时网游成瘾问题依然严重，对游戏的主流舆论评价仍然呈负面，但显然政策导向对主流媒体话语塑造具有更大的影响力。&lt;/p>
&lt;p>另外，由于主流媒体传播的是社会的主流声音，其对游戏产业的报道受社会主流舆论影响较大。这种影响不仅如前文所述体现在报道内容和价值取向上，更隐含在由不同群体舆论话语权的不同而导致的主流媒体报道立场偏向性中。根据何威等的研究，在其所统计的《人民日报》历年对游戏负面报道中，引用专家学者观点的有 49 篇，引用管理部门观点的有 40 篇，而引用玩家的观点仅有 11 篇&lt;sup id="fnref:24">&lt;a href="#fn:24" class="footnote-ref" role="doc-noteref">24&lt;/a>&lt;/sup>。可以看到，在“游戏的负面影响”这一主题下，与游戏产业直接相关的主体的话语权是缺失的，无论是游戏从业人员还是游戏玩家；他们不满与自身话语无法得到关注，却又对此无能为力。游戏杂志《家用电脑与游戏机》在 2000 年 7 月刊登了一篇反驳《光明日报》将游戏称作“电子海洛因”的文章，作者在其中愤怒地抱怨道：&lt;/p>
&lt;blockquote>
&lt;p>为什么作者宁愿“暗访”地下游戏室三次，却不愿意去一趟正大光明的游戏公司或游戏媒体，甚至多走访一些真正热爱电脑游戏的普通人呢？看来是“非不能也，实不愿也”，既然早就定下了“电子游戏是瞄准孩子的‘电子海洛因’”的标题（或者说是结论），一切就要围绕这个中心服务，如此而已。&lt;sup id="fnref:25">&lt;a href="#fn:25" class="footnote-ref" role="doc-noteref">25&lt;/a>&lt;/sup>&lt;/p>
&lt;/blockquote>
&lt;p>这篇文章是主流媒体与游戏产业之间冲突的直接表达，虽然已有 20 多年的历史，但即使是现在，由舆论话语权不平等导致的主流媒体与游戏产业发展之间割裂的问题也还没有得到较好的改善。这是因为，中国电子游戏在上世纪 90 年代刚刚兴起，而其影响力进一步扩大则是在十年后开始的网游时代。绝大部分出生于游戏流行年代的“游戏原住民”出生于 90 年代或 21 世纪，目前大多为 20 多岁的青少年，对主流舆论的影响力还远未成型，而他们恰恰是与游戏产业联系最为密切、最为庞大的群体，因此长期作为无法发声的、被批评的对象出现在游戏报道中；另一方面，40 多年来塑造主流舆论的群体大多作为旁观者的身份目睹游戏产业的发展，与游戏产业之间存在着天然的距离；即使对于部分人而言这种距离可能随着了解的不断深入而缩短，但大多数人由于主流媒体对游戏形象持续的污名化构建，其与游戏产业的距离只会越来越远，从而进一步加剧主流媒体与游戏产业发展之间的割裂。&lt;/p>
&lt;h2 id="总结如何看待主流媒体与游戏产业发展的割裂">总结：如何看待主流媒体与游戏产业发展的割裂&lt;/h2>
&lt;p>如前所述，主流媒体与游戏产业发展之间的割裂主要表现在主流媒体对游戏的报道中游戏产业作为主体的缺失，以及主流媒体报道话语的偏向性。随着历史进程的推进，主流媒体报道逐渐向游戏产业靠近，报道的价值取向取向也逐渐多元化，但是主流媒体与游戏产业发展之间的割裂是必然存在，而且将长期存在的：游戏易成瘾等特性导致大众舆论关于游戏社会影响的警惕，以及国家意志关于游戏对国家发展的影响的考虑一直存在，而这两者是影响主流媒体报道话语的最重要因素。尽管舆论对游戏的警惕可能会随着主流话语权的变更而放松，有关政策可能会因为游戏与国家发展向契合的属性而对游戏发展更加宽容，但它们背后的决定因素——游戏的消极影响是始终存在的。&lt;/p>
&lt;p>与游戏媒体的报道与游戏产业发展较为同步的关系相比，主流媒体与游戏产业之间保持适当的距离在某些方面是有益的。主流媒体对游戏消极影响的关注催生了游戏防沉迷、实名制等机制的产生，在客观上对规范游戏产业的发展起到了一定的作用；同时，主流媒体对游戏产业发展的参与程度越来越高，利用自身舆论影响力引导游戏产业与国家战略接轨，在一定程度上促进了游戏产业的发展。但是，主流媒体对游戏的报道需要避免重新回到以预设立场完全否定游戏价值、无视游戏产业发展历程的状态，这对游戏产业发展，以及游戏与人之间的关系存在着不可忽视的消极影响。&lt;/p>
&lt;p>游戏作为复杂的文化、社会现象，其影响必定是多元的。游戏产业的发展需要具有社会和舆论影响力的主流媒体起到化解不同群体之间冲突与矛盾、促进游戏产业与社会发展相统一的作用。保持一定的距离，批判地看待游戏，给出有建设性的意见，不失为达到这一目标的一种行之有效的方法。&lt;/p>
&lt;div class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1">
&lt;p>如无特别说明，本文中“游戏”专指“电子游戏”，即一切“依靠电子媒体平台而运行的互动游戏”。&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2">
&lt;p>戚休：《广州东方宾馆利用外资更新客房效果显著》，1981年05月25日《人民日报》第2版。&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:3">
&lt;p>邓剑：《中国电子游戏文化的源流与考辨》，《上海文化》2020年第12期，第45页。&amp;#160;&lt;a href="#fnref:3" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:4">
&lt;p>刘建华：《电子游戏热中的负效应》，《人民日报》1990年4月4日第6版。&amp;#160;&lt;a href="#fnref:4" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:5">
&lt;p>夷水寒：《电子游戏的暴力内容对孩子不利》，《人民日报》1991年2月12日第5版。&amp;#160;&lt;a href="#fnref:5" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:6">
&lt;p>马永梅，吴光志，周开荣：《游戏岂能“犯规”——青少年沉迷电子游戏现象扫描》，《人民日报》1993年12月21日第10版。&amp;#160;&lt;a href="#fnref:6" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:7">
&lt;p>何威，曹书乐:《从“电子海洛因”到“中国创造”: 〈人民日报〉游戏报道（1981-2017）的话语变迁》，《国际新闻界》第40期，2018年，第58页。&amp;#160;&lt;a href="#fnref:7" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:8">
&lt;p>汉娜·沃曼：《西方新闻媒体的“中国-东方主义”：被漫画化的游戏文化和商业》，宋雅文译，见何威、刘梦霏编：《游戏研究读本》，上海：华东师范大学出版社，2020年，第220页。&amp;#160;&lt;a href="#fnref:8" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:9">
&lt;p>使用“人民日报图文数据库”对“游戏、网游、网瘾、手游、电竞、电子竞技”进行标题检索，包括新闻、评论、读者来信等所有类别，并剔除无关文章，得到1989年至2021年刊登的共194篇相关文章。&amp;#160;&lt;a href="#fnref:9" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:10">
&lt;p>邓剑：《中国当代游戏史的思想谱系：从本土现代化到资本与市场逻辑》，《探索与争鸣》2022年第2期，第166页。&amp;#160;&lt;a href="#fnref:10" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:11">
&lt;p>朱丹：《学生入迷，家长忧心——上海街头游戏机成灾》，《人民日报》1989年4月17日第2版。&amp;#160;&lt;a href="#fnref:11" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:12">
&lt;p>《电子游戏的分类》，《人民日报》1999年3月14日第7版。&amp;#160;&lt;a href="#fnref:12" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:13">
&lt;p>本刊评论员：《乌鸦·乌鸦·叫:该谈大陆电子游戏业了》，《电子游戏软件》1995 年第 4 期。&amp;#160;&lt;a href="#fnref:13" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:14">
&lt;p>国务院办公厅等：《国务院办公厅转发文化部等部门关于开展电子游戏经营场所专项治理意见的通知》，2000年6月15日，http://www.gov.cn/gongbao/content/2000/content_60240.htm，2022年7月21日。&amp;#160;&lt;a href="#fnref:14" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:15">
&lt;p>邓剑：《中国当代游戏史的思想谱系：从本土现代化到资本与市场逻辑》，《探索与争鸣》2022年第2期，第169页。&amp;#160;&lt;a href="#fnref:15" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:16">
&lt;p>周萍：《网易推出网络游戏〈大话西游〉》，《人民日报》2002年1月1日第5版。&amp;#160;&lt;a href="#fnref:16" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:17">
&lt;p>崔扬：《国产游戏新出网络版》，《人民日报》2003年7月30日第11版。&amp;#160;&lt;a href="#fnref:17" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:18">
&lt;p>任建民：《网络游戏：超越电影票房之后（热点解读）》，《人民日报》2003年12月15日第5版。&amp;#160;&lt;a href="#fnref:18" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:19">
&lt;p>刘成友：《道德力量汇聚孔孟之乡——全国道德模范与身边好人现场交流活动侧记》，《人民日报》2009年4月4日第3版。&amp;#160;&lt;a href="#fnref:19" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:20">
&lt;p>何威，曹书乐:《从“电子海洛因”到“中国创造”: 〈人民日报〉游戏报道（1981-2017）的话语变迁》，《国际新闻界》第40期，2018年，第71-72页。&amp;#160;&lt;a href="#fnref:20" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:21">
&lt;p>世界卫生组织：《国际疾病分类》第11次修订本，https://www.who.int/zh/news-room/spotlight/international-classification-of-diseases。&amp;#160;&lt;a href="#fnref:21" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:22">
&lt;p>汪晓东：《让党心和民心贴得更紧——写在习近平总书记“2·19”重要讲话发表3周年之际》，《人民日报》2019年2月19日第1版。&amp;#160;&lt;a href="#fnref:22" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:23">
&lt;p>温红彦，李舫：《向世界讲述好中国故事——文化部副部长赵少华谈中华文化“走出去”》，2010年9月30日。&amp;#160;&lt;a href="#fnref:23" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:24">
&lt;p>何威，曹书乐:《从“电子海洛因”到“中国创造”: 〈人民日报〉游戏报道（1981-2017）的话语变迁》，《国际新闻界》第40期，2018年，第77页。&amp;#160;&lt;a href="#fnref:24" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:25">
&lt;p>王亚晖：《中国游戏风云》，北京：中国发展出版社，2018年11月，第113至114页。&amp;#160;&lt;a href="#fnref:25" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/div></content></item><item><title>2022 春季学期总结</title><link>https://yeshe.ng/blog/posts/2022/06/2022-spring-summary/</link><pubDate>Mon, 20 Jun 2022 16:26:00 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/06/2022-spring-summary/</guid><description>2022 春季学期总结</description><content>&lt;p>春季学期对我来说有一个定律，或者说一个魔咒：学业一塌糊涂，学业之外收获甚多。&lt;/p>
&lt;p>20 年的春季学期在家里上课，鬼使神差般地接触了 iOS 开发，从此一整个学期都在写一个 app，上课和下课都在写代码，导致那个学期的绩点异常整齐，除了体育之外全部都是 3.6，排名刷刷地往下掉，还退了微积分和概统。21 年春季学期进行转系，同时在高强度写树洞 app，没什么心思听课，最后离散 (2) 喜提 3.0，绩点进一步雪崩，并且又退了一次微积分（于是大学总共修了三次微积分 A2，唉！）。&lt;/p>
&lt;p>但是，这两个学期对于我大学四年有着非常深远的影响。最重要的当然是开始写程序之后逐渐喜欢上了 CS，并直接导致最终转到计算机系；写树洞是第一次接触正经的 C/S 架构的开发，积累了大量的网络、git、前后端等等知识，这也使得我现在的技术能力放在贵系也还算可以（？）。&lt;/p>
&lt;p>与之相反，秋季学期在经过前一个春季学期的“惨痛”经历后，往往走向另一个极端：抑制自己在其他地方的兴趣，以 GPA 为首要目标。这当然是有效的，我秋季学期的 GPA 都挺不错；但同时也是无聊的，天天跑图书馆写作业刷题对某些同学来说可能是享受，但我真的不喜欢以提高分数为唯一目的的学习。&lt;/p>
&lt;p>我也曾妄想过“小孩子才做选择，我全都要”，每个学期之前都想着如何能够更高效地利用时间，把学业和其他的所有事情都做到极致。然而，这种想法未曾成为现实，主要有几个原因：第一，我时间管理极差，无法利用正常作息下白天和晚上大部分可以利用的时间。第二，我对于我感兴趣的事情往往有着极高的热情，而这种热情往往导致时间安排上极不均衡的优先级，进而导致一些目前阶段可能（或者说表面上）较为重要的任务没有充足的时间完成（用计算机的专业术语来说，starvation /笑）。&lt;/p>
&lt;p>尽管在这个学期开学前有想过避免以前春季学期的问题，但很不幸，课内学习不够投入的问题并没有得到解决。现在想来，主要的原因是软工占用了大部分时间，其他课程上得也不舒服：数电 tp 老师讲课风格实在是不喜欢，人智导讲的内容实在是没兴趣，搜索引擎实在是太水，而我又比较喜欢捣鼓软工所用的一些技术……因此很多课都用来写软工了（数电前几周的课、搜索引擎后面的课、人智导大部分的课）。微积分倒是坚持认真听了十几周，但耐不住记 P/F 这一重大利好（终于能在不影响 GPA 的情况下修完微积分了 /哭），在记了 P/F 之后就没听了。&lt;/p>
&lt;p>另一个原因，与之前相似，是课外的工作占用了大量的时间，这个学期主要是科研。这个学期开始跟着学长做科研项目，从寒假开始一直在恶补操作系统的知识，后面换方向后又从零开始学习了数据库的知识。等到终于开始上手代码了，花了很多时间调试和修改以前论文的代码，然后考试周就来了，只能搁置。总的来说，这一段科研经历的开端并不是特别顺利，也还没有完全找到自己感兴趣的方向，但总算是开了个头，也算是迈出了一小步。&lt;/p>
&lt;p>总之这个学期就这么不太明白地过去了。有很多不好的经历：软工开题后焦虑、软工进展不顺、数电听不懂、数电实验遇到各种问题、人智导没兴趣、科研进展缓慢……同时也有很多美好的回忆：软工助教和用户代表高度评价我们的项目、软工做了一个（自认为）非常精彩的展示、科研开了一个还算可以的头……&lt;/p>
&lt;p>和&lt;a href="https://yeshe.ng/blog/posts/2022/01/2021-fall-summary">一个学期前&lt;/a>相比，我的乐观精神显然消退了不少。或许是因为第一次遇到了对于计算机不太感兴趣或不太擅长的方面，或许是发现在某些方面自己没办法做到某些同学的高度，或许是在读文章时感受到自己的水平与顶尖水平的巨大差距……总之，发现有些事情是自己做不到的，这对于我自己能力的认识，以及对未来的规划都产生了不小的影响。当然，我也时常想起软工被用户代表和 rls 夸上天的时候的那种兴奋感。或许，我还是能够在一些地方找到自己的独特之处的。&lt;/p>
&lt;p>至于暑假和下学期，还是花多点时间在科研上吧。如果这条路能走通，那就坚定走下去；如果走不通，那就要及时考虑一下其他道路了。&lt;/p></content></item><item><title>音乐剧 Come From Away 赏析</title><link>https://yeshe.ng/blog/posts/2022/05/come-from-away-review/</link><pubDate>Sat, 28 May 2022 14:27:27 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/05/come-from-away-review/</guid><description>\"Tonight, we honor what was lost, but we also commemorate what we found.\"</description><content>&lt;blockquote>
&lt;p>本文为 2022 年春季学期《西方音乐剧史》课程期末作业&lt;/p>
&lt;/blockquote>
&lt;p>《Come From Away》的故事原型是，在 911 事件发生时，加拿大的一个小镇接纳了超过 7000 个因飞行禁令临时降落在此地的乘客。故事本身并不复杂，主要讲述了当地居民尽其所能为滞留乘客提供住宿等帮助、乘客从一开始的恐慌、焦虑到最终融入当地人的生活中，并获得了继续前行的动力。&lt;/p>
&lt;p>下面主要从舞台设计和主题表达两个方面进行赏析。&lt;/p>
&lt;h2 id="舞台设计">舞台设计&lt;/h2>
&lt;p>这部音乐剧最令人印象深刻的是对转场、道具和灯光的令人惊叹的设计，可以说充分展现了音乐剧作为舞台表演艺术的魅力。&lt;/p>
&lt;h3 id="场景切换">场景切换&lt;/h3>
&lt;p>本部音乐剧总体上是刻画群像，涉及到当地居民和乘客等多个群体，并且群体内部又分成很多不同的角色。如何在 Cast 有限的条件下（只有 12 人）演出这么多不同的角色，并且营造本剧“来的人很多”的背景氛围呢？方法很简单：一个演员演多个角色，并在不同场景中切换。但是这种方法有一个最大的问题：如何让观众清楚地知道这个时刻舞台上的演员演的是哪个角色、现在是在哪个场景？&lt;/p>
&lt;p>《Come From Away》的方法是，利用服装、道具、灯光等，来主动地为人物提供“背景”，并结合演员的动作和台词，来进行快速的场景切换。&lt;/p>
&lt;p>本剧角色和场景切换的速度之快令人瞠目结舌，同时又达到了非常好的效果。比如，在下面这短短三分钟中，一共表现了 6 个场景的情形。&lt;/p>
&lt;p>首先，场景是在小镇的酒吧内，大家刚刚得知恐怖袭击的消息：&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/1.webp" alt="">&lt;/p>
&lt;p>此时，灯光熄灭，演员打开手电筒，瞬间切换另一个场景：&lt;/p>
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/2.webp">
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/3.webp">
&lt;p>这里演员使用手电筒营造了光线不强、主要发光源是屏幕的环境，结合台词观众很容易就知道现在场景切换到了塔台上。&lt;/p>
&lt;p>接下来的场景是塔台和机长进行交流。首先是塔台上管制员在繁忙地向机长传达必须马上降落的信息：&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/4.webp" alt="">&lt;/p>
&lt;p>然后，椅子一转，所有演员面朝观众。这时候，演员以机长的身份分别与塔台交流（这里同时有多架飞机），我们因此可以知道现在场景切换到了飞机的驾驶舱内。&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/5.webp" alt="">&lt;/p>
&lt;p>突然，灯光一暗，演员向右转 90 度，瞬间切换到了机舱内乘客们的场景，同时乘务员开始讲话。我们可以看到这里其实还有原先的桌子，实际上是不符合机舱的情况的，但是并不会造成观众的误解。&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/6.webp" alt="">&lt;/p>
&lt;p>过了一段时间，演员站起来，抬头向上看，开始在数降落的飞机的数量。这里，场景又切换到了机场的地面上。我们可以看到，后面的部分演员仍然保持之前的坐姿，但由于他们都在向上看，并逐渐站起来，因此这两个场景的过渡非常自然。之前的乘务员脱下帽子和外套，拿起麦克风，摇身一变成为在地面进行报道的记者，毫无违和感。&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/7.webp" alt="">&lt;/p>
&lt;p>最后，灯光由冷变暖，场景重新回到室内的酒吧。这里有个细节，切换前各个演员是分散的，而一切换到酒吧后，演员们都朝着镇长聚集，这也加强了对场景的构建。&lt;/p>
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/8.webp">
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/9.webp">
&lt;p>总的来看，本剧进行场景切换的主要方法有：&lt;/p>
&lt;ul>
&lt;li>&lt;strong>演员表演&lt;/strong>：通过台词、动作和表情演绎不同角色，如机舱内乘客拨动头顶的开关、管制员用严肃的语气进行无线电通话&lt;/li>
&lt;li>&lt;strong>服装&lt;/strong>：通过有职业/角色特点的服装区分同一个演员饰演的不同角色&lt;/li>
&lt;li>&lt;strong>灯光&lt;/strong>：通过灯光区分不同场景，如室内的酒吧是暖色的，机舱和地面是冷色的，而塔台和驾驶舱是暗的&lt;/li>
&lt;li>&lt;strong>道具&lt;/strong>：通过道具（这里主要是椅子）表现空间的特点&lt;/li>
&lt;/ul>
&lt;p>上面的片段使用非线性叙事的方法，将“下达降落命令”这一时刻前后不同地点、不同角色的情况，包括角色的身份、心情等串联起来，立体地表现了不同人群在这一时间对事件的反应，颇有概念音乐剧的影子。&lt;/p>
&lt;p>同时，演员所表现的是群像特征，而不是特定角色的特点，因此成功地表现了各个群体总体在此刻的境遇。这也使得观众无需将演员与特定角色绑定（除了酒吧的场景），使得场景切换非常自然。&lt;/p>
&lt;p>有意思的是，剧组在剧中还故意展示了他们通过这种方式“省人手”的技巧。同一个人戴上眼镜，长了胡子，或者戴上帽子，配上不同的表情，就是不同性格的、不同镇的镇长，引来阵阵笑声：&lt;/p>
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/10.webp">
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/11.webp">
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/12.webp">
&lt;h3 id="道具">道具&lt;/h3>
&lt;p>这部剧除了十几张椅子外几乎没有任何道具。但就是这十几张椅子，剧组把它们玩出了花。这些椅子的最大作用，如前所述，是用于场景构建。下面这两张图非常明显地展示了这点：&lt;/p>
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/13.webp">
&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/14.webp">
&lt;p>将椅子拍成两排，就变成了机舱的座位。&lt;/p>
&lt;p>又如，机长在讲述自己的故事时，其余的人围成一圈：&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/15.webp" alt="">&lt;/p>
&lt;p>情侣在野外登高时，用凳子构成一条栈道：&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/16.webp" alt="">&lt;/p>
&lt;p>这种方式非常有效，因为不同场景的设施具有迥异的几何排布特征，使用凳子表现这些几何特征，就可以让观众迅速识别并沉浸入场景中（同时非常省经费！）。&lt;/p>
&lt;p>另外，灯光和服装等设计，以及演员的表演也对舞台效果起了非常大的作用，在这里限于篇幅不再进行深入分析。让我印象比较深刻的有这些场景：&lt;/p>
&lt;p>众人进行默哀，此时一束光穿过树林投下来，渲染了庄严的气氛：&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/18.webp" alt="">&lt;/p>
&lt;p>这个是乘客和司机坐在校车上，司机脸前有亮光，而乘客的区域是暗的；同时乘客头瘫向一边非常生动地表现了乘客此时是在车上（急转弯），并且非常疲惫：&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/19.webp" alt="">&lt;/p>
&lt;p>你很难想象在舞台上能够表演出的比这个更生动形象的场景了。&lt;/p>
&lt;h2 id="主题表现">主题表现&lt;/h2>
&lt;p>本剧最主要的主题是表现人们在面对灾难时互相帮助的精神，但又不只于此。&lt;/p>
&lt;p>剧组使用相同的演员来饰演不同的角色。这里固然有演员人数的考虑，但我相信这其中蕴含了另一层深意。在剧中，同一个演员既是需要住宿的乘客，也是提供帮助的本地人；既是与事件有直接关系的遇难者家属，也是因看到电视画面而产生心理创伤的普通人……在恐怖主义面前，全体人类都是需要帮助的对象。你就是我，我就是你。帮助他人，从另一个角度来看，也是帮助自己。&lt;/p>
&lt;p>通过将灾难下的所有人视为一个整体，恐怖主义被放置在了全体人类的对立面。这以一种非常温和，却非常有力的方式蔑视了恐怖主义，即使全剧几乎没有一句台词直接谴责恐怖主义行为。&lt;/p>
&lt;p>这也是本剧的主调，即以普通人的视角来审视 911 事件给人们带来的创伤，以及我们从灾难后人们的互相帮助中所看到的人类本性中最珍贵的特质：善良，乐于助人，等等。本剧之所以有很强的感染力，在于它不仅仅赞颂了人类的珍贵特质——这样的话就成“主旋律”“唱颂歌”类型的剧了；它同时通过去世消防员的母亲等角色提醒我们恐怖主义给我们带来的巨大创伤，提醒我们不要忘记每一个亲历者以及背后无数个家庭的痛苦经历。正如镇长的唱段中所言：&lt;/p>
&lt;blockquote>
&lt;p>Tonight, we honor what was lost, but we also commemorate what we found.&lt;/p>
&lt;/blockquote>
&lt;p>这里有个小插曲，在《Something&amp;rsquo;s Missing》中，消防员的母亲在得知儿子去世后开口唱“You are here / At the end of the moment&amp;hellip;” 的时候，背后拉小提琴的小姐姐忍不住哭了：&lt;/p>
&lt;p>&lt;img src="https://yeshe.ng/blog/2022/05/come-from-away/17.webp" alt="">&lt;/p>
&lt;p>大概，共情是人类能够站在一起最重要的原因吧。&lt;/p>
&lt;h2 id="其他">其他&lt;/h2>
&lt;p>这部音乐剧有比较浓的商业音乐剧的气息，比如无处不在的政治正确，以及时不时的玩梗和抛笑话，非常的 entertaining；这部剧也非常“小清新”，并没有激烈的冲突，也没有非常深入的主题挖掘。另一方面，近乎完美的编排、令人眼花缭乱的舞台表现也反映了当下音乐剧商业化、产业化的趋势。当然，这些并不妨碍这部剧成为一部非常优秀的音乐剧作品。&lt;/p>
&lt;h2 id="后记">后记&lt;/h2>
&lt;p>《Come From Away》实际上是我看过的第一部音乐剧。去年官摄版上架 Apple TV+，恰巧我有订阅，之前也或多或少感受过音乐剧的魅力，于是便找了一个周末看了，之后就爱上了音乐剧（并选了《西方音乐剧史》课程 ^_^）。现在感觉自己挺幸运，第一次就能看到这样一部能够将音乐剧的魅力展现得淋漓尽致的剧（并且有 4k 画质和 Dolby Atmos 音效，which is 非常难得）。&lt;/p>
&lt;p>当然，上完课之后再重新看一遍这部音乐剧，获得了完全不一样的体验。与之前只能看剧情不同，现在能够看懂一些表现手法了，也能在上面有模有样地分析它究竟好在哪里。希望以后能够继续深入音乐剧的世界。感谢罗老师的指导！&lt;/p></content></item><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>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>软件工程：The Missing Semester</title><link>https://yeshe.ng/blog/posts/2022/03/se-missing-semester/</link><pubDate>Tue, 22 Mar 2022 23:01:03 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/03/se-missing-semester/</guid><description>软件工程项目缺失的亿点文档</description><content>&lt;p>要是让我总结一个“在清华体验不太好的课程列表”，软件工程一定位列其中。这并不意味着我不喜欢软工项目中的环境配置、CI/CD、单元测试、部署等等工作；相反，我在其中找到了很多乐趣——毕竟，我是一个爱折腾的人。&lt;/p>
&lt;p>问题出在作用接近于 0 的文档上：对于很多依赖于详细的文档才能上手的工作，这门课几乎没有提供任何有用的文档。对于这些工作，课程组只提供了没有任何注释的“样例”让我们照猫画虎。虽然说面向 Google 是计算机专业最好的学习方式，但是将一堆东西扔给学生自学，还几乎不提供任何的引导，这是这门课最大的问题。虽然其中很多知识我以前都接触过，但是每当我站在没有开发经验的同学的角度上思考这些复杂的技术栈对于同学们来说意味着什么，就不禁感到愤怒。&lt;/p>
&lt;p>在摸索了几天、踩了无数坑之后，我总算是把我们项目的代码部署到了软工提供的服务器上。在这里，我将把一些在软工平台上部署的经验写成文档，作为软工课程文档的补充。&lt;/p>
&lt;p>By the way，“The Missing Semester”在这里有两个含义：一是致敬 CMU 的“The Missing Semester”课程，一是表达我对于软工试图仅用一学期就让我们成为全栈工程师的不满。在我看来，软工至少需要两个学期；在这两个学期中，教学团队的引导和同学们的开发应当是相辅相成的，而不能只是学生自己独自面对一个完整的、陌生的、无从下手的、令人恐惧的项目，即使可能有很多有开发经验的同学并不这么觉得。&lt;/p>
&lt;p>感谢 &lt;a href="https://github.com/prnake">@prnake&lt;/a> 学长在 GitHub 上公开他们的项目 &lt;a href="https://github.com/prnake/Quick-Red">Quick-Red&lt;/a>，下面的很多例子源于此项目。没有它，很多问题我根本无从下手。&lt;/p>
&lt;h2 id="创建项目">创建项目&lt;/h2>
&lt;h4 id="在软工平台上新建仓库">在软工平台上新建仓库&lt;/h4>
&lt;p>如果需要部署仓库内容到服务器上，必须要在软工平台（SECoder）上的 &lt;code>项目管理 - 仓库管理&lt;/code> 中新建，否则之后无法使用 &lt;code>deployer&lt;/code> 部署到提供的服务器上。&lt;/p>
&lt;h4 id="分支管理">分支管理&lt;/h4>
&lt;p>仓库中应该有如下分支：&lt;/p>
&lt;ul>
&lt;li>&lt;code>master&lt;/code>（或 &lt;code>main&lt;/code>）：主分支，用于部署生产环境&lt;/li>
&lt;li>&lt;code>dev&lt;/code>：开发分支，所有最新的改动合并到此分支，定期合并到 &lt;code>master&lt;/code> 分支&lt;/li>
&lt;li>其他开发分支（例如 &lt;code>feat/login&lt;/code>）：开发某一新功能时新建的分支，开发完成后合并到 &lt;code>dev&lt;/code> 分支&lt;/li>
&lt;/ul>
&lt;p>需要注意 &lt;code>master&lt;/code> 和 &lt;code>dev&lt;/code> 只能通过 merge request 由其他分支合并，不能直接 push。另外，新建开发分支时一定要从 &lt;code>dev&lt;/code> 分支上新建，不能从其他开发分支上新建。&lt;/p>
&lt;h4 id="仓库配置">仓库配置&lt;/h4>
&lt;p>GitLab 中有几项设置对后续协作开发有不小的帮助，值得花点时间配置一下：&lt;/p>
&lt;ol>
&lt;li>在 &lt;code>General - Merge request&lt;/code> 中取消选择 &lt;code>Merge options&lt;/code> 下的 &lt;code>Enable 'Delete source branch' option by default&lt;/code>。课程需要保留开发过程中的分支，需要避免不小心删除分支的情况。&lt;/li>
&lt;li>在 &lt;code>Repository - Default Branch&lt;/code> 中将 &lt;code>Default Branch&lt;/code> 设置为 &lt;code>dev&lt;/code>，使得进行 merge request 时默认合并到 &lt;code>dev&lt;/code> 分支。&lt;/li>
&lt;li>在 &lt;code>Repository - Protected Branches&lt;/code> 中添加 &lt;code>master&lt;/code> 和 &lt;code>dev&lt;/code> 都设置为受保护的分支，并禁止任何人直接 push 到这两个分支上。&lt;/li>
&lt;/ol>
&lt;h4 id="增加-issue-template-和-merge-request-template">增加 issue template 和 merge request template&lt;/h4>
&lt;p>在仓库目录下新建文件夹 &lt;code>.gitlab/issue_templates/&lt;/code>，并在其中添加若干 Markdown 文件，这些文件将可用作新建 issue 时作为模版供选择。例如，针对 bug 的 issue：&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-markdown" data-lang="markdown">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e">&amp;lt;!-- Title 格式：[bug] Bug 描述 --&amp;gt;&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 style="color:#75715e">&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>1.
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>2.
&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:#75715e">## 当前的行为
&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">&amp;lt;!-- 当前版本存在的问题行为 --&amp;gt;&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:#75715e">## 预期的行为
&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">&amp;lt;!-- 正常应当有怎样的行为 --&amp;gt;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>添加模版之后，在新建 issue 时可以选择其中之一作为模版，保证 issue 的规范和质量。&lt;/p>
&lt;p>同理，可以为 merge request 添加模版，可以参考 &lt;a href="https://github.com/prnake/Quick-Red/blob/master/frontend/.gitlab/merge_request_templates/CodeReview.md">Quick-Red 的模版&lt;/a>。&lt;/p>
&lt;h2 id="cicd">CI/CD&lt;/h2>
&lt;p>CI/CD 简单来说就是在服务器上执行一系列命令，从而自动化地在每次提交代码之后完成测试、检查、构建等工作，并在需要时完成部署。&lt;/p>
&lt;h4 id="stage-和-job">Stage 和 job&lt;/h4>
&lt;p>Stage 指的是 CI/CD 的不同阶段，用来完成不同阶段的工作，一般情况下&lt;strong>顺序执行&lt;/strong>。你可以使用 GitLab 的 DAG 功能使得没有依赖关系的 stage 并行运行以加快速度，具体可以参考 &lt;a href="https://docs.gitlab.com/ee/ci/directed_acyclic_graph/">官方文档&lt;/a>。&lt;/p>
&lt;p>在软工项目中的 stage 通常包括：&lt;/p>
&lt;ul>
&lt;li>pre：准备阶段，完成依赖库缓存的拉取和保存等&lt;/li>
&lt;li>build：构建 Docker 镜像&lt;/li>
&lt;li>test：单元测试、代码风格检查、上传代码质量报告（sonar）&lt;/li>
&lt;li>deploy：将镜像部署到服务器上&lt;/li>
&lt;/ul>
&lt;p>在 &lt;code>.gitlab-ci.yml&lt;/code> 开头声明 stage：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">stages&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">pre&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">test&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">deploy&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>需要注意的是，不同 stage 之间的环境是相互隔离的。比如，在前面的 stage 中如果安装了项目依赖（如 &lt;code>npm install&lt;/code>），在后面的 stage 中仍然需要安装这些依赖。在后面将会介绍加快这一过程的方法。&lt;/p>
&lt;p>Job 指的是 stage 内部的若干个任务单元，执行具体的命令，同一个 stage 的所有 jobs 是&lt;strong>并行执行&lt;/strong>的，环境也是相互隔离的。&lt;/p>
&lt;p>Job 一些常用的属性有：&lt;/p>
&lt;ul>
&lt;li>&lt;code>stage&lt;/code>：指定这个 job 所属的 stage；若不指定，默认为 &lt;code>test&lt;/code>&lt;/li>
&lt;li>&lt;code>script&lt;/code>：这个 job 执行的命令行命令&lt;/li>
&lt;li>&lt;code>before_script&lt;/code>：在执行 &lt;code>script&lt;/code> 之前执行的命令，一般用于环境配置&lt;/li>
&lt;li>&lt;code>after_script&lt;/code>：在执行 &lt;code>script&lt;/code> 之后执行的指令，比如使用 &lt;code>sonar-scanner&lt;/code> 上传在 &lt;code>script&lt;/code> 中得到的覆盖率报告&lt;/li>
&lt;li>&lt;code>only&lt;/code> 和 &lt;code>except&lt;/code>：用于指定运行此 job 的条件，比如生产环境的部署对应的 job 只在 &lt;code>master&lt;/code> 分支上进行&lt;/li>
&lt;/ul>
&lt;p>具体的用法可以参考 GitLab 文档。&lt;/p>
&lt;h4 id="hidden-jobs-与继承">Hidden jobs 与继承&lt;/h4>
&lt;p>如果多个 job 有大量相同的配置，可以使用继承的方法避免重复编写相同配置。比如，我们对 Node.js 项目分别进行单元测试和代码风格检查，而它们都需要安装相同的依赖、都属于 &lt;code>test&lt;/code> stage。&lt;/p>
&lt;p>首先，定义一个以 &lt;code>.&lt;/code> 开头的 job，这个 job 为 hidden job，不会被执行：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">.test&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>: &lt;span style="color:#ae81ff">test&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">before_script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm config set registry http://mirrors.cloud.tencent.com/npm/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm install&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;blockquote>
&lt;p>第一条命令用于将 npm 使用的源设置为国内腾讯云镜像，这将大大加快加载速度。&lt;/p>
&lt;/blockquote>
&lt;p>然后，在真正的 job 中，使用 &lt;code>extends&lt;/code> 继承此 job：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">lint-test&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">extends&lt;/span>: &lt;span style="color:#ae81ff">.test&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm run lint&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:#f92672">unit-test&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">extends&lt;/span>: &lt;span style="color:#ae81ff">.test&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm run test:ci&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这两个 job 因此拥有了与 &lt;code>.test&lt;/code> 相同的 &lt;code>stage&lt;/code> 和 &lt;code>before_script&lt;/code> 属性。&lt;/p>
&lt;p>你还可以继承多个 hidden job，具体行为可以查阅有关文档。&lt;/p>
&lt;h4 id="加快速度cache">加快速度：cache&lt;/h4>
&lt;p>对于使用如 Node.js 这样的需要安装大量依赖库的项目，如果在每个 stage 中、每次 CI/CD 都重新安装一次依赖，CI/CD 的效率将会非常低。对这个问题，我们可以使用 GitLab 的 cache 功能将这些依赖缓存起来供重复利用。&lt;/p>
&lt;p>因为每个 job 的环境都是独立的，因此每个 job 都需要定义缓存。我们使用 hidden job 完成统一配置：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">.cache&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">cache&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">key&lt;/span>: &lt;span style="color:#ae81ff">${CI_COMMIT_REF_SLUG}&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">paths&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">node_modules/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">package-lock.json&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">policy&lt;/span>: &lt;span style="color:#ae81ff">pull&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其中，&lt;code>key&lt;/code> 使用分支名（&lt;code>${CI_COMMIT_REF_SLUG}&lt;/code>），即不同的分支使用不同的缓存，而同一分支下的所有 job 共享这份缓存。&lt;code>paths&lt;/code> 指定了缓存项的路径，这里我们将 Node.js 的依赖文件缓存起来。&lt;code>policy&lt;/code> 设置为 &lt;code>pull&lt;/code> 的目的是，每次只从服务器上拉取缓存，而不需要在结束之后将此缓存上传到服务器上。这很重要，因为我们不希望每个 job 都重复地上传相同的缓存。&lt;/p>
&lt;p>我们在 &lt;code>pre&lt;/code> 阶段中添加一个 job 用来更新缓存：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">node-cache&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>: &lt;span style="color:#ae81ff">pre&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">extends&lt;/span>: &lt;span style="color:#ae81ff">.cache&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">cache&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">policy&lt;/span>: &lt;span style="color:#ae81ff">pull-push&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm config set registry http://mirrors.cloud.tencent.com/npm/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm install&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>注意到，我们将 cache policy 更改为了 &lt;code>pull-push&lt;/code>，这是因为我们需要将此缓存上传到服务器上供其他 job 和 pipeline 使用。&lt;/p>
&lt;p>然后，在需要用到此缓存的 job 中，继承 &lt;code>.cache&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">build&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>: &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">extends&lt;/span>: &lt;span style="color:#ae81ff">.cache&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm config set registry http://mirrors.cloud.tencent.com/npm/&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">npm install&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">...&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这个 job 将会从服务器上拉取最新的缓存，从而无需重新下载。&lt;/p>
&lt;p>当然，以防万一，还是加上一句 &lt;code>npm install&lt;/code>，虽然正常情况下这条命令并不会从网络上下载依赖，因为在 &lt;code>pre&lt;/code> 阶段我们已经下载好了需要的依赖。&lt;/p>
&lt;h2 id="sonar-与单元测试的配置">Sonar 与单元测试的配置&lt;/h2>
&lt;p>课程要求使用 Sonar 对源代码进行静态分析。既然是静态分析，那么 Sonar 需要测试工具执行测试生成测试报告，才能进行分析。因此，测试与报告的流程为：&lt;/p>
&lt;ol>
&lt;li>测试工具执行测试，并输出测试结果&lt;/li>
&lt;li>使用 &lt;code>sonar-scanner&lt;/code> 对测试结果和源代码进行分析并上传分析结果&lt;/li>
&lt;/ol>
&lt;h4 id="使用测试工具生成测试报告">使用测试工具生成测试报告&lt;/h4>
&lt;p>Sonar 需要提供 xml 格式的测试报告，对于不同语言、框架的方法不同，可以到搜索引擎上搜索生成报告的方法。在这里分享一下我们项目使用的框架（Django、React（create-react-app）、Go）适用的方法。&lt;/p>
&lt;p>&lt;strong>Django&lt;/strong>&lt;/p>
&lt;p>如果直接使用 &lt;code>pytest&lt;/code>，可以参考 &lt;a href="https://github.com/prnake/Quick-Red/tree/master/backend">Quick-Red&lt;/a>。&lt;/p>
&lt;p>如果使用 Django 自带的测试框架 &lt;code>django.test&lt;/code>：&lt;/p>
&lt;p>需要安装以下依赖：&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-text" data-lang="text">&lt;span style="display:flex;">&lt;span>pytest
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>coverage
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>unittest-xml-reporting
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在 &lt;code>settings.py&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-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 指定 test runner 为 xmlrunner&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>TEST_RUNNER &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;xmlrunner.extra.djangotestrunner.XMLTestRunner&amp;#39;&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>TEST_OUTPUT_DIR &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;xunit-reports/&amp;#39;&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>TEST_OUTPUT_FILE_NAME &lt;span style="color:#f92672">=&lt;/span> &lt;span style="color:#e6db74">&amp;#39;xunit-result.xml&amp;#39;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后，运行&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>python -m coverage run --source path1,path2,...,pathn manage.py test
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>python -m coverage xml -o coverage-reports/coverage.xml
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>其中，&lt;code>path1&lt;/code>、&lt;code>path2&lt;/code> 等替换为包括在覆盖率报告中的文件/文件夹路径。&lt;/p>
&lt;p>这样，分别生成了测试报告 &lt;code>xunit-reports/xunit-result.xml&lt;/code> 和覆盖报告 &lt;code>coverage-reports/coverage.xml&lt;/code>。&lt;/p>
&lt;p>这些命令仅完成了最基本的测试报告的配置。关于可选的命令行参数，请参考 &lt;a href="https://coverage.readthedocs.io/en/6.3.2/cmd.html">官方文档&lt;/a>。&lt;/p>
&lt;p>&lt;strong>React（create-react-app）&lt;/strong>&lt;/p>
&lt;p>create-react-app 预置了 Jest，无需手动安装。&lt;/p>
&lt;p>安装 &lt;code>jest-sonar&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-bash" data-lang="bash">&lt;span style="display:flex;">&lt;span>npm install --save-dev jest-sonar
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在 &lt;code>package.json&lt;/code> 的 &lt;code>scripts&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-json" data-lang="json">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#e6db74">&amp;#34;test:ci&amp;#34;&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">:&lt;/span> &lt;span style="color:#e6db74">&amp;#34;CI=true react-scripts test --coverage --reporters=jest-sonar --collectCoverageFrom=src/**/*.js&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后，执行：&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>npm run test:ci
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样，在 &lt;code>coverage&lt;/code> 目录下会生成测试报告。&lt;/p>
&lt;p>这条命令仅完成了最基本的测试报告的配置。关于可选的命令行参数，请参考 &lt;a href="https://jestjs.io/docs/cli#options">官方文档&lt;/a>。&lt;/p>
&lt;h4 id="golang">Golang&lt;/h4>
&lt;p>执行&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>go test -cover -race -v -coverprofile&lt;span style="color:#f92672">=&lt;/span>coverage.out ./...
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h4 id="配置-sonar-projectproperties-文件">配置 sonar-project.properties 文件&lt;/h4>
&lt;p>我们通过项目根目录下的 &lt;code>sonar-project.properties&lt;/code> 文件对 Sonar 进行配置。一些配置如下：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf"># 源代码所在目录
sonar.sources=src
# 包含测试文件的路径，sonar 将会寻找其中的所有测试文件
sonar.tests=src
# 包含在测试分析内的测试文件
sonar.test.inclusions=src/**/__tests__/*.test.js
# 排除在所有分析之外的路径
sonar.exclusions=node_modules/**,coverage/**
# 覆盖率分析时排除在外的路径
sonar.coverage.exclusions=src/utils/**/*.js
&lt;/code>&lt;/pre>&lt;p>另外，还需要提供已经得到的测试报告的路径。对于 JavaScript：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">sonar.javascript.lcov.reportPaths=coverage/lcov.info
sonar.testExecutionReportPaths=coverage/sonar-report.xml
&lt;/code>&lt;/pre>&lt;p>对于使用 &lt;code>coverage&lt;/code> 进行分析的 Python 代码：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">sonar.python.coverage.reportPaths=coverage-reports/coverage.xml
sonar.python.xunit.reportPath=xunit-reports/xunit-result.xml
&lt;/code>&lt;/pre>&lt;p>对于 Go：&lt;/p>
&lt;pre tabindex="0">&lt;code class="language-conf" data-lang="conf">sonar.go.coverage.reportPaths=coverage.out
&lt;/code>&lt;/pre>&lt;p>这里路径需要改成上面一步所设置的覆盖报告和测试报告的路径。&lt;/p>
&lt;h4 id="设置-ci">设置 CI&lt;/h4>
&lt;p>在 CI 中，定义一个 job 用来执行测试和上传报告：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">unit-test&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#ae81ff">...&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&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 style="color:#f92672">after_script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">SUFFIX=$RANDOM&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">curl &amp;#34;http://10.0.0.11/sonar-scanner.tar.gz&amp;#34; -s -o &amp;#34;/tmp/sonar-$SUFFIX.tar.gz&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">tar -xf &amp;#34;/tmp/sonar-$SUFFIX.tar.gz&amp;#34; -C /opt&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">/opt/sonar-scanner/bin/sonar-scanner&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;h2 id="dockerfile">Dockerfile&lt;/h2>
&lt;p>将代码部署在服务器上需要配置环境、编译应用、安装应用、开启服务等一系列操作，而 Docker 使得我们可以预先完成或指定这些操作，从而能够实现自动部署；Dockerfile 就是我们定义这些操作的地方。&lt;/p>
&lt;p>在软工平台上使用 &lt;code>deployer&lt;/code> 构建、上传 Docker 镜像，并使用此镜像进行部署。大概的逻辑是：&lt;/p>
&lt;ol>
&lt;li>通过 Dockerfile 内的一系列语句预先配置好所需的环境并打包成镜像&lt;/li>
&lt;li>将镜像上传到软工平台的镜像站上&lt;/li>
&lt;li>部署时，拉取之前构建好的镜像，并在一个容器中运行，这样就完成了部署&lt;/li>
&lt;/ol>
&lt;p>这个过程在 &lt;code>.gitlab-ci.yml&lt;/code> 中使用多个 job 完成：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 构建 Docker 镜像&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">build&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>: &lt;span style="color:#ae81ff">build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">export BUILD_IMAGE_NAME=$CI_REGISTRY_IMAGE&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">export BUILD_IMAGE_TAG=$CI_COMMIT_REF_SLUG&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">export BUILD_IMAGE_USERNAME=$CI_REGISTRY_USER&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">export BUILD_IMAGE_PASSWORD=$CI_REGISTRY_PASSWORD&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">deployer build&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:#75715e"># 使用镜像部署&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">deploy&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">stage&lt;/span>: &lt;span style="color:#ae81ff">deploy&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">deployer dyno replace $CI_PROJECT_NAME &amp;#34;$CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG&amp;#34; &amp;#34;$REGISTRY_USER&amp;#34; &amp;#34;$REGISTRY_PWD&amp;#34;&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>&lt;code>build&lt;/code> 的多个 &lt;code>export&lt;/code> 指定了上传时使用的镜像名、tag、使用的用户名和密码；&lt;code>deployer build&lt;/code> 命令完成了 (1) 使用 &lt;code>docker build&lt;/code> 根据 Dockerfile 进行镜像构建，(2) 将镜像上传到平台上；&lt;code>deploy&lt;/code> 工作将 &lt;code>build&lt;/code> 中上传的镜像部署到服务器上。这样就完成了整个部署过程。&lt;/p>
&lt;p>所以，我们需要做的，就是在 Dockerfile 中描述配置容器的过程。&lt;/p>
&lt;h4 id="配置-dockerfile">配置 Dockerfile&lt;/h4>
&lt;p>首先，指定一个“基础镜像”，即使用一个预先配置好的镜像，在这个镜像的基础上配置我们的镜像。比如，对于 Node.js 应用，我们使用官方的 &lt;code>node&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-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">FROM&lt;/span>&lt;span style="color:#e6db74"> node:latest&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在这之后，我们就在一个配置好了 Node.js 的环境中了。&lt;/p>
&lt;p>接下来，无非就是执行一系列语句来编译我们的应用，并将其拷贝到合适的位置。这部分内容可以参考软工课程给出的样例文件。&lt;/p>
&lt;p>比如，编译 React 应用：&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-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#75715e"># 第一个阶段：使用 node 镜像编译 React 应用；将此阶段命名为 build&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">FROM&lt;/span>&lt;span style="color:#e6db74"> node:latest as build&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 设置环境变量 HOME&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">ENV&lt;/span> HOME&lt;span style="color:#f92672">=&lt;/span>/opt/app&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 将工作目录设为 HOME，并拷贝当前目录（项目代码）到 HOME 中&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">WORKDIR&lt;/span>&lt;span style="color:#e6db74"> $HOME&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">COPY&lt;/span> . $HOME&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 安装依赖&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> npm config set registry http://mirrors.cloud.tencent.com/npm/&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> npm install&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 执行 React 的 build，获得网页文件&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">RUN&lt;/span> npm run build&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 第二个阶段：配置使用 nginx 容器，使得可以通过 HTTP 访问&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">FROM&lt;/span>&lt;span style="color:#e6db74"> nginx:1.19&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 从第一个阶段（build）拷贝文件&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 拷贝编译后的网页文件到 nginx 的目录下&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">COPY&lt;/span> --from&lt;span style="color:#f92672">=&lt;/span>build /opt/app/build /usr/share/nginx/html&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 拷贝 nginx 配置文件（储存在源代码中）&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 对于 nginx 的配置和使用，你可能需要查阅相关文档，也可以直接使用已有的样例&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">COPY&lt;/span> --from&lt;span style="color:#f92672">=&lt;/span>build /opt/app/nginx.conf /etc/nginx/nginx.conf&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 暴露容器的 80 端口&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">EXPOSE&lt;/span>&lt;span style="color:#e6db74"> 80&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#75715e"># 这里定义了容器启动时执行的命令（开启 Nginx 服务），注意并不是在构建镜像时执行&lt;/span>&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span>&lt;span style="color:#960050;background-color:#1e0010">&lt;/span>&lt;span style="color:#66d9ef">CMD&lt;/span> [&lt;span style="color:#e6db74">&amp;#34;nginx&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;-g&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;daemon off;&amp;#34;&lt;/span>]&lt;span style="color:#960050;background-color:#1e0010">
&lt;/span>&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>有关 Dockerfile 的语句，可以参考 &lt;a href="https://yeasy.gitbook.io/docker_practice/image/build">Docker - 从入门到实践&lt;/a>（中文）或 &lt;a href="https://docs.docker.com/engine/reference/builder/">Dockerfile 官方文档&lt;/a>（英文）。&lt;/p>
&lt;h4 id="在-ci-中向-dockerfile-传递环境变量">在 CI 中向 Dockerfile 传递环境变量&lt;/h4>
&lt;p>有时候，我们需要向 Dockerfile 中传递环境变量，比如使用不同的环境部署生产环境和开发环境的代码。但是，&lt;code>deployer&lt;/code> 并不会向 &lt;code>docker build&lt;/code> 传递环境变量，因此在 Dockerfile 中是无法获取到我们希望传递的环境变量的。&lt;/p>
&lt;p>一种不太优雅的解决办法是：使用不同的 Dockerfile，直接在 Dockerfile 中用 &lt;code>ENV&lt;/code> 语句设置环境变量。&lt;/p>
&lt;p>比如，当前项目根目录下有两个文件：用于生产环境的 &lt;code>Dockerfile&lt;/code>，以及用于开发环境的 &lt;code>Dockerfile-dev&lt;/code>，它们的唯一区别是，&lt;code>Dockerfile-dev&lt;/code> 设置了一个名为 &lt;code>REACT_APP_IS_DEV_SERVER&lt;/code> 的环境变量，在我们的 React 应用中需要根据此环境变量判断所处的是否为生产环境：&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-dockerfile" data-lang="dockerfile">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ENV&lt;/span> REACT_APP_IS_DEV_SERVER&lt;span style="color:#f92672">=&lt;/span>true
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>但是，因为 &lt;code>deployer&lt;/code> 无法指定构建镜像所使用的 Dockerfile 而只能使用文件名为 &lt;code>Dockerfile&lt;/code> 的文件，我们需要将文件更名为 &lt;code>Dockerfile&lt;/code>。在 CI 中，分别为生产环境和开发环境定义 build 的 job：&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-yml" data-lang="yml">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#f92672">build&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">extends&lt;/span>: &lt;span style="color:#ae81ff">.build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">only&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">master&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:#f92672">build-dev&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">extends&lt;/span>: &lt;span style="color:#ae81ff">.build&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">before_script&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">rm Dockerfile&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">mv Dockerfile-dev Dockerfile&lt;/span>
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#f92672">except&lt;/span>:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> - &lt;span style="color:#ae81ff">master&lt;/span>
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>我们用 &lt;code>Dockerfile-dev&lt;/code> 替代了 &lt;code>Dockerfile&lt;/code>，实现了根据条件传递环境变量的目的。&lt;/p>
&lt;h2 id="数据库配置">数据库配置&lt;/h2>
&lt;p>对于需要使用 Client/Server 通信模式数据库的情况，需要部署一个独立的数据库服务器。下面以 MySQL 为例。&lt;/p>
&lt;p>对于其他数据库，过程大同小异，只需要完成：&lt;/p>
&lt;ul>
&lt;li>创建仓库并设置好 CI/CD&lt;/li>
&lt;li>将持久卷挂载到数据库储存文件的位置&lt;/li>
&lt;li>设置好数据库访问的用户名和密码、创建数据库、授予权限等初始化工作&lt;/li>
&lt;li>在后端上配置好访问数据库的信息&lt;/li>
&lt;/ul>
&lt;h4 id="建立仓库配置持久卷和端口">建立仓库、配置持久卷和端口&lt;/h4>
&lt;p>在软工平台上创建数据库所用的仓库，并在软工平台 &lt;code>项目管理 - 部署管理 - 持久储存&lt;/code> 中新建一个持久卷。&lt;/p>
&lt;p>之后，在上方 &lt;code>容器&lt;/code> 中打开数据库容器，在 &lt;code>挂载&lt;/code> 一栏中选择此持久卷，挂载点设置为 &lt;code>/var/lib/mysql&lt;/code>。这是因为 MySQL 所有的数据库文件均储存在此目录下，将持久卷挂载到此目录上可以保持数据库文件不因重新部署而丢失。&lt;/p>
&lt;p>在容器的 &lt;code>端口&lt;/code> 中添加 MySQL 使用的 &lt;code>3306&lt;/code> 端口。&lt;/p>
&lt;h4 id="配置-cicd">配置 CI/CD&lt;/h4>
&lt;p>&lt;code>.gitlab-ci.yml&lt;/code> 和 Dockerfile 直接使用 &lt;a href="https://github.com/prnake/Quick-Red/tree/master/mysql">Quick-Red&lt;/a> 的配置即可。&lt;/p>
&lt;h4 id="初始化数据库">初始化数据库&lt;/h4>
&lt;p>初次部署完成后，需要进入容器初始化 MySQL。在软工平台数据库容器下打开 &lt;code>管理 - 终端&lt;/code>，进入容器。&lt;/p>
&lt;p>然后，执行 &lt;code>mysql&lt;/code> 命令进入 MySQL。&lt;/p>
&lt;p>首先，设置数据库 &lt;code>root&lt;/code> 用户的密码（&lt;code>&amp;lt;password_root&amp;gt;&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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">ALTER&lt;/span> &lt;span style="color:#66d9ef">USER&lt;/span> &lt;span style="color:#e6db74">&amp;#39;root&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;localhost&amp;#39;&lt;/span> IDENTIFIED &lt;span style="color:#66d9ef">BY&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;lt;password_root&amp;gt;&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>接下来，创建供外部访问的账号（&lt;code>&amp;lt;password_remote&amp;gt;&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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">USER&lt;/span> &lt;span style="color:#e6db74">&amp;#39;django&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;%&amp;#39;&lt;/span> IDENTIFIED &lt;span style="color:#66d9ef">BY&lt;/span> &lt;span style="color:#e6db74">&amp;#39;&amp;lt;password_remote&amp;gt;&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>在这里，&lt;code>'django'@'%'&lt;/code> 的意思是，创建一个从任意 IP 访问（&lt;code>%&lt;/code>）、用户名为 &lt;code>django&lt;/code> 的账户。&lt;code>django&lt;/code> 可以设置为你想设置的用户名。注意用户名尽量不要使用 &lt;code>root&lt;/code>，会产生冲突。
然后，创建一个空的数据库（&lt;code>db_name&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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">CREATE&lt;/span> &lt;span style="color:#66d9ef">DATABASE&lt;/span> db_name;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>将此数据库的访问权限授予远程用户（注意修改 &lt;code>db_name&lt;/code> 和 &lt;code>django&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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">GRANT&lt;/span> &lt;span style="color:#66d9ef">ALL&lt;/span> &lt;span style="color:#66d9ef">PRIVILEGES&lt;/span> &lt;span style="color:#66d9ef">ON&lt;/span> db_name.&lt;span style="color:#f92672">*&lt;/span> &lt;span style="color:#66d9ef">TO&lt;/span> &lt;span style="color:#e6db74">&amp;#39;django&amp;#39;&lt;/span>&lt;span style="color:#f92672">@&lt;/span>&lt;span style="color:#e6db74">&amp;#39;%&amp;#39;&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>最后，更新权限：&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-sql" data-lang="sql">&lt;span style="display:flex;">&lt;span>FLUSH &lt;span style="color:#66d9ef">PRIVILEGES&lt;/span>;
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>这样，我们就可以在后端服务器上访问此数据库了。&lt;/p>
&lt;h4 id="配置-django">配置 Django&lt;/h4>
&lt;p>这里以 Django 为例配置使用上面已经配置好的 MySQL 数据库。&lt;/p>
&lt;p>在软工平台中，创建一个“配置”，并上传一个包含数据库访问信息的 JSON 文件：&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-json" data-lang="json">&lt;span style="display:flex;">&lt;span>{
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// 在 MySQL 中新建的数据库名
&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">&amp;#34;MYSQL_NAME&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;db_name&amp;#34;&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 style="color:#75715e">&lt;/span> &lt;span style="color:#f92672">&amp;#34;MYSQL_USER&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;root&amp;#34;&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 style="color:#75715e">&lt;/span> &lt;span style="color:#f92672">&amp;#34;MYSQL_PASSWORD&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;&amp;lt;password_remote&amp;gt;&amp;#34;&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 style="color:#75715e">&lt;/span> &lt;span style="color:#f92672">&amp;#34;MYSQL_HOST&amp;#34;&lt;/span>: &lt;span style="color:#e6db74">&amp;#34;example-app.secoder.local&amp;#34;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#75715e">// MySQL 端口
&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">&amp;#34;MYSQL_PORT&amp;#34;&lt;/span>: &lt;span style="color:#ae81ff">3306&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>然后，在软工平台的后端容器中将此文件（配置）挂载到源代码所在的子目录下（这里你可能需要查看你的 Dockerfile 来获取容器中源代码所在的目录）。注意，这个操作会将此目录的所有内容替换为上面配置的内容，因此&lt;strong>不要将其挂载到源代码根目录下，也不要将其挂载到任何一个原有的子目录下&lt;/strong>。&lt;/p>
&lt;p>在 settings.py 中，读取配置文件（这里，我们把配置文件目录挂载到了 代码目录/config，文件名为 ‌config.json）：&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-python" data-lang="python">&lt;span style="display:flex;">&lt;span>&lt;span style="color:#66d9ef">if&lt;/span> os&lt;span style="color:#f92672">.&lt;/span>path&lt;span style="color:#f92672">.&lt;/span>isfile(&lt;span style="color:#e6db74">&amp;#34;config/config.json&amp;#34;&lt;/span>):
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#66d9ef">with&lt;/span> open(&lt;span style="color:#e6db74">&amp;#34;config/config.json&amp;#34;&lt;/span>, &lt;span style="color:#e6db74">&amp;#34;r&amp;#34;&lt;/span>) &lt;span style="color:#66d9ef">as&lt;/span> file:
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> env &lt;span style="color:#f92672">=&lt;/span> json&lt;span style="color:#f92672">.&lt;/span>load(file)
&lt;/span>&lt;/span>&lt;/code>&lt;/pre>&lt;/div>&lt;p>然后，将 &lt;code>DATABASES&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-python" data-lang="python">&lt;span style="display:flex;">&lt;span>DATABASES &lt;span style="color:#f92672">=&lt;/span> {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;default&amp;#39;&lt;/span>: {
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;ENGINE&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;django.db.backends.mysql&amp;#39;&lt;/span>,
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;NAME&amp;#39;&lt;/span>: env[&lt;span style="color:#e6db74">&amp;#34;MYSQL_NAME&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;USER&amp;#39;&lt;/span>: env[&lt;span style="color:#e6db74">&amp;#34;MYSQL_USER&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;PASSWORD&amp;#39;&lt;/span>: env[&lt;span style="color:#e6db74">&amp;#34;MYSQL_PASSWORD&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;HOST&amp;#39;&lt;/span>: env[&lt;span style="color:#e6db74">&amp;#34;MYSQL_HOST&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;PORT&amp;#39;&lt;/span>: env[&lt;span style="color:#e6db74">&amp;#34;MYSQL_PORT&amp;#34;&lt;/span>],
&lt;/span>&lt;/span>&lt;span style="display:flex;">&lt;span> &lt;span style="color:#e6db74">&amp;#39;OPTIONS&amp;#39;&lt;/span>: {&lt;span style="color:#e6db74">&amp;#39;charset&amp;#39;&lt;/span>: &lt;span style="color:#e6db74">&amp;#39;utf8mb4&amp;#39;&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;p>至此，Django 就可以使用 MySQL 服务器了。注意，你应该避免在开发环境中使用生产环境的 MySQL 服务器，这个问题可以通过挂载不同的配置文件解决。&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><item><title>2021 秋季学期总结</title><link>https://yeshe.ng/blog/posts/2022/01/2021-fall-summary/</link><pubDate>Sat, 08 Jan 2022 01:05:00 +0800</pubDate><author>liang2kl@outlook.com (Liang Yesheng)</author><guid>https://yeshe.ng/blog/posts/2022/01/2021-fall-summary/</guid><description>2021 秋季学期总结</description><content>&lt;h2 id="关于各课程">关于各课程&lt;/h2>
&lt;p>这个学期总共 17 学分，除了体育和信概，上了五门课：汇编、自动机、数据结构、网原、概统。&lt;/p>
&lt;p>汇编的收获挺大。老师其实讲得很一般，但课程内容很有意思，计算机系统的知识对于之前只接触过上层软件的我来说是一个全新的世界。计算机系统各个部分、各个层次的运转和协调非常精妙，下学期应该有机会继续深入研究。&lt;/p>
&lt;p>自动机属于个人感受先抑后扬的课程。前两节课上来就讲上下文无关文法无异于当头一棒，这部分内容过于抽象，而且老师并没有作很好的解释，体验极其不好。后来随着正规语言、DFA 这些更符合直觉的内容的引入，终于慢慢弄清楚了研究这些各种各样的语言有什么用，它们分别对应程序设计中的什么问题，也逐渐适应了其中的逻辑。老师非常温柔，也非常认真负责，但是讲课实在是有点无聊。&lt;/p>
&lt;p>邓公的数据结构可以说是清华课程的天花板，上课时都是抱着“上一节少一节”的虔诚心态来享受的。PPT 的组织、老师的讲解、课堂的演示几乎都达到了完美，每堂课听完都有酣畅淋漓的感觉。当然，课讲得好，不代表学生上完课水平就高。上课感觉啥都会了，开始做 PA 还是一脸懵逼，最后的考试更是直接把“你并没有学懂”写在了试卷上。虽然但是，数据结构的确是一门深刻改变编程思想的课程，经过一个学期的学习，受益匪浅。&lt;/p>
&lt;p>网原的学习体验并不好。我对网络还算挺感兴趣，计算机网络本身也很有意思，但是这课恨不得把每一层历经几十年发展积累的无论是过时的还是先进的技术都一股脑塞给你，知识过于繁杂，导致上课根本没有一个清晰的脉络。而且，自底向上的方法让人在前几章学数据通信、物理层和链路层时很难理解这些东西有什么用，这些协议这样设计的意义是什么。不过最后考试出的题目还挺有意思，这个出乎意料。&lt;/p>
&lt;p>概统选的是胡家信老师的课，全程很水，特别是和之前上的唐宏岩的课相比，知识的广度和深度都差太多，统计竟然只讲到参数估计，连假设检验都没讲。老师上课讲得很详细，但是统计部分基本念 PPT，也没有什么原理性、启发性的解释。考试挺难的，有几道题不会做。&lt;/p>
&lt;h2 id="关于项目">关于项目&lt;/h2>
&lt;p>这个学期做的项目最主要是&lt;a href="https://github.com/liang2kl/mkdocs-blogging-plugin">一个静态网站生成器的博客插件&lt;/a>，给 Mkdocs 增加了博客功能。维护用了不少时间，陆续加了不少功能，用的人也逐渐多了起来，感受还是挺不错的。不过用插件很难构建一个功能完善的博客，最后还是转向了 Hugo 来建博客。&lt;/p>
&lt;p>另外，还写了一点 SwiftUI 教程，主要想对 SwiftUI 一些难懂的概念、原理作一下介绍，不过写了一半就停下来了（的确太花时间），写的效果也没有达到预期，可能寒假会继续写下去。&lt;/p>
&lt;h2 id="总结">总结&lt;/h2>
&lt;p>前几天和 lqw 吃饭的时候聊到，感觉这个学期过得比以往任何一个学期都要快。前几个学期要么有繁忙的社工，要么在玩命写项目，要么在紧张准备转系，相比之下这个学期显得过于平淡了。&lt;/p>
&lt;p>这个调子其实在开学前就定下了。我的 GPA 在机械还能勉强排到中上游，而在各路大神齐聚的贵系，这个分数对应的排名就变得非常难看，压力变得空前巨大。于是，暑假的时候我就先后退掉了宣中的社工、拒绝了一个创业团队的邀请。我给出的理由都是：成绩不好，之后只能专心学业。&lt;/p>
&lt;p>结果就是，大多数日子里，我基本上都在宿舍-教室-食堂-图书馆的循环中度过，有时候想做一些好玩的东西，也的确写了几个玩具项目，但最终还是一次次地告诫自己：不要让这些事情干扰到学习。&lt;/p>
&lt;p>现在回想，专心学习的确是有好处的。我在大一第一个学期之后第一次做到线下上课全勤，上课基本上都认真地听讲并做了笔记，每天课后也对内容做了回顾。这样坚持下来的最明显的好处是：我能够很清晰地掌握每个课程的脉络，清楚地知道自己在这些课程中学到了什么东西、这些课程的精髓在什么地方。之前的学习过于匆忙，没有回顾、思考的时间，囫囵吞枣，因此几乎没有对哪一门课有“学懂”了的感觉。这个学期，投入了更多的时间进行复习、思考，对知识的理解就有了深度。&lt;/p>
&lt;p>但是，毕竟学习只是生活的一部分，阅读、游戏、游玩、写程序等等都不应该因此而荒废。以后应当提高学习效率，减少浪费在漫无目的的刷手机上的时间，把更多的时间放在这些有意思的事情上面。&lt;/p>
&lt;p>总的来说，这个学期算是挺有收获。在贵系的第一个学期，（应该？但愿？）没有掉队，而且学习的内容都挺感兴趣，虽然学习的过程并不一直都那么顺利。下学期学习任务更重，希望到时候能够既保持课内学习的状态，同时在科研和其他方面有一定的发展吧。&lt;/p>
&lt;h2 id="更新">更新&lt;/h2>
&lt;p>今天（1 月 21 日）不经意间看到机械班群里面赵导发的群公告，才知道出成绩了，来这里更新一下。&lt;/p>
&lt;p>不得不说我的学习感觉和最终的成绩还是比较吻合的，几门比较用功并且学得比较透彻的专业课（汇编、自动机、网原）都得到了 4.0；花了比较多时间但并没有学得很透彻的数据结构，以及没怎么花时间的概统得了 3.6。&lt;/p>
&lt;p>其实考试过程中和考完试之后一直挺担心一件事，就是我自认为学得很透彻的几门课可能由于我考试时经常犯的计算错误、审题错误、脑子短路等问题而不能够取得一个比较好的成绩；考完汇编和概统之后我悲观地认为这个学期，甚至以后的学期一个 4.0 都拿不到了。事实证明我还是多虑了，大多数的考试都是所学内容的子集，如果已经学得比较透彻，那已经超过（大部分的）考试要求了，也基本上不会出现上面这些所谓的问题。虽然这是一个高中就已经非常清楚的常识，但说实话，在没有什么自信的情况下（特别是在贵系中）是很难相信这一点，也很难相信“有付出就有回报”的。&lt;/p>
&lt;p>这个学期的学习算是达到了预期，下学期继续努力吧。&lt;/p></content></item></channel></rss>