核心版本:2.6.34
報文的IP校正和、ICMP校正和、TCP/UDP校正和使用相同的演算法,在RFC1071中定義,網上這方面的 資料和例子很多,就不解釋演算法流程了,而是側重於在實現的變化和技巧。
The checksum algorithm is simply to add up all the 16-bit words in one's complement and then to take the one's complement of the sum.
校正和的計算可以分為兩步:累加、取反。這個劃分很重要,它大大減少了校正和計算的消耗。校正和計 算首要要明確一點:校正和計算是很耗時的!原因並不在於演算法複雜,而是在於輸入資料的龐大,試想傳送500M檔案,則核心要 校正500M位元組的資料,並且對於每個報文,都是要進行校正和。所以協議棧的校正和實現並不是簡單明了的,使用了很多方法來 規避這種開銷。
第一:延遲校正和計算
按照協議的規定,報文到達每一層,首先驗證校正 和是否正確,丟棄掉不正確的報文,再才會進行後續操作。對於傳輸層下的協議,核心是這樣做的,因為IP只需要校正IP前序, 最多60位元組;而對於網路層上的協議,核心就不是這樣做的,ICMP/TCP/UDP都需要校正報文的內容,而這部分消耗是很大的。
以UDP為例,在報文傳遞到UDP處理時,它並不會去驗證校正和是否正確,而是直接將報文skb插入 到相應socket的接收隊列sk_receive_queue中。等到真正有程式要接收這個報文,從接收隊列中取出時,核心才去計算校正和。 考量下這種做法,由於延遲了校正和計算,因此很多錯誤的報文都被接收了,它們會佔用處理報文的流程,直到報文準備進入用 戶空間時,這時候才計算了校正和,發現錯誤並丟棄掉。這樣看似乎平白無故增加了開銷,必竟校正和的計算是一定要進行的。 但這樣做,將校正和計算延遲到了拷貝報文到使用者空間時,這兩個操作的綁定是很關鍵的。本來,校正和計算要遍曆一次報文, 而拷貝又要遍曆一次報文,這樣就是兩次遍曆操作,合并後用一次遍曆搞定,它所節約的開銷是遠遠多於額外支付的。
第二:分離校正和計算步驟
開始提到校正和的計算分為兩步:累加、取反,將這兩步分開後,會 發現校正和是可以一部分一部分計算的,最後再用每部分計算的值求和取反。這個特性在另一方面對拷貝和校正和計算同時進行 提供了支援。並且,由於報文可能是分區重組的,這樣報文內容並不是一起儲存線上性地址空間中,而是將分區掛在第一個分區 skb的frag_list上,這部分內容是儲存在非線性地址空間的。因此,拷貝會一個分區一個分區的進行,由於校正和計算的劃分, 它也可以一個分區一個分區的計算。csum_partial()和csum_fold()就是為此而生的,前者計算累加,後者計算取反。
所以一般校正和會這樣計算,skb_checksum()計算skb的累加和,並和之前已經計算出的累加和skb- >csum相加,然後csum_fold()對最後結果取反,就是得到的校正和。s
um = csum_fold(skb_checksum(skb, 0, len, skb->csum));
第三:改進校正和計算
RFC1071中校正和計算是每16bit為 單位的,但實際在累加這一步是可以調整的,核心計算是每32bit計算的,單位越大,迴圈就少,效率也自然會高。下面要說明 的是32bit累加與16bit累加結果是一致的。
假設要計算8個位元組的校正和,這8位元組按每16bit分成 4份:1,2,3,4。左邊是每16bit累加的過程,右邊是每32bit累加的過程:
會出現疑惑的地方就是累加的進位問題,左邊16bit累加進位加到sum中,右邊32bit累加進位也要加到sum中,至於2,4相 加產生的進位,和16bit累加進位的結果是一樣的。下面就是32bit累加的程式碼片段,w>result判斷是否產生了進位,假設X+Y=Z 產生了進位溢出,則X<Z且Y<Z,否則Z>X且Z>Y。
unsigned int carry = 0; do { unsigned int w = *(unsigned int *) buff; count--; buff += 4; result += carry; result += w; carry = (w > result); } while (count); result += carry; result = (result & 0xffff) + (result >> 16);
第四:校正和計算技巧
節 省校正和最好的辦法就是不計算校正和,這在某些情況下是可行的,比如大流量發包時或區域網路中,這時效率比正確性更為重要 。skb->ip_summed參數就是為此目的,CHECKSUM_UNNECESSARY就跳過校正和計算。或者使用者在發包時設定校正和欄位 checksum=0,也會跳過校正和計算。
skb->ip_summed = CHECKSUM_UNNECESSARY;
另外為了加速校正和計算,很多網卡都提供了硬體計算校正和,特別的,linux使用了skb->ip_summed和skb->csum 來使用硬體計算能力來協助校正TCP/UDP報文。CHECKSUM_COMPLETE表示硬體進行了計算,計算結果儲存在skb->csum中。
skb->ip_summed == CHECKSUM_COMPLETE;
在很多晶片的實現上,校正和的計 算代碼都是用彙編來實現了,這也是出於校正和計算的效率考慮。
最後,簡單分析下校正和計算的兩個核心函數 。
do_csum() 校正和累加
校正和計算的主體部分是32bit為單位計算的,並假設buff起始地 址是對齊過的,長度也是對齊過的。因此,傳入的buff要進行處理以滿足假設。