<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>WSLIN</title><link>https://wslin.dev/</link><description>Recent content on WSLIN</description><generator>Hugo</generator><language>zh-tw</language><lastBuildDate>Fri, 02 Oct 2026 15:53:49 +0800</lastBuildDate><atom:link href="https://wslin.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Project</title><link>https://wslin.dev/projects/</link><pubDate>Fri, 02 Oct 2026 15:53:49 +0800</pubDate><guid>https://wslin.dev/projects/</guid><description>&lt;hr&gt;&#10;&lt;h2 id="01-自製簡易-os---使用c與risc-v從零開始實作"&gt;&#10; 01 自製簡易 OS - 使用C與RISC-V從零開始實作&#10; &lt;a class="heading-link" href="#01-%e8%87%aa%e8%a3%bd%e7%b0%a1%e6%98%93-os---%e4%bd%bf%e7%94%a8c%e8%88%87risc-v%e5%be%9e%e9%9b%b6%e9%96%8b%e5%a7%8b%e5%af%a6%e4%bd%9c"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;為了更深入學習CS做的專案。同時支援在真實RISC-V CPU C906與QEMU上運行。&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://wslin.dev/blog/os-process-context-switch-cpu-time-slicing/" &gt;【OS】兩個Process做Context Switch實現CPU Time Slicing機制&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="02-linux-driver---spi--gpio-驅動"&gt;&#10; 02 Linux Driver - SPI &amp;amp; GPIO 驅動&#10; &lt;a class="heading-link" href="#02-linux-driver---spi--gpio-%e9%a9%85%e5%8b%95"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://www.threads.com/@chennn_lin/post/Dd1LeRVD1CS?xmt=AQG0FGa1MIzt4QDdfzAQbQtMhmSDqUok6UF8bpzVSoKEoQ" class="external-link" target="_blank" rel="noopener"&gt;Threads 貼文&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>【Linux Kernel】侵入式鏈結串列 Intrusive Linked List</title><link>https://wslin.dev/blog/linux-kernel-linked-list/</link><pubDate>Fri, 19 Jun 2026 14:57:59 +0800</pubDate><guid>https://wslin.dev/blog/linux-kernel-linked-list/</guid><description>&lt;p&gt;一直以為Linked List是最簡單最基礎的資料結構之一，寫法就是很標準的struct裡面塞data跟指向下個節點的指標。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; Node {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; data; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; Node &lt;span style="color:#f92672"&gt;*&lt;/span&gt;next; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但這個作法有個小問題，就是直接把data的型別寫死在struct裡面，不能動態替換，如果哪天data要換型別，就要寫一個新的struct來處理，很顯然這是不太好的設計。但我也一直都認為這就是C的特性，沒有高階語言的泛型，就只能乖乖接受。&lt;/p&gt;&#10;&lt;p&gt;直到學習Linux後才發現，他們用了一個超聰明的方法來實現類似泛型的功能，就是把包裝的順序反過來，Node的struct裡不再包含data欄位，只留下很單純的下個節點的指標，然後將Node本身嵌入進原本data的struct裡面當作一個欄位。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; list_head {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; list_head &lt;span style="color:#f92672"&gt;*&lt;/span&gt;next;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; my_data {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;int&lt;/span&gt; data;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; list_head list;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;};&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;更厲害的操作還在後面，就是當我們loop through List找到目標節點的時候，要如何才能拿到真正的data呢？Linux Kernel首先透過offsetof這個macro找出Node在data struct中的offset，再用container_of macro去把node的記憶體位址扣掉offset回推出實際data的指標，從而拿到真正的資料。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#define offsetof(type, member) ((size_t) &amp;amp;((type *)0)-&amp;gt;member)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#define container_of(ptr, type, member) ((type *)( (char *)(ptr) - offsetof(type, member) ))&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; my_data instance1 &lt;span style="color:#f92672"&gt;=&lt;/span&gt; { .data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;42&lt;/span&gt;, .list &lt;span style="color:#f92672"&gt;=&lt;/span&gt; { .next &lt;span style="color:#f92672"&gt;=&lt;/span&gt; NULL } };&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; list_head &lt;span style="color:#f92672"&gt;*&lt;/span&gt;target_node &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;instance1.list;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; my_data &lt;span style="color:#f92672"&gt;*&lt;/span&gt;real_data &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;container_of&lt;/span&gt;(target_node, &lt;span style="color:#66d9ef"&gt;struct&lt;/span&gt; my_data, list);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;printf&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;Data: %d&amp;#34;&lt;/span&gt;, real_data&lt;span style="color:#f92672"&gt;-&amp;gt;&lt;/span&gt;data);&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>【Observability】用Vector + VictoriaMetrics + Loki + Grafana蒐集Metrics和Logs</title><link>https://wslin.dev/blog/observability-vector-victoriametrics-loki-grafana-metrics-logs/</link><pubDate>Sat, 02 May 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/observability-vector-victoriametrics-loki-grafana-metrics-logs/</guid><description>&lt;p&gt;可觀測性Observability，簡稱o11y，有3大支柱: Metrics, Logs, Traces，這篇文章就來記錄我用Vector + VictoriaMetrics + Loki + Grafana蒐集Metrics和Logs的過程。&lt;/p&gt;&#10;&lt;h2 id="why-not-prometheus-or-elk-"&gt;&#10; Why not Prometheus or ELK ?&#10; &lt;a class="heading-link" href="#why-not-prometheus-or-elk-"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;最主要的原因就是因為我的VPS只有2GB的RAM，不能跑太重量的服務。&lt;/p&gt;&#10;&lt;p&gt;Prometheus本身是一個時序資料庫，會定期去拉取Metrics資料後存入Db，但如果Metrics多起來就會消耗記憶體，加上他的設計是主動拉取資料，不能被動推進來，在某些場景下會不太方便，因此我決定採用更輕量的VictoriaMetrics來當作時序資料庫。&lt;/p&gt;&#10;&lt;p&gt;ELK(Elasticsearch + Logstash + Kibana)是一套功能非常強大的Log分析系統，但也是因為功能太強，因此占用非常大的資源(像是Elasticsearch就會對所有Log建全文索引，非常消耗硬碟空間，且因為是Java寫的因此也很占用記憶體)。&lt;/p&gt;&#10;&lt;p&gt;以我的需求來說不需要ELK這麼強大的功能，只需要能採集Log即可，因此選擇更輕量的Loki，他是一個資料庫，只會對Label做索引，且記憶體消耗非常低。&lt;/p&gt;&#10;&lt;p&gt;選好了存Metrics跟Logs的Db後，接下來就要考慮怎麼拿到系統Metrics跟Logs的問題了，一般來說我們可以用Node Exporter來取得各種硬體資訊(CPU, RAM使用率&amp;hellip;)、Promtail取得各個Process的Logs，但這樣我們就要裝兩個東西維護上有點麻煩，而Vector就是可以一次都拿到的方案，加上他也足夠輕量，因此最後我就選他了。&lt;/p&gt;&#10;&lt;h2 id="安裝"&gt;&#10; 安裝&#10; &lt;a class="heading-link" href="#%e5%ae%89%e8%a3%9d"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;我直接選擇用Docker Compose一次安裝好Vector + VictoriaMetrics + Loki + Grafana，這樣對我來說維護最方便，只要同個資料夾下，設置好每個App的config檔，然後&lt;code&gt;docker compose up -d&lt;/code&gt;就可以無腦使用了。&lt;/p&gt;&#10;&lt;h3 id="docker-composeyml"&gt;&#10; docker-compose.yml&#10; &lt;a class="heading-link" href="#docker-composeyml"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h3&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yml" data-lang="yml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vector&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;timberio/vector:0.34.X-alpine&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;vector&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./vector.yaml:/etc/vector/vector.yaml:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/lib/docker/containers:/var/lib/docker/containers:ro&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 有些系統docker container是放在/var/snap/docker/common/var-lib-docker/containers&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/proc:/host/proc:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/sys:/host/sys:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;victoriametrics&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;victoriametrics/victoria-metrics:v1.96.0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;victoriametrics&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;vmdata:/storage&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;-storageDataPath=/storage&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;-retentionPeriod=1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;-promscrape.config=/etc/prometheus/prometheus.yml&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;loki&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana/loki:2.9.2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;loki&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;./loki-config.yaml:/etc/loki/local-config.yaml:ro&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;lokidata:/loki&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;command&lt;/span&gt;: -&lt;span style="color:#ae81ff"&gt;config.file=/etc/loki/local-config.yaml&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;grafana&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana/grafana:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;grafana&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;GF_SECURITY_ADMIN_PASSWORD=admin&lt;/span&gt; &lt;span style="color:#75715e"&gt;# 首次登入請修改&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;grafana_data:/var/lib/grafana&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;3000:3000&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;unless-stopped&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;vmdata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;lokidata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;grafana_data&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果遇到權限問題可以試試加入 &lt;code&gt;user:root&lt;/code&gt;&lt;/p&gt;</description></item><item><title>【Ubuntu】Fail2Ban基本使用</title><link>https://wslin.dev/blog/ubuntu-fail2ban/</link><pubDate>Thu, 30 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/ubuntu-fail2ban/</guid><description>&lt;p&gt;我們可以先用&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo lastb&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;來看一下所有失敗的登入紀錄&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;last&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;來看登入成功的紀錄&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo grep &lt;span style="color:#e6db74"&gt;&amp;#34;Accepted&amp;#34;&lt;/span&gt; /var/log/auth.log | tail -n &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;或是顯示更詳細的登入資訊&lt;/p&gt;&#10;&lt;p&gt;基本上只要是公開的IP，有開啟22 port就很容易看到一堆登入失敗的資訊，因此我們要來用Fail2Ban把這些掃port的機器人ban掉。&lt;/p&gt;&#10;&lt;h2 id="基本使用"&gt;&#10; 基本使用&#10; &lt;a class="heading-link" href="#%e5%9f%ba%e6%9c%ac%e4%bd%bf%e7%94%a8"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;安裝&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt-get update&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo apt-get install -y fail2ban&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;複製config檔，避免改到預設的&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果想設置嘗試次數、Ban多久等參數的話，可以編輯他，這邊我就先不改，用預設的即可&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo nano /etc/fail2ban/jail.local&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;然後就可以啟動服務了&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo systemctl enable fail2ban&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo systemctl start fail2ban&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;查看目前的封鎖資訊&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo fail2ban-client status sshd&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>【OS】兩個Process做Context Switch實現CPU Time Slicing機制</title><link>https://wslin.dev/blog/os-process-context-switch-cpu-time-slicing/</link><pubDate>Thu, 23 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/os-process-context-switch-cpu-time-slicing/</guid><description>&lt;p&gt;經過了好幾個月的努力，終於成功的在RISC-V開發版上讓兩個Process 做context switch ，實現CPU time slicing 機制，雖然只是個小玩具，但絕對是學coding 這麼久以來最令人興奮的事情之一。&lt;/p&gt;&#10;&lt;h2 id="demo"&gt;&#10; Demo&#10; &lt;a class="heading-link" href="#demo"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;div style="position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden;"&gt;&#10;&#9;&#9;&#9;&lt;iframe allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; fullscreen" loading="eager" referrerpolicy="strict-origin-when-cross-origin" src="https://www.youtube.com/embed/L50YAd4V6M0?autoplay=0&amp;amp;controls=1&amp;amp;end=0&amp;amp;loop=0&amp;amp;mute=0&amp;amp;start=0" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; border:0;" title="YouTube video"&gt;&lt;/iframe&gt;&#10;&#9;&#9;&lt;/div&gt;&#10;&#10;&lt;h2 id="小小踩坑紀錄"&gt;&#10; 小小踩坑紀錄&#10; &lt;a class="heading-link" href="#%e5%b0%8f%e5%b0%8f%e8%b8%a9%e5%9d%91%e7%b4%80%e9%8c%84"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;同時也被一個bug困擾了快兩小時，症狀是在QEMU中都可以正常創建Process，但到了開發版上就完全沒有輸出。&#10;Debug超久才發現是QEMU預設會幫我們清零.bss（未初始化的全域變數），而開發版則不會，導致有些變數一開始就是髒的，果然真實硬體要注意的細節還是很多啊。&lt;/p&gt;</description></item><item><title>【RISC-V】Sv39 - 開啟MMU實現虛擬記憶體</title><link>https://wslin.dev/blog/sv39-risc-v-mmu/</link><pubDate>Thu, 23 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/sv39-risc-v-mmu/</guid><description>&lt;p&gt;虛擬記憶體Virtual Memory絕對是現代OS最重要的核心機制，同時也是最複雜的系統之一，這篇文章就來記錄我對他的理解以及怎麼在OS實作。&lt;/p&gt;&#10;&lt;h2 id="why-virutual-memoryvm"&gt;&#10; Why Virutual Memory(VM)?&#10; &lt;a class="heading-link" href="#why-virutual-memoryvm"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;h3 id="傳統作法"&gt;&#10; 傳統作法&#10; &lt;a class="heading-link" href="#%e5%82%b3%e7%b5%b1%e4%bd%9c%e6%b3%95"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h3&gt;&#10;&lt;p&gt;首先我們要先了解傳統的記憶體管理像是分段Segment，有哪些缺點，以及VM如何改善的。&lt;/p&gt;&#10;&lt;p&gt;以分段為例，假設Process A的實體記憶體位址是&lt;code&gt;0x00001000-0x00002000&lt;/code&gt;，Process B是&lt;code&gt;0x00002000-0x00003000&lt;/code&gt;，可能會有以下問題:&lt;/p&gt;&#10;&lt;h4 id="載入時位址需relocation"&gt;&#10; 載入時位址需Relocation&#10; &lt;a class="heading-link" href="#%e8%bc%89%e5%85%a5%e6%99%82%e4%bd%8d%e5%9d%80%e9%9c%80relocation"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;一般編譯器編譯出來的可執行檔，記憶體位址預設都是從&lt;code&gt;0x00000000&lt;/code&gt;開始，如果此時OS分配該Process的起始位址到&lt;code&gt;0x00001000&lt;/code&gt;，loader就要負責處理這個Offset&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="擴充不易"&gt;&#10; 擴充不易&#10; &lt;a class="heading-link" href="#%e6%93%b4%e5%85%85%e4%b8%8d%e6%98%93"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;如果Process A記憶體不夠用了，想要擴充，就只能申請&lt;code&gt;0x00003000&lt;/code&gt;以後的位址使用，導致Process A內部要存取時必須處理位址不連續的狀況&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="權限管理不易"&gt;&#10; 權限管理不易&#10; &lt;a class="heading-link" href="#%e6%ac%8a%e9%99%90%e7%ae%a1%e7%90%86%e4%b8%8d%e6%98%93"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;如果某個Process意外存取另一個Process的位址空間應該要觸發Exception；或是某段位址空間理論上應該要W/R only，Segement都很難做到&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h4 id="外部碎片化"&gt;&#10; 外部碎片化&#10; &lt;a class="heading-link" href="#%e5%a4%96%e9%83%a8%e7%a2%8e%e7%89%87%e5%8c%96"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h4&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;當系統長時間運行後，可能不同Process間會有空閒的記憶體區塊，但卻因為太小不能放下新的連續空間的Process，導致整體看起來記憶體還夠，實際上卻放不了的狀況&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="virutual-memoryvm作法"&gt;&#10; Virutual Memory(VM)作法&#10; &lt;a class="heading-link" href="#virutual-memoryvm%e4%bd%9c%e6%b3%95"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h3&gt;&#10;&lt;p&gt;VM就是為了解決以上問題出現的，他的概念就是將記憶體切分成多個統一大小的Page，一般是4096 Bytes(4kb)當作基本分配記憶體的單位，並導入&lt;code&gt;邏輯/虛擬位址(Logical/Virtual Address，VA)&lt;/code&gt;轉&lt;code&gt;實體位址(Physical Address，PA)&lt;/code&gt;的概念，VA就是編譯後可執行檔從&lt;code&gt;0x00000000&lt;/code&gt;開始的位址，或是說整個Process內部實際使用的位址，PA則是真實物理記憶體的位址，也就是實際存放指令跟資料的地方。&lt;/p&gt;</description></item><item><title>About</title><link>https://wslin.dev/about/</link><pubDate>Thu, 23 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/about/</guid><description>&lt;hr&gt;&#10;&lt;h2 id="education"&gt;&#10; Education&#10; &lt;a class="heading-link" href="#education"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;國立中央大學 資訊工程學系軟體工程碩士班 (預計2027畢業)&lt;/strong&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Linux作業系統 , 嵌入式系統設計 , 無線通訊與網路 , 軟體工程&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;國立臺北教育大學 數位科技設計學系&lt;/strong&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;數位邏輯設計 , 電子電路 , 電腦網路 , 計算機圖學&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="projects"&gt;&#10; Projects&#10; &lt;a class="heading-link" href="#projects"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://wslin.dev/projects/" &gt;自製簡易 OS - 使用C與RISC-V從零開始實作&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://wslin.dev/projects/" &gt;Linux Driver - SPI &amp;amp; GPIO 驅動&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="skills"&gt;&#10; Skills&#10; &lt;a class="heading-link" href="#skills"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Category&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Technologies &amp;amp; Tools&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Languages&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;C/C++ , Python , C#&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;System&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Linux , Linux Driver , RISC-V , UART , I2C , SPI , GPIO&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Tools&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Git , Docker , GDB&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;DevOps&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;GitLab CI/CD , Prometheus , Grafana , Loki&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;AI&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;LangChain , Langfuse , NVIDIA MPS , YOLO , vLLM , Triton Inference Server&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Application&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Vue.js , FastAPI , MySQL , Unity3D&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h2 id="experience"&gt;&#10; Experience&#10; &lt;a class="heading-link" href="#experience"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;AI Software Intern&lt;/strong&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;研究 &amp;amp; 開發 AI 視覺應用技術 (模型部屬, AI Agent, GPU 資源管理, MLOps&amp;hellip;)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;遊戲程式&lt;/strong&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;參與 4000+ DAU 手機遊戲開發與維護 (活動系統、儲值串接)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;</description></item><item><title>【Docker】Multi-Stage Build 減少Image大小</title><link>https://wslin.dev/blog/docker-multi-stage-build-image/</link><pubDate>Tue, 21 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/docker-multi-stage-build-image/</guid><description>&lt;p&gt;為了可以在CI build Docker Image時節省Image的大小，學了一下Multi-Stage build，這篇文章就來記錄一下。&lt;/p&gt;&#10;&lt;h2 id="image-layer"&gt;&#10; Image Layer&#10; &lt;a class="heading-link" href="#image-layer"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;首先最重要的概念就是，Docker的每個Image都是由多個Layer組成的，當我們在Dockerfile裡&lt;strong&gt;每執行一個指令&lt;/strong&gt;(e.g.&lt;code&gt;FROM, RUN, COPY...&lt;/code&gt;)時，都會在原有的Layer之上再建立一個新的Layer，且該指令對檔案的任何操作或是Diff都只會記錄在這個Layer中(如果是改上一個Layer就有的檔案會複製一份再改，不影響上個Layer的完整性，Copy-on-Write)。&lt;/p&gt;&#10;&lt;p&gt;所以說當我們要build一個新的Image時，裡面可能就會包含很多不同的階段，像是安裝套件、編譯、打包可執行檔等等，每個步驟可能都會在對應的Layer裡產生很多最終執行檔不需要的東西，例如快取、編譯工具，導致整包Image很肥大。&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;FROM&lt;/span&gt; &lt;span style="color:#e6db74"&gt;node:18&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;WORKDIR&lt;/span&gt; &lt;span style="color:#e6db74"&gt;/app&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 複製 package.json 並安裝所有依賴 (包含 devDependencies)&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; package*.json ./&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; npm install&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 複製所有原始碼並進行編譯&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;COPY&lt;/span&gt; . .&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;RUN&lt;/span&gt; npm run build&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 啟動伺服器 (通常是 npm start)&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CMD&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;npm&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;]&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# 【結果】映像檔大小：可能高達 1GB+ (包含 node_modules 和原始碼)&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="multi-stage-build"&gt;&#10; Multi-Stage Build&#10; &lt;a class="heading-link" href="#multi-stage-build"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;為了處理這個問題 Docker提供了多階段build的功能，我們只需要使用&lt;code&gt;FROM AS&lt;/code&gt;就可以把每個不同階段拆開來，只傳遞該階段重要的結果(Artifact)，其他用不到的東西都可以直接丟掉。&lt;/p&gt;</description></item><item><title>【RISC-V】常用Register以及Caller/Callee Saved</title><link>https://wslin.dev/blog/risc-v-register-caller-callee-saved/</link><pubDate>Tue, 21 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/risc-v-register-caller-callee-saved/</guid><description>&lt;p&gt;開發OS最重要的事情之一就是要熟悉使用的指令集，但我經常忘記RISC-V有哪些暫存器以及它們的功能等等，這篇文章就來記錄一下。&lt;/p&gt;&#10;&lt;h2 id="分類"&gt;&#10; 分類&#10; &lt;a class="heading-link" href="#%e5%88%86%e9%a1%9e"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;首先RISC-V每顆CPU核心都有幾類暫存器:&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;x0-x31: 通用暫存器，最常計算用的&lt;/li&gt;&#10;&lt;li&gt;f0-f31: 浮點數運算使用，xv6沒有處理這個，編譯時就擋掉浮點數運算，我暫時也先不考慮支援這個，等到哪天真的碰到計算浮點數的Exception再說&lt;/li&gt;&#10;&lt;li&gt;csr (Control and Status Registers): 各種控制CPU行為的暫存器，非常重要&lt;/li&gt;&#10;&lt;li&gt;pc (Program Counter): 下一行指令的Address&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="x0-x31"&gt;&#10; x0-x31&#10; &lt;a class="heading-link" href="#x0-x31"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Register&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;ABI 名稱&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;Caller/Callee Saved&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th style="text-align: left"&gt;用途&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x0&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;zero&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;—&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;恆為 0&lt;/strong&gt;。寫入會被丟棄，讀取永遠得到 0。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x1&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;ra&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Caller&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;返回地址 (Return Address)&lt;/strong&gt;。執行 &lt;code&gt;call&lt;/code&gt; 時自動更新。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x2&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;sp&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Callee&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;堆疊指標 (Stack Pointer)&lt;/strong&gt;。必須保持 16-byte 對齊。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x3&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;gp&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;—&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;全域指標 (Global Pointer)&lt;/strong&gt;。指向靜態全域變數區。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x4&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;tp&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;—&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;執行緒指標 (Thread Pointer)&lt;/strong&gt;。OS 常拿來存 CPU HartID 或 PCB。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x5 - x7&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;t0 - t2&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Caller&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;臨時暫存器 (Temporary) 0 - 2。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x8&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;s0 / fp&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Callee&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Saved 0 / 幀指標 (Frame Pointer)&lt;/strong&gt;。用於 Trace 呼叫堆疊。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x9&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;s1&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Callee&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Saved 1。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x10 - x11&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;a0 - a1&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Caller&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;函式參數 0 - 1 / 回傳值&lt;/strong&gt;。Syscall 結果也存於此。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x12 - x17&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;a2 - a7&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Caller&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;函式參數 2 - 7。&lt;code&gt;a7&lt;/code&gt; 通常用於傳遞 Syscall ID。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x18 - x27&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;s2 - s11&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;Callee&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Saved 2 - 11。&lt;code&gt;swtch.S&lt;/code&gt; 必須手動保存這組暫存器。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;x28 - x31&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;&lt;strong&gt;t3 - t6&lt;/strong&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;Caller&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td style="text-align: left"&gt;臨時暫存器 3 - 6。&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;註:&lt;/p&gt;</description></item><item><title>【RISC-V】指令、Regitster、Address長度小筆記</title><link>https://wslin.dev/blog/risc-v-regitster-address/</link><pubDate>Tue, 21 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/risc-v-regitster-address/</guid><description>&lt;p&gt;每次都會忘記RISC-V各種東西的長度，因此要記錄下來。&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;指令: 32 bits&lt;/li&gt;&#10;&lt;li&gt;Regitster: 64 bits&lt;/li&gt;&#10;&lt;li&gt;Address: 64 bits (但SV39只用了39 bits 剩下的保留)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;因此如果要Load uint32到暫存器Lower的地方，就會需要lui (Load Upper Immediate) + addi指令&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-riscv" data-lang="riscv"&gt;lui a0, 0x12345 # 載入高 20 位元，a0 = 0x12345000&#10;addi a0, a0, 0x678 # 加上低 12 位元，a0 = 0x12345678&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;而如果是uint64則可以用Pseudo Instruction li&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-riscv" data-lang="riscv"&gt;li t0, 0x1234567812345678&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果不用li就會比較麻煩，需要再加上slli (Shift Left Logical Immediate)&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code class="language-riscv" data-lang="riscv"&gt;# 步驟 1：處理最高 32 位元的前半段 (0x12345)&#10;lui t0, 0x12345 # t0 = 0x0000000012345000&#10;addi t0, t0, 0x678 # t0 = 0x0000000012345678&#10;&#10;# 步驟 2：將這段數據推到最左邊 (左移 32 位)&#10;slli t0, t0, 32 # t0 = 0x1234567800000000&#10;&#10;# 步驟 3：開始蓋下半層大樓 (0x12345)&#10;# 注意：這裡通常會用另一個暫存器來湊，或者直接在 t0 上疊加&#10;lui t1, 0x12345 # t1 = 0x0000000012345000&#10;addi t1, t1, 0x678 # t1 = 0x0000000012345678&#10;&#10;# 步驟 4：將兩者合體&#10;or t0, t0, t1 # t0 = 0x1234567812345678&#10;&lt;/code&gt;&lt;/pre&gt;</description></item><item><title>【OS】OpenSBI 開啟Timer Interrupt</title><link>https://wslin.dev/blog/os-opensbi-timer-interrupt/</link><pubDate>Mon, 20 Apr 2026 13:48:45 +0800</pubDate><guid>https://wslin.dev/blog/os-opensbi-timer-interrupt/</guid><description>&lt;p&gt;最近在實作RISC-V OS的Trap時，剛好要做Timer Interrupt，這在QEMU中非常簡單，只要follow xv6的做法就可以實現，但在Milkv開發版上就不太一樣。&lt;/p&gt;&#10;&lt;p&gt;因為我的Milkv開發版上不是裸機執行OS，而是透過OpenSBI來載入並降級到S-Mode，且又因為RISC-V的Timer Interrupt 是只能在M-Mode設置，因此做起來就比較麻煩，需要在S-Mode下透過ecall 委託M-Mode的OpenSBI來完成。&lt;/p&gt;&#10;&lt;h2 id="xv6在qemu上的做法"&gt;&#10; xv6在QEMU上的做法&#10; &lt;a class="heading-link" href="#xv6%e5%9c%a8qemu%e4%b8%8a%e7%9a%84%e5%81%9a%e6%b3%95"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;先來快速go through xv6是怎麼做Timer Interrupt的，首先需要先還在M-Mode時就先設置menvcfg (Machine Environment Configuration) 這個csr暫存器的bit 63來允許 S-mode 使用 stimecmp (Sstc or Supervisor-mode Timer Compare extension)。 (&lt;a href="https://github.com/mit-pdos/xv6-riscv/blob/riscv/kernel/start.c" class="external-link" target="_blank" rel="noopener"&gt;xv6-start.c line:59&lt;/a&gt;)&lt;/p&gt;&#10;&lt;p&gt;然後再設置mcounteren的bit 1，允許 S-mode 讀 time 計數器，沒設的話在執行&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;asm&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;volatile&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;csrr %0, time&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;=r&amp;#34;&lt;/span&gt; (x));&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;時就會觸發 illegal instruction。&lt;/p&gt;&#10;&lt;p&gt;以上兩個都開啟後，就可以在S-Mode中設置stimecmp來開啟timer的計時功能了，時間到就會Trap，然後跳到我們寫好的Trap Handler處理。&lt;/p&gt;&#10;&lt;h2 id="milkv-duo-s-opensbi-上的做法"&gt;&#10; Milkv Duo S (OpenSBI) 上的做法&#10; &lt;a class="heading-link" href="#milkv-duo-s-opensbi-%e4%b8%8a%e7%9a%84%e5%81%9a%e6%b3%95"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;然而因為我目前的Milkv Duo S是跑在OpenSBI下的，所以OS一進來就是S-Mode，沒辦法碰到M-Mode的暫存器，加上他的CPU C906不支援Sstc，不能直接在S-Mode設timer。&lt;/p&gt;&#10;&lt;p&gt;但好在OpenSBI有開啟前面兩點，並提供直接設置Timer計數的功能，因此只要ecall呼叫一下OpenSBI請他開始計數即可。&lt;/p&gt;</description></item><item><title>【DevOps】 自架Gitlab以及Gitlab Runner跑CI/CD</title><link>https://wslin.dev/blog/devops-gitlab-gitlab-runner-ci-cd/</link><pubDate>Fri, 19 Dec 2025 15:00:15 +0800</pubDate><guid>https://wslin.dev/blog/devops-gitlab-gitlab-runner-ci-cd/</guid><description>&lt;h2 id="docker-compose"&gt;&#10; Docker Compose&#10; &lt;a class="heading-link" href="#docker-compose"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;services&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gitlab&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitlab/gitlab-ce:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitlab&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;environment&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;GITLAB_OMNIBUS_CONFIG&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; # 讓網頁上的 Clone 連結的ip顯示正確&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; external_url &amp;#39;http://&amp;lt;區網ip&amp;gt;:12080&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; nginx[&amp;#39;listen_port&amp;#39;] = 80&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; nginx[&amp;#39;listen_https&amp;#39;] = false&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; # 讓網頁上的 Clone 連結的ssh port顯示正確&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; gitlab_rails[&amp;#39;gitlab_shell_ssh_port&amp;#39;] = 12022&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; prometheus_monitoring[&amp;#39;enable&amp;#39;] = false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;12080:80&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;12443:443&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;12022:22&amp;#34;&lt;/span&gt; &lt;span style="color:#75715e"&gt;# ssh改12022，避免衝突&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/path/to/your/volume/gitlab/gitlab-config:/etc/gitlab&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/path/to/your/volume/gitlab/gitlab-logs:/var/log/gitlab&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/path/to/your/volume/gitlab/gitlab-data:/var/opt/gitlab&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;shm_size&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#39;256m&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;gitlab-network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gitlab-runner&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitlab/gitlab-runner:latest&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;container_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gitlab-runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;restart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;depends_on&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;gitlab&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;volumes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/path/to/your/volume/gitlab/gitlab-runner-config:/etc/gitlab-runner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;gitlab-network&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networks&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;gitlab-network&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;driver&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;bridge&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;預設創建了root帳號，預設密碼可以用以下指令查詢&lt;/p&gt;</description></item><item><title>【Linux Kernel】Process and Thread的關係</title><link>https://wslin.dev/blog/linux-kernel-process-and-thread/</link><pubDate>Wed, 10 Dec 2025 14:57:59 +0800</pubDate><guid>https://wslin.dev/blog/linux-kernel-process-and-thread/</guid><description>&lt;h2 id="前言"&gt;&#10; 前言&#10; &lt;a class="heading-link" href="#%e5%89%8d%e8%a8%80"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;Linux Kernel中的Process, Thread其實跟我們一般認知的定義有些不太一樣，因此這篇文章來記錄這些名詞上的差異。&lt;/p&gt;&#10;&lt;h2 id="歷史"&gt;&#10; 歷史&#10; &lt;a class="heading-link" href="#%e6%ad%b7%e5%8f%b2"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;最開始的Linux，並沒有follow POSIX的標準，且當初也沒有針對多執行緒去設計架構，都還是以單執行緒為主，因此最早期設計了task_struct這個資料結構當作Scheduler調度CPU資源的單位，一個task_struct就代表一個Process。&lt;/p&gt;&#10;&lt;h2 id="後來的設計"&gt;&#10; 後來的設計&#10; &lt;a class="heading-link" href="#%e5%be%8c%e4%be%86%e7%9a%84%e8%a8%ad%e8%a8%88"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;後來多核CPU逐漸成為主流後，多執行緒的設計就很有必要了，但Linux為了方便便延續了之前的設計，將task_struct定義改為Lightweight Process(LWP)，其實就是我們一般認知的Thread的概念，而Process這個名詞則變成了一組LWP的集合，Linux稱之為Thread Group。&lt;/p&gt;&#10;&lt;h2 id="task_struct"&gt;&#10; task_struct&#10; &lt;a class="heading-link" href="#task_struct"&gt;&#10; &lt;i class="fa-solid fa-link" aria-hidden="true" title="Link to heading"&gt;&lt;/i&gt;&#10; &lt;span class="sr-only"&gt;Link to heading&lt;/span&gt;&#10; &lt;/a&gt;&#10;&lt;/h2&gt;&#10;&lt;p&gt;task_struct又被稱作Process descriptor(也就是OS中的PCB)，存放所有跟該LWP有關的資料，其中有兩個重要的欄位：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;pid ：這個LWP的Id，所有LWP的Id都是唯一的&lt;/li&gt;&#10;&lt;li&gt;tgid：同一組LWP(Thread)形成的Process的Id，如果是main thread(或稱Thread Group Leader)則跟pid一致，如果是其他thread則會記錄main thread的pid&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// task_struct中的欄位&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pid_t&lt;/span&gt; pid;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;pid_t&lt;/span&gt; tgid;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;以及另外兩個重要的function：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;getpid()：回傳tgid欄位&lt;/li&gt;&#10;&lt;li&gt;gettid()：回傳pid 欄位，也就是說pid其實實際上的意義是Thread Id，但task_struct中並沒有一個專屬的欄位紀錄tid，而是統一用pid&lt;/li&gt;&#10;&lt;/ul&gt;</description></item></channel></rss>