精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

Linux Netlink 機制原理:內核與用戶態雙向通信

系統 Linux
Netlink 是 Linux 操作系統特有的一種進程間通信機制,主要用于內核空間與用戶空間的進程之間的通信。從本質上講,它是一種雙向通信機制,允許內核和用戶態應用程序之間進行高效的數據傳輸和交互。

在 Linux 系統中,內核態與用戶態的隔離是保障穩定性的關鍵,但 “隔離” 也帶來了通信需求 —— 用戶態工具需配置內核參數(如網絡路由)、獲取系統狀態(如進程資源),內核也需主動推送事件(如設備熱插拔、網絡鏈路變化)。傳統通信方式卻存在明顯局限:ioctl 僅支持同步請求、靈活性差,proc 文件系統適合靜態數據讀取、無法高效傳遞動態事件,而系統調用則需內核代碼修改,擴展性不足。

正是這種需求與局限的矛盾,催生了 Netlink 機制。它作為 Linux 特有的進程間通信(IPC)方案,最核心的價值便是打破內核與用戶態的單向通信壁壘,實現高效雙向交互。無論是用戶態通過標準 Socket API 向內核發起配置請求,還是內核主動向用戶態推送異步事件,Netlink 都能以低開銷、模塊化的方式完成消息傳遞。要理解這種雙向通信的實現邏輯,需從其底層模型、關鍵數據結構與消息流轉流程入手 —— 這不僅是掌握 Netlink 機制的核心,更是理解 Linux 網絡配置、系統監控等功能底層實現的關鍵。接下來,我們就拆解 Netlink 如何搭建起內核與用戶態之間的 “雙向通信橋梁”。

一、什么是 Netlink 機制

Netlink 是 Linux 操作系統特有的一種進程間通信機制,主要用于內核空間與用戶空間的進程之間的通信。從本質上講,它是一種雙向通信機制,允許內核和用戶態應用程序之間進行高效的數據傳輸和交互。

Netlink 基于 Socket API 實現,這使得它在使用上對于熟悉 Socket 編程的開發者來說較為友好。用戶態應用可以像使用普通 Socket 一樣,通過標準的 Socket 接口函數,如 socket ()、bind ()、sendmsg ()、recvmsg () 和 close () 等來與內核進行通信 ,而內核態則使用專門的內核 API 來處理 Netlink 消息。這種設計就像是在兩個不同區域(內核空間和用戶空間)之間搭建了一座橋梁,且兩邊的 “居民” 都能通過自己熟悉的方式走上這座橋進行交流。

與傳統的進程間通信方式相比,Netlink 具有顯著的獨特性。以管道為例,管道分為無名管道和有名管道,無名管道只能在具有親緣關系(如父子進程)的進程間使用,而且數據只能單向流動,是半雙工的通信方式;有名管道雖然可以在不相關的進程間使用,但依然是半雙工,在需要雙向通信的場景下就顯得力不從心。消息隊列采用異步通信,能在一定程度上解耦進程,但它的通信效率相對較低,消息的處理和排隊機制較為復雜,不太適合大量數據的頻繁傳輸。共享內存雖然是最快的進程間通信方式,因為它直接在內存中開辟共享區域,進程可以直接讀寫該區域的數據,減少了數據拷貝的開銷,但它缺乏同步機制,需要開發者自行實現復雜的同步邏輯來保證數據的一致性和完整性,否則很容易出現數據競爭和沖突等問題。

而 Netlink 支持全雙工通信,內核和用戶空間都能主動發起通信,實現了真正意義上的雙向交互。在網絡配置工具中,用戶空間的程序不僅可以向內核發送配置請求,內核在配置完成后也能主動向用戶空間返回配置結果和狀態信息 。它采用異步通信機制,發送方將消息放入接收方的 Socket 緩存隊列后即可返回,無需等待接收方處理消息,這大大提高了通信效率,尤其適用于高并發的場景,避免了同步通信中可能出現的阻塞問題,使得系統的響應更加及時和高效。

Netlink通信機制的簡易流程如下圖所示:

一般來說用戶空間和內核空間的通信方式有三種:/proc、ioctl、Netlink。而前兩種都是單向的,而Netlink可以實現雙工通信。Netlink 相對于系統調用,ioctl 以及 /proc 文件系統而言具有以下優點:

  1. 為了使用 netlink,用戶僅需要在 include/linux/netlink.h 中增加一個新類型的 netlink 協議定義即可, 如 #define NETLINK_MYTEST 17 然后,內核和用戶態應用就可以立即通過 socket API 使用該 netlink 協議類型進行數據交換。但系統調用需要增加新的系統調用,ioctl 則需要增加設備或文件, 那需要不少代碼,proc 文件系統則需要在 /proc 下添加新的文件或目錄,那將使本來就混亂的 /proc 更加混亂。
  2. netlink是一種異步通信機制,在內核與用戶態應用之間傳遞的消息保存在socket緩存隊列中,發送消息只是把消息保存在接收者的socket的接 收隊列,而不需要等待接收者收到消息,但系統調用與 ioctl 則是同步通信機制,如果傳遞的數據太長,將影響調度粒度。
  3. 使用 netlink 的內核部分可以采用模塊的方式實現,使用 netlink 的應用部分和內核部分沒有編譯時依賴,但系統調用就有依賴,而且新的系統調用的實現必須靜態地連接到內核中,它無法在模塊中實現,使用新系統調用的應用在編譯時需要依賴內核。
  4. netlink 支持多播,內核模塊或應用可以把消息多播給一個netlink組,屬于該neilink 組的任何內核模塊或應用都能接收到該消息,內核事件向用戶態的通知機制就使用了這一特性,任何對內核事件感興趣的應用都能收到該子系統發送的內核事件,在 后面的文章中將介紹這一機制的使用。
  5. 內核可以使用 netlink 首先發起會話,但系統調用和 ioctl 只能由用戶應用發起調用。
  6. netlink 使用標準的 socket API,因此很容易使用,但系統調用和 ioctl則需要專門的培訓才能使用。

Netlink協議基于BSD socket和AF_NETLINK地址簇,使用32位的端口號尋址,每個Netlink協議通常與一個或一組內核服務/組件相關聯,如NETLINK_ROUTE用于獲取和設置路由與鏈路信息、NETLINK_KOBJECT_UEVENT用于內核向用戶空間的udev進程發送通知等。

二、Netlink 機制的工作原理

Netlink 的架構就像是一個精心構建的通信網絡,各個部分協同工作,實現了內核與用戶空間之間高效的通信。我們來看下面這張:

從圖中可以看出,Netlink 主要由以下幾個部分組成:

  • 用戶空間應用:這是我們日常使用的各種應用程序,它們通過標準的 socket API 與 Netlink 套接字進行交互。比如我們前面提到的網絡配置工具、系統監控程序等,它們通過 Netlink 向內核發送請求,獲取系統信息或者執行特定的操作。
  • Netlink 套接字:作為用戶空間與內核空間通信的橋梁,Netlink 套接字負責在兩者之間傳遞數據。它基于 BSD socket 和 AF_NETLINK 地址簇,采用 32 位的端口號尋址 。每個 Netlink 套接字都有一個對應的協議類型,用于標識通信的內容和目的。
  • 內核空間:內核是 Linux 系統的核心,它包含了各種設備驅動、網絡協議棧等重要組件。內核通過 Netlink 與用戶空間進行通信,接收用戶空間的請求并返回相應的結果,同時也可以主動向用戶空間發送通知和事件信息。
  • Netlink 協議族:Netlink 支持多種協議類型,每種協議類型都與特定的內核服務或組件相關聯。例如,NETLINK_ROUTE 用于網絡路由相關的操作,NETLINK_KOBJECT_UEVENT 用于內核向用戶空間發送設備事件通知等 。不同的協議類型使得 Netlink 能夠滿足各種不同的通信需求。

2.1 通信模型

Netlink 采用異步通信模型,這是它區別于許多傳統通信機制的重要特點。在這種模型下,當一個進程(無論是內核空間還是用戶空間的進程)發送 Netlink 消息時,消息并不會立即被接收方處理。發送方將消息放入接收者的 Socket 緩存隊列后,就可以繼續執行其他任務,無需等待接收方的響應,這就好比我們寄快遞,把包裹交給快遞員(放入緩存隊列)后,就不用一直等著對方簽收,我們可以去做別的事情。

