contributed by < allenliao666 >
原連結掛掉,新連結如上!
當我們需要傳遞或儲存資料時,可能會發生錯誤或是資料遺失。這時就需要 Error-Correcting Codes (ECC) 把不正確的資料修正回來,所謂 ECC 就是在在資料發生缺漏時,我們可以透過數學架構,去猜出一個最可能性最高的答案。 ECC 的實作為將原有的資料加入額外的校驗碼後經由是先建構的數學規則組成碼字,碼字在面對可容忍數量的錯誤時可以發現錯誤的位置並更正,以下介紹 SEC 、 Galois Field 和 Reed Solomon code (RS Code), Linux kernel 中與 ECC 相關的程式碼。
為什麼我們需要了解 Galois Field 呢?因為 Galois Field 具有一些特別的性質,讓我們更容易在上面使用 RS Code 作為編解碼器(Codec)。
最簡單的 ECC 就是 SEC ,顧名思義 SEC 可以發現並修正單一位元的錯誤。以下舉 (7,4) Hamming code 為例。首先定義如下, 是我們要傳送的4位元資料, 。把 經過編碼(encode)後,會得到 , $c 被稱為碼字(codeword),就是在傳輸或是保存時使用的格式。將前述行為寫成數學式為 以矩陣乘法表示即為
這裡用到的 被稱為生成矩陣(Generator matrix),他的功能就是把 編碼為 ,而 所組成的向量空間 為 的子空間, 為 行生成的子空間,即 ,可歸納為 。
的二進位格式為其中 到 為我們要傳遞的資料(前述的 ), 是用來檢測並修正錯誤的奇偶校驗碼,對應如下圖
圖中3個集合兩兩相交,資料位於集合相交處,每個集合各有一個奇偶校驗碼。 Hamming code 規定每個集合中1的數量皆為偶數。若發出的碼字 為0111001
,但在傳遞過程中發生錯誤,導致 從0變成1,故收信方收到錯誤碼字 0111011
。收信方會逐一比對每個集合中1的數量是否符合規定,因此就會發現 的值應更正為0。舉個例子,若 發生錯誤,則會導致三個集合1的數量都是奇數。
接下來用數學的觀點解釋上述的例子,首先先定義 Hamming Code 上的運算,包含加法和乘法。此處的加法和乘法皆為模二運算,加法的規則等同互斥或( exclusive or , XOR);乘法的規則即一般的乘法後 mod 2。把上圖轉換為方程式,考慮集合 。令 對應到 且 對應到 。可以寫成下列方程式:
改寫成矩陣如下:
上述矩陣為奇偶檢驗矩陣(Parity check matirx) , , 。 為 的解集合,即為 的 null space (kernel space) 。據秩—零化度定理, 的維度為 7 ,且 的三條方程式線性獨立,故秩(rank) 為 3 , 的維度即為 4 。因此 。
討論完編碼的規則後,在解碼時我們需要校驗子(Syndrome) 確認收到的碼字是否有誤。假設碼字 在傳遞過程發生錯誤 ,收信方收到有錯的碼字 。 檢驗碼字的方法就是把 乘 ,若碼字無誤, 會是零矩陣;若碼字有誤則 非零矩陣。寫成數學式 。因為 ,所以 ,因此可以使用 把 給反推出來。然而有可能會產生多組解,通常我們會選擇錯的位元數最少的向量,因為一般情況下發生錯誤的機率較正確的機率低,所以錯的位元數最少的向量的機率最大,得到 後我們就可修正 。舉例來說, 為 0111001
,第六位元發生錯誤,故 為 0000010
, 則 為 0111011
。
因為 非零矩陣,故一定有某個位元發生錯誤,我們發現 和 的第六行相同,故可以反推 最有可能為 0000010
,因為他錯的位元數最少。由於我們假設碼字發生錯誤的機率很小,因此有一個位元錯誤的機率會遠大於兩個位元錯誤,所以我們會傾向比較少的位元錯誤。經過修正錯誤後,就能還原 的值了!由於 行獨立,因此錯誤的位元位置不同,生成的 就會不同,例如錯誤發生在第四位元,則 為 。
藉由上述例子我們理解 (7, 4) Hamming Code 的編解碼規則,接下來從數學的角度討論碼字的長度和錯誤修正能力的關聯。
首先定義 Hamming Distance 為兩個向量間相異位元數量;Hamming Weight 為向量非零的位元數。舉例來說 為 0001011
, 為 1101010
,則 , , 。可以發現 ,也就是兩者的 Hamming Distance 等同 先和 做 XOR 再取 Hamming Weight 。
接著定義 的 minimum distance 為 中任兩個相異碼字的 Hamming Distance 之最小值:
還有定義 的 minimum weight,即 中不為 0 的碼字之 Hamming Distance 的最小值:
由於 是 的 kernel space ,所以 是一個線性空間,故具有封閉性, 中碼字 XOR 後,仍存在於 。因此可以推論 。
以 (7, 4) Hamming Code 為例,他的 minimum distance 等於 3 。因為 為 的行組合,且 行獨立,表示若要使 , 至少有 3 個以上 1 。
為什麼我們會需要知道 的 minimum distance 呢?因為 minimum distance 的大小決定碼字可以修正多少位元的錯誤。若 minimum distance 為 t ,則可以修復 位元的錯誤,想法如下:
若把 想像為一個平面,所有碼字分布在上面(包括正確和有誤的碼字),以每個正確的碼字為圓心,以半徑 畫出圓形,在這範圍內的錯誤碼字將會被修正為該圓心的碼字。因此必須保證每個圓形之間不能重疊,不然錯誤碼字若在兩圓交疊處,會不知道應該修正為哪個正確碼字才對。故兩圓心間的距離至少為 ,即為 minimum distance 。
Reed Solomon Code (RS Code)運用前面章節提到的,以加入多餘位元的方式去對抗傳遞過程中可能發生的資訊錯誤。因此 RS Code 被廣泛運用在硬碟、快閃記憶體等領域。 RS Code 需建構在有限體上,常使用 。
給定一個環 以及一個未知數 ,則可建構多項式 如下:
其中 為 的元素, 的最高次項被記做 。且兩多項式的和、差與積仍是多項式,因此多項式也會組成環 ,稱為 上的多項式環。
在有限體 中的元素共有 個二進位多項式(多項式的係數只會是 0 或 1),且任一多項式的最高項次不超過 ,因此這些元素可以表示 個字串。例如 表示 10100011
。
需要注意的是,因為 上的運算要模2,因此加減法等同 XOR 。例如 。乘法的運算可以視為位元移動後再 XOR ,例如 。因為現在的最高次項超過 7 了,因此需要 對質式(irreducible polynomial) 取模。所謂質式指一個多項式不能再被分解成其他多項式的乘積,而且他的 為 。本例以 作為質式,因此前面的乘法結果為 。
在編碼之前,我們要先找到有限體的本原元/生成元 (primitive element / generator),上述兩者意思相同,即找到一個有限體中的元素,可以藉由改變次方而生成所有元素,舉 為例,3 就是本原元,因為把 到 寫成集合 就是 中的所有元素。
此外,我們令傳輸資料的長度為 ;並加入 個錯誤修正碼, ,可修正最多 個錯誤, RS Code 由上述兩者組成,總長度 。
RS Code 的編碼過程非常簡單,首先定義資料多項式(message polynomial):
共 k 項,其中 為有限體中的元素。我們的目標就是透過映射 把資料編碼成碼字 。因此我們需要生成多項式(generator polynomial):
該式為 m 次多項式,且最高項係數為 1 。經過編碼的碼字如下:
可以發現 ,且 必定整除於 ,因此正確傳輸的碼字在解碼時應為零多項式。其中 到 共 k 項的係數和 相同,等同我們把資料記錄在前 k 項係數。此外 的 最高為 ,故不會影響到要傳輸的資料 ( 的前 k 項係數)。
RS Code 有很多解碼方式,以下介紹 Linux kernel 中 linux/lib/reed_solomon 使用的 Berlekamp-Massey 演算法,分為下列五個步驟:
如同先前 SEC Code 章節談過的,校驗子矩陣若為零矩陣表示資料無誤。因為 RS Code 是多項式形式,所以需要校驗子多項式協助判斷資料的正確性。建構校驗子多項式的方式很簡單,只要把編碼時使用的本原元 (即生成多項式的因式)代入收到的 RS Code 即可,若得到的校驗子多項式不為 0 ,表示碼字錯誤。
為了找到錯誤的位置,因此需要計算錯誤位置多項式。
使用 Chien search
又被稱為伽羅瓦體,顧名思義, Galois Field 即為一個包含有限多個元素的代數結構,在這個結構中的加法和乘法具有下列提到的性質。
令 為一有限體,則 為阿貝爾環 (Abelian ring) 且 為群,兩者分別具有以下性質,這裡提到的加法和乘法都是模術運算。
若 為阿貝爾環表示 為阿貝爾群(交換群)、 為半群(semigroup) 且 × 對 + 符合分配律。
阿貝爾群為具有交換律的群,即群具下列前四項性質,阿貝爾群則是五項性質都有, 即為一阿貝爾群,接著以 為例:
封閉性:
結合律:
具單位元素:
具反元素:
交換律:
半群則僅具有封閉性和結合律,例如 ,同樣以 為例:
封閉性:
結合律:
最後是 中 × 對 + 的分配律:
經過上面的舉例可知 為有限體,其實 皆屬於有限體,即只要 為質數,在 上的模數加法和乘法都符合上述性質。那為什麼在合數為模的運算就不是有限體呢?舉 為例,發現2的乘法反元素不存在,即 , 不存在,只要是合數的因數,他的乘法反元素就不存在。與之相反, 中每個元素的乘法反元素都存在,其對應關係如下 。
我們經常把有限體記為 ,其中 為質數; 為正整數,表示在該有限體上的運算要對 取模,且有限體的大小為 。 例如 RS Code 建構在 上,即 RS Code 的加法和乘法都要對2取模,且 的元素個素共256個。
Linux kernel 中有關 RS Code 的程式碼主要出現在 linux/lib/reed_solomon 和 linux/include/linux/rslib.h 。以下的解說參考主要維護者 Thomas Gleixner 寫的 Reed-Solomon Library Programming Interface 。
init_rs
init_rs
用於初始化 rs_control 結構,該結構包括一個指向 rs_codec
結構的指標和 buffers 陣列, buffers 陣列在解碼時會派上用場。
Linux 提供兩種初始化 rs_control 的函式,分別是init_rs
和 init_rs_non_canonical
,兩者間的差別在於第二個引數。init_rs
使用本原多項式(primitive polynomial); init_rs_non_canonical
則使用 gffunc
生成一個體:
需要注意的是 prim
表示 。若不使用 gffunc
的狀況下,則 Linux 把 預設為 2。舉例來說,若 prim
= 1,則生成元為 2。
init_rs
會呼叫 lib/reed_solomon/reed_solomon.c 中的 init_rs_gfp
,之後 init_rs_gfp
再呼叫 init_rs_internal
:
在 init_rs_internal
中首先檢查引數是否合理:
為了避免解碼時使用過多記憶體資源,因此需要分配 buffer 給 rs_control
, RS_DECODE_NUM_BUFFERS
= 8,在解碼時這些 buffer 會派上用場。
接著走訪 codec_list
尋找是否已經有相同的 rs_codec
結構,若是存在,則指向該 rs_codec
即可,若不存在則呼叫 codec_init
新增一個 rs_codec
結構。
codec_init
中,有一段程式碼使用 alpha_to 和 index_of 陣列建立生成多項式如下:
rs_modnn
rs_modnn
用於計算對 nn
取餘的值。因為 nn
= (1<<mm)-1 ,故為梅森數,可以對該運算最佳化如下,舉例來說,令 nn
= 3, mm
= 4,則 可以改寫為 。相關細節請參考Mersenne Numbers: Mod 3, Mod 7, Mod () 。此外,梅森數應也可以2024q1 第 4 週測驗二的方式實作。
encode_rs8
首先檢查輸入的引數是否合理:
decode_rs8
首先參考作業六中針對 Linux 核心模組的內容,核心模組定義為視核心需要可以動態掛載和卸載的程式碼,使核心無須重新開機即可使用所需功能。
在實作核心模組前,我們必須先檢查 Kernel 是否編譯相關的 Reed-Solomon 模組:
發現 CONFIG_REED_SOLOMON=m
,因此手動加載 Reed-Solomon 模組,並檢查是否掛載成功: