Wednesday, September 28, 2022

Linux ldd - 列出動態函式庫的工具

引言
有時候,當執行一些 Linux 程序時,系統會回報一個莫名其妙的錯誤:

-bash: ./executableABC: No such file or directory

遇到這個情況,大部分人都會想:
明明我的程序就在,為什麼系統會找不到它呢 ?

其實,它找不到的不是程序本身,而是它需要的 dependencies。

所以,這個時候,使用 ldd 找出所有需要的 dynamic libraries,就是其中一個最常見的除錯方法。

---

方法
使用方法十分簡單,只需要 ldd "executable name" 即可。

例子:

ldd executableABC

./executableABC: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.34' not found (required by ./executableABC)
./executableABC: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.32' not found (required by ./executableABC)
./executableABC: /lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.33' not found (required by ./executableABC)

        linux-vdso.so.1 (0x00007ffdd8146000)
        libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007fdda6ca6000)
        libdl.so.2 => /lib/x86_64-linux-gnu/libdl.so.2 (0x00007fdda6ca0000)
        librt.so.1 => /lib/x86_64-linux-gnu/librt.so.1 (0x00007fdda6c95000)
        libstdc++.so.6 => /lib/x86_64-linux-gnu/libstdc++.so.6 (0x00007fdda6a7b000)
        libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007fdda692c000)
        libgcc_s.so.1 => /lib/x86_64-linux-gnu/libgcc_s.so.1 (0x00007fdda6911000)
        libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fdda671d000)
        ld-linux-x86-64.so.2 => /lib64/ld-linux-x86-64.so.2 (0x00007fdda6cd3000)

由以上的訊息可見,程序需要 libc.so.6 以及其他 dependencies,
查找後發現,系統並沒有把它們成功安裝好。

故此,只需把所有 dependencies 都用 apt / 其他 package manager 安裝好,
程序就可以成功運行。

Wednesday, July 27, 2022

在 Ubuntu 中增加 Swap 交換檔案

引言
要明白 Swap,首先要明白「隨機存取記憶體」 (RAM)。

隨機存取記憶體 (RAM),是一種能與處理器 (CPU) 快速交換資料的記憶體裝置。一般而言,作業系統需要先把程式和資料,由硬碟載入到記憶體中。然後,CPU 才能通過記憶控制器 (Memory Controller) ,去讀取記憶體中的資料,以及執行裏面的程式。

不過,由於 RAM 的價格比較高昂,一般主機都不會有太多 RAM。所以,當系統沒有足夠的 RAM,但又需要載入一些大型的軟件時,系統就有可能因為 RAM 空間不足,導致無法載入,或者被迫中止程式。為了解決這個問題,作業系統需要把一些閒置、不常用的的程式,先從 RAM 中抽取,放到硬碟中。然後,系統就能將需用的資料和程式,重新載入到釋出的 RAM 中。 這樣,系統便能在有限的 RAM 空間下,執行較多的程式。

Linux 系統設計之時,就已經考慮到這一點,並把這個概念命名為 Swap (交換):將 RAM 中的資料,與硬碟進行抽替交換 (swapping) 。而在 Windows 中,相似的概念則被稱為「虛擬記憶體」(Virtual Memory)。但為避免與「虛擬地址」(Virtual Memory Address / Virtual Address Space) 的概念混淆, 大部分參考資料,均會採用 "Swap" 的叫法。

而這一篇文章,則是在簡單介紹:如何使用數行指令,增加 Ubuntu Linux 的 Swap 空間。擁有更大的 swap 空間,Ubuntu 就能載入更多程式,可玩性自然大大增加。

---

指令
一般而言,Linux 會在安裝系統之時,已經劃分好一個硬碟分區,並命名為 swap。
不過,如果該分區不夠用(或者根本沒有),就可以使用以下指令,動態增加 swap 檔案:

Step 1. 建立新的 swap 檔案 (4GB)

sudo dd if=/dev/zero of=swapfile bs=1MiB = count=$((4*1024)) 

Step 2. 初始化 swap 檔案的內容

sudo mkswap swapfile

Step 3. 更新權限(確保非 root 用戶不能讀取/改動內容)

sudo chmod 600 swapfile 

Step 4. 開啟系統的 Swap 功能

sudo swapon swapfile

Step 5. 檢查 swapfile 是否已經生效

sudo swapon --show

完成後,可使用 cat /proc/meminfo 查看記憶體空間有否變多。

想要把它關掉,也可以用 sudo swapoff -v swapfile ,以及用 sudo rm swapfile 把文件刪掉。

---

參考

[1] - How to Add Swap Space on Ubnutu 20.04 - https://linuxize.com/post/how-to-add-swap-space-on-ubuntu-20-04/

Wednesday, February 2, 2022

DIMM 的不同單位 (Channel, Rank, Chip, Bank, Row/Column)

引言
如果你有組裝電腦的經驗、或者正在從事記憶體相關的開發工作,相信一定會經常接觸到 DIMM (Dual Inline Memory Module) 的各種單位名詞:包括例如 Channel、Rank、Chip、Bank 等等。這篇文章,我們將會先從 Byte Addressing 說起,再解釋 SIMM 和 DIMM 的架構變化。最後,我們會詳細了解各種 DIMM 中常見的單位。

---

Byte Addressing

首先,有一個基本概念:現時大部分電腦中的記憶體地址,都以 1-byte (8-bits) 作為單位的。也就是說,每一個 byte 都會有自己的記憶體地址。這一點,跟 C Programming 中的 char array 操作方法一致:

例如:有一個 C char array:

char arr[] = { 'a', 'b', 'c', 'd' };

由於 'a' 和 'b' 都是完整的 8-bit (1-byte),所以,它們都會有自己的記憶體地址:

printf("%p\n", &arr[0]);  // example output: 0x00FC00000
printf("%p\n", &arr[1]);  // example output: 0x00FC00001
printf("%p\n", &arr[2]);  // example output: 0x00FC00002
printf("%p\n", &arr[3]);  // example output: 0x00FC00003

---

SIMM (Single Inline Memory Module)
大部分電腦及程式,都是以上述的 byte-addressing 為基礎,所以,當我們向 DRAM 查詢某個地址的資料(例如 0x00FC00001),我們只需期待記憶體能在一個 clock cycle 返回一個 byte 的內容(例如 'b')就好。初代的設計,正正是以這種方向作考慮,所以第一代的 SIMM RAM,都只能提供 8-bit 的資料頻寛。也就是說,一塊記憶體模塊,只能在一個 clock cycle 下傳輸 8-bit 的資料。

及後,人們開始注意到:電腦程式在大部分情況下,都會符合局部性原則(Principle Of Locality)。舉例來說:以上的 C 例子,開發者通常不止會用到 'a',還會經常用 loop 把 'b', 'c', 'd' 等等這些鄰近的變數都讀起來。所以,SIMM 的第二代設計,也開始容許 CPU 同一個 clock cycle 下存取多個鄰近的 bytes(也就是 Burst Read)。當時,第二代 SIMM 能提供 32-bit 的資料頻寛。也就是說,一個記憶體模塊,能在一個 clock cycle 下傳輸 32-bit (4 bytes) 的資料。

---

DIMM (Double Inline Memory Module)
由於 CPU 發展不斷加快,記憶體的密度及容量也越來越高,廠商開始覺得 32-bit 不太夠用。所以,它們又推出了 Double 版的 SIMM(也就是 DIMM),把資料頻寛翻倍成 64-bit。換句話說,現時一塊 DIMM 記憶體模塊,能在一個 clock cycle 下傳輸 64-bit (8 bytes) 的資料。

想像一下,如果一塊記憶體模組,想要在一個 clock cycle 下返回 64 bit,理論上,它就需要有 64 個儲存器同時運作。那麼,這些儲存器,到底是如何排列的呢?

---

Row / Column
簡單來說,DRAM 是由電容所組成的揮發性儲存裝置 (Volatile Memory)。每一顆電容,都可以儲存一個 bit 的資料(高電壓 = 1,低電壓 = 0),而且需要定期充電更新。而它們的排列方式,是以 Row(行)和 Column(列)進行的。

假設現在有一個 8 * 8 的矩陣,裏面放著總共 64 個電容,我們就可以用 6 個 bit(3 行,3 列,2^6 = 64)去定址它們。圖中紅色的一個電容,我們可以用 R=5 (101), C=3 (011)(也就是 101 011)表示:

▲ 在這個例子中,如果我們想要取得 101011 (紅色格)的內容,Row Address Decoder 會先開啟第六行,將內容複製到 Sense Amplifier 之中,其後,Column Selector 就會把第四列的內容挑出,並輸出至 Input/Output Buffer 中。這裡,我們就能得到 1-bit 的資料。

---

Bank
文章開始的時候,我們提到:由於大部分電腦均採用 Byte Addressing,所以每一個地址,都應該要指向 8-bits 的內容,而非 1-bit。所以,記憶體應該要儲存 8 bits,而非只有 1 bit。要解決這個問題,最簡單的做法,就是把同一個矩陣複製八份。這樣,一個地址,就能同時回傳 8 個 bits。這種排列方法,我們稱為一個 Bank:

