貢獻者: RinHizakura, jouae
本文先探討比較容易理解的 Hamming codes,並主要針對 Reed–Solomon codes 及其在 Linux 核心中的應用作探討。
Error-Correcting Codes(ECC) 廣泛用於通訊傳輸,補正因為資料損壞、雜訊所導致的錯誤。
過往當人們談及通訊系統的改進時,幾乎只著眼於如何讓訊號接收得更清楚,像是藉由加大天線或增強訊號的傳輸功率,這些方法都以物理學的角度來改進。
1948 年,Claude Shannon 發表一篇劃時代的論文《通訊的數學理論》(A Mathematical Theory of Communications),他挑戰當時的主流思維,他指出,通訊系統的改進不必依賴於實體設備的提升,他認為只要傳送資料的速度低於該頻道的容量上限 (亦即頻道容量 [channel capacity],指通訊頻道能傳輸資訊的速度,以每秒位元數 [bps] 為測量單位),就能大幅降低資料傳輸錯誤的機率,甚至可將錯誤率控制到幾乎為零。
Shannon 的理論不僅顛覆了人們對於通訊系統改進的傳統觀點,更是以一己之力將通訊乃至資料的儲存、壓縮、傳輸,帶進全新的紀元,從而奠定資訊理論 (information theory) 這個學科的基礎。
通常伺服器等級主機配置 BMC (一種 out-of-band 系統,裡面往往是小型的 Linux 系統),後者便於監控硬體的錯誤,且有 EDAC 機制,觸發 ECC 修復記憶體錯誤,從而提升伺服器整體的穩定性。於是乎,主流伺服器品牌會改進其 BMC 裡頭的 ECC 機制。
科普短片:
短片 What are Reed-Solomon Codes? How computers recover lost data 提供 Reed-Solomon (RS) 在內演算法的視覺化解說。
Under working: 閱讀第一手資料。整理 Claude Shannon 的 A Mathematical Theory of Communications 以中文敘述跟更易懂的例子解釋論文中的數學原理。並解釋當初基於什麼樣的公理與假設建立起
參考文獻:
- Error Correction Coding Mathematical Methods and Algorithms by Todd K. Moon
- Error Control Coding: Fundamentals and Applications by Shu Lin
在 Todd 所著一書中描述:
一序列的數個位元可以藉由一對應關係寫成波形(waveform)的形式透過通道(channel)傳遞。二元相移鍵控(Binary phase-shift keying, BPSK)為其中一種調幅(amplitude modulation)形式,其中波形的正負號用於表示位元為 0
或是 1
。
以 構成的序列
表示傳遞的訊息。其中每個位元到達的時間需要 單位時間,換言之,週期為 ,頻率則為 。
要以波形的正負號來表示位元為 0
或 1
,需要先做一個變數變換。以 表示 經過映射至 的結果,具體的表示為:
我們要確立該波形的數學表示式,在有該波形的週期 時,再來要知道該波形的振福(amplitude),所以藉由找出一位元對應振福的映射關係,來確定該波形的振福。
令振福 表示為
得到該振幅後,我們想要藉由一個坐標系來描述在一個訊號中什麼樣的位置會表示為位元為 0
或 1
。先從我們熟知的一維坐標系來思考,一維坐標系就是數線。數線的構成要素為原點、方向,及單位度,二維坐標系就是在一維的基礎上,找出兩同原點且互相正交的單位度。所以在以訊號為坐標軸該怎麼表示單位度?所以要先定義單位訊號是什麼,更具體的說法是單位能量(unit-energy)訊號。
對一連續時間的訊號,可以藉由對時間積分的方式來描述在該段時間內的能量為多少,在《Signal and System》一書 1.1.2 節提及從伏特 與電流 流經一電阻 的物理角度思考該電阻的順時功率為:
第二個等式藉由歐姆定律(Ohm's law) 得來。
在自 至 時間內的功耗則可以表示為:
藉由該物理概念,將一訊號 的能量計算方式定義成自身對自身的 內積
其中,一單位能量訊號表示為:
要注意的是該訊號 在區間 有支撐集(support)。簡單的想在該區間 中 有非零值, 中皆為零。
所以藉由該單位能量訊號 ,我們可以假想一個一維訊號坐標系以 表示為一個軸上的單位訊號,該軸上的點表示不同的相位(phase)。這樣的空間稱之為訊號空間(signal space)。
所以該位元 在時間點 到達的波形,能以一訊號表示:
其中, 對應到的訊號為 , 對應到的訊號為 。在此以 為一維訊號空間維基底的空間,會有兩個座標點 和 。
將此訊號空間與相位繪製成圖,該圖稱之為調變號分佈圖(constellation diagram)。
顯而易見的傳送一位元 需要的能量為:
以此方式,可以將有限個位元以依連續訊號表示:
這個手法讓我能夠將離散的資訊以連續的訊號傳送。再來討論更一般化的情況,在單一個訊號波形僅能傳遞一個位元的資訊,即 0
和 1
。假設想要傳遞 00
、01
、 10
和 11
該怎麼以連續訊號表示?
此節內容摘錄並重新整理自 錯誤更正碼簡介
其中的一類被稱 Single–Error Correcting (SEC) Codes,顧名思義,這個方法可以修正編碼中的單位元的錯誤,其中一個經典的例子為 (7, 4) hamming code,意指資料以 7 個位元編碼的資料中,有 4 個位元是真實的資料,其做法大致為:
對於一個資料 d1 d2 d3 d4
,加上額外的 parity p1 p2 p3
,此 parity 定義為使下圖中的三個大圓中的 1 數量為偶數,則在僅有至多一碼錯誤的前提下:
p1
或 p2
或 p3
產生錯誤,因此反轉 p1
或 p2
或 p3
即可d1
或 d2
或 d3
產生錯誤,因此反轉 d1
或 d2
或 d3
d4
產生錯誤,三個大圓內的 1 個數皆不符合,反轉 d4
以數學的角度來探討,為什麼 (7, 4) hamming code 可修正一個位元的錯誤呢?
考慮集合 以及其元素 對 。假設 對應到 ,而 對應到 ,則表示其需滿足(注意這裡的加法指的是二進位單一位元的加法,例如: ):
第一個方程對應上述文式圖紅圖部分,第二個方程對應藍圖部分,第三個方程對應綠圖部分。
將上述線性方程式寫成矩陣則是:
令 為所有編碼資料的集合即是 H 的 null space,也就是 ,又 ,根據 Rank–nullity theorem 則 ,表示對於上述式子的解,事實上只要給定 x1~x7 中任意 4 個,其他 3 個就會因應產生,又這任意 4 個可以為任意的 0 或 1,因此共有 = 16 個可能,對應可以編碼的 4 個位元。
接著,我們可以從 Hamming distance 和 Hamming weight 來思考。
則我們可以定義一段 code word 的 minimum distance 為此 code word 集合中任兩個編碼的最小 Hamming distance,也就是
而 minimum weight,則定義為
由於 null space 是同時也是一個 linear subspace,根據加法封閉性,集合中的任兩個 x、y 相加仍會存在集合 C 中,又前面提到 x、y 相加的 Hamming weight 會等同 x、y 的 Hamming distance,因此可以歸納出 。
那麼回過頭看, 的 minimum weight 會是多少呢?,我們知道 是 的 nullspace,由此下去推理的話:
由於對於 d 個 bits 的錯誤,其 minumum distance 必須至少要是 2d + 1,因此我們知道這個演算法僅能修正 1 個位元的錯誤 (詳情請參考錯誤更正碼簡介第 7 頁)
Hamming codes 的編碼與解碼操作可以被轉換成矩陣的運算,首先要先定義編碼的生成矩陣 和檢驗的矩陣
可以看到前四列形成一個單位矩陣,後三列則分別是 p1、p2、p3 所在的大圓中,根據包含的 為何,其對應的位元就設為 1 形成的。
而 就是我們前面的線性方程式所形成的矩陣:
則對於一段資料,舉例來說 p = ,其編碼的計算就是。再次注意到,這裡的矩陣運算是對單一位元的加乘法:
假設經過傳輸後得到的向量為 r,如果在傳輸過程中沒有錯誤的位元產生,則會得到 0 向量:
如果 r 產生 1位元的錯誤,舉例來說,第 6 個位元有錯,則輸入第 6 個位元有錯的 code word 之後,得到的結果為:
因為我們知道對第 6 個位元為 1 的向量運算會得到:
則表示輸入的 codeword 第 6 個位元出錯,因此需要對其取補數調整,就可得原始的 codeword。(如果我們把 parity 位元擺在 2^n 的位置的話,事實上這個矩陣運算可得更美妙(?)的結果,詳情請參考(7, 4) hamming code)
參考自 Introduction to Abstract Algebra by W. Keith Nicholson - 2.11 An Application to Binary Linear Code
令 為一 摺笛卡爾積(n-fold cartesian product),以及集合
為以 為中心,漢明距離 為測度(metric),蒐集與 距離 以內的所有元素。 也可以叫做以 為距離函數,圓心為 ,半徑為 在 中的 t-ball,在點集拓樸中該球又稱作一開球(open ball)。與之相對的為閉球(closed ball) 差別在於明漢距離可等於
我們可以用從點集拓樸的術語,來描述錯誤碼的偵測與糾正。以下節錄自《Introduction to Abstract Algebra by W. Keith Nicholson》第 147 頁:
假設 且 最小距離為 ,換言之 集合中任意兩不相同元素的集合距離不小於 。更進一步的具象來講,對任意的 且 為圓心,以半徑 的開球(open ball)皆不會相交,用集合符號敘述則為 。
假設一個 code word 被傳送後,接收者得到的 code word 帶有 個錯誤,其中 ,藉由漢明距離可以得到 。因此,開球 在以漢明距離 為半徑所圈起來的圓,可以收到最多 個錯誤。
那在此我們要以集合論的方式說明兩個漢明碼的功能偵測錯誤與校正錯誤:
先假設 該集合沒有任何 code word 落在以漢明距離 為半徑,另一個 code word 的開球中,則
依據這個概念,我們可以藉由漢明距離和開球等集合論方式寫出漢明碼偵測錯誤與校正錯誤的能力。
不同距離函數下的圓形,可參考 Unit circles with -norm
再來我們有興趣的是,開球 究竟有多少個元素,換言之該集合的基數(cardinal number)為多少?藉由漢明距離的定義, 成立時,若且為若 兩者有 個不同的位元。換句話說對於一個 位元長的元素,選取 個不同位置為 位元差異處,寫成組合數可以表示成 。藉此,該集合的基數可以表示成:
TODO: 如何從機率的觀點解釋 hamming code
TODO: ECC 校正錯誤的能力要如何量化?
可以參考:
在一個非空集合 定義的二元運算 為一函數 (或稱為映射),其映射關係為該集合的笛卡兒積至該集合本身 ,另一種更常見的寫法為 ,其中 屬於 。
同時該二元運算滿足:
假如任意 其二元運算 滿足:
則稱該二元運算子具有結合律。
假如任意 其二元運算 滿足:
則稱該二元運算具有交換律。
當一非空集合 中的二元運算 滿足以下性質時稱之為群:
該二元運算子具有結合律。
存在一單位(identity)元素 ,使得對任意 滿足
對任意 存在一反元素 ,使得
例子, 與一運算 ,有以下映射關係:
該集合的運算子是否為二元運算?假如是,該集合是否為有一二元運算 的群?
顯而易見的該運算為二元運算,對任意元素的運算結果皆是集合中的元素。同時, 為一單位元素; 的反元素為自己本身; 的反元素為自己本身。結合律可以藉由列舉的方式說明,在此不贅述。所以 為一群。
假如對任意 該二元運算 滿足交換律 則稱為阿貝爾群(abelian group)。顯而易見的上述例子 為一阿貝爾群。
Reed–Solomon error correction 也是 ECC 的一種,透過在原始的資料中加上額外的資訊,則在定義內的錯誤數量以下,在資料傳輸後產生的錯仍可以被修正,這個 ECC 算法被廣泛使用於諸多領域。
對於 primitive element 的敘述可以參考 Primitive element (finite field) 和 Primitive root modulo n。
Reed-Solomon error-correcting codes 具有不同的形式與處理方式,這裡所說明的是比較普遍的 BCH code。這意味著原始資料會視為是一個多項式的係數,而接續的糾正碼也會接續著成為新的多項式項。
根據 和 定義一個生成多項式
則此多項式為 m degree,且最大冪次項 的係數為 1
假設原始的資料是由 個值組成的 (, , … ,), 是 field 中的一個數值,則將以這 個值形成的多項式定義為
則 Reed-solomon codeword 的編碼被定義為
這裡我們可以看到, 會形成 , 到 ,總共 項,而這 項的係數其實也是資料的內容。 而因為 會是 degree 更低,因此不會有和 的計算互動,因此整個 的 degree 會為 也就是
因此,最後的編碼即為
多項式的係數,也就是
我們需要先找到錯誤的位置。假設訊息中有 個錯誤,如我們在前提中所說, 需滿足 。所以說,除非有時間或空間的限制,我們會期望 可以大一些,可以最大化能糾正的錯誤
假設這 錯誤的位置在 , 之間是沒有順序的且滿足 ,這表示在這位置上的誤差 會是任意值(可能是 ),而其他的 則必須等於
接著,再根據錯誤的地方 定義,對於
因為我們知道其他的 都為 ,因此可以重新定義 syndrome,把前面的矩陣方程式改寫成
接著,定義一個 error locator polynomial
因為 ,因此我們知道
現在,對於 且 j ,將兩側同乘上
然後把範圍中的所有 得到的結果相加
然後再將式子調整成
其中
則如果將每個 都列出等式:
一樣可以將此轉換成矩陣運算的形式:
現在,我們終於得到了可以求解的方程式了,我們可以將其寫成擴增矩陣:
並且透過 Gauss-Jordan elimination 求解。
如果對此線性矩陣的解為 inconsistent(無解),那表示 codeword 中有超過 數量的錯誤,因此無法得到解。如果我們的 可以更大,則應該將其調大並且重新解碼,否則,就是無法完成 error correction。
如果是 consistent 但是 under-determined(無限多解),這可能發生於錯誤少於 個錯誤時,因為任何一個 的位置都可以被視為是一個 virtual error,此時就必須把 降低並重新解碼。
如果找到不為 的 多餘 個,表示傳輸後的資料錯誤太多,無法修正。如果少於 個,表示誤差比預計中的少,我們只需將 重新定義為較低的數字,並刪除編號較高的 變量即可。
對於大於等於 的 毋需被代入,這是因為錯誤不能出現在長度為 的資料以外。
同樣可以透過 Gauss-Jordan elimination 來求解。
如果 linear system 是 consistent 且 independent 的,則存在唯一解,可以成功修正錯誤。如果是 inconsistent,則表示錯誤無法被修正,或者結果是 consistent 但 dependent,那表示沒有唯一的解(即使唯一解是必須的)。
decode 的部份,Peterson-Gorenstein-Zierler decoder 是 的解,但在速度的考量上,則應該透過 Berlekamp-Massey algorithm 求出位置,加上 Forney algorithm 還原錯誤,其中兩者的時間複雜度皆為
此節內容譯自: Reed–Solomon codes for coders
這裡引用另一篇文章,來理解相關的操作可以如何被實作。
想像一下,資料在傳輸的過程中有資料遺失了,最終我們收到的資料叫作 a p p * e
。不過我們可以猜到遺失的字母是什麼,因為當我們把字典翻開,我們找到字典中最接近的字就是 apple
。
那麼如果我們收到的資料是 b * d
呢? 這時候它應該要是 bad
還是 bed
?
為了解決這個問題,我們可以在原始資料後面加上一下額外的字母然後傳輸,例如:
這時候,假如我們收到的資料有遺失,變成 b * d x * z
,那麼對照字典,最接近的字應該是 badxyz
,所以我們就有足夠的證據猜測原始的資料應該是 bad
了。
BCH code 是 Reed–Solomon codes 經常使用的編碼形式,在前面的章節也有提到,可以簡單理解為是透過多項式的係數來表達資料。
BCH 的錯誤檢查類似整數長除法,不過減法的運算會改用 XOR。舉例來說,對於 000111101011001 這個 formating infomation 以及 10100110111 這個 generator,如果可以整除(餘數為 0),那就是表示沒有錯誤發生,此時得到的商就是原始的資料,否則的話就表示有錯誤需要被修正。
因為原文中是以 QR 為例來說明的,關於 formating infomation 請參考原文,還蠻有趣的XD,有空我就把完整內容整理上來。我們可以先簡單理解成 000111101011001 的前 5 個位元是真正的資料,後面的 10 個位元則是用來修正錯誤的編碼。
對應的 python code 實作如下
所以我們也可以反過來用此來編碼 5 位元的 formating infomation
如果得到的餘數不為 0,表示資料有誤,那麼我們就要「猜出」原本的資料為何。對於 BCH 的解碼有許多演算法,不過這裡我們可以用一個簡單的方式來理解: 因為我們的原始資料只有 32 種可能,因此我們可以一一比較,並以 hamming distance 最小的那個作為猜出的結果
事實上 Reed–Solomon 就是依據類似的概念來解碼的,不過顯然資料很大的時候,我們就不能採取這種搜尋方法了,一些比較複雜的作法,如 Berlekamp-Massey,就必須被使用。
要了解 Reed–Solomon,就不得不對 finite field 有一定的了解。
我們需要定義對於一個 8 位元數字加法、減法、乘法、除法,且經過這些運算後仍為 8 bit,一個比較 naive 的作法是,我們可以定義這些運算和自然數的作法相同,並 mod 256 以避免 overflow,對於這些範圍的數字,我們就將其叫作 Galois Field 2^8,這個作法事實上是可以成立的(你可能會質疑在除法的可行性?下面會解釋之)。
要簡單的說明什麼是 Galois Fields 的話: 一個 finite field 就是在有限集合中的一群數字,在這個 field 裡,我們需要定義加法、減法、乘法、除法滿足: 封閉性、結合律、交換律、分配律、Identity 和 inverse 六大 property。
例如,實數域 ℝ 是一個 field,但整數域 ℤ 就不是,因為不滿足乘法的 inverse property(找不到整數域中的 x 可以滿足 x * 4 = 5)。為了處理此問題,一個可行的作法是用質數來當作 modulo,例如 257,或者是任何質數的正整數數次方。也就是說,整數域 ℤ modulo 任何的質數都可以被稱為 Galois Field。
而 modulo 2 則是有趣的一類,例如這裡的例子中,因為 256 = ,這是質數 2 的 8 次方,這使得 GF(2^8) 的計算定義有一些特性。
細節可以參考原文以及 Non-prime fields
對於 GF(2^n) ,加法和減法可以同時被 XOR 運算所替代,這很符合邏輯,因為相加 modulo 2 就像 XOR,而相減 modulo 2 和相加 modulo 2 並沒有不同。
或者從多項式的角度去思考
注意到這是因為我們是在 GF(2^n) 中,才可以用 XOR 取代加減法,在其他的 GF 中則不一定如此。
乘法的運算是基於多項式的乘法,例如 10001001 乘以 00101010:
這等同於
注意要把加法替換成 XOR。
顯然,此時得到的位元已經超過原本的 8 bits,因此我們需要 modulo 一個神祕的數字 100011101,同等的操作也可以寫成:
對應的程式碼為:
為什麼需要 mod 100011101 呢,其數學證明可能會有些複雜,不過簡而言之,這是為了確保 多項式是 "irreducible" (可以想成是一個質數的多項式,所以不能被任兩個 degree 小的多項式相乘而得到),這個數字被稱做 primitive polynomial \ irreducible polynomial \ prime polynomial, 100011101 (0x11d) 是其中常用的一種。至於該如何生成這個數字呢,請參考 Universal Reed-Solomon Codec。
雖然前面的作法可以達成 GF 中數字的乘法,然而並不是一個高效率的作法,一個更好的作法是透過查表,不過8位元數字會有 256 種結果,我們也沒必要把 256 x 256 的乘法結果完全的紀錄起來。下面介紹一種更精簡的作法:
首先,定義 為一個 generator number = 2,則對於 的計算就只是對 做位元的位移,然後和 100011101 做 XOR (如果有 overflow) 運算而已。我們可得如下:
如果依此類推建立表格,從 直到 前的數值都不會有重複(這是 GF 的特性),也就是說,除了 0 之外的數都會是某個 。
那麼,我們知道 10001001 乘以 00101010 就會是某兩個 相乘
因此我們只要建立起對於 查到的值,以及反過來對於一個值對應的 就可以有效的進行乘法運算了!
稍微瀏覽一下程式碼,我們可以看出其中的 gf_exp
是用來計算從 到 的數值的,而 gf_log
則是反過來的 index
。也可以看到程式碼中紀錄的事實上是 到 ,這是使我們在查表時 gf_log[x] + gf_log[y] 超過範圍也可以找到對應的 i。
透過對 建立的表格,則除法的運算也就可以一併加速了
注意這裡 + 255
可以保證 index > 0
同樣的,冪次和 inverse 也可以一併加速
接下來,我們還需要再定義一些對於多項式係數的操作。因為 GF 的元素本身,做為多項式的係數也是個多項式,所以勢必不要搞混了。以下面的多項式為例:
在程式中,我們可以把這個係數在矩陣中寫成 [0x01, 0x0f, 0x36, 0x78, 0x40],根據實作的不同考量,也可以把順序反過來。
則第一個 function 用來與一個 scalar 相乘
將兩個多項式相加 (記得用 XOR)
將兩個多項式相乘
最後,我們需要一個可以對特定的 x 求出多項式值的 function。這裡使用了 Horner's method 使得 的計算可以迭代累積,不需要每次都重複計算
最後,我們還需要一個較為複雜的多項式除法,這個留待下一節來講解。
終於來到 Reed–Solomon codes 的關鍵實作了。
在前一節的文章中,使用 python 實作 Reed–Solomon codes,作為認識該如何實作 RS 是個很好的出發點。接著我們從另一個應用來嘗試了解 Reed-solomon codes 的實作,那就是 linux 中的 linux/lib/reed_solomon/。沒錯! linux 中是有相關的 API 可以使用的。
下面,我們就以 Reed-Solomon Library Programming Interface 為出發點,嘗試從 API 的使用深入到程式內部吧!
rs_control
我們可以在 linux/include/linux/rslib.h 找到相關的資料結構:
codec
: 整個 Reed-Solomon 的編碼相關參數buffer
: 在 decode_rs()
內部會使用的 bufferrs_codec 則是整個 Reed-Solomon 中最關鍵的資料結構:
mm
: 每個 symbol 總共的位元數量nn
: 每個 block 中共包含幾個 symbol (就是 (1<<mm)-1
)alpha_to
: 為了增加在 Galois field 中乘法運算的效率,會建立每個 的表index_of
: 同樣考量效率,對於每個值,也要可以反回去找到對應的 genpoly
: 生成式的係數矩陣nroots
: 生成多項式的 degree,也就是額外的 parity 數量fcr
: first consecutive root(index form)。這可以使計算出生成多項式時的 consecutive root 改變,通常都設為 0
prim
: 表示用 Galois field 中的 primitive element / generator 來當 generator,參照 Primitive element (finite field)在 linux 的實作中,如果不考慮 gffunc
的使用,則 α 固定 = 2。輸入的 prim
是對此的 index,因此實際的 genearator = ,於是 … 以此類推。
為了方便解說,後面我們也會把這裡設定的 primitive element 稱為
iprim
: ??gfpoly
: Primitive polynomial 的多項式,這是為了把多項式制約在 Galois field 中。以 0x409 = 0b0100 0000 1001
為例,表示 primitive polynomial 為 init_rs
想要使用 Reed–Solomon codes,首先我們需要先初始化相關的資料結構。
我們可以在 linux/include/linux/rslib.h 找到其定義,首先先來看相關的參數設定:
init_rs(int symsize, int gfpoly, int fcr, int prim, int nroots)
symsize
: 每個 symbol 的位元數量根據 Reed-Solomon Library Programming Interface
The databytes are expanded to the given symbol size on the fly. There is no support for decoding continuous bitstreams with a symbolsize != 8 at the moment. If it is necessary it should be not a big deal to implement such functionality.
rs_codec
中的同名稱init_rs
會呼叫 init_rs_gfp
,init_rs_gfp
再呼叫 init_rs_internal
,針對額外的參數說明:
gffunc
: 在 non-canonical 模式下生成不同的 ,不過在 init_rs
下會被設為 NULL(對此的使用還沒概念,暫時先忽略qq)gfp
: 使用 kmalloc 時的 flag,會影響記憶體配置的區域和行為不同init_rs_internal
初始 rs_control
結構。
幾個基本的檢查被進行:
symsize
是每個 symbol 的位元數量,因此不能小於 1fcr
的範圍,參見前面的說明prim
的範圍,因為 primitive element 不能是 也不能是 GF 外的 (1<<symsize)-1
個 symbol,nroots
大於這個範圍是不合理的,小於 0 顯然也是RS_DECODE_NUM_BUFFERS
= 8。list_for_each(tmp, &codec_list)
逐一走訪整個鏈結串列,檢查是否已經存在相同的編碼結構,如果有,則直接 reference 一個相同的即可,不需要另外 init 一份codec_init
去建立一個新的結構codec_init
rs_codec
所需的空間,並透過 INIT_LIST_HEAD
初始化這個鏈結串列節點rs->alpha_to[rs->nn] = 0
if (sr & (1 << symsize))
判斷是否有 overflow,如果有,則需要額外對 gfpoly
,也就是 primitive polynomial 做 XOR 運算,最後則 sr &= rs->nn
以確保得到的數字仍在 GF 中iprim
(??)for (j = i; j > 0; j--)
迴圈中的運算,可以理解為例如當下的 genpoly 對應的多項式可能是 ,然後要乘上下一個 ,因此新的多項式係數,假設是 項好了,就會變成 rs_modnn(rs,rs->index_of[rs->genpoly[j]] + root)
去調整genpoly
是係數的真實值,為了方便計算,透過 index_of
改成用 index 來代表rs->users = 1
計算在 reference 這個結構的數量rs_modnn
rs->nn = (1<<mm)-1
,表示 overflow 所以不存在定義的 GF 中,此時需調整成 x % rs->nn
encode_rs8
初始化 RS 相關的資料結構後,接著我們就可以來進行編碼了,下面以 encode_rs8
為範例
rsc
就是我們初始化過的結構data
是要被 encode 的資料,其長度為 len
par
則是呈裝 parity data 的地方,注意需要由使用者自行初始化invmsk
可以讓我們在編碼時加上一個 mask(關於為什麼需要 mask,可以稍微參考 Reed–Solomon codes for coders: Masking)nn - nroots - len
,也就是檢查資料數量加上 parity 數量是否超過總 symbol 數量,這是因為編碼數量必須小於 GF 的元素總量 以編碼而言,事實上就是把資料對生成多項式做長除法,並把餘數紀錄到 par
中,為了儘量說清楚,這裡我借用 Polynomial_division 為例來解說,以這個長除法為例,假設我們的資料是 12 34 56
,生成多項式為 01 0f 36 78 40
,然後其 par
的大小為 4 個 uint8:
par
成員就是 00 00 00 00
,fb
先取出 12
ee 2b 23
,這會是透過 par[j] ^= alpha_to[rs_modnn(rs, fb + genpoly[nroots - j])]
來得到12
* 0f 36 78
為何。在這裡 12
就是 ,而 generator 的 0f 36 78
會對應某個 12
* 0f 36 78
對應每個 ,也就是程式中的 alpha_to[rs_modnn(rs, fb + genpoly[nroots - j])];
12 ee 2b 23
,接著我們把它向左位移 (也就是這裡的 memmove
),變成 ee 2b 23 23
(雖然這裡我說位移,不過最後一個 byte 沒有變成 0 ,這是因為事實上是使用 memmove,不過稍候它就會被覆蓋掉)ee 2b 23 f4
34 ^ ee = da
,然後就依尋前面的步驟,一直做到處理完整個 datadecode_rs8
最複雜的部份應該就是這裡了。對於 RS 的 decode,可以有許多不同的演算法被應用。不過我們可以大致總結成 5 點:
rsc
就是我們初始化好的 rs_control
結構len
的 data
跟 par
s
: 解碼的過程中會需要 syndromes polynomial,找出發生錯誤的位置,因為 syndromes 的計算可能可以用硬體來計算,因此 API 提供此參數可以直接依據這個 syndromes 來修正錯誤
no_eras
、eras_pos
: 有些 channel 中,錯誤會發生的情形是可以預知的,這種錯誤被稱做 erasure。要修正一個 erasure,只要添加一個 symbol 就可以做 erasure correction 了
invmsk
就等於我們在 encode 時所使用的corr
: 根據 eras_pos
可以要做 correction 相關的 bitmsk
s
、eras_pos
等得到 corr
的 mask,再透過自定義的程式去處理問題rs->buffers
嗎? 當時我們 allocate 了 8 * (nroots + 1)
uint16_t 大小的空間,現在我們把它切在 8 等份給不同的需求使用rs->alpha_to[rs->nn] = 0
,所以這邊只要檢查 syndrome 是否等於 nn
即可s
參數,我們就要自行計算 syndromes polynomialsyn_error
計算是不是所有 syndrome 都是 0,如果不是就要去修正01 02 03 04
,對應的多項式是 ,index 0 的地方是多項式的最高次方,但是如果錯誤是發生在 03
(index = 2) 的地方,要代入的是 ,nn - 1 - (eras_pos[0] + pad)
就是在做此計算b
對應 Berlekamp-Massey algorithm 的多項式 B(x),用 lamda 的 index 初始化nroots
減去已知的錯誤次(換句話說,如果沒有 erasure 的話就是進行 nroots 次)discr_r == nn
表示原求的 discrepancy d
為 0
t
就是 的暫存空間 r + no_eras
表示目前是第幾個 iteration (從 1 開始數)el
的用途是用來判斷某些 iteration 中需要額外的調整 B(x)
,在條件滿足的 el
下:
deg_lambda
b
來保存係數)corr
和 eras_pos
的話,就把該誤差多項式填入 corr
並把有錯的位置填入 eras_pos
, 當成回傳data
和 par
的話,那就是看誤差項的位置在 data
還是 par
,並且將結果修正回去