精品欧美一区二区三区在线观看 _久久久久国色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技術棧公眾號

www.亚洲.com| 久久影视中文字幕| 老司机精品视频在线播放| 欧美日韩精品在线| 亚洲精品白虎| 懂色av一区二区三区四区| 午夜亚洲视频| 久久影视免费观看 | 蜜桃麻豆影像在线观看| 欧美国产综合一区二区| 国产精品国产精品| 瑟瑟视频在线免费观看| 亚洲福利专区| 久久久精品一区| 色噜噜日韩精品欧美一区二区| 综合久久伊人| 欧美性色综合网| 久久久久久人妻一区二区三区| 欧美黑人激情| 久久精品综合网| 成人免费在线看片| 中文字幕乱码在线观看| 国产欧美在线| 久久免费视频网| 内射一区二区三区| 精品国产一区一区二区三亚瑟| 精品久久久久久久久久久久久久久 | 精品国产乱码久久久久久久软件 | 亚洲十八**毛片| 一区二区在线免费观看| 天堂资源在线亚洲资源| 日韩一二三四| 91在线视频在线| 国产精品亚洲不卡a| 国产精品乱码一区二区| 日本色综合中文字幕| 欧美一级大胆视频| 日韩 欧美 综合| 亚洲婷婷免费| 欧美日韩国产第一页| 日韩一卡二卡在线观看| 成人网18免费网站| 亚洲最新视频在线| 女人又爽又黄免费女仆| 奇米影视777在线欧美电影观看| 精品少妇一区二区三区在线视频| 亚洲第一区第二区第三区| 国产成人午夜性a一级毛片| 色88888久久久久久影院野外| 青青青在线视频播放| 动漫一区二区| 午夜天堂影视香蕉久久| 99在线观看视频免费| 在线黄色网页| 一区二区三区成人| 免费网站永久免费观看| 午夜av在线播放| 亚洲成人激情综合网| 99久久免费观看| 草美女在线观看| 天天色综合成人网| 99热在线这里只有精品| 日本综合字幕| 欧美日韩在线直播| 国产乱码一区二区三区四区| 91精品福利观看| 欧美一区日本一区韩国一区| 蜜桃视频无码区在线观看| 日本在线一区二区三区| 精品国产一区二区亚洲人成毛片| www.555国产精品免费| 久久99国产精品久久99大师| 国产视频在线一区二区| 精品亚洲aⅴ无码一区二区三区| 久久人体视频| 欧美精品在线视频观看| 国产精品二区一区二区aⅴ| 99精品视频免费| 国产精品777| 国产精品毛片一区视频播| 国产成人鲁色资源国产91色综| 国产精品亚洲一区| 国产福利第一视频在线播放| 国产精品盗摄一区二区三区| 奇米777四色影视在线看| hd国产人妖ts另类视频| 色丁香久综合在线久综合在线观看| 九九热免费精品视频| 国产一区 二区| 亚洲国产欧美久久| 国精产品久拍自产在线网站| 国产一区欧美| 国产精品吴梦梦| 午夜精品久久久久久久第一页按摩 | 在线亚洲精品福利网址导航| xxx国产在线观看| 澳门久久精品| 在线观看国产精品日韩av| 欧美日韩在线观看成人| 亚洲自啪免费| 97人人模人人爽视频一区二区| 五月激情丁香婷婷| 中文字幕在线不卡一区| 日本福利视频一区| gogo大尺度成人免费视频| 亚洲第一av网| 免费成人深夜夜行网站| 国产精品嫩草99av在线| 91久久精品一区| 国产尤物视频在线| 亚洲va欧美va天堂v国产综合| 青青草精品视频在线观看| 136福利精品导航| 色婷婷综合久久久久| 欧美成人精品欧美一级乱黄| 韩国女主播成人在线| 欧美精品与人动性物交免费看| 日本三级韩国三级欧美三级| 欧美日韩一区二区在线视频| 女同毛片一区二区三区| 综合激情在线| 国产一区香蕉久久| yw视频在线观看| 懂色av一区二区三区| 污免费在线观看| 日韩精品免费一区二区三区| 欧美中文在线观看国产| 欧美 日韩 国产 精品| 亚洲视频在线观看三级| 欧美成人三级在线播放| 免费精品国产的网站免费观看| 久久免费在线观看| www.国产黄色| 亚洲免费观看高清完整版在线观看熊 | 日韩激情视频| 欧美xx视频| 亚洲精品视频免费在线观看| 日本少妇全体裸体洗澡| 国产高清不卡一区二区| a级网站在线观看| 伊人久久大香| 日韩亚洲精品电影| 亚洲图片小说视频| 亚洲欧洲日韩女同| 天天综合网久久| 水蜜桃久久夜色精品一区| 国产成人拍精品视频午夜网站| 理论视频在线| 在线观看亚洲a| 能直接看的av| 美女在线视频一区| 亚洲午夜精品一区二区三区| 日本中文字幕视频一区| 久久亚洲国产精品成人av秋霞| 亚洲天堂一二三| 成人免费在线观看入口| 制服丝袜中文字幕第一页 | 911亚洲精品| 欧美疯狂性受xxxxx另类| www.色播.com| 性久久久久久久久| 国内精品久久99人妻无码| 免费在线亚洲欧美| 欧美日韩在线一区二区三区| 天天综合网天天| 色午夜这里只有精品| 国产一区二区三区视频免费观看| 亚洲天堂精品在线观看| 国产91在线免费观看| 在线成人黄色| 欧美午夜精品久久久久免费视| 欧美free嫩15| 久久国产加勒比精品无码| www.蜜臀av.com| 亚洲18女电影在线观看| xxxx日本免费| 久久精品国产免费| 妞干网在线播放| 红桃成人av在线播放| 国产有码在线一区二区视频| 欧美videosex性极品hd| 亚洲成人xxx| 国产精品第6页| 夜夜嗨av一区二区三区中文字幕 | 精品人妻一区二区三区换脸明星 | 性做久久久久久久| 一本色道久久综合精品竹菊| 少妇高潮在线观看| 不卡影院免费观看| 无人在线观看的免费高清视频 | 波多野结衣乳巨码无在线| 国产欧美日韩一区二区三区四区| 91青草视频久久| 91精品论坛| 欧美巨乳美女视频| 暖暖视频在线免费观看| 日韩一区二区免费在线电影| 国产原创视频在线| 亚洲视频在线观看三级| 国产精品1000部啪视频| 国产一区二区三区免费看| 男女视频网站在线观看| 三级电影一区| 久久影视中文粉嫩av| 日韩三级不卡| 国产精品一区av| 性欧美18~19sex高清播放| 精品激情国产视频| 欧美日韩在线中文字幕| 精品免费日韩av| 91久久精品国产91性色69| 亚洲国产aⅴ成人精品无吗| 婷婷丁香综合网| 国产亚洲欧美激情| a级一a一级在线观看| 国产一区中文字幕| 91热这里只有精品| 亚洲一区网站| 免费一级特黄毛片| 中文字幕一区二区av| 亚洲欧美99| 欧美伦理影院| 蜜桃av色综合| 日韩三区视频| 精品日产一区2区三区黄免费| 国产亚洲久久| 亚洲va码欧洲m码| 欧美爱爱视频| 国产成人精品久久二区二区| 九九精品调教| 欧美日韩xxxxx| 最新黄网在线观看| 不卡av电影院| 国产在线二区| 久久国产精品久久久久| 精品国产99久久久久久| 日韩视频永久免费观看| aⅴ在线视频男人的天堂 | 男人的天堂官网| 久久久亚洲精品石原莉奈 | 国产精品灌醉下药二区| 中文字幕人妻一区二区三区在线视频| 久久综合狠狠综合| 精品久久久久久中文字幕人妻最新| 成人白浆超碰人人人人| 天天躁日日躁狠狠躁av麻豆男男| 国产91色综合久久免费分享| 四虎成人在线播放| 国产一区不卡视频| 男插女视频网站| 国产成人亚洲综合a∨婷婷| 中文字幕欧美视频| 丁香激情综合国产| 国产精品九九视频| 26uuu成人网一区二区三区| 青青草成人免费视频| 久久亚洲二区三区| 欧美 日韩 国产 成人 在线观看 | 成人高清dvd| 激情婷婷亚洲| 黄色一级片播放| 天堂在线亚洲视频| 69久久久久久| 国产精品1区二区.| 国产+高潮+白浆+无码| 久久综合999| 老司机福利在线观看| 亚洲人成7777| a v视频在线观看| 欧美性一级生活| 国产精品久久久久久69| 日韩免费高清av| 午夜视频福利在线观看| 亚洲丝袜在线视频| 日本a在线播放| 欧美剧在线观看| 中文字幕在线高清| 国产女精品视频网站免费| 亚洲日本va午夜在线电影| 久久亚洲综合网| 亚洲精品国产首次亮相| 无罩大乳的熟妇正在播放| 蜜臂av日日欢夜夜爽一区| 伊人影院在线观看视频| 2024国产精品| 欧美爱爱免费视频| 欧美午夜激情视频| 国产精品视频在线观看免费| 亚洲成人精品视频| www免费网站在线观看| 欧美国产精品va在线观看| 欧美亚洲韩国| 99re国产视频| 日韩免费av| 久久久999视频| 狠狠色狠狠色合久久伊人| 亚洲av无码一区二区三区网址 | 天堂网av手机版| 欧美一区二区视频网站| 天天操天天干天天插| 主播福利视频一区| 亚洲第一av| 成人做爰66片免费看网站| 欧美色女视频| 欧美成人xxxxx| 国产精品 日产精品 欧美精品| 成人黄色免费网址| 午夜久久电影网| 99热这里只有精品在线观看| 亚洲欧美日韩国产成人| 女同视频在线观看| 成人情趣片在线观看免费| 综合亚洲色图| 亚洲 自拍 另类小说综合图区| 久久er99热精品一区二区| 国产精品揄拍100视频| 一区二区三区精品在线| 国产精品无码专区av免费播放| 亚洲乱码av中文一区二区| 高清电影在线免费观看| 91欧美精品午夜性色福利在线| 不卡在线一区| 日韩有码免费视频| 成人精品视频一区二区三区| 男人的天堂久久久| 欧美人xxxx| 9色在线观看| 国产精品wwww| 精品福利久久久| 美女福利视频在线| 91在线视频观看| 日本道在线观看| 日韩电视剧免费观看网站| 国内高清免费在线视频| 亚洲一区中文字幕在线观看| 国产精品国内免费一区二区三区| wwwwxxxx日韩| 欧美经典一区二区| 伊人免费在线观看| 中文字幕免费国产精品| 日韩久久一区二区三区| 日本高清久久一区二区三区| 久久久久欧美精品| 中文字幕第20页| 欧美亚洲高清一区二区三区不卡| 精品美女视频在线观看免费软件 | 国产伦精品一区二区三区四区视频_| 国产精品综合在线视频| 国产67194| 精品久久久久久久久久久院品网| 不卡一本毛片| 久久综合久久久| 久久久久国产精品午夜一区| 手机免费看av| 欧美吞精做爰啪啪高潮| 亚洲欧美视频一区二区| 91九色单男在线观看| 欧美福利专区| 人妻av一区二区| 色香色香欲天天天影视综合网| 精品三级久久久久久久电影聊斋| 国产精品爽爽爽| 91精品啪在线观看国产18| 一个人看的视频www| 亚洲成人av电影| 韩国精品视频| 91在线免费看网站| 亚洲激情二区| 亚洲综合网在线观看| 欧美日韩一区二区在线观看| gogo在线高清视频| 久久精品日韩| 六月丁香婷婷色狠狠久久| 九九视频在线观看| 国产丝袜精品视频| 日韩城人网站| av日韩一区二区三区| 久久久精品国产99久久精品芒果| 一区二区乱子伦在线播放| 欧美裸体xxxx极品少妇| 蜜桃精品噜噜噜成人av| 中文字幕免费高清在线| 亚洲国产色一区| porn视频在线观看| 成人看片在线| 日韩高清在线一区| 欧美三级小视频| 亚洲天堂男人天堂女人天堂| 成人豆花视频| 北条麻妃在线观看| 亚洲女同ⅹxx女同tv| 日色在线视频| 91丨九色丨国产| 美国一区二区三区在线播放| 国产系列精品av| y97精品国产97久久久久久| 日韩中出av| 91精品国产高清91久久久久久 | 欧美影院三区| av电影在线播放|