歡迎光臨
每天分享高質量文章

netlink通訊和裝置管理的三種機制簡介

netlink socket是linux特有的一種socket,其主要用於使用者應用程式與核心之間的互動,同時,也是網路應用程式與核心通訊的最常用的介面。

netlink是一種在內核和使用者應用程式之間進行雙向資料傳輸的非常好的方式,使用者態應用程式使用標準的socket API就能使用netlink提供的強大功能,而核心態需要使用專門的核心API來使用netlink。

netlink的特點:

  1. 使用者態採用socket風格的API

  2. 除了預定義的協議型別之外,支援自定義協議型別

  3. 非同步通訊

  4. 支援訊息組播

  5. 全雙工(特別是支援核心主動發起會話)

先看一下與netlink相關的資料結構體,其中主要的是netlink設定地址的結構體,以及netlink接收和傳送資料時所需要的結構體。如圖:

圖中可以看到有4個結構體,但是這些結構體主要用於資料的傳送與接收,並且不同的結構體對應著不同的系統呼叫。其中有三個結構體都被struct msghdr結構體所包含。結構體之間的關係是依靠socket訊息的傳送和接收函式所聯絡的。

比如recv/send、readv/writev、recvfrom/sendto、recvmsg/sendmsg。前面三對函式各有各的特點功能,而recvmsg/sendmsg就是要囊括前面三對的所有功能,當然還有自己特殊的用途。msghdr的前兩成員就是為了滿足recvfrom/sendto的功能,中間兩個成員msg_iov和msg_iovlen則是為了滿足readv/writev的功能,而最後的msg_flags則是為了滿足recv/send中flags的功能,剩下的msg_control和msg_controllen則是滿足recnmsg/sendmsg特有的功能。

上述結構體中,struct sockaddr_nl以及struct nlmsghdr這兩個結構體是neilink中最主要的結構體。

其中,struct sockaddr_nl結構體 為netlink的地址,和通常socket程式設計中的sockaddr_in作用一樣,結構的對比如下:

struct sockaddr_nl{
    sa_family_t nl_family; /*該欄位總是為AF_NETLINK*/
    unsigned short nl_pad; /*目前未用到,填充為0*/
    __u32 nl_pid; /*process pid*/
    __u32 nl_groups; /*multicast groups mask*/};
  1. nl_pid:在netlink規範裡,PID全稱是port-ID(32bits)其主要作用是用於唯一的標識一個基於netlink的socket通道。通常情況下nl_pid都設定為當前行程的行程號。前面說過,netlink不僅可以實現使用者-核心空間的通訊還可實現使用者空間兩個行程之間,或核心空間兩個行程之間的通訊。該屬性為0時指代核心。

  2. nl_groups:如果使用者空間的行程希望加入某個多播組,則必須執行bind()系統呼叫。該欄位指明瞭呼叫者希望加入的多播組號的掩碼。如果該欄位為0則表示呼叫者不希望加入任何多播組。對於每個隸屬於netlink協議域的協議,最多可支援32個多播組(因為nl_groups的長度為32位元),每個多播組用一個位元來表示。

另外,關於netlink中傳送和接收的報文,該報文由訊息頭和訊息體構成,struct nlmsghdr即為訊息頭。訊息頭定義在檔案裡,由結構體nlmsghdr表示,而訊息體緊接著該訊息頭。

struct nlmsghdr{
    __u32 nlmsg_len; /*length of message including essay-header*/
    __u16 nlmsg_type; /*message content*/
    __u16 nlmsg_flags; /*additional flags*/
    __u32 nlmsg_seq; /*sequence number*/
    __u32 nlmsg_pid; /*sending process PID*/};