▲ 在這個例子中,如果我們向八個矩陣都發出相同的 Row/Column 指令,我們便能同時得到 8 個 bit,也就是一個 byte 的內容。

---

Chip
Chip 就是指一塊黑色的 memory chip。通常,基於效能等種種原因,一個 memory chip 內會有多於一個 bank,而每個 Bank 均可以完全獨立運作。例如下圖的 memory chip 所示,它一共有 8 個 banks。這樣,當用戶正在存取 Bank 0 的時候,Bank 1-7 就可以忙其他事情(例如預先存取 Prefetch、更新電容等等):

▲ 在這個例子中,我們可以用 3 個 bits 去表示不同 Bank。所以,如果我們想要第 Bank 0 的 Row 1, Column 2, 我們可以把地址寫成 Bank(000), Row(001), Column(010),也就是 000 001 010。

---

Rank
從以上例子所見,每一顆 memory chip 都能在同一個 cycle 中返回 8 bits(1 byte)。但是,由於 DIMM 能支援在同一個 cycle 內返回 64 bits(8 bytes)的資料,如果我們只採用一顆 memory chip,理論上,我們就只能用到 8 bit / 64 bit = 12.5% 的頻寛,十分浪費。有見及此,DIMM 會把八顆 8-bit 的 memory chip 並聯在一起(或者四顆 16-bit 的 memory chip,視乎情況),並以 Burst Mode 等方法,令八顆 memory chip 能同時進行讀寫。這種排列,則稱為一個 Rank:

 

普遍而言,一塊單邊 DIMM 會有 8 顆 memory chip。如果每一粒 memory chip 的頻寬均為 8-bit,那麼 8 顆同時運作,就有 64 bits,就代表一個 Rank,這種就稱為 single rank。

有些 DIMM 的 memory chip 為 16 bit,那麼四顆就已經 64 bit (16 bits * 4 = 64 bits)。這種情況下,擁有八顆就稱為 dual rank。還有一種雙面 DIMM,計算方法也大同小異。

---

Channel
Memory Controller 的數量,決定了 memory channel 的數量。大部分情況下,一個 CPU 可以有一個或多個 memory controller,而每一個 memory controller 則只能有一個 channel。每一個 channel 也只能同時讀寫同一個 Rank 的 memory chip。

例如有一個 CPU,它只有一個 memory controller,而該 memory controller 也只支援一個 channel。如果該主機插上了兩條 single rank 的 DIMM,它也只能同時對其中一條 DIMM 進行讀寫。

又例如有一個 CPU,它有兩個 memory controller,所以它能同時用到兩個 channel(dual channel)。如果該主機插上了兩條 single rank 的 DIMM,它就能同時對兩條 DIMM 進行讀寫。

理論上,如果 CPU 的 memory controller 支援兩個 channel,CPU 就能在一個 clock cycle 讀取 64 bits * 2 = 128 bits 的資料。但如果只有一個 channel,它就只能在一個 clock cycle 讀取 64 bits 的資料。所以,同一個資料量,在理想的情況下,dual channel 能比 single channel 節省一半的存取時間。

當然,現實情況下,資料擺放的位置不一、程式設計造成的瓶頸等,都會令實際速度有所折扣。所以,現實中的 dual channel 並沒有想像中那麼快。

▲ 從以上圖片可見,一個 CPU 可以支援兩個(或以上)的 channel,而每一個 channel 可以支援多個 DIMM 模塊。不過,在同一個 channel 下,只有一個 rank 的 memory chips 能被同時讀取。

---

近年發展
隨著近年 DDR4 / DDR5 等高速記憶體開始普及,廠商也積極引入更多新的技術,以便進行更多的並行運算(parallelism)和更低的延遲(例如 Group Memory Banks 等等)。最經典的例子則是 DDR (Double Data Rate):DDR 相比起 SDR (Single Data Rate),會同時用上 reference clock 的 rising edge 和 falling edge 進行讀寫。但整體而言,記憶體的基本架構仍然不變。

---

參考

[1] Random Access Memory - https://www.youtube.com/playlist?list=PLTd6ceoshpreE_xQfQ-akUMU1sEtthFdB

[2] 圖解RAM結構與原理,系統記憶體的Channel、Chip與Bank - https://www.techbang.com/posts/18381-from-the-channel-to-address-computer-main-memory-structures-to-understand