以網絡配置工具修改網絡接口的 IP 地址為例,用戶空間的網絡配置工具通過 Netlink 向內核發送配置請求消息。消息發送后,網絡配置工具不會被阻塞,它可以繼續處理用戶的其他操作,比如顯示當前的網絡狀態信息等。而內核在接收到消息后,會將其從 Socket 緩存隊列中取出并進行處理,處理完成后再將響應消息發送回用戶空間的網絡配置工具。

在這個過程中,Socket 緩存隊列起著關鍵的作用。它就像是一個臨時的 “倉庫”,用于存儲等待處理的消息。當消息到達時,會按照先后順序被放入隊列中,接收方則按照先進先出(FIFO)的原則從隊列中讀取消息進行處理 。這種方式使得 Netlink 在處理高并發的消息時具有很高的效率,避免了同步通信機制中可能出現的阻塞問題。

與同步通信機制相比,同步通信要求發送方在發送消息后,必須等待接收方處理完消息并返回響應,才能繼續執行后續操作。這就像打電話,打電話的人必須等對方接聽并交流完后,才能掛斷電話去做其他事情。如果接收方處理消息的時間較長,或者網絡出現延遲,發送方就會被長時間阻塞,導致系統的響應速度變慢,效率降低。而 Netlink 的異步通信機制則有效地避免了這些問題,提高了系統的整體性能和響應能力,使得系統能夠更加高效地處理各種任務。

2.2 協議類型

在 Linux 內核中,定義了多種 Netlink 協議類型,每種類型都有其特定的用途,它們就像是不同類型的 “快遞服務”,各自負責傳遞不同類型的 “包裹”(消息)。

NETLINK_ROUTE 是非常常用的一種協議類型,主要用于網絡路由和設備相關的操作。它可以傳遞網絡接口的狀態信息,比如網絡接口的開啟、關閉,以及 IP 地址的配置、路由表的更新等消息。我們使用 iproute2 工具配置網絡接口的 IP 地址時,iproute2 工具就是通過 NETLINK_ROUTE 協議與內核進行通信,將用戶設置的 IP 地址等信息傳遞給內核,內核再根據這些信息進行相應的網絡配置。

NETLINK_KOBJECT_UEVENT 用于內核向用戶空間發送設備相關的事件通知,尤其是在設備熱插拔的場景中發揮著重要作用。當有 USB 設備插入或拔出計算機時,內核會通過 NETLINK_KOBJECT_UEVENT 協議向用戶空間的 udev 進程發送設備插拔事件的通知,udev 進程接收到通知后,就可以對設備進行相應的管理和處理,比如為新插入的 USB 設備分配設備節點,加載相應的驅動程序等。

NETLINK_NFLOG 協議與網絡包過濾和日志記錄相關。在網絡安全領域,防火墻等網絡安全設備需要對網絡數據包進行過濾和監控。當數據包通過防火墻時,防火墻可以使用 NETLINK_NFLOG 協議將數據包的相關信息(如源 IP 地址、目的 IP 地址、端口號等)發送給用戶空間的日志記錄程序,日志記錄程序就可以將這些信息記錄下來,以便管理員進行安全分析和審計,及時發現潛在的網絡安全威脅。

除了這些預定義的協議類型,開發者還可以根據自己的需求自定義 Netlink 協議類型。在開發一個自定義的內核模塊與用戶空間程序進行通信時,開發者可以定義一個新的 Netlink 協議類型,用于在兩者之間傳遞特定的消息和數據,實現特定的功能,這為 Linux 系統的擴展和定制提供了很大的靈活性。

2.3 數據結構

(1)sockaddr_nl

sockaddr_nl 是 Netlink 通信中用于標識通信端點的地址結構,它包含了多個重要字段,每個字段都在通信過程中扮演著關鍵角色。

nl_family 字段表示地址族,對于 Netlink 通信來說,它始終被設置為 AF_NETLINK(等同于 PF_NETLINK),這個字段就像是一個 “通信語言” 的標識,告訴系統這是 Netlink 通信,就如同我們在國際交流中,通過語言標識來確定交流所使用的語言一樣,讓系統能夠正確地識別和處理 Netlink 相關的通信。

nl_pid 字段用于指定進程標識符(Port ID)。在用戶空間,通常將其設置為進程的 PID,這樣內核就可以根據這個 PID 將消息準確地發送給對應的用戶空間進程;在內核空間,該字段被固定設置為 0,因為內核作為消息的發送源,不需要特定的 PID 標識,就像一個大型工廠的中央控制中心(內核)向各個車間(用戶空間進程)發送指令時,控制中心不需要用自己的 “身份標識” 來標記指令,而各個車間則需要有明確的 “身份標識” 以便接收指令。例如,當用戶空間的某個網絡監控程序通過 Netlink 與內核通信時,該程序會將自己的 PID 設置到 nl_pid 字段,內核在返回網絡狀態信息時,就可以根據這個 PID 將信息發送回該監控程序。

nl_groups 字段是一個多播組掩碼,用于指定接收者想要訂閱的消息組標識集合。每個 Netlink 協議族都可以定義自己的多播組,通過設置 nl_groups,進程可以訂閱一個或多個多播組。當內核有相關事件發生時,會向這些多播組發送消息,訂閱了相應多播組的套接字就會接收到這些消息。在網絡路由更新時,內核會將路由更新消息發送到 RTMGRP_IPV4_ROUTE 多播組,所有訂閱了該多播組的路由守護進程(如 quagga、bird 等)都能接收到這個消息,從而及時更新自己的路由表,確保網絡數據包能夠正確轉發。

(2)nlmsghdr

nlmsghdr 是 Netlink 消息頭結構,它定義了消息的元數據,對于消息的正確傳輸和處理至關重要。

nlmsg_len 字段表示整個 Netlink 消息的長度,包括消息頭和有效載荷的總長度。這個字段就像是包裹的 “重量標簽”,告訴接收方整個消息占用了多少字節的空間,以便接收方正確地分配內存來存儲消息,并且根據這個長度來準確地解析消息內容,避免讀取到錯誤的數據。

nlmsg_type 字段用于指定消息的類型,它就像是包裹上的 “內容標簽”,告訴接收方消息的具體含義和用途。內核定義了多種通用的消息類型,如 NLMSG_NOOP 表示空操作消息,接收方應忽略該消息,就像收到一個空的包裹,直接忽略即可;NLMSG_ERROR 表示錯誤指示消息,包含錯誤代碼,當接收方收到這個類型的消息時,就知道在通信或處理過程中出現了錯誤,需要根據錯誤代碼進行相應的錯誤處理;NLMSG_DONE 用于標識多部分消息的結束,在傳輸大量數據時,可能會將數據分成多個部分發送,接收方通過這個標識來判斷是否已經接收完所有的數據部分。

nlmsg_flags 字段包含了一些附加標志,用于控制消息的行為和語義。NLM_F_REQUEST 標志表示這是一個請求消息,就像我們向別人發出的詢問或請求幫助的信號;NLM_F_MULTI 標志指示這是多部分消息中的一部分,結合 NLMSG_DONE 標志,接收方可以正確地組裝多部分消息;NLM_F_ACK 標志要求接收方發送確認消息,類似于我們寄快遞時選擇了 “簽收確認” 服務,發送方希望知道接收方是否成功收到消息。

nlmsg_seq 字段是消息序列號,用于將消息排隊,有些類似于 TCP 協議中的序號,但在 Netlink 中這個字段是可選的,不強制使用。它可以幫助接收方按照正確的順序處理消息,特別是在處理多個相關消息時,能夠確保消息的處理順序與發送順序一致,避免因消息亂序導致的處理錯誤。

nlmsg_pid 字段表示發送端口 ID,對于內核發送的消息,該值為 0,因為內核作為發送源,其 PID 固定為 0;對于用戶進程發送的消息,該值就是其 socket 所綁定的 ID 號,這樣接收方就可以知道消息是從哪個進程發送過來的,以便進行相應的響應和處理 。

(3)nlattr

nlattr 結構在 Netlink 消息的有效載荷中起著關鍵作用,它采用類型 - 長度 - 值(TLV,Type - Length - Value)的編碼格式,這種格式為消息的擴展性提供了有力支持。

nla_len 字段表示屬性的總長度,包括屬性類型、長度字段本身以及屬性值所占的字節數,就像一個小包裹的 “小重量標簽”,告訴接收方這個屬性占用了多少空間。

nla_type 字段指定屬性的類型,每個屬性類型都有其特定的含義,它就像是小包裹上的 “小內容標簽”,表明這個屬性代表的具體信息。在網絡接口配置消息中,可能會有一個屬性類型表示 IP 地址,另一個屬性類型表示子網掩碼等。

緊跟在 nla_type 和 nla_len 字段后面的就是屬性值,這是屬性的具體內容。這種 TLV 格式的設計使得 Netlink 消息具有很好的擴展性,當需要在消息中添加新的屬性時,只需要定義新的屬性類型,并按照 TLV 格式將其添加到消息中即可。接收方在解析消息時,如果遇到不認識的屬性類型,只需要根據 nla_len 字段跳過該屬性,而不會影響對其他已知屬性的解析和處理,就像我們收到一個包含不認識物品(新屬性)的包裹時,我們可以先把不認識的物品放在一邊,先處理我們認識的物品(已知屬性),這保證了消息格式的向后兼容性,使得舊的程序能夠正確處理新格式的消息,即使它們不理解新添加的屬性。例如,在新的 Linux 內核版本中,如果為網絡設備添加了新的功能,需要在 Netlink 消息中傳遞相關的配置信息,就可以通過定義新的 nla_type 來實現,而不會影響舊版本的網絡配置工具對其他常規屬性的處理。

三、Netlink API 函數詳解

3.1 內核態 API

在內核態中,Netlink 提供了一系列專門的 API 函數,用于實現與用戶態之間的通信功能,這些函數是內核與用戶態交互的關鍵紐帶 。

(1)創建和銷毀 Socket:netlink_kernel_create函數用于創建一個 Netlink 套接字,它是內核與用戶態通信的基礎。其函數原型如下:

struct sock *netlink_kernel_create(struct net *net, int unit, struct netlink_kernel_cfg *cfg);

在這個函數中,net參數通常使用&init_net,它代表了網絡命名空間,就像是一個大型社區,所有的網絡相關活動都在這個社區中進行 。unit參數指定了 Netlink 協議類型,比如NETLINK_ROUTE用于路由相關的通信,NETLINK_FIREWALL用于防火墻管理通信等,不同的協議類型就像是不同的社區服務部門,負責處理不同類型的事務 。cfg是一個指向netlink_kernel_cfg結構體的指針,該結構體包含了一些配置參數,其中最重要的是input回調函數,當有消息到達這個 Netlink 套接字時,就會調用這個回調函數來處理消息,就像社區里的快遞代收點,當有快遞到達時,會通知收件人來取件 。這個函數成功時會返回一個指向struct sock的指針,代表創建好的 Netlink 套接字;如果創建失敗,則返回NULL 。

當不再需要這個 Netlink 套接字時,就需要使用netlink_kernel_release函數來釋放它,釋放資源,避免內存泄漏 。其函數原型為:

void netlink_kernel_release(struct sock *sk);

這里的sk參數就是之前netlink_kernel_create函數返回的套接字指針,通過這個指針,系統可以準確地找到要釋放的套接字資源 。

(2)發送消息:netlink_unicast函數用于將 Netlink 消息單播給指定的接收者 。函數原型如下:

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

其中,ssk是指向struct sock的指針,表示 Netlink 套接字,就像是一個通信通道;skb是指向要發送的 Netlink 消息的struct sk_buff緩沖區,sk_buff結構體就像是一個包裹,里面裝著要發送的消息內容;pid是接收者的進程 ID,通過這個 ID,消息能夠準確地找到目標接收者,就像快遞需要知道收件人的地址才能送達;nonblock是非阻塞標志,設置為非零值以進行非阻塞操作,如果設置為非零,當發送消息時,如果接收方的緩沖區已滿或者其他原因導致消息無法立即發送,函數會立即返回,而不會阻塞等待,就像快遞員如果發現收件人的收件箱已滿,不會一直等待收件箱有空位,而是先離開并返回一個無法投遞的通知;如果設置為零,函數將一直阻塞,直到消息完全發送成功或發生錯誤 。該函數返回一個整數值,表示發送是否成功,如果發送成功,則返回 0;如果發送失敗,則返回一個負數錯誤碼 。

netlink_broadcast函數則用于將消息多播給一個 Netlink 組 。函數原型為:

int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, __u32 portid, __u32 group, gfp_t allocation);

ssk和skb參數與netlink_unicast中的含義相同 。portid是通信的端口號,對應用態的端口號;group是所有目標多播組對應掩碼的 “OR” 操作的合值,通過這個參數,可以指定要發送到的多個多播組,就像一個廣播通知,可以同時發送給多個不同的區域;allocation指定內核內存分配方式,通常GFP_ATOMIC用于中斷上下文,在這種情況下,內存分配必須立即完成,不能等待,就像在緊急情況下,必須馬上分配資源;而GFP_KERNEL用于其他場合,這種情況下,內存分配可以在適當的時候進行等待 。該函數返回值的含義與netlink_unicast類似,0 表示發送成功,負數表示發送失敗 。

(3)消息處理:當有消息到達時,內核會調用之前在netlink_kernel_cfg結構體中注冊的input回調函數來處理消息 。這個回調函數的原型通常如下:

void input(struct sk_buff *skb);

skb參數是接收到的消息緩沖區,在這個回調函數中,可以從skb中提取消息頭和有效載荷,然后根據消息類型和內容進行相應的處理 。比如,在處理網絡設備狀態變化的消息時,從消息中解析出設備的名稱、狀態等信息,然后更新內核中的設備狀態表 。在處理消息時,還可以使用一些輔助函數,nlmsg_hdr函數用于從sk_buff中獲取nlmsghdr結構體指針,該結構體包含了消息的元數據,如消息類型、長度、序列號等,通過這些信息,可以更好地理解和處理消息 。其函數原型為:

static inline struct nlmsghdr *nlmsg_hdr(const struct sk_buff *skb);

還有NLMSG_DATA宏,用于獲取消息的有效載荷數據,通過它,可以直接訪問到消息中真正要傳遞的數據內容 。例如:

struct nlmsghdr *nlh = nlmsg_hdr(skb);
char *data = NLMSG_DATA(nlh);

這樣就可以方便地獲取到消息的有效載荷數據,進行進一步的處理 。

3.2 用戶態 API

在用戶態,使用 Netlink 通信主要依賴于標準的 Socket API 函數,這些函數對于熟悉 Socket 編程的開發者來說并不陌生,它們為用戶態與內核態之間的通信提供了便捷的方式 。

(1)創建套接字:使用socket函數來創建一個 Netlink 套接字 。函數原型如下:

int socket(int domain, int type, int protocol);

對于 Netlink 通信,domain參數應設置為AF_NETLINK,表示使用 Netlink 協議族,就像選擇了一條特定的通信道路;type參數通常設置為SOCK_RAW,表示使用原始套接字,這種套接字可以直接處理底層的網絡數據,更適合 Netlink 這種需要精確控制消息的通信方式;protocol參數指定具體的 Netlink 協議類型,比如NETLINK_ROUTE、NETLINK_GENERIC等,不同的協議類型對應不同的通信用途 。如果函數成功執行,將返回一個文件描述符,這個描述符就像是一個通信通道的標識,后續的操作都將通過這個標識來進行;如果創建失敗,將返回 -1,并設置errno變量來指示錯誤原因 。例如:

int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_GENERIC);
if (sock_fd < 0) {
    perror("socket");
    exit(EXIT_FAILURE);
}

(2)綁定地址:創建套接字后,需要使用bind函數將其綁定到一個 Netlink 地址上 。函數原型為:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

addr參數是一個指向struct sockaddr_nl結構體的指針,該結構體定義了 Netlink 地址,包括協議族(nl_family始終為AF_NETLINK)、進程標識符(nl_pid)和多播組掩碼(nl_groups) 。在綁定地址時,需要根據實際需求設置這些字段 。如果是與內核通信,通常將nl_pid設置為 0,表示目標是內核;如果是與其他用戶態進程通信,則需要設置為對方的進程 ID 。addrlen參數是地址結構體的長度 。綁定成功時返回 0,失敗返回 -1 。示例代碼如下:

struct sockaddr_nl src_addr;
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); // 設置為自身進程ID
src_addr.nl_groups = 0; // 不加入多播組

if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) {
    perror("bind");
    close(sock_fd);
    exit(EXIT_FAILURE);
}

(3)發送消息:發送 Netlink 消息使用sendmsg函數,它可以發送一個完整的消息,包括消息頭和有效載荷 。函數原型為:

ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);

msg參數是一個指向struct msghdr結構體的指針,該結構體包含了要發送的消息的詳細信息,包括目標地址(msg_name)、地址長度(msg_namelen)、消息數據(msg_iov)等 。在構建struct msghdr結構體時,需要先填充好消息頭和有效載荷,然后將它們與msg_iov關聯起來 。flags參數用于指定一些發送標志,通常設置為 0 。發送成功時返回發送的字節數,失敗返回 -1 。下面是一個簡單的發送消息的示例:

struct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = getpid();
nlh->nlmsg_type = NLMSG_NOOP;
nlh->nlmsg_flags = 0;

strcpy(NLMSG_DATA(nlh), "Hello, Kernel!"); // 設置消息內容

struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = nlh->nlmsg_len;

struct sockaddr_nl dest_addr;
memset(&dest_addr, 0, sizeof(dest_addr));
dest_addr.nl_family = AF_NETLINK;
dest_addr.nl_pid = 0; // 發送給內核
dest_addr.nl_groups = 0;

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&dest_addr;
msg.msg_namelen = sizeof(dest_addr);
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

if (sendmsg(sock_fd, &msg, 0) < 0) {
    perror("sendmsg");
    free(nlh);
    close(sock_fd);
    exit(EXIT_FAILURE);
}

(4)接收消息:接收 Netlink 消息使用recvmsg函數,它從指定的套接字接收消息 。函數原型為:

ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

msg參數與sendmsg中的類似,用于存儲接收到的消息信息 。在調用recvmsg之前,需要先分配足夠的緩沖區來存儲消息頭和有效載荷,并將緩沖區與msg_iov關聯起來 。接收成功時返回接收到的字節數,失敗返回 -1 。如果返回 0,表示對方關閉了連接 。示例代碼如下:

struct nlmsghdr *nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD));
memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD));

struct iovec iov;
iov.iov_base = (void *)nlh;
iov.iov_len = NLMSG_SPACE(MAX_PAYLOAD);

struct sockaddr_nl src_addr;
socklen_t src_addr_len = sizeof(src_addr);

struct msghdr msg;
memset(&msg, 0, sizeof(msg));
msg.msg_name = (void *)&src_addr;
msg.msg_namelen = src_addr_len;
msg.msg_iov = &iov;
msg.msg_iovlen = 1;

ssize_t len = recvmsg(sock_fd, &msg, 0);
if (len < 0) {
    perror("recvmsg");
    free(nlh);
    close(sock_fd);
    exit(EXIT_FAILURE);
} else if (len > 0) {
    char *data = NLMSG_DATA(nlh);
    printf("Received: %s\n", data);
}
free(nlh);

(5)關閉套接字:當完成 Netlink 通信后,使用close函數關閉套接字,釋放資源 。函數原型為:

int close(int fd);

fd參數是之前創建套接字時返回的文件描述符 。關閉成功返回 0,失敗返回 -1 。例如:

close(sock_fd);

通過上述這些用戶態 API 函數的組合使用,就可以實現與內核態之間的 Netlink 通信,完成各種數據交互和控制操作 。

四、Netlink 機制的應用場景

4.1 網絡配置與管理

在網絡配置與管理領域,iproute2 工具是 Netlink 機制的典型應用代表。iproute2 是一套通過 Netlink 套接字與 Linux 內核通信的工具集,其核心命令ip支持多種網絡對象的配置,包括接口、IP 地址、路由表等 。

在配置網絡接口時,使用ip link命令可以輕松管理網絡接口的狀態。ip link show命令用于查看所有接口狀態,它會輸出接口的詳細信息,如接口名稱、MAC 地址、MTU(最大傳輸單元)、接口啟用狀態(UP/DOWN)以及物理連接狀態(LOWER_UP)等。當需要啟用或禁用網卡時,執行sudo ip link set eth0 up或sudo ip link set eth0 down命令即可,這里的eth0是網絡接口名稱,通過 Netlink,這些命令能夠快速將配置信息傳遞給內核,內核接收到消息后,會對網絡接口進行相應的狀態設置。

在 IP 地址配置方面,ip addr命令發揮著重要作用。sudo ip addr add 192.168.1.100/24 dev eth0命令用于為eth0接口添加 IP 地址192.168.1.100,子網掩碼為24位。執行該命令時,用戶空間的ip工具通過 Netlink 向內核發送包含 IP 地址和接口信息的消息,內核根據接收到的消息完成 IP 地址的配置,并將配置結果通過 Netlink 返回給用戶空間。如果需要刪除 IP 地址,使用sudo ip addr del 192.168.1.100/24 dev eth0命令即可,同樣是借助 Netlink 實現與內核的通信,完成 IP 地址的刪除操作。

路由表管理也是網絡配置的關鍵部分,ip route命令負責這一任務。ip route show用于查看路由表,它能展示系統當前的路由信息,包括目標網絡、下一跳地址、出接口等。當需要添加默認網關時,執行sudo ip route add default via 192.168.1.1 dev eth0命令,通過 Netlink,ip工具將添加默認網關的請求發送給內核,內核更新路由表并將結果反饋給用戶空間。添加靜態路由時,如sudo ip route add 10.0.0.0/24 via 192.168.1.2,也是利用 Netlink 實現與內核的交互,完成靜態路由的添加,確保網絡數據包能夠按照設定的路由規則進行轉發。

4.2 設備驅動與用戶空間交互

在設備驅動與用戶空間交互中,Netlink 發揮著至關重要的橋梁作用。以網絡設備驅動為例,當網絡設備的狀態發生變化時,比如網絡電纜被拔出或插入,設備驅動需要及時將這一信息傳遞給用戶空間的程序,以便用戶空間的程序做出相應的處理,如更新網絡狀態顯示、重新配置網絡連接等。

設備驅動通過 Netlink 向用戶空間發送設備狀態變化的消息。當網絡設備驅動檢測到網絡電纜被拔出時,它會構造一個 Netlink 消息,消息中包含設備的相關信息以及狀態變化的描述,然后通過 Netlink 將這個消息發送給用戶空間中訂閱了該設備狀態變化的程序。在 Linux 系統中,udev是一個用戶空間程序,它負責管理設備節點的創建和刪除等工作。當設備驅動通過 Netlink 發送設備插拔事件通知時,udev接收到消息后,會根據消息中的設備信息,創建或刪除相應的設備節點,確保用戶空間能夠正確訪問設備。

設備驅動還可以通過 Netlink 接收用戶空間程序發送的控制指令。用戶空間的網絡配置程序可以通過 Netlink 向網絡設備驅動發送配置指令,如設置網絡設備的 MTU 值。用戶空間程序構造包含 MTU 配置信息的 Netlink 消息,并發送給設備驅動。設備驅動接收到消息后,解析消息內容,根據配置指令對網絡設備的 MTU 進行設置,從而實現對設備的有效管理。這種通過 Netlink 進行的雙向通信,使得設備驅動與用戶空間程序能夠緊密協作,提高了設備管理的效率和靈活性。

4.3 系統監控與性能優化

在系統監控與性能優化方面,Netlink 為監控工具提供了獲取內核中系統資源使用情況的有效途徑。以nlbwmon這款輕量級的網絡流量監控工具為例,它通過 Netlink 套接字從 Linux 內核獲取網絡使用信息,并從linux conntrack條目中收集統計信息 。

nlbwmon在運行過程中,通過 Netlink 向內核發送請求消息,請求獲取網絡帶寬使用情況、網絡連接狀態等信息。內核接收到請求后,會根據請求內容收集相關的系統資源使用數據,如各個網絡接口的上傳和下載流量數據,然后將這些數據通過 Netlink 返回給nlbwmon。nlbwmon接收到數據后,對其進行分析和處理,以直觀的方式展示給用戶,用戶可以通過這些數據了解網絡帶寬的使用情況,判斷是否存在網絡擁塞或異常流量。

通過 Netlink 獲取的系統資源使用數據還能為性能優化提供有力的數據支持。系統管理員可以根據nlbwmon提供的網絡流量數據,分析網絡流量的分布情況,找出網絡流量較大的時間段和應用程序,從而針對性地進行網絡資源的優化配置,如限制某些非關鍵應用的帶寬使用,為關鍵業務應用預留足夠的網絡帶寬,以提高整個系統的網絡性能。在企業網絡中,如果發現某個部門在特定時間段內的網絡流量過大,導致其他部門的網絡訪問受到影響,管理員可以根據監控數據,對該部門的網絡帶寬進行限制,確保網絡資源的合理分配,提升系統的整體性能。

4.4 安全與防火墻管理

在安全與防火墻管理領域,Netlink 起著關鍵的作用,防火墻工具如iptables借助 Netlink 與內核netfilter模塊通信,實現安全策略的配置和管理。

iptables是 Linux 系統中常用的防火墻工具,它通過 Netlink 與內核中的netfilter模塊進行交互。當用戶通過iptables命令設置防火墻規則時,iptables會將這些規則通過 Netlink 發送給netfilter模塊。用戶執行iptables -A INPUT -p tcp --dport 80 -j ACCEPT命令,這條命令的含義是在INPUT鏈中添加一條規則,允許目的端口為 80 的 TCP 數據包通過。iptables接收到這個命令后,會構造一個包含該規則信息的 Netlink 消息,并發送給netfilter模塊。netfilter模塊接收到消息后,解析其中的規則內容,并將其應用到網絡數據包的過濾過程中。

當網絡數據包到達系統時,netfilter模塊會根據配置的防火墻規則對數據包進行檢查。如果數據包符合iptables設置的拒絕規則,netfilter模塊會根據規則對數據包進行相應的處理,如丟棄數據包。而當netfilter模塊在處理數據包過程中發生一些與安全相關的事件時,它也可以通過 Netlink 向iptables發送通知消息,iptables可以根據這些通知消息更新防火墻的狀態或日志記錄,以便管理員進行安全審計和分析,及時發現潛在的安全威脅,從而實現對系統的安全防護和管理。

五、Netlink 機制的使用方法

5.1 用戶態編程示例

在用戶態使用 Netlink 進行通信,主要借助 socket API 中的 socket ()、bind ()、sendmsg ()、recvmsg () 等函數,下面通過一個簡單的示例代碼來詳細說明每一步操作。

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <linux/netlink.h>

#define NETLINK_TEST 30 // 自定義Netlink協議類型
#define MSG_LEN 100
#define MAX_PLOAD 200

int main() {
    int sockfd;
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh;
    char buffer[MAX_PLOAD];
    struct iovec iov = { buffer, MAX_PLOAD };
    struct msghdr msg;

    // 創建Netlink套接字
    sockfd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if (sockfd < 0) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    // 初始化源地址
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = getpid(); // 使用當前進程ID
    src_addr.nl_groups = 0;

    // 綁定套接字到源地址
    if (bind(sockfd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) {
        perror("bind");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 初始化目的地址(發往內核,nl_pid為0)
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;
    dest_addr.nl_groups = 0;

    // 構造Netlink消息頭
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MSG_LEN));
    memset(nlh, 0, NLMSG_SPACE(MSG_LEN));
    nlh->nlmsg_len = NLMSG_SPACE(MSG_LEN);
    nlh->nlmsg_type = 0; // 自定義消息類型
    nlh->nlmsg_flags = 0;
    nlh->nlmsg_seq = 0;
    nlh->nlmsg_pid = src_addr.nl_pid;

    // 設置消息內容
    char *msg_content = "Hello, Kernel!";
    memcpy(NLMSG_DATA(nlh), msg_content, strlen(msg_content));

    // 構造msghdr結構體
    msg.msg_name = &dest_addr;
    msg.msg_namelen = sizeof(dest_addr);
    msg.msg_iov = &iov;
    msg.msg_iovlen = 1;
    msg.msg_control = NULL;
    msg.msg_controllen = 0;
    msg.msg_flags = 0;

    // 發送消息
    if (sendmsg(sockfd, &msg, 0) < 0) {
        perror("sendmsg");
    } else {
        printf("Message sent to kernel: %s\n", msg_content);
    }

    // 接收內核回復消息
    memset(buffer, 0, MAX_PLOAD);
    if (recvmsg(sockfd, &msg, 0) < 0) {
        perror("recvmsg");
    } else {
        nlh = (struct nlmsghdr *)buffer;
        printf("Received from kernel: %s\n", (char *)NLMSG_DATA(nlh));
    }

    // 清理資源
    free(nlh);
    close(sockfd);

    return 0;
}
  1. 創建 Netlink 套接字:使用socket函數創建一個 Netlink 套接字,參數PF_NETLINK表示協議族為 Netlink,SOCK_RAW表示使用原始套接字,NETLINK_TEST是自定義的 Netlink 協議類型。這一步就像是在兩座城市(用戶空間和內核空間)之間搭建了一條專門的通信線路。
  2. 綁定套接字到源地址:通過bind函數將創建的套接字綁定到源地址src_addr。源地址中,nl_family設置為AF_NETLINK,nl_pid設置為當前進程 ID,nl_groups設置為 0 表示不加入任何多播組。這就好比給這條通信線路指定了一個明確的發送起點。
  3. 構造 Netlink 消息頭和消息內容:首先為nlmsghdr結構體分配內存,并設置其各個字段,包括消息長度nlmsg_len、消息類型nlmsg_type、消息標志nlmsg_flags、消息序列號nlmsg_seq和發送進程 IDnlmsg_pid。然后將消息內容復制到NLMSG_DATA(nlh)指向的位置,就像是在信封上填寫好收件人信息(消息頭),并裝入信件內容(消息內容)。
  4. 構造 msghdr 結構體并發送消息:初始化msghdr結構體msg,設置目的地址msg_name、目的地址長度msg_namelen、數據向量msg_iov及其長度msg_iovlen等字段。最后使用sendmsg函數發送消息,這就相當于把信封(消息)投遞到通信線路上,發送給內核。
  5. 接收內核回復消息:接收消息前,先清空接收緩沖區buffer。使用recvmsg函數接收內核返回的消息,接收到消息后,通過nlmsghdr結構體解析消息頭,并提取消息內容進行輸出,就像是接收并拆開內核寄回的回信。
  6. 清理資源:使用free函數釋放分配的nlmsghdr內存,使用close函數關閉套接字,釋放相關資源,就像是在通信結束后,清理通信線路和相關物品,為下次通信做好準備。

5.2 內核態編程要點

在內核態中使用 Netlink 進行通信,需要完成注冊 Netlink 套接字、處理消息等關鍵步驟,涉及到一些內核 API 和回調函數的使用。

(1)注冊 Netlink 套接字:使用netlink_kernel_create函數創建并注冊一個 Netlink 套接字。這個函數需要傳入 Netlink 協議類型、消息處理回調函數等參數。例如:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/netlink.h>
#include <net/sock.h>

#define NETLINK_TEST 30
static struct sock *nl_sk = NULL;

static void netlink_rcv_msg(struct sk_buff *skb) {
    // 消息處理邏輯
}

static int __init netlink_init(void) {
    struct netlink_kernel_cfg cfg = {
       .input = netlink_rcv_msg,
    };
    nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if (!nl_sk) {
        printk(KERN_ERR "Failed to create netlink socket\n");
        return -1;
    }
    return 0;
}

static void __exit netlink_exit(void) {
    netlink_kernel_release(nl_sk);
}

module_init(netlink_init);
module_exit(netlink_exit);
MODULE_LICENSE("GPL");

在這段代碼中,netlink_kernel_create函數創建了一個 Netlink 套接字,協議類型為NETLINK_TEST,并將netlink_rcv_msg函數注冊為消息處理回調函數。netlink_kernel_create函數的第一個參數&init_net表示網絡命名空間,這里使用全局的init_net;第二個參數是 Netlink 協議類型;第三個參數是netlink_kernel_cfg結構體指針,用于配置套接字的一些屬性,這里主要配置了消息處理回調函數。

(2)處理消息:當有 Netlink 消息到達時,內核會調用注冊的回調函數(如netlink_rcv_msg)來處理消息。在回調函數中,首先從skb(struct sk_buff)結構體中獲取nlmsghdr消息頭,然后根據消息頭的信息,如消息類型、消息長度等,進一步解析消息內容并進行相應的處理。例如:

static void netlink_rcv_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh;
    nlh = nlmsg_hdr(skb);
    // 檢查消息類型
    if (nlh->nlmsg_type == /* 自定義消息類型 */) {
        char *msg_data = NLMSG_DATA(nlh);
        // 處理消息數據
        printk(KERN_INFO "Received message from user: %s\n", msg_data);
        // 構造回復消息
        struct sk_buff *reply_skb;
        struct nlmsghdr *reply_nlh;
        reply_skb = nlmsg_new(NLMSG_SPACE(MSG_LEN), GFP_KERNEL);
        if (!reply_skb) {
            printk(KERN_ERR "Failed to allocate reply skb\n");
            return;
        }
        reply_nlh = nlmsg_put(reply_skb, 0, 0, NETLINK_TEST, strlen("Reply from kernel"), 0);
        if (!reply_nlh) {
            printk(KERN_ERR "Failed to put reply nlmsg\n");
            nlmsg_free(reply_skb);
            return;
        }
        memcpy(NLMSG_DATA(reply_nlh), "Reply from kernel", strlen("Reply from kernel"));
        // 發送回復消息
        netlink_unicast(nl_sk, reply_skb,  /* 接收方PID */, MSG_DONTWAIT);
    }
}

在netlink_rcv_msg函數中,首先通過nlmsg_hdr宏從skb中獲取nlmsghdr消息頭。然后根據消息類型進行判斷,如果是期望的消息類型,就通過NLMSG_DATA宏獲取消息數據,并進行處理,這里只是簡單地打印消息內容。接著構造回復消息,使用nlmsg_new函數分配一個新的sk_buff用于存放回復消息,使用nlmsg_put函數填充回復消息的nlmsghdr頭,并將回復消息內容復制到消息數據部分。最后使用netlink_unicast函數將回復消息發送回用戶態,其中nl_sk是之前創建的 Netlink 套接字,MSG_DONTWAIT表示不等待發送完成。

內核態編程中,還需要注意內存管理,如使用nlmsg_new分配的內存需要在不再使用時通過nlmsg_free釋放,以避免內存泄漏;同時要處理好并發訪問,確保在多線程或多 CPU 環境下的正確性 。

六、實戰項目:構建 Netlink 通信系統

6.1 項目架構設計

在這個實戰項目中,我們將構建一個基于 Netlink 機制的簡單通信系統,實現用戶態應用程序與內核模塊之間的雙向通信 。整個項目架構主要由兩部分組成:內核模塊和用戶空間程序 。

  1. 內核模塊:負責創建 Netlink 套接字,監聽來自用戶空間的消息,并處理這些消息 。當接收到用戶空間發送的消息后,內核模塊會根據消息的內容進行相應的處理,然后將處理結果返回給用戶空間 。例如,用戶空間發送一個獲取系統內存使用情況的請求,內核模塊接收到后,會讀取系統內存信息,然后將內存使用情況作為響應消息發送回用戶空間 。內核模塊使用 netlink_kernel_create 函數創建 Netlink 套接字,并注冊一個回調函數來處理接收到的消息 。當有消息到達時,內核會自動調用這個回調函數,在回調函數中,通過 nlmsg_hdr 和 NLMSG_DATA 等函數和宏來解析消息內容,并根據消息類型進行相應的處理 。如果需要發送響應消息,內核模塊會使用 netlink_unicast 函數將消息發送回用戶空間 。
  2. 用戶空間程序:創建 Netlink 套接字,綁定到指定的地址,然后向內核發送消息,并接收內核返回的響應消息 。用戶空間程序可以根據實際需求發送不同類型的消息,比如配置參數、查詢系統信息等 。用戶空間程序使用socket函數創建 Netlink 套接字,使用bind函數將套接字綁定到一個特定的地址,包括進程 ID 和多播組等信息 。在發送消息時,首先構建一個 Netlink 消息頭和消息體,設置好消息類型、長度、序列號等信息,然后使用sendmsg函數將消息發送給內核 。接收消息時,使用recvmsg函數從套接字接收內核返回的消息,并根據消息頭中的信息解析出消息內容 。

下面是項目架構的示意圖:

+------------------+                +------------------+
| 用戶空間程序     |                | 內核模塊         |
|                  |                |                  |
| 1. 創建套接字     |<--------------->| 1. 創建套接字     |
| 2. 綁定地址       |                | 2. 注冊回調函數   |
| 3. 發送消息       |----消息----->  | 3. 處理消息       |
| 4. 接收消息       |<----響應----  | 4. 發送響應消息   |
|                  |                |                  |
+------------------+                +------------------+

通過這樣的架構設計,用戶空間程序和內核模塊之間可以實現高效、靈活的雙向通信,為各種系統級應用開發提供了堅實的基礎 。

6.2 內核模塊實現

下面是內核模塊的實現代碼,詳細注釋了每一步的邏輯 。

#include <linux/init.h>
#include <linux/module.h>
#include <linux/types.h>
#include <net/sock.h>
#include <linux/netlink.h>
#include <linux/kernel.h>

#define NETLINK_TEST 31 // 自定義Netlink協議類型,就像給通信通道取個獨特的名字
static struct sock *nl_sk = NULL; // 定義一個Netlink套接字指針,用于后續操作

// 接收消息的回調函數,當有消息到達時,內核會調用這個函數
static void nl_recv_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh;
    int pid;
    char *msg;

    // 從接收到的skb緩沖區中獲取Netlink消息頭
    nlh = (struct nlmsghdr*)skb->data;
    // 獲取發送者的PID,就像知道是誰寄來的信件
    pid = nlh->nlmsg_pid;
    // 獲取消息內容
    msg = nlh->nlmsg_data;

    // 打印接收到的消息和發送者的PID,方便調試和查看
    printk(KERN_INFO "Received message: %s from pid: %d\n", msg, pid);

    // 回復用戶空間
    nlh->nlmsg_pid = 0; // 目標為用戶空間,因為內核發送消息到用戶空間時,pid設為0
    nlh->nlmsg_type = NLMSG_DONE; // 設置消息類型為完成,告知用戶空間這是響應消息
    strcpy(nlh->nlmsg_data, "Hello from Kernel"); // 設置響應消息內容

    // 使用netlink_unicast函數將響應消息發送回用戶空間,就像回信給寄信人
    netlink_unicast(nl_sk, skb, pid, MSG_DONTWAIT);
}

// 內核模塊初始化函數,在模塊加載時被調用
static int __init hello_init(void) {
    struct netlink_kernel_cfg cfg = {
       .input = nl_recv_msg // 注冊接收消息的回調函數,讓內核知道消息來了該怎么處理
    };

    // 使用netlink_kernel_create函數創建Netlink套接字
    // 參數依次為:網絡命名空間(通常用&init_net)、Netlink協議類型、配置結構體指針
    nl_sk = netlink_kernel_create(&init_net, NETLINK_TEST, &cfg);
    if (!nl_sk) {
        // 如果創建失敗,打印錯誤信息并返回錯誤碼
        printk(KERN_ALERT "Error creating netlink socket.\n");
        return -ENOMEM;
    }

    // 創建成功,打印初始化成功信息
    printk(KERN_INFO "Netlink module initialized.\n");
    return 0;
}

// 內核模塊退出函數,在模塊卸載時被調用
static void __exit hello_exit(void) {
    // 使用netlink_kernel_release函數釋放Netlink套接字資源
    netlink_kernel_release(nl_sk);
    // 打印模塊退出信息
    printk(KERN_INFO "Netlink module exited.\n");
}

// 模塊初始化和退出函數聲明
module_init(hello_init);
module_exit(hello_exit);
// 聲明模塊許可證,這里是GPL
MODULE_LICENSE("GPL");

在這段代碼中,首先定義了一個自定義的 Netlink 協議類型NETLINK_TEST 。然后在hello_init函數中,創建了一個 Netlink 套接字,并注冊了消息接收回調函數nl_recv_msg 。當有消息到達時,nl_recv_msg函數會被調用,它會解析消息內容,打印相關信息,并向發送者發送一個響應消息 。最后,在hello_exit函數中,釋放 Netlink 套接字資源,確保模塊卸載時資源被正確回收 。

6.3 用戶空間程序實現

下面是用戶空間程序的代碼,展示了如何創建套接字、綁定地址、發送和接收消息 。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NETLINK_TEST 31 // 與內核模塊中的協議類型一致,確保雙方在同一條通信通道上
#define MAX_PLOAD 1024 // 定義最大消息負載長度,就像規定包裹的最大容量

int main() {
    struct sockaddr_nl src_addr, dest_addr; // 定義源地址和目標地址結構體
    struct nlmsghdr *nlh = NULL; // 定義Netlink消息頭指針
    int sock_fd, msg_size; // 定義套接字文件描述符和消息大小變量
    char *msg = "Hello from User"; // 定義要發送的消息內容

    // 創建Netlink套接字,使用AF_NETLINK協議族、SOCK_RAW套接字類型和自定義協議類型
    sock_fd = socket(PF_NETLINK, SOCK_RAW, NETLINK_TEST);
    if (sock_fd < 0) {
        // 如果創建失敗,打印錯誤信息并返回
        perror("socket");
        return -1;
    }

    // 初始化源地址結構體
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK; // 設置協議族為AF_NETLINK
    src_addr.nl_pid = getpid(); // 設置源地址的PID為當前進程ID,就像填寫自己的地址
    src_addr.nl_groups = 0; // 不加入多播組

    // 將套接字綁定到源地址
    if (bind(sock_fd, (struct sockaddr *)&src_addr, sizeof(src_addr)) < 0) {
        // 如果綁定失敗,打印錯誤信息,關閉套接字并返回
        perror("bind");
        close(sock_fd);
        return -1;
    }

    // 初始化目標地址結構體,目標是內核
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK; // 設置協議族為AF_NETLINK
    dest_addr.nl_pid = 0; // 目標地址的PID為0,表示內核
    dest_addr.nl_groups = 0; // 不加入多播組

    // 分配內存用于存儲Netlink消息頭和消息內容
    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PLOAD));
    nlh->nlmsg_len = NLMSG_LENGTH(strlen(msg) + 1); // 設置消息總長度,包括消息頭和消息內容
    nlh->nlmsg_pid = getpid(); // 設置消息發送者的PID為當前進程ID
    nlh->nlmsg_flags = 0; // 設置消息標志,這里為0表示普通消息
    strcpy(NLMSG_DATA(nlh), msg); // 將消息內容復制到消息數據部分

    // 發送消息到內核
    msg_size = sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr *)&dest_addr, sizeof(dest_addr));
    if (msg_size < 0) {
        // 如果發送失敗,打印錯誤信息,釋放內存,關閉套接字并返回
        perror("sendto");
        free(nlh);
        close(sock_fd);
        return -1;
    }

    // 接收內核的回復
    memset(nlh, 0, NLMSG_SPACE(MAX_PLOAD)); // 清空消息頭內存
    msg_size = recvfrom(sock_fd, nlh, NLMSG_SPACE(MAX_PLOAD), 0, NULL, NULL);
    if (msg_size < 0) {
        // 如果接收失敗,打印錯誤信息,釋放內存,關閉套接字并返回
        perror("recvfrom");
        free(nlh);
        close(sock_fd);
        return -1;
    }

    // 打印接收到的內核回復消息
    printf("Received from kernel: %s\n", (char *)NLMSG_DATA(nlh));

    // 釋放內存,關閉套接字
    free(nlh);
    close(sock_fd);

    return 0;
}

在這段代碼中,首先創建了一個 Netlink 套接字,并將其綁定到當前進程的地址 。然后構建一個 Netlink 消息,設置好消息頭和消息內容,將消息發送給內核 。接著等待接收內核的回復消息,當接收到消息后,打印出內核回復的內容 。最后,釋放內存資源,關閉套接字,完成整個通信過程 。

6.4 編譯和測試

①編譯內核模塊:創建一個 Makefile 文件,內容如下:

ifneq ($(KERNELRELEASE),)
obj - m := netlink_kernel.o
else
KERNELDIR?= /lib/modules/$(shell uname - r)/build
PWD := $(shell pwd)
modules:
$(MAKE) - C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm - rf *.o *~ core.depend.*.cmd *.ko *.mod.c.tmp_versions.cache.mk modules.order Module.symvers

在終端中執行make命令,即可編譯內核模塊,生成netlink_kernel.ko文件 。

②編譯用戶程序:在終端中執行以下命令編譯用戶程序:

gcc - o netlink_user netlink_user.c

這里netlink_user.c是用戶空間程序的源文件名,編譯后生成可執行文件netlink_user 。

③加載模塊并測試

首先,使用以下命令加載內核模塊:

sudo insmod netlink_kernel.ko

然后,運行用戶程序:

./netlink_user

④預期輸出結果:運行用戶程序后,應該可以看到終端輸出Received from kernel: Hello from Kernel,表示用戶空間程序成功接收到了內核模塊返回的消息 。同時,在內核日志中(可以通過dmesg命令查看),可以看到Received message: Hello from User from pid: xxxx,其中xxxx是用戶程序的進程 ID,表示內核模塊成功接收到了用戶空間發送的消息 。

責任編輯:武曉燕 來源: 深度Linux
相關推薦

2023-10-26 11:39:54

Linux系統CPU

2017-08-16 16:20:01

Linux內核態搶占用戶態搶占

2025-09-26 02:22:00

2025-04-17 01:44:00

2009-12-22 09:11:31

WCF雙向通信

2023-10-17 17:13:14

內存程序源碼

2010-02-23 17:55:24

WCF雙向通信

2020-07-28 08:54:39

內核通信Netlink

2009-12-08 11:17:41

WCF雙向通信

2021-12-20 09:53:51

用戶態內核態應用程序

2022-03-25 12:31:49

Linux根文件內核

2021-08-31 07:54:24

TCPIP協議

2021-01-08 05:59:39

Linux應用程序Linux系統

2020-11-10 10:00:10

HarmonyOS

2021-09-08 10:21:33

內核網絡包Tcpdump

2021-09-17 11:59:21

tcpdump網絡包Linux

2009-10-29 09:41:01

Linux內核DeviceMappe

2009-12-07 09:31:23

Linux系統調用表地址

2014-07-17 09:55:23

Linux程序計時

2023-01-06 08:04:10

GPU容器虛擬化
點贊
收藏

51CTO技術棧公眾號

国产免费一级视频| 欧美 日本 国产| 日韩少妇视频| 北岛玲一区二区三区四区| 久久久久久久激情视频| 欧美老熟妇乱大交xxxxx| 国产人妖一区| 亚洲一卡二卡三卡四卡五卡| 欧美一级二级三级九九九| 一本到在线视频| 欧美黄污视频| 亚洲系列中文字幕| 中文字幕在线观看91| 欧美magnet| 亚洲欧美日韩电影| 久久久一本精品99久久精品66| 一区二区乱子伦在线播放| 中文字幕一区二区三区欧美日韩| 精品视频中文字幕| 香蕉视频色在线观看| 伊人久久视频| 一二三区精品视频| 亚洲精品国产精品国自产观看| 亚洲第一色视频| 蜜桃91丨九色丨蝌蚪91桃色| 国内偷自视频区视频综合| 精品熟妇无码av免费久久| 丁香婷婷成人| 日韩一级成人av| 91欧美视频在线| 亚洲天堂资源| 亚洲综合av网| 免费成人进口网站| aⅴ在线视频男人的天堂 | 中文字幕精品一区二区三区在线| 538在线视频| 一区二区三区.www| 中文字幕av久久| 成人77777| 久久精品亚洲一区二区三区浴池 | 男人舔女人下面高潮视频| 牛牛电影国产一区二区| 亚洲欧美在线高清| 亚洲免费不卡| bbbbbbbbbbb在线视频| 久久综合九色欧美综合狠狠| 国产尤物99| 日本高清视频免费看| 国产成人午夜片在线观看高清观看| 国产精品情侣自拍| 丰满人妻一区二区三区四区| 日韩精品一级中文字幕精品视频免费观看| 久久免费少妇高潮久久精品99| 欧美片一区二区| 欧美 日韩 国产一区二区在线视频 | 色播五月综合网| 欧美与亚洲与日本直播| 日韩欧美a级成人黄色| 亚洲色欲综合一区二区三区| 天堂资源在线| 色天使色偷偷av一区二区| 老熟妇仑乱视频一区二区| 成人做爰视频www| 色av一区二区| 色悠悠久久综合网| 日韩福利影视| 欧美一区二区三区在线观看| 免费观看黄网站| 超碰在线亚洲| 亚洲美女久久久| 丁香激情五月少妇| 久久国产电影| 欧美日韩国产999| 日本一区二区三区免费视频| 亚洲视频二区| 国产精品高清在线观看| 91theporn国产在线观看| 国产一区久久久| 国产精品日韩欧美一区二区| 亚洲三区在线观看无套内射| 久久精品免费在线观看| 在线综合视频网站| 国产99在线观看| 91福利精品视频| 亚洲精品国产久| 精品五月天堂| 中文字幕亚洲一区二区三区五十路 | 五月婷婷丁香色| a看欧美黄色女同性恋| 精品在线欧美视频| www.5588.com毛片| 一区二区三区国产在线| 国产精品一区二区久久| 二区三区在线视频| 欧美激情综合五月色丁香小说| 欧美 另类 交| 深夜成人在线| 91精品欧美久久久久久动漫 | 免费黄色片视频| 精品一区二区三区免费| 国产综合动作在线观看| 免费在线观看黄色| 精品成人在线视频| 99999精品| 国产成人三级| 欧美激情国产精品| 做爰无遮挡三级| 成人做爰69片免费看网站| 日本高清不卡一区二区三| 菠萝菠萝蜜在线视频免费观看| 欧美日韩国产精品专区 | 韩国19禁主播vip福利视频| 欧美一级视频免费观看| 久久狠狠亚洲综合| 另类小说综合网| 手机在线免费观看av| 欧美中文字幕亚洲一区二区va在线 | 亚洲国产精品女人久久久| 妖精视频在线观看免费| 国产一区二区三区的电影 | 日本女优一区| 98精品在线视频| 国产男男gay体育生白袜| 久久先锋影音av鲁色资源 | 久久精选视频| 国产精品v欧美精品v日韩精品| аⅴ资源新版在线天堂| 欧美视频在线视频| 国产精品一区二区人妻喷水| 911久久香蕉国产线看观看| 国产91对白在线播放| 亚洲国产中文字幕在线| 成人免费在线播放视频| 亚洲第一中文av| 欧美男gay| 97精品视频在线观看| 成人久久久精品国产乱码一区二区| 国产精品久久网站| 在线观看国产中文字幕| 精品免费在线| 国产精品久久久久久久久久久新郎 | 国产一区二区三区国产| 亚洲国产精品久久久久婷婷老年| 精品国产第一福利网站| 日韩毛片在线看| xxxx.国产| 久久亚洲影视婷婷| 成人免费观看视频在线观看| 亚洲a级精品| 欧美在线免费视频| 久草在线青青草| 色激情天天射综合网| 少妇按摩一区二区三区| 欧美一级播放| 日日噜噜噜噜夜夜爽亚洲精品| 欧美大胆性生话| 国产性色av一区二区| 真实新婚偷拍xxxxx| 国产精品福利在线播放| 亚洲午夜精品一区| 欧美黄污视频| 精品欧美国产| 成人h在线观看| 久久久99免费视频| 亚洲精品.www| 欧美日韩午夜视频在线观看| 国产三级视频网站| 日本视频一区二区三区| 日本免费在线视频观看| 亚洲精品福利| 69精品小视频| av中文字幕一区二区三区| 欧美精品aⅴ在线视频| 亚洲精品卡一卡二| 成人丝袜视频网| 日韩精品免费播放| 雨宫琴音一区二区三区| 国产伦精品一区二区三区照片| 亚洲精品mv| 精品国产网站地址| 天天综合在线视频| 欧美视频第二页| 欧美久久久久久久久久久久| 91在线精品秘密一区二区| 人人干人人干人人| 今天的高清视频免费播放成人| 麻豆视频成人| 成人免费观看49www在线观看| 欧美黑人又粗大| 黄色片在线看| 日韩一级片网址| 国产视频1区2区| 亚洲精品高清视频在线观看| 人妻丰满熟妇av无码久久洗澡 | 日韩欧美一区二区三区| 成人一级片免费看| jlzzjlzz亚洲日本少妇| 亚洲少妇久久久| 国产欧美欧美| 99热都是精品| 成人同人动漫免费观看| 国产伦精品一区二区三区视频免费| 久久久人成影片一区二区三区在哪下载| 久久久www成人免费精品张筱雨| 刘亦菲久久免费一区二区| 欧美在线不卡一区| 日韩免费在线视频观看| 中文字幕一区二区三区四区| 欧美 日本 国产| 国产ts人妖一区二区| 日本激情视频在线播放| 一本色道88久久加勒比精品| 熟女视频一区二区三区| 国产剧情一区| 激情小说综合网| 欧美日韩国产一区二区在线观看| 国产成人久久久| 2001个疯子在线观看| 欧美成年人视频网站| 国产视频在线看| 亚洲精品久久在线| www.亚洲天堂.com| 欧美乱妇15p| 中文字幕av片| 91精品办公室少妇高潮对白| 日韩字幕在线观看| 一区二区成人在线观看| 极品久久久久久| 中文字幕一区不卡| www久久久久久久| 国产视频一区二区三区在线观看| 污污免费在线观看| 成人性生交大片免费看中文 | 99国产精品99久久久久久| 女人扒开双腿让男人捅| 激情综合色综合久久综合| 欧美一级特黄a| 日本免费在线视频不卡一不卡二| 欧美成人xxxxx| 午夜影院日韩| 国产极品尤物在线| 一本久久知道综合久久| 免费av手机在线观看| 国产综合自拍| cao在线观看| 尹人成人综合网| 你真棒插曲来救救我在线观看| 欧美三级午夜理伦三级中文幕| 正在播放一区| 综合一区在线| 喜爱夜蒲2在线| 在线高清一区| 国产精品无码av在线播放| 999亚洲国产精| 日韩av黄色网址| 丝袜a∨在线一区二区三区不卡| 好男人www社区| 美腿丝袜亚洲三区| gai在线观看免费高清| 黑人巨大精品欧美黑白配亚洲| 激情在线观看视频| 亚洲一级大片| 亚洲视频小说图片| 精品无码人妻一区| 国产亚洲精品福利| 国产又粗又长又硬| 亚洲欧美国产毛片在线| 九九视频在线观看| 五月综合激情婷婷六月色窝| 久久久久久久久久久久久av| 色一情一伦一子一伦一区| 欧美在线视频精品| 日韩欧美一二三| 无码精品黑人一区二区三区 | 国产精品久久久久久久龚玥菲| 亚洲图片制服诱惑| 黄网站视频在线观看| 欧美精品第一页在线播放| 成人免费无遮挡| 国产日韩欧美自拍| 国产劲爆久久| 日韩hmxxxx| 综合天堂av久久久久久久| 久久久999视频| 精品一区二区三区的国产在线播放| 午夜影院免费观看视频| 久久综合色综合88| 免费在线观看a级片| 午夜电影网亚洲视频| 在线播放国产一区| 亚洲国产精品成人av| av大片在线观看| 久久久久久97| 欧美成a人片免费观看久久五月天| 51午夜精品| 精品国产乱码久久久久久蜜坠欲下| 午夜久久久久久久久久久| 亚洲欧美日本视频在线观看| 亚洲精品性视频| 99久久精品99国产精品| 三级全黄做爰视频| 日韩欧美成人精品| 高h调教冰块play男男双性文| 中文字幕久久久av一区| hd国产人妖ts另类视频| 国产日韩在线视频| 亚洲理论电影| 91成人综合网| 麻豆91精品91久久久的内涵| 午夜视频在线观看国产| 亚洲丝袜自拍清纯另类| 国产精品露脸视频| 日韩精品亚洲元码| 欧美人与动牲性行为| 国产精品一区二区三区在线播放| 岛国精品一区| 男人天堂网站在线| 久久www免费人成看片高清| 一级黄色片大全| 亚洲电影激情视频网站| 国产黄a三级三级三级| 色悠悠久久88| 中文字幕系列一区| 欧美精品久久| 亚洲伊人观看| 私密视频在线观看| 亚洲成人一区二区在线观看| 国产偷拍一区二区| 精品精品国产国产自在线| jizz免费一区二区三区| 欧美深深色噜噜狠狠yyy| 日韩午夜av在线| 老司机午夜免费福利| 亚洲狠狠丁香婷婷综合久久久| 国产一区二区麻豆| 色妞欧美日韩在线| 精品176极品一区| 色婷婷精品国产一区二区三区| 久久99伊人| 欧美成人午夜精品免费| 欧美性猛交xxxx黑人| 亚洲欧美色视频| 欧美在线视频播放| 亚洲日产av中文字幕| 欧美a v在线播放| 99免费精品在线观看| 91九色丨porny丨肉丝| 亚洲国产精品yw在线观看| 操人在线观看| 久久亚洲高清| 先锋亚洲精品| 国产一级久久久久毛片精品| 在线精品视频免费观看| 二人午夜免费观看在线视频| 国产精品久久久久aaaa九色| 成人精品视频| 男人午夜视频在线观看| 亚洲靠逼com| 成人午夜免费福利| 97国产精品免费视频| 精品一区亚洲| 91插插插插插插插插| 亚洲欧美激情视频在线观看一区二区三区 | 国产精品播放| 一本色道久久综合一区| 色一情一交一乱一区二区三区| 在线观看网站黄不卡| 婷婷视频在线| 91国产丝袜在线放| 99精品国产在热久久婷婷| 一区二区黄色片| 欧美视频完全免费看| 超碰在线观看免费| 国产伦精品一区二区三区在线 | 国内自拍偷拍视频| 精品福利在线视频| www 日韩| 91高跟黑色丝袜呻吟在线观看| 野花国产精品入口| 中字幕一区二区三区乱码| 欧美一区二区在线视频| h片在线观看视频免费| 日韩精品不卡| 国产福利一区二区三区在线视频| 日本少妇裸体做爰| 一区二区三区在线播放欧美| vam成人资源在线观看| 国产96在线 | 亚洲| 国产精品三级在线观看| 亚洲av无码一区二区三区dv| 欧美在线国产精品| 在线看片不卡| 精品无人区无码乱码毛片国产| 91精品欧美久久久久久动漫| 在线亚洲人成| 欧洲精品视频在线| 国产视频一区二区在线观看| 亚洲黄色在线播放| 国产精品久久一区主播| 亚洲精品1区2区|