其中,各個欄位分別代表著:

  1. nlmsg_len:整個訊息的長度,按位元組計算,包括了netlink訊息頭本身。

  2. nlmsg_type:訊息的型別,既是資料還是控制訊息。目前(核心版本2.6.21)netlink僅支援四種型別的控制訊息,如下:

  • NLMSG_NOOP:空訊息,什麼也不做;

  • NLMSG_ERROR:指明該訊息中包含一個錯誤;

  • NLMSG_DONE:如果核心透過netlink佇列傳回了多個訊息,那麼佇列的最後一條訊息的型別為NLMSG_DONE,其餘所有訊息的nlmsg__flags屬性都被設定BLM_F_MULTI位有效。

  • NLMSG_OVERRUN:暫時沒用到。

  • nlmsg_flags:附加在訊息上的額外說明資訊,如NLM_F_MULIT。

  • 以上,就是netlink所需要掌握的結構體,接下來,看一下netlink在核心中提供的獨特的API。

    建立netlink socket

    struct sock *netlink_kernel_create(struct net *net, int unit, unsigned int groups, void (*input)(struct sk_buff *skb), struct mutex *cb_mutex, struct module *module);

    引數說明:

    1. net:是一個網路名字空間namespace,在不同的名字空間裡面可以有自己的轉發資訊庫。有自己的一套net_device等等。預設情況下都是使用init_net這個全域性變數。

    2. unit:表示netlink協議型別,如NETLINK_TEST、NETLINK_SELINUX。

    3. groups:多播地址

    4. input:為核心模組定義的netlink訊息處理函式,當有訊息到達這個netlink socket時,該input函式指標就會被取用,且只有此函式傳回時,呼叫者的sendmsg才能傳回。

    5. cb_mutex:為訪問資料時的互斥訊號量。

    6. module:一般為THIS_MODULE。

    傳送單播訊息netlink_unicast

    int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock)

    引數說明:

    1. ssk:為函式netlink_kernel_create()傳回的socket。

    2. skb:存放訊息,它的data欄位指向要傳送的netlink訊息結構,而skb的控制塊儲存了訊息的地址資訊,紅NETLINK_CB(skb)就用於方便設定該控制塊。

    3. pid:為接收此訊息行程的pid,即標的地址,如果標的為組或核心,它設定為0。

    4. nonblock:便是該函式是否為非阻塞,如果為1,該函式將在沒有接收快取可利用時立即傳回;而如果為0,該函式在沒有接受快取可利用定時睡眠。

    傳送廣播訊息

    int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation)

    前面的三個引數與netlink_unicast相同,引數group為接收訊息的多播組,該引數的每一個位代表一個多播組,因此如果傳送給多個多播組,就把該引數設定為多個多播組,就把該引數設定為多個多播組ID的位或。引數allocation為核心記憶體分配型別,一般的為GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的背景關係(即不可以睡眠),而GFP_KERNEL用於非原子背景關係。

    釋放netlink socket

    void netlink_kernel_release(struct sock *sk)

    以上,就是核心中會用到的netlink相關的API函式。

    另外,關於netlink中的客戶端使用bond()函式,在這裡分析一下socket中的客戶端是否需要使用bind()函式,且在何時使用。

    無連線的socket的客戶端和服務端以及面向連線socket的服務端透過呼叫bind函式來配置本地資訊。使用bind函式時,透過將my_addr.sin_port置為0,函式會自動為你選擇一個未佔用的埠來使用。

    bind()函式在成功被呼叫時傳回0;出現錯誤時傳回“-1”並將errno置為相應的錯誤號。需要註意的是,在呼叫bind函式時一般不要將埠號置為小於1024的值,因為1到1024是保留埠號,你可以選擇大於1024中的任何一個沒有被佔用的埠號。

    有連結的ocket客戶端透過呼叫connect函式在socket資料結構中儲存本地和遠端資訊,無需呼叫bind(),因為這種情況下只需知道目的及其的IP地址,而客戶透過哪個埠與伺服器建立連線並不需要關心,socket執行體為你的程式自動選擇一個未被佔用的埠,並通知你的程式資料什麼時候開啟埠。(當然也有特殊情況,linux系統中rlogin命令應當呼叫bind函式系結一個魏永的保留埠號,還有當客戶端需要用指定的網路裝置介面和埠號進行通訊等等)

    總之:

    1. 需要在建連線前就知道埠的話,需要bind;

    2. 需要透過只懂得埠來通訊的話,需要bind。

    首先,伺服器和客戶端都可以bind,bind並不是伺服器的專利

    客戶端行程bind埠:由行程選擇一個埠去連線伺服器,(如果預設情況下,呼叫bind函式時,核心指定的埠是同一個,呢麼執行多個呼叫了bind的client程式,會出現埠被佔用的錯誤)註意這裡的埠是客戶端的埠。如果不分配就表示交給核心去選擇一個可用埠。

    客戶端行程bind IP地址:相當於為發送出去的IP資料報分配了源IP地址,但交給行程分配IP地址時候(就是這樣寫明瞭bind IP地址的時候)這個IP地址必須是主機的一個介面,不能分配一個不存在的IP。如果不分配就表示由核心根據所使用的輸出介面來選擇源IP地址。

    一般情況下客戶端是不用呼叫bind函式的,一切都交給核心搞定!

    服務端行程bind埠:基本是必須要做的事情,比如一個伺服器啟動時(比如freebsd),它會一個一個的捆綁眾所周知的埠來提供服務,同樣,如果bind了一個埠就表示我這個伺服器會在這個埠提供一些“特殊服務”。

    服務端行程bind IP地址:目的是限制了服務地段行程建立的socket只接收那些目的地為此IP地址的客戶連結,一般一個伺服器程式裡都有

    servaddr.sin_addr.s_addr = htonl(INADDR_ANY); //只是針對IP4,IP6程式碼不太一樣

    這樣一句話,意思就是:我不指定客戶端的IP,隨便連,來者不拒!

    總之只要你bind時候沒有指定哪一項(置為0),核心會幫你選擇。

    1. 採用TCP通訊時,客戶端不需要bind()它自己的IP和埠,而伺服器必須要bind()自己本機的IP和埠號

    2. 若採用UDP通訊時(這裡有客戶端和伺服器之分才這麼說的,若是指定特定埠的UDP對等通訊則不一樣了),客戶端也可以不需要bind()它自己的IP和埠號,而伺服器需要bind自己IP地址和埠號。


    理解devfs、sysfs、udev

    Linux下有專門的檔案系統用來對裝置進行管理,devfs和sysfs就是其中兩種。

    一、devfs

    devfs是在2.4核心就出現了,它是用來解決linux裝置管理混亂的問題,檢視一下/dev下的裝置檔案就知道其中有許多是空的(也就是沒有對應的硬體的),但是它們卻必須存在,所以這給Linux裝置管理帶來了很多麻煩,為瞭解決這個問題,Linux核心開發人員開發了devfs,並用一個守護行程devfsd來做一些與以前硬體驅動相容的事情。

    devfs和sysfs都是和procfs一樣,是一個虛擬的檔案系統,向devfs註冊的驅動程式,devfs將會在/dev下建立相應的裝置檔案;但是為了相容,devfsd這個守護行程將會在某個設定的目錄中建立以主裝置號為索引的裝置檔案,如果不這麼做,以前的許多應用將不能執行。

    在2.6內核以前一直使用的是devfs,devfs掛載於/dev目錄下,提供了一種類似於檔案的方法來管理位於/dev目錄下的所有裝置,/dev目錄下的每一個檔案都對應的是一個裝置,至於當前該裝置存在與否先且不論,而且這些特殊檔案是位於根檔案系統上的,在製作檔案系統的時候就已經建立了這些裝置檔案,因此透過操作這些特殊檔案,可以實現與核心進行互動。

    但是devfs檔案系統有一些缺點,例如:不確定的裝置對映,有時一個裝置對映的裝置檔案可能不同,當裝置過多的時候,就會造成問題;/dev目錄下檔案太多而且不能表示當前系統上的實際裝置;命名不夠靈活,不能任意指定等等.

    二、sysfs

    sysfs是Linux2.6所提供的一種虛擬檔案系統。這個檔案系統不僅可以把裝置和驅動程式的相關的資訊從kernel space輸出到user space,也可以直接對裝置以及驅動程式做設定。它把實際連線到系統上的裝置和匯流排組織成一個分組的檔案,使用者空間的程式同樣可以利用這些資訊已實現和內核的互動。

    sysfs的目的是把一些原本在procfs中關於裝置的部分獨立出來,以裝置樹的形式呈現。sysfs檔案系統中,尋找檔案路徑的方法為”sysfs backing store path“,降低在大型系統中記憶體的需求量。它掛載在/sys目錄下。

    sysfs是當前系統上實際裝置樹的一個直觀反應,它是透過kobject子系統來建立這個資訊的,當一個kobject被建立的時候,對應的檔案盒目錄也就被建立了,位於/sys的相關目錄下,既然每個裝置在sysfs中都有唯一對應的目錄,那麼也就可以被使用者讀寫了。

    三、udev

    udev是使用者空間下的一種工具,udev就是利用了sysfs提供的資訊來實現所有devfs的功能的,udev機制能夠根據系統中的硬體裝置的狀況動態更新裝置檔案,包括檔案的建立,刪除等。裝置檔案通常放在/dev目錄下,使用udev後,在/Dev下麵只包含系統中真實存在的裝置,它與硬體平臺無關,位於使用者空間,需要核心sysyfs和tmpfs的支援,sysfs為udev提供裝置入口和uevent通訊,tmpfs為udev裝置檔案提供存放空間。

    Linux udev(Linux userspace device management):Linux使用者空間裝置管理

    libudev為核心中使用udev所能使用的庫函式,該庫函式分為六類,分別是:udev、udev_init、udev_device、udev_monitor、udev_enumerate、udev_queue。

    關於udev的結構體,主要包括了以下幾個結構體:

    struct udev:主要儲存從配置檔案讀取的預設值。 struct udev_list_entry:裝置連結串列入口。 struct udev_device:udev裝置連結串列。 struct udev_monitor:udev裝置事件源。 struct udev_enumerate:查詢和排序sys。 struct udev_queue:存取當前活動事件。

    以上,就是裝置管理三種機制的簡單介紹。

    贊(0)

    分享創造快樂