#2001年,ForCES IETF委員會正式對Netlink進行了標準化的工作。 Jamal Hadi Salim提議將Netlink定義成一種用於網路設備的路由引擎元件和其控制管理元件之間通訊的協定。不過他的建議最終沒有被採納,取而代之的是我們今天所看到的格局:Netlink被設計成一個新的協議域,domain。
Linux之父托瓦斯曾說過「Linux is evolution, not intelligent design」。什麼意思?就是說,Netlink也同樣遵循了Linux的某些設計理念,即沒有完整的規範文檔,亦沒有設計文檔。只有什麼?你懂得---「Read the f**king source code」。
當然,本文不是分析Netlink在Linux上的實現機制,而是就「什麼是Netlink」以及「如何用好Netlink」的話題和大家做個分享,只有在遇到問題時才需要去閱讀內核源碼弄清個所以然。
什麼是Netlink關於Netlink的理解,需要掌握幾個關鍵點:
1、以資料封包為導向的無連線訊息子系統
2、基於通用的BSD Socket架構而實作
關於第一點使我們很容易聯想到UDP協議,能想到這一點就非常棒了。按著UDP協議來理解Netlink不是不無道理,只要你能觸類旁通,做到“活學”,善於總結歸納、聯想,最後實現知識遷移這就是學習的本質。 Netlink可以實現內核->用戶以及用戶->內核的雙向、異步的數據通信,同時它還支援兩個用戶進程之間、甚至兩個內核子系統之間的數據通信。本文中,對後兩者我們不予考慮,焦點集中在如何實現使用者<->核心之間的資料通訊。
看到第二點腦海中是不是瞬間閃現了下面這張圖片呢?如果是,表示你確實有慧根;當然,不是也沒關係,慧根可以慢慢長嘛,呵呵。
在後面實戰Netlink套接字程式設計時我們主要會用到socket(),bind(),sendmsg()
和recvmsg()等系統調用,當然還有socket提供的輪訓(polling)機制。
Netlink通訊類型Netlink支援兩種類型的通訊方式:單播和多播。
單播:常用於一個使用者程序和一個核心子系統之間1:1的資料通訊。使用者空間發送命令到內核,然後從內核接受命令的返回結果。
多播:經常用於一個核心行程和多個使用者行程之間的1:N的資料通訊。核心作為會話的發起者,用戶空間的應用程式是接收者。為了實現這個功能,核心空間的程式會創建一個多播組,然後所有用戶空間的對該內核進程發送的消息感興趣的進程都加入到該組即可接收來自內核發送的消息了。如下:
#其中進程A和子系統1之間是單播通信,進程B、C和子系統2是多播通信。上圖也向我們說明了一個訊息。從使用者空間傳遞到核心的資料是不需要排隊的,即其操作是同步完成;而從核心空間向使用者空間傳遞資料時需要排隊,是異步的。了解了這一點在開發基於Netlink的應用模組時可以使我們少走很多彎路。假如,你向核心發送了一個訊息需要獲取核心中某些訊息,例如路由表,或其他訊息,如果路由表過於龐大,那麼核心在透過Netlink向你返回資料時,你可以好生琢磨一下如何接收這些數據的問題,畢竟你已經看到了那個輸出隊列了,不能視而不見啊。
Netlink的訊息格式Netlink訊息由兩部分組成:訊息頭和有效資料載荷,且整個Netlink訊息是4位元組對齊,一般按主機位元組序列進行傳遞。訊息頭為固定的16位元組,訊息體長度可變:
#Netlink的訊息標頭訊息頭定義在
點選(此處)折疊或開啟
訊息標頭中各成員屬性的解釋及說明:
nlmsg_len:整個訊息的長度,以位元組計算。包括了Netlink訊息頭本身。
nlmsg_type:訊息的類型,也就是資料還是控制訊息。目前(核心版本2.6.21)Netlink僅支援四種類型的控制訊息,如下:
NLMSG_NOOP-空訊息,什麼事都不做;
NLMSG_ERROR-指明該訊息中包含一個錯誤;
NLMSG_DONE-如果核心透過Netlink佇列傳回了多個訊息,那麼佇列的最後一則訊息的型別為NLMSG_DONE,其餘所有訊息的nlmsg_flags屬性都被設定NLM_F_MULTI位元有效。
NLMSG_OVERRUN-暫時沒用到。
nlmsg_flags:附加在訊息上的額外說明訊息,如上面提到的NLM_F_MULTI。摘錄如下:
大家只要知道nlmsg_flags有多種取值就可以,至於每種值的作用和意義,透過Google和原始碼一定可以找到答案,這裡就不展開了。上一張2.6.21核心中所有的取值情況:
#nlmsg_seq:訊息序號。因為Netlink是面向資料封包的,所以存在遺失資料的風險,但是Netlink提供如何確保訊息不遺失的機制,讓程式開發人員根據其實際需求而實現。訊息序號一般和NLM_F_ACK類型的訊息聯合使用,如果使用者的應用程式需要保證其發送的每個訊息都成功被核心收到的話,那麼它發送訊息時需要用戶程式自己設定序號,核心收到該訊息後來對提取其中的序號,然後在發送給用戶程式回應訊息裡設定相同的序號。有點類似TCP的回應和確認機制。
注意:當核心主動向用戶空間發送廣播訊息時,訊息中的該欄位總是為0。
nlmsg_pid:當使用者空間的程序和核心空間的某個子系統之間透過Netlink建立了資料交換的通道後,Netlink會為每個這樣的通道分配一個唯一的數位識別。其主要作用是將來自用戶空間的請求訊息和回應訊息進行關聯。說得直白一點,假如用戶空間存在多個用戶進程,內核空間同樣存在多個進程,Netlink必須提供一種機制用於確保每一對「用戶-內核」空間通信的進程之間的數據交互不會發生紊亂。
#即,進程A、B透過Netlink向子系統1取得資訊時,子系統1必須確保回送給進程A的回應資料不會傳送到進程B那裡。主要適用於使用者空間的程序從核心空間取得資料的場景。通常情況下,用戶空間的進程在向核心發送訊息時一般透過系統呼叫getpid()將當前進程的進程號賦給該變量,即用戶空間的進程希望得到內核的回應時才會這麼做。從核心主動發送到用戶空間的消息該字段都被設定為0。
Netlink的訊息體Netlink的訊息體採用TLV(Type-Length-Value)格式:
#Netlink每個屬性都由
#Netlink提供的錯誤指示訊息
內容當使用者空間的應用程式和核心空間的進程之間透過Netlink通訊時發生了錯誤,Netlink必須向使用者空間通報這種錯誤。 Netlink對錯誤訊息進行了單獨封裝,
點選(此處)折疊或開啟
基於Netlink的使用者-核心通信,有兩種情況可能會導致丟包:
1、記憶體耗盡;
2、用戶空間接收程序的緩衝區溢位。導致緩衝區溢位的主要原因有可能是:使用者空間的進程運作太慢;或接收佇列太短。
如果Netlink無法將訊息正確傳遞到使用者空間的接收進程,那麼使用者空間的接收進程在呼叫recvmsg()系統呼叫時就會傳回一個記憶體不足(ENOBUFS)的錯誤,這一點需要注意。換句話說,緩衝區溢位的狀況是不會發送在從用戶->核心的sendmsg()系統呼叫裡,原因前面我們也說過了,請大家自己思考一下。
當然,如果使用的是阻塞型socket通信,也就不存在記憶體耗盡的隱患了,這又是為什麼呢?趕快去谷歌一下,查什麼是阻塞型socket吧。學而不思則罔,思而不學則殆嘛。
Netlink的位址結構體在TCP部落格文章中我們提到在Internet程式設計過程中所用到的位址結構體和標準位址結構體,它們和Netlink位址結構體的關係如下:
struct sockaddr_nl{}的詳細定義與描述如下:
#點選(此處)折疊或開啟
nl_pid:該屬性為發送或接收訊息的進程ID,前面我們也說過,Netlink不僅可以實現用戶-核心空間的通訊還可使現實用戶空間兩個進程之間,或核心空間兩個進程之間的通信。此屬性為0時一般適用於下列兩種情況: 第一,我們要發送的目的地是內核,也就是從用戶空間發送到內核空間時,我們建構的Netlink位址結構體中nl_pid通常情況下都置為0。這裡有一點需要跟大家交代一下,在Netlink規範裡,PID全名為Port-ID(32bits),其主要作用是用於唯一的標識一個基於netlink的socket通道。通常情況下nl_pid都會設定為目前進程的進程號。然而,對於一個行程的多個執行緒同時使用netlink socket的情況,nl_pid的設定一般採用如下這個樣子來實現: 點選(此處)折疊或開啟
pthread_self() << 16 | getpid();
第二,從核心發出的多播封包到用戶空間時,如果用戶空間的進程處在該多播群組中,那麼其位址結構體中nl_pid也設為0,同時也要結合下面介紹到的另一個屬性。
###nl_groups:如果使用者空間的程序希望加入某個多重播送群組,則必須執行bind()系統呼叫。此欄位指明了呼叫者希望加入的多重播放組號的######遮罩######(注意不是群組號,後面我們會詳細講解這個欄位)。如果該欄位為0則表示呼叫者不希望加入任何多播群組。對於每個隸屬於Netlink協議域的協議,最多可支援32個多播組(因為nl_groups的長度為32位元),每個多播群組都以一個位元來表示。 ### ###關於Netlink剩下的知識點,我們在後面的實戰環節有用到時再討論。 ### ###未完,待續…### ### ###以上是使用Netlink進行使用者空間和核心空間之間的通信的詳細內容。更多資訊請關注PHP中文網其他相關文章!