linux 内核连接跟踪,Linux内核连接跟踪锁的优化分析(1)
Linux內核連接跟蹤鎖的優化分析(1)
作者:gfree.wind@gmail.com
博客:linuxfocus.blog.chinaunix.net
微博:weibo.com/glinuxer
QQ技術群:4367710
簡介
很多網絡設備都使用Linux作為自己的OS,但是由于Linux本身是一個通用的操作系統,因此各家廠商都會根據自己的技術水平和需求,對內核進行或多或少的改動。而Linux隨著版本的迭代,也在不斷的改善自己的代碼,吸收各廠商的patch,如google提交的著名的RPS/RFS補丁。
本文將對最近內核提交的一個commit 93bb0ceb75be2fdfa9fc0dd1fb522d9ada515d9c 進行分析。該改動是用于改善多CPU環境下多全局的連接跟蹤鎖nfconntracklock的優化。優化的手段也是常見的方法,將鎖的粒度變細,由一個全局的自旋鎖,改為1024個自旋鎖。其中還會涉及幾個之前的內核優化。
優化分析
功能簡介
連接跟蹤是大多數網絡設備的基礎功能,其它高級功能如NAT,Firewall等都是建立在連接跟蹤之上的。Linux內核要負責創建連接,匹配連接,過期連接,銷毀連接等等工作。可以說,連接跟蹤的性能要在相當大的程度上影響數據包的轉發性能。
2.6內核代碼分析
在Linux 2.6內核中,nf_conntrack_lock是一個全局的自旋鎖,用于保護內核的連接跟蹤表。但是,實際上連接跟蹤表并不是簡單的一個表。而老代碼只是使用nf_conntrack_lock來保護,這樣就相當于給一個鎖賦予了太多太寬泛的職責。
內核的連接跟蹤依賴于net命名空間struct netns_ct的成員變量。一個連接的完整生命過程要涉及多個表的操作,如下所示:
struct netns_ct {
struct hlist_nulls_head *hash;
struct hlist_head *expect_hash;
struct hlist_nulls_head unconfirmed;
struct hlist_nulls_head dying;
其中hash是真正的連接跟蹤表,expect_hash用于expectation連接,unconfirmed用于未確定的連接,而dying用于即將銷毀的連接。
在老的內核中對這四個表的訪問,都依賴于這個全局的自旋鎖nf_conntrack_lock的保護。很自然,這已經充分影響了多核的并行處理。
3.16 內核代碼分析
內核雖然對連接跟蹤鎖一直在做優化,但是直到今年3月才徹底拋棄nf_conntrack_lock(說實話,我沒想到內核這么晚才對這個鎖動手。對于設備廠商來說,這樣的鎖早就給扔了)。下面我們就內核對于2.6中的各個連接表的優化進行分析。
后文的代碼以最新穩定版本3.16的代碼為準
expectation連接
內核引入了一個新的全局自旋鎖nf_conntrack_expect_lock專門用于保護expectation連接。具體的代碼在此就不羅列了。需要注意的是,為了避免死鎖,這兩個鎖要么不能同時上鎖,要么必須按照一定的順序上鎖。
unconfirmed和dying連接
這兩種情況的表,在新代碼中有了比較大的改動。struct netns_ct中的unconfirmed和dying被變更為動態per cpu變量struct ct_pcpu __percpu *pcpu_lists;。而struct ct_pcpu的結構如下:
struct ct_pcpu {
spinlock_t lock;
struct hlist_nulls_head unconfirmed;
struct hlist_nulls_head dying;
struct hlist_nulls_head tmpl;
};
這里的ct_pcpu中的lock是用于用戶空間與內核空間的同步。而多cpu間,即使仍然執行spinlock的上鎖解鎖動作,但實際上由于它們都是per cpu變量。因此并不會引起cpu之間的競爭。
一般的連接跟蹤
真正的連接跟蹤表netns_ct->hash實際上是一個hash表。之前的nf_conntrack_lock是對整個兒的hash表進行保護,那么我們可以減小鎖的粒度來降低cpu之間的鎖競爭。新內核去掉了獨立的nf_conntrack_lock,取而代之的是1024個自旋鎖。
-extern spinlock_t nf_conntrack_lock ;
+#ifdef CONFIG_LOCKDEP
+# define CONNTRACK_LOCKS 8
+#else
+# define CONNTRACK_LOCKS 1024
+#endif
+extern spinlock_t nf_conntrack_locks[CONNTRACK_LOCKS];
之所以在CONFIG_LOCKDEP下,將CONNTRACK_LOCKS的數量縮小為8,因為這種情況下spinlock占用內存過大。
1024個自旋鎖依然小于連接hash表的桶的數量,但在cpu不多,且hash算法良好的情況下,依然可以取得相當不錯的效果。如何確定使用哪個自旋鎖呢?是根據original和reply兩個方向的tuple計算hash值來確定使用哪個所。
由于同時需要兩個鎖,所以這時候引入了一個新問題。如何確定這兩個鎖的使用順序呢?最直接的想法是,先上original方向的鎖,再上reply方向的鎖。然而這第一個念頭,無疑是錯誤的。假設兩個連接A和B,那么極有可能A的original方向的hash值和B的reply方向的hash值一樣,并且A的reply方向的hash值與B的original方向的hash值也一樣。這時,就出現了兩個CPU需要使用兩個鎖a和b,結果CPU1已經擁有了b鎖期望a鎖,CPU2擁有了a鎖期望b鎖,因此出現了死鎖。
內核采用一種小技巧。不考慮方向,只看hash值,永遠讓cpu先擁有hash值小的鎖,再嘗試另外一個。代碼如下:
+/* return true if we need to recompute hashes (in case hash table was resized) */
+static bool nf_conntrack_double_lock(struct net *net, unsigned int h1,
+ unsigned int h2, unsigned int sequence)
+{
+ h1 %= CONNTRACK_LOCKS;
+ h2 %= CONNTRACK_LOCKS;
+ if (h1 <= h2) {
+ spin_lock(&nf_conntrack_locks[h1]);
+ if (h1 != h2)
+ spin_lock_nested(&nf_conntrack_locks[h2],
+ SINGLE_DEPTH_NESTING);
+ } else {
+ spin_lock(&nf_conntrack_locks[h2]);
+ spin_lock_nested(&nf_conntrack_locks[h1],
+ SINGLE_DEPTH_NESTING);
+ }
+ if (read_seqcount_retry(&net->ct.generation, sequence)) {
+ nf_conntrack_double_unlock(h1, h2);
+ return true;
+ }
+ return false;
+}
這個函數通過比較h1和h2,先獲得小hash值的鎖,再嘗試大hash值鎖,最終同時擁有兩個鎖。
(最近由于工作太忙,已經大半年沒有更新了。今天覺得實在是過意不去了,特意寫了一篇,這是我最近兩天看內核改動的所得。還沒有寫完,未完待續。貌似我又挖了一個坑)
總結
以上是生活随笔為你收集整理的linux 内核连接跟踪,Linux内核连接跟踪锁的优化分析(1)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: linux打开dc软件,Linux bc
- 下一篇: 外部链接linux下的mysql,Lin