netlink通信和設備管理的三種機制簡介

尋夢新聞LINE@每日推播熱門推薦文章,趣聞不漏接❤️

加入LINE好友

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作用一樣,結構的對比如下:

structsockaddr_nl{sa_family_tnl_family; /*該字段總是為AF_NETLINK*/unsignedshortnl_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表示,而消息體緊接著該消息頭。

structnlmsghdr{ __ u32nlmsg_len; /*length of message including header*/__ u16nlmsg_type; /*message content*/__ u16nlmsg_flags; /*additional flags*/__ u32nlmsg_seq; /*sequence number*/__ u32nlmsg_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

    structsock *netlink_kernel_create( structnet *net, intunit, unsignedintgroups, void(* input)( structsk_buff *skb), structmutex *cb_mutex, structmodule* module);

    參數說明:

    1. net:是一個網路名字空間namespace,在不同的名字空間里面可以有自己的轉PO信息庫。有自己的一套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( structsock*ssk, structsk_buff*skb, u32pid, 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( structsock*ssk, structsk_buff*skb, u32pid, u32group, gfp_t allocation)

    前面的三個參數與netlink_unicast相同,參數group為接收消息的多播組,該參數的每一個位代表一個多播組,因此如果發送給多個多播組,就把該參數設置為多個多播組,就把該參數設置為多個多播組ID的位或。參數allocation為內核記憶體分配類型,一般的為GFP_ATOMIC或GFP_KERNEL,GFP_ATOMIC用於原子的上下文(即不可以睡眠),而GFP_KERNEL用於非原子上下文。

    釋放netlink socket

    void netlink_kernel_release( structsock*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:存取當前活動事件。

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