偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

Linux Netlink 機(jī)制原理:內(nèi)核與用戶態(tài)雙向通信

系統(tǒng) Linux
Netlink 是 Linux 操作系統(tǒng)特有的一種進(jìn)程間通信機(jī)制,主要用于內(nèi)核空間與用戶空間的進(jìn)程之間的通信。從本質(zhì)上講,它是一種雙向通信機(jī)制,允許內(nèi)核和用戶態(tài)應(yīng)用程序之間進(jìn)行高效的數(shù)據(jù)傳輸和交互。

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

正是這種需求與局限的矛盾,催生了 Netlink 機(jī)制。它作為 Linux 特有的進(jìn)程間通信(IPC)方案,最核心的價(jià)值便是打破內(nèi)核與用戶態(tài)的單向通信壁壘,實(shí)現(xiàn)高效雙向交互。無(wú)論是用戶態(tài)通過(guò)標(biāo)準(zhǔn) Socket API 向內(nèi)核發(fā)起配置請(qǐng)求,還是內(nèi)核主動(dòng)向用戶態(tài)推送異步事件,Netlink 都能以低開(kāi)銷、模塊化的方式完成消息傳遞。要理解這種雙向通信的實(shí)現(xiàn)邏輯,需從其底層模型、關(guān)鍵數(shù)據(jù)結(jié)構(gòu)與消息流轉(zhuǎn)流程入手 —— 這不僅是掌握 Netlink 機(jī)制的核心,更是理解 Linux 網(wǎng)絡(luò)配置、系統(tǒng)監(jiān)控等功能底層實(shí)現(xiàn)的關(guān)鍵。接下來(lái),我們就拆解 Netlink 如何搭建起內(nèi)核與用戶態(tài)之間的 “雙向通信橋梁”。

一、什么是 Netlink 機(jī)制

Netlink 是 Linux 操作系統(tǒng)特有的一種進(jìn)程間通信機(jī)制,主要用于內(nèi)核空間與用戶空間的進(jìn)程之間的通信。從本質(zhì)上講,它是一種雙向通信機(jī)制,允許內(nèi)核和用戶態(tài)應(yīng)用程序之間進(jìn)行高效的數(shù)據(jù)傳輸和交互。

Netlink 基于 Socket API 實(shí)現(xiàn),這使得它在使用上對(duì)于熟悉 Socket 編程的開(kāi)發(fā)者來(lái)說(shuō)較為友好。用戶態(tài)應(yīng)用可以像使用普通 Socket 一樣,通過(guò)標(biāo)準(zhǔn)的 Socket 接口函數(shù),如 socket ()、bind ()、sendmsg ()、recvmsg () 和 close () 等來(lái)與內(nèi)核進(jìn)行通信 ,而內(nèi)核態(tài)則使用專門(mén)的內(nèi)核 API 來(lái)處理 Netlink 消息。這種設(shè)計(jì)就像是在兩個(gè)不同區(qū)域(內(nèi)核空間和用戶空間)之間搭建了一座橋梁,且兩邊的 “居民” 都能通過(guò)自己熟悉的方式走上這座橋進(jìn)行交流。

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

而 Netlink 支持全雙工通信,內(nèi)核和用戶空間都能主動(dòng)發(fā)起通信,實(shí)現(xiàn)了真正意義上的雙向交互。在網(wǎng)絡(luò)配置工具中,用戶空間的程序不僅可以向內(nèi)核發(fā)送配置請(qǐng)求,內(nèi)核在配置完成后也能主動(dòng)向用戶空間返回配置結(jié)果和狀態(tài)信息 。它采用異步通信機(jī)制,發(fā)送方將消息放入接收方的 Socket 緩存隊(duì)列后即可返回,無(wú)需等待接收方處理消息,這大大提高了通信效率,尤其適用于高并發(fā)的場(chǎng)景,避免了同步通信中可能出現(xiàn)的阻塞問(wèn)題,使得系統(tǒng)的響應(yīng)更加及時(shí)和高效。

Netlink通信機(jī)制的簡(jiǎn)易流程如下圖所示:

一般來(lái)說(shuō)用戶空間和內(nèi)核空間的通信方式有三種:/proc、ioctl、Netlink。而前兩種都是單向的,而Netlink可以實(shí)現(xiàn)雙工通信。Netlink 相對(duì)于系統(tǒng)調(diào)用,ioctl 以及 /proc 文件系統(tǒng)而言具有以下優(yōu)點(diǎn):

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

Netlink協(xié)議基于BSD socket和AF_NETLINK地址簇,使用32位的端口號(hào)尋址,每個(gè)Netlink協(xié)議通常與一個(gè)或一組內(nèi)核服務(wù)/組件相關(guān)聯(lián),如NETLINK_ROUTE用于獲取和設(shè)置路由與鏈路信息、NETLINK_KOBJECT_UEVENT用于內(nèi)核向用戶空間的udev進(jìn)程發(fā)送通知等。

二、Netlink 機(jī)制的工作原理

Netlink 的架構(gòu)就像是一個(gè)精心構(gòu)建的通信網(wǎng)絡(luò),各個(gè)部分協(xié)同工作,實(shí)現(xiàn)了內(nèi)核與用戶空間之間高效的通信。我們來(lái)看下面這張:

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

  • 用戶空間應(yīng)用:這是我們?nèi)粘J褂玫母鞣N應(yīng)用程序,它們通過(guò)標(biāo)準(zhǔn)的 socket API 與 Netlink 套接字進(jìn)行交互。比如我們前面提到的網(wǎng)絡(luò)配置工具、系統(tǒng)監(jiān)控程序等,它們通過(guò) Netlink 向內(nèi)核發(fā)送請(qǐng)求,獲取系統(tǒng)信息或者執(zhí)行特定的操作。
  • Netlink 套接字:作為用戶空間與內(nèi)核空間通信的橋梁,Netlink 套接字負(fù)責(zé)在兩者之間傳遞數(shù)據(jù)。它基于 BSD socket 和 AF_NETLINK 地址簇,采用 32 位的端口號(hào)尋址 。每個(gè) Netlink 套接字都有一個(gè)對(duì)應(yīng)的協(xié)議類型,用于標(biāo)識(shí)通信的內(nèi)容和目的。
  • 內(nèi)核空間:內(nèi)核是 Linux 系統(tǒng)的核心,它包含了各種設(shè)備驅(qū)動(dòng)、網(wǎng)絡(luò)協(xié)議棧等重要組件。內(nèi)核通過(guò) Netlink 與用戶空間進(jìn)行通信,接收用戶空間的請(qǐng)求并返回相應(yīng)的結(jié)果,同時(shí)也可以主動(dòng)向用戶空間發(fā)送通知和事件信息。
  • Netlink 協(xié)議族:Netlink 支持多種協(xié)議類型,每種協(xié)議類型都與特定的內(nèi)核服務(wù)或組件相關(guān)聯(lián)。例如,NETLINK_ROUTE 用于網(wǎng)絡(luò)路由相關(guān)的操作,NETLINK_KOBJECT_UEVENT 用于內(nèi)核向用戶空間發(fā)送設(shè)備事件通知等 。不同的協(xié)議類型使得 Netlink 能夠滿足各種不同的通信需求。

2.1 通信模型

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

以網(wǎng)絡(luò)配置工具修改網(wǎng)絡(luò)接口的 IP 地址為例,用戶空間的網(wǎng)絡(luò)配置工具通過(guò) Netlink 向內(nèi)核發(fā)送配置請(qǐng)求消息。消息發(fā)送后,網(wǎng)絡(luò)配置工具不會(huì)被阻塞,它可以繼續(xù)處理用戶的其他操作,比如顯示當(dāng)前的網(wǎng)絡(luò)狀態(tài)信息等。而內(nèi)核在接收到消息后,會(huì)將其從 Socket 緩存隊(duì)列中取出并進(jìn)行處理,處理完成后再將響應(yīng)消息發(fā)送回用戶空間的網(wǎng)絡(luò)配置工具。

在這個(gè)過(guò)程中,Socket 緩存隊(duì)列起著關(guān)鍵的作用。它就像是一個(gè)臨時(shí)的 “倉(cāng)庫(kù)”,用于存儲(chǔ)等待處理的消息。當(dāng)消息到達(dá)時(shí),會(huì)按照先后順序被放入隊(duì)列中,接收方則按照先進(jìn)先出(FIFO)的原則從隊(duì)列中讀取消息進(jìn)行處理 。這種方式使得 Netlink 在處理高并發(fā)的消息時(shí)具有很高的效率,避免了同步通信機(jī)制中可能出現(xiàn)的阻塞問(wèn)題。

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

2.2 協(xié)議類型

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

NETLINK_ROUTE 是非常常用的一種協(xié)議類型,主要用于網(wǎng)絡(luò)路由和設(shè)備相關(guān)的操作。它可以傳遞網(wǎng)絡(luò)接口的狀態(tài)信息,比如網(wǎng)絡(luò)接口的開(kāi)啟、關(guān)閉,以及 IP 地址的配置、路由表的更新等消息。我們使用 iproute2 工具配置網(wǎng)絡(luò)接口的 IP 地址時(shí),iproute2 工具就是通過(guò) NETLINK_ROUTE 協(xié)議與內(nèi)核進(jìn)行通信,將用戶設(shè)置的 IP 地址等信息傳遞給內(nèi)核,內(nèi)核再根據(jù)這些信息進(jìn)行相應(yīng)的網(wǎng)絡(luò)配置。

NETLINK_KOBJECT_UEVENT 用于內(nèi)核向用戶空間發(fā)送設(shè)備相關(guān)的事件通知,尤其是在設(shè)備熱插拔的場(chǎng)景中發(fā)揮著重要作用。當(dāng)有 USB 設(shè)備插入或拔出計(jì)算機(jī)時(shí),內(nèi)核會(huì)通過(guò) NETLINK_KOBJECT_UEVENT 協(xié)議向用戶空間的 udev 進(jìn)程發(fā)送設(shè)備插拔事件的通知,udev 進(jìn)程接收到通知后,就可以對(duì)設(shè)備進(jìn)行相應(yīng)的管理和處理,比如為新插入的 USB 設(shè)備分配設(shè)備節(jié)點(diǎn),加載相應(yīng)的驅(qū)動(dòng)程序等。

NETLINK_NFLOG 協(xié)議與網(wǎng)絡(luò)包過(guò)濾和日志記錄相關(guān)。在網(wǎng)絡(luò)安全領(lǐng)域,防火墻等網(wǎng)絡(luò)安全設(shè)備需要對(duì)網(wǎng)絡(luò)數(shù)據(jù)包進(jìn)行過(guò)濾和監(jiān)控。當(dāng)數(shù)據(jù)包通過(guò)防火墻時(shí),防火墻可以使用 NETLINK_NFLOG 協(xié)議將數(shù)據(jù)包的相關(guān)信息(如源 IP 地址、目的 IP 地址、端口號(hào)等)發(fā)送給用戶空間的日志記錄程序,日志記錄程序就可以將這些信息記錄下來(lái),以便管理員進(jìn)行安全分析和審計(jì),及時(shí)發(fā)現(xiàn)潛在的網(wǎng)絡(luò)安全威脅。

除了這些預(yù)定義的協(xié)議類型,開(kāi)發(fā)者還可以根據(jù)自己的需求自定義 Netlink 協(xié)議類型。在開(kāi)發(fā)一個(gè)自定義的內(nèi)核模塊與用戶空間程序進(jìn)行通信時(shí),開(kāi)發(fā)者可以定義一個(gè)新的 Netlink 協(xié)議類型,用于在兩者之間傳遞特定的消息和數(shù)據(jù),實(shí)現(xiàn)特定的功能,這為 Linux 系統(tǒng)的擴(kuò)展和定制提供了很大的靈活性。

2.3 數(shù)據(jù)結(jié)構(gòu)

(1)sockaddr_nl

sockaddr_nl 是 Netlink 通信中用于標(biāo)識(shí)通信端點(diǎn)的地址結(jié)構(gòu),它包含了多個(gè)重要字段,每個(gè)字段都在通信過(guò)程中扮演著關(guān)鍵角色。

nl_family 字段表示地址族,對(duì)于 Netlink 通信來(lái)說(shuō),它始終被設(shè)置為 AF_NETLINK(等同于 PF_NETLINK),這個(gè)字段就像是一個(gè) “通信語(yǔ)言” 的標(biāo)識(shí),告訴系統(tǒng)這是 Netlink 通信,就如同我們?cè)趪?guó)際交流中,通過(guò)語(yǔ)言標(biāo)識(shí)來(lái)確定交流所使用的語(yǔ)言一樣,讓系統(tǒng)能夠正確地識(shí)別和處理 Netlink 相關(guān)的通信。

nl_pid 字段用于指定進(jìn)程標(biāo)識(shí)符(Port ID)。在用戶空間,通常將其設(shè)置為進(jìn)程的 PID,這樣內(nèi)核就可以根據(jù)這個(gè) PID 將消息準(zhǔn)確地發(fā)送給對(duì)應(yīng)的用戶空間進(jìn)程;在內(nèi)核空間,該字段被固定設(shè)置為 0,因?yàn)閮?nèi)核作為消息的發(fā)送源,不需要特定的 PID 標(biāo)識(shí),就像一個(gè)大型工廠的中央控制中心(內(nèi)核)向各個(gè)車間(用戶空間進(jìn)程)發(fā)送指令時(shí),控制中心不需要用自己的 “身份標(biāo)識(shí)” 來(lái)標(biāo)記指令,而各個(gè)車間則需要有明確的 “身份標(biāo)識(shí)” 以便接收指令。例如,當(dāng)用戶空間的某個(gè)網(wǎng)絡(luò)監(jiān)控程序通過(guò) Netlink 與內(nèi)核通信時(shí),該程序會(huì)將自己的 PID 設(shè)置到 nl_pid 字段,內(nèi)核在返回網(wǎng)絡(luò)狀態(tài)信息時(shí),就可以根據(jù)這個(gè) PID 將信息發(fā)送回該監(jiān)控程序。

nl_groups 字段是一個(gè)多播組掩碼,用于指定接收者想要訂閱的消息組標(biāo)識(shí)集合。每個(gè) Netlink 協(xié)議族都可以定義自己的多播組,通過(guò)設(shè)置 nl_groups,進(jìn)程可以訂閱一個(gè)或多個(gè)多播組。當(dāng)內(nèi)核有相關(guān)事件發(fā)生時(shí),會(huì)向這些多播組發(fā)送消息,訂閱了相應(yīng)多播組的套接字就會(huì)接收到這些消息。在網(wǎng)絡(luò)路由更新時(shí),內(nèi)核會(huì)將路由更新消息發(fā)送到 RTMGRP_IPV4_ROUTE 多播組,所有訂閱了該多播組的路由守護(hù)進(jìn)程(如 quagga、bird 等)都能接收到這個(gè)消息,從而及時(shí)更新自己的路由表,確保網(wǎng)絡(luò)數(shù)據(jù)包能夠正確轉(zhuǎn)發(fā)。

(2)nlmsghdr

nlmsghdr 是 Netlink 消息頭結(jié)構(gòu),它定義了消息的元數(shù)據(jù),對(duì)于消息的正確傳輸和處理至關(guān)重要。

nlmsg_len 字段表示整個(gè) Netlink 消息的長(zhǎng)度,包括消息頭和有效載荷的總長(zhǎng)度。這個(gè)字段就像是包裹的 “重量標(biāo)簽”,告訴接收方整個(gè)消息占用了多少字節(jié)的空間,以便接收方正確地分配內(nèi)存來(lái)存儲(chǔ)消息,并且根據(jù)這個(gè)長(zhǎng)度來(lái)準(zhǔn)確地解析消息內(nèi)容,避免讀取到錯(cuò)誤的數(shù)據(jù)。

nlmsg_type 字段用于指定消息的類型,它就像是包裹上的 “內(nèi)容標(biāo)簽”,告訴接收方消息的具體含義和用途。內(nèi)核定義了多種通用的消息類型,如 NLMSG_NOOP 表示空操作消息,接收方應(yīng)忽略該消息,就像收到一個(gè)空的包裹,直接忽略即可;NLMSG_ERROR 表示錯(cuò)誤指示消息,包含錯(cuò)誤代碼,當(dāng)接收方收到這個(gè)類型的消息時(shí),就知道在通信或處理過(guò)程中出現(xiàn)了錯(cuò)誤,需要根據(jù)錯(cuò)誤代碼進(jìn)行相應(yīng)的錯(cuò)誤處理;NLMSG_DONE 用于標(biāo)識(shí)多部分消息的結(jié)束,在傳輸大量數(shù)據(jù)時(shí),可能會(huì)將數(shù)據(jù)分成多個(gè)部分發(fā)送,接收方通過(guò)這個(gè)標(biāo)識(shí)來(lái)判斷是否已經(jīng)接收完所有的數(shù)據(jù)部分。

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

nlmsg_seq 字段是消息序列號(hào),用于將消息排隊(duì),有些類似于 TCP 協(xié)議中的序號(hào),但在 Netlink 中這個(gè)字段是可選的,不強(qiáng)制使用。它可以幫助接收方按照正確的順序處理消息,特別是在處理多個(gè)相關(guān)消息時(shí),能夠確保消息的處理順序與發(fā)送順序一致,避免因消息亂序?qū)е碌奶幚礤e(cuò)誤。

nlmsg_pid 字段表示發(fā)送端口 ID,對(duì)于內(nèi)核發(fā)送的消息,該值為 0,因?yàn)閮?nèi)核作為發(fā)送源,其 PID 固定為 0;對(duì)于用戶進(jìn)程發(fā)送的消息,該值就是其 socket 所綁定的 ID 號(hào),這樣接收方就可以知道消息是從哪個(gè)進(jìn)程發(fā)送過(guò)來(lái)的,以便進(jìn)行相應(yīng)的響應(yīng)和處理 。

(3)nlattr

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

nla_len 字段表示屬性的總長(zhǎng)度,包括屬性類型、長(zhǎng)度字段本身以及屬性值所占的字節(jié)數(shù),就像一個(gè)小包裹的 “小重量標(biāo)簽”,告訴接收方這個(gè)屬性占用了多少空間。

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

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

三、Netlink API 函數(shù)詳解

3.1 內(nèi)核態(tài) API

在內(nèi)核態(tài)中,Netlink 提供了一系列專門(mén)的 API 函數(shù),用于實(shí)現(xiàn)與用戶態(tài)之間的通信功能,這些函數(shù)是內(nèi)核與用戶態(tài)交互的關(guān)鍵紐帶 。

(1)創(chuàng)建和銷毀 Socket:netlink_kernel_create函數(shù)用于創(chuàng)建一個(gè) Netlink 套接字,它是內(nèi)核與用戶態(tài)通信的基礎(chǔ)。其函數(shù)原型如下:

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

在這個(gè)函數(shù)中,net參數(shù)通常使用&init_net,它代表了網(wǎng)絡(luò)命名空間,就像是一個(gè)大型社區(qū),所有的網(wǎng)絡(luò)相關(guān)活動(dòng)都在這個(gè)社區(qū)中進(jìn)行 。unit參數(shù)指定了 Netlink 協(xié)議類型,比如NETLINK_ROUTE用于路由相關(guān)的通信,NETLINK_FIREWALL用于防火墻管理通信等,不同的協(xié)議類型就像是不同的社區(qū)服務(wù)部門(mén),負(fù)責(zé)處理不同類型的事務(wù) 。cfg是一個(gè)指向netlink_kernel_cfg結(jié)構(gòu)體的指針,該結(jié)構(gòu)體包含了一些配置參數(shù),其中最重要的是input回調(diào)函數(shù),當(dāng)有消息到達(dá)這個(gè) Netlink 套接字時(shí),就會(huì)調(diào)用這個(gè)回調(diào)函數(shù)來(lái)處理消息,就像社區(qū)里的快遞代收點(diǎn),當(dāng)有快遞到達(dá)時(shí),會(huì)通知收件人來(lái)取件 。這個(gè)函數(shù)成功時(shí)會(huì)返回一個(gè)指向struct sock的指針,代表創(chuàng)建好的 Netlink 套接字;如果創(chuàng)建失敗,則返回NULL 。

當(dāng)不再需要這個(gè) Netlink 套接字時(shí),就需要使用netlink_kernel_release函數(shù)來(lái)釋放它,釋放資源,避免內(nèi)存泄漏 。其函數(shù)原型為:

void netlink_kernel_release(struct sock *sk);

這里的sk參數(shù)就是之前netlink_kernel_create函數(shù)返回的套接字指針,通過(guò)這個(gè)指針,系統(tǒng)可以準(zhǔn)確地找到要釋放的套接字資源 。

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

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

其中,ssk是指向struct sock的指針,表示 Netlink 套接字,就像是一個(gè)通信通道;skb是指向要發(fā)送的 Netlink 消息的struct sk_buff緩沖區(qū),sk_buff結(jié)構(gòu)體就像是一個(gè)包裹,里面裝著要發(fā)送的消息內(nèi)容;pid是接收者的進(jìn)程 ID,通過(guò)這個(gè) ID,消息能夠準(zhǔn)確地找到目標(biāo)接收者,就像快遞需要知道收件人的地址才能送達(dá);nonblock是非阻塞標(biāo)志,設(shè)置為非零值以進(jìn)行非阻塞操作,如果設(shè)置為非零,當(dāng)發(fā)送消息時(shí),如果接收方的緩沖區(qū)已滿或者其他原因?qū)е孪o(wú)法立即發(fā)送,函數(shù)會(huì)立即返回,而不會(huì)阻塞等待,就像快遞員如果發(fā)現(xiàn)收件人的收件箱已滿,不會(huì)一直等待收件箱有空位,而是先離開(kāi)并返回一個(gè)無(wú)法投遞的通知;如果設(shè)置為零,函數(shù)將一直阻塞,直到消息完全發(fā)送成功或發(fā)生錯(cuò)誤 。該函數(shù)返回一個(gè)整數(shù)值,表示發(fā)送是否成功,如果發(fā)送成功,則返回 0;如果發(fā)送失敗,則返回一個(gè)負(fù)數(shù)錯(cuò)誤碼 。

netlink_broadcast函數(shù)則用于將消息多播給一個(gè) Netlink 組 。函數(shù)原型為:

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

ssk和skb參數(shù)與netlink_unicast中的含義相同 。portid是通信的端口號(hào),對(duì)應(yīng)用態(tài)的端口號(hào);group是所有目標(biāo)多播組對(duì)應(yīng)掩碼的 “OR” 操作的合值,通過(guò)這個(gè)參數(shù),可以指定要發(fā)送到的多個(gè)多播組,就像一個(gè)廣播通知,可以同時(shí)發(fā)送給多個(gè)不同的區(qū)域;allocation指定內(nèi)核內(nèi)存分配方式,通常GFP_ATOMIC用于中斷上下文,在這種情況下,內(nèi)存分配必須立即完成,不能等待,就像在緊急情況下,必須馬上分配資源;而GFP_KERNEL用于其他場(chǎng)合,這種情況下,內(nèi)存分配可以在適當(dāng)?shù)臅r(shí)候進(jìn)行等待 。該函數(shù)返回值的含義與netlink_unicast類似,0 表示發(fā)送成功,負(fù)數(shù)表示發(fā)送失敗 。

(3)消息處理:當(dāng)有消息到達(dá)時(shí),內(nèi)核會(huì)調(diào)用之前在netlink_kernel_cfg結(jié)構(gòu)體中注冊(cè)的input回調(diào)函數(shù)來(lái)處理消息 。這個(gè)回調(diào)函數(shù)的原型通常如下:

void input(struct sk_buff *skb);

skb參數(shù)是接收到的消息緩沖區(qū),在這個(gè)回調(diào)函數(shù)中,可以從skb中提取消息頭和有效載荷,然后根據(jù)消息類型和內(nèi)容進(jìn)行相應(yīng)的處理 。比如,在處理網(wǎng)絡(luò)設(shè)備狀態(tài)變化的消息時(shí),從消息中解析出設(shè)備的名稱、狀態(tài)等信息,然后更新內(nèi)核中的設(shè)備狀態(tài)表 。在處理消息時(shí),還可以使用一些輔助函數(shù),nlmsg_hdr函數(shù)用于從sk_buff中獲取nlmsghdr結(jié)構(gòu)體指針,該結(jié)構(gòu)體包含了消息的元數(shù)據(jù),如消息類型、長(zhǎng)度、序列號(hào)等,通過(guò)這些信息,可以更好地理解和處理消息 。其函數(shù)原型為:

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

還有NLMSG_DATA宏,用于獲取消息的有效載荷數(shù)據(jù),通過(guò)它,可以直接訪問(wèn)到消息中真正要傳遞的數(shù)據(jù)內(nèi)容 。例如:

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

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

3.2 用戶態(tài) API

在用戶態(tài),使用 Netlink 通信主要依賴于標(biāo)準(zhǔn)的 Socket API 函數(shù),這些函數(shù)對(duì)于熟悉 Socket 編程的開(kāi)發(fā)者來(lái)說(shuō)并不陌生,它們?yōu)橛脩魬B(tài)與內(nèi)核態(tài)之間的通信提供了便捷的方式 。

(1)創(chuàng)建套接字:使用socket函數(shù)來(lái)創(chuàng)建一個(gè) Netlink 套接字 。函數(shù)原型如下:

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

對(duì)于 Netlink 通信,domain參數(shù)應(yīng)設(shè)置為AF_NETLINK,表示使用 Netlink 協(xié)議族,就像選擇了一條特定的通信道路;type參數(shù)通常設(shè)置為SOCK_RAW,表示使用原始套接字,這種套接字可以直接處理底層的網(wǎng)絡(luò)數(shù)據(jù),更適合 Netlink 這種需要精確控制消息的通信方式;protocol參數(shù)指定具體的 Netlink 協(xié)議類型,比如NETLINK_ROUTE、NETLINK_GENERIC等,不同的協(xié)議類型對(duì)應(yīng)不同的通信用途 。如果函數(shù)成功執(zhí)行,將返回一個(gè)文件描述符,這個(gè)描述符就像是一個(gè)通信通道的標(biāo)識(shí),后續(xù)的操作都將通過(guò)這個(gè)標(biāo)識(shí)來(lái)進(jìn)行;如果創(chuàng)建失敗,將返回 -1,并設(shè)置errno變量來(lái)指示錯(cuò)誤原因 。例如:

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

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

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

addr參數(shù)是一個(gè)指向struct sockaddr_nl結(jié)構(gòu)體的指針,該結(jié)構(gòu)體定義了 Netlink 地址,包括協(xié)議族(nl_family始終為AF_NETLINK)、進(jìn)程標(biāo)識(shí)符(nl_pid)和多播組掩碼(nl_groups) 。在綁定地址時(shí),需要根據(jù)實(shí)際需求設(shè)置這些字段 。如果是與內(nèi)核通信,通常將nl_pid設(shè)置為 0,表示目標(biāo)是內(nèi)核;如果是與其他用戶態(tài)進(jìn)程通信,則需要設(shè)置為對(duì)方的進(jìn)程 ID 。addrlen參數(shù)是地址結(jié)構(gòu)體的長(zhǎng)度 。綁定成功時(shí)返回 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(); // 設(shè)置為自身進(jìn)程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)發(fā)送消息:發(fā)送 Netlink 消息使用sendmsg函數(shù),它可以發(fā)送一個(gè)完整的消息,包括消息頭和有效載荷 。函數(shù)原型為:

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

msg參數(shù)是一個(gè)指向struct msghdr結(jié)構(gòu)體的指針,該結(jié)構(gòu)體包含了要發(fā)送的消息的詳細(xì)信息,包括目標(biāo)地址(msg_name)、地址長(zhǎng)度(msg_namelen)、消息數(shù)據(jù)(msg_iov)等 。在構(gòu)建struct msghdr結(jié)構(gòu)體時(shí),需要先填充好消息頭和有效載荷,然后將它們與msg_iov關(guān)聯(lián)起來(lái) 。flags參數(shù)用于指定一些發(fā)送標(biāo)志,通常設(shè)置為 0 。發(fā)送成功時(shí)返回發(fā)送的字節(jié)數(shù),失敗返回 -1 。下面是一個(gè)簡(jiǎn)單的發(fā)送消息的示例:

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!"); // 設(shè)置消息內(nèi)容

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; // 發(fā)送給內(nèi)核
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函數(shù),它從指定的套接字接收消息 。函數(shù)原型為:

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

msg參數(shù)與sendmsg中的類似,用于存儲(chǔ)接收到的消息信息 。在調(diào)用recvmsg之前,需要先分配足夠的緩沖區(qū)來(lái)存儲(chǔ)消息頭和有效載荷,并將緩沖區(qū)與msg_iov關(guān)聯(lián)起來(lái) 。接收成功時(shí)返回接收到的字節(jié)數(shù),失敗返回 -1 。如果返回 0,表示對(duì)方關(guān)閉了連接 。示例代碼如下:

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)關(guān)閉套接字:當(dāng)完成 Netlink 通信后,使用close函數(shù)關(guān)閉套接字,釋放資源 。函數(shù)原型為:

int close(int fd);

fd參數(shù)是之前創(chuàng)建套接字時(shí)返回的文件描述符 。關(guān)閉成功返回 0,失敗返回 -1 。例如:

close(sock_fd);

通過(guò)上述這些用戶態(tài) API 函數(shù)的組合使用,就可以實(shí)現(xiàn)與內(nèi)核態(tài)之間的 Netlink 通信,完成各種數(shù)據(jù)交互和控制操作 。

四、Netlink 機(jī)制的應(yīng)用場(chǎng)景

4.1 網(wǎng)絡(luò)配置與管理

在網(wǎng)絡(luò)配置與管理領(lǐng)域,iproute2 工具是 Netlink 機(jī)制的典型應(yīng)用代表。iproute2 是一套通過(guò) Netlink 套接字與 Linux 內(nèi)核通信的工具集,其核心命令ip支持多種網(wǎng)絡(luò)對(duì)象的配置,包括接口、IP 地址、路由表等 。

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

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

路由表管理也是網(wǎng)絡(luò)配置的關(guān)鍵部分,ip route命令負(fù)責(zé)這一任務(wù)。ip route show用于查看路由表,它能展示系統(tǒng)當(dāng)前的路由信息,包括目標(biāo)網(wǎng)絡(luò)、下一跳地址、出接口等。當(dāng)需要添加默認(rèn)網(wǎng)關(guān)時(shí),執(zhí)行sudo ip route add default via 192.168.1.1 dev eth0命令,通過(guò) Netlink,ip工具將添加默認(rèn)網(wǎng)關(guān)的請(qǐng)求發(fā)送給內(nèi)核,內(nèi)核更新路由表并將結(jié)果反饋給用戶空間。添加靜態(tài)路由時(shí),如sudo ip route add 10.0.0.0/24 via 192.168.1.2,也是利用 Netlink 實(shí)現(xiàn)與內(nèi)核的交互,完成靜態(tài)路由的添加,確保網(wǎng)絡(luò)數(shù)據(jù)包能夠按照設(shè)定的路由規(guī)則進(jìn)行轉(zhuǎn)發(fā)。

4.2 設(shè)備驅(qū)動(dòng)與用戶空間交互

在設(shè)備驅(qū)動(dòng)與用戶空間交互中,Netlink 發(fā)揮著至關(guān)重要的橋梁作用。以網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)為例,當(dāng)網(wǎng)絡(luò)設(shè)備的狀態(tài)發(fā)生變化時(shí),比如網(wǎng)絡(luò)電纜被拔出或插入,設(shè)備驅(qū)動(dòng)需要及時(shí)將這一信息傳遞給用戶空間的程序,以便用戶空間的程序做出相應(yīng)的處理,如更新網(wǎng)絡(luò)狀態(tài)顯示、重新配置網(wǎng)絡(luò)連接等。

設(shè)備驅(qū)動(dòng)通過(guò) Netlink 向用戶空間發(fā)送設(shè)備狀態(tài)變化的消息。當(dāng)網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)檢測(cè)到網(wǎng)絡(luò)電纜被拔出時(shí),它會(huì)構(gòu)造一個(gè) Netlink 消息,消息中包含設(shè)備的相關(guān)信息以及狀態(tài)變化的描述,然后通過(guò) Netlink 將這個(gè)消息發(fā)送給用戶空間中訂閱了該設(shè)備狀態(tài)變化的程序。在 Linux 系統(tǒng)中,udev是一個(gè)用戶空間程序,它負(fù)責(zé)管理設(shè)備節(jié)點(diǎn)的創(chuàng)建和刪除等工作。當(dāng)設(shè)備驅(qū)動(dòng)通過(guò) Netlink 發(fā)送設(shè)備插拔事件通知時(shí),udev接收到消息后,會(huì)根據(jù)消息中的設(shè)備信息,創(chuàng)建或刪除相應(yīng)的設(shè)備節(jié)點(diǎn),確保用戶空間能夠正確訪問(wèn)設(shè)備。

設(shè)備驅(qū)動(dòng)還可以通過(guò) Netlink 接收用戶空間程序發(fā)送的控制指令。用戶空間的網(wǎng)絡(luò)配置程序可以通過(guò) Netlink 向網(wǎng)絡(luò)設(shè)備驅(qū)動(dòng)發(fā)送配置指令,如設(shè)置網(wǎng)絡(luò)設(shè)備的 MTU 值。用戶空間程序構(gòu)造包含 MTU 配置信息的 Netlink 消息,并發(fā)送給設(shè)備驅(qū)動(dòng)。設(shè)備驅(qū)動(dòng)接收到消息后,解析消息內(nèi)容,根據(jù)配置指令對(duì)網(wǎng)絡(luò)設(shè)備的 MTU 進(jìn)行設(shè)置,從而實(shí)現(xiàn)對(duì)設(shè)備的有效管理。這種通過(guò) Netlink 進(jìn)行的雙向通信,使得設(shè)備驅(qū)動(dòng)與用戶空間程序能夠緊密協(xié)作,提高了設(shè)備管理的效率和靈活性。

4.3 系統(tǒng)監(jiān)控與性能優(yōu)化

在系統(tǒng)監(jiān)控與性能優(yōu)化方面,Netlink 為監(jiān)控工具提供了獲取內(nèi)核中系統(tǒng)資源使用情況的有效途徑。以nlbwmon這款輕量級(jí)的網(wǎng)絡(luò)流量監(jiān)控工具為例,它通過(guò) Netlink 套接字從 Linux 內(nèi)核獲取網(wǎng)絡(luò)使用信息,并從linux conntrack條目中收集統(tǒng)計(jì)信息 。

nlbwmon在運(yùn)行過(guò)程中,通過(guò) Netlink 向內(nèi)核發(fā)送請(qǐng)求消息,請(qǐng)求獲取網(wǎng)絡(luò)帶寬使用情況、網(wǎng)絡(luò)連接狀態(tài)等信息。內(nèi)核接收到請(qǐng)求后,會(huì)根據(jù)請(qǐng)求內(nèi)容收集相關(guān)的系統(tǒng)資源使用數(shù)據(jù),如各個(gè)網(wǎng)絡(luò)接口的上傳和下載流量數(shù)據(jù),然后將這些數(shù)據(jù)通過(guò) Netlink 返回給nlbwmon。nlbwmon接收到數(shù)據(jù)后,對(duì)其進(jìn)行分析和處理,以直觀的方式展示給用戶,用戶可以通過(guò)這些數(shù)據(jù)了解網(wǎng)絡(luò)帶寬的使用情況,判斷是否存在網(wǎng)絡(luò)擁塞或異常流量。

通過(guò) Netlink 獲取的系統(tǒng)資源使用數(shù)據(jù)還能為性能優(yōu)化提供有力的數(shù)據(jù)支持。系統(tǒng)管理員可以根據(jù)nlbwmon提供的網(wǎng)絡(luò)流量數(shù)據(jù),分析網(wǎng)絡(luò)流量的分布情況,找出網(wǎng)絡(luò)流量較大的時(shí)間段和應(yīng)用程序,從而針對(duì)性地進(jìn)行網(wǎng)絡(luò)資源的優(yōu)化配置,如限制某些非關(guān)鍵應(yīng)用的帶寬使用,為關(guān)鍵業(yè)務(wù)應(yīng)用預(yù)留足夠的網(wǎng)絡(luò)帶寬,以提高整個(gè)系統(tǒng)的網(wǎng)絡(luò)性能。在企業(yè)網(wǎng)絡(luò)中,如果發(fā)現(xiàn)某個(gè)部門(mén)在特定時(shí)間段內(nèi)的網(wǎng)絡(luò)流量過(guò)大,導(dǎo)致其他部門(mén)的網(wǎng)絡(luò)訪問(wèn)受到影響,管理員可以根據(jù)監(jiān)控?cái)?shù)據(jù),對(duì)該部門(mén)的網(wǎng)絡(luò)帶寬進(jìn)行限制,確保網(wǎng)絡(luò)資源的合理分配,提升系統(tǒng)的整體性能。

4.4 安全與防火墻管理

在安全與防火墻管理領(lǐng)域,Netlink 起著關(guān)鍵的作用,防火墻工具如iptables借助 Netlink 與內(nèi)核netfilter模塊通信,實(shí)現(xiàn)安全策略的配置和管理。

iptables是 Linux 系統(tǒng)中常用的防火墻工具,它通過(guò) Netlink 與內(nèi)核中的netfilter模塊進(jìn)行交互。當(dāng)用戶通過(guò)iptables命令設(shè)置防火墻規(guī)則時(shí),iptables會(huì)將這些規(guī)則通過(guò) Netlink 發(fā)送給netfilter模塊。用戶執(zhí)行iptables -A INPUT -p tcp --dport 80 -j ACCEPT命令,這條命令的含義是在INPUT鏈中添加一條規(guī)則,允許目的端口為 80 的 TCP 數(shù)據(jù)包通過(guò)。iptables接收到這個(gè)命令后,會(huì)構(gòu)造一個(gè)包含該規(guī)則信息的 Netlink 消息,并發(fā)送給netfilter模塊。netfilter模塊接收到消息后,解析其中的規(guī)則內(nèi)容,并將其應(yīng)用到網(wǎng)絡(luò)數(shù)據(jù)包的過(guò)濾過(guò)程中。

當(dāng)網(wǎng)絡(luò)數(shù)據(jù)包到達(dá)系統(tǒng)時(shí),netfilter模塊會(huì)根據(jù)配置的防火墻規(guī)則對(duì)數(shù)據(jù)包進(jìn)行檢查。如果數(shù)據(jù)包符合iptables設(shè)置的拒絕規(guī)則,netfilter模塊會(huì)根據(jù)規(guī)則對(duì)數(shù)據(jù)包進(jìn)行相應(yīng)的處理,如丟棄數(shù)據(jù)包。而當(dāng)netfilter模塊在處理數(shù)據(jù)包過(guò)程中發(fā)生一些與安全相關(guān)的事件時(shí),它也可以通過(guò) Netlink 向iptables發(fā)送通知消息,iptables可以根據(jù)這些通知消息更新防火墻的狀態(tài)或日志記錄,以便管理員進(jìn)行安全審計(jì)和分析,及時(shí)發(fā)現(xiàn)潛在的安全威脅,從而實(shí)現(xiàn)對(duì)系統(tǒng)的安全防護(hù)和管理。

五、Netlink 機(jī)制的使用方法

5.1 用戶態(tài)編程示例

在用戶態(tài)使用 Netlink 進(jìn)行通信,主要借助 socket API 中的 socket ()、bind ()、sendmsg ()、recvmsg () 等函數(shù),下面通過(guò)一個(gè)簡(jiǎn)單的示例代碼來(lái)詳細(xì)說(shuō)明每一步操作。

#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協(xié)議類型
#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;

    // 創(chuàng)建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(); // 使用當(dāng)前進(jìn)程ID
    src_addr.nl_groups = 0;

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

    // 初始化目的地址(發(fā)往內(nèi)核,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;

    // 構(gòu)造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;

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

    // 構(gòu)造msghdr結(jié)構(gòu)體
    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;

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

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

5.2 內(nèi)核態(tài)編程要點(diǎn)

在內(nèi)核態(tài)中使用 Netlink 進(jìn)行通信,需要完成注冊(cè) Netlink 套接字、處理消息等關(guān)鍵步驟,涉及到一些內(nèi)核 API 和回調(diào)函數(shù)的使用。

(1)注冊(cè) Netlink 套接字:使用netlink_kernel_create函數(shù)創(chuàng)建并注冊(cè)一個(gè) Netlink 套接字。這個(gè)函數(shù)需要傳入 Netlink 協(xié)議類型、消息處理回調(diào)函數(shù)等參數(shù)。例如:

#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函數(shù)創(chuàng)建了一個(gè) Netlink 套接字,協(xié)議類型為NETLINK_TEST,并將netlink_rcv_msg函數(shù)注冊(cè)為消息處理回調(diào)函數(shù)。netlink_kernel_create函數(shù)的第一個(gè)參數(shù)&init_net表示網(wǎng)絡(luò)命名空間,這里使用全局的init_net;第二個(gè)參數(shù)是 Netlink 協(xié)議類型;第三個(gè)參數(shù)是netlink_kernel_cfg結(jié)構(gòu)體指針,用于配置套接字的一些屬性,這里主要配置了消息處理回調(diào)函數(shù)。

(2)處理消息:當(dāng)有 Netlink 消息到達(dá)時(shí),內(nèi)核會(huì)調(diào)用注冊(cè)的回調(diào)函數(shù)(如netlink_rcv_msg)來(lái)處理消息。在回調(diào)函數(shù)中,首先從skb(struct sk_buff)結(jié)構(gòu)體中獲取nlmsghdr消息頭,然后根據(jù)消息頭的信息,如消息類型、消息長(zhǎng)度等,進(jìn)一步解析消息內(nèi)容并進(jìn)行相應(yīng)的處理。例如:

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);
        // 處理消息數(shù)據(jù)
        printk(KERN_INFO "Received message from user: %s\n", msg_data);
        // 構(gòu)造回復(fù)消息
        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"));
        // 發(fā)送回復(fù)消息
        netlink_unicast(nl_sk, reply_skb,  /* 接收方PID */, MSG_DONTWAIT);
    }
}

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

內(nèi)核態(tài)編程中,還需要注意內(nèi)存管理,如使用nlmsg_new分配的內(nèi)存需要在不再使用時(shí)通過(guò)nlmsg_free釋放,以避免內(nèi)存泄漏;同時(shí)要處理好并發(fā)訪問(wèn),確保在多線程或多 CPU 環(huán)境下的正確性 。

六、實(shí)戰(zhàn)項(xiàng)目:構(gòu)建 Netlink 通信系統(tǒng)

6.1 項(xiàng)目架構(gòu)設(shè)計(jì)

在這個(gè)實(shí)戰(zhàn)項(xiàng)目中,我們將構(gòu)建一個(gè)基于 Netlink 機(jī)制的簡(jiǎn)單通信系統(tǒng),實(shí)現(xiàn)用戶態(tài)應(yīng)用程序與內(nèi)核模塊之間的雙向通信 。整個(gè)項(xiàng)目架構(gòu)主要由兩部分組成:內(nèi)核模塊和用戶空間程序 。

  1. 內(nèi)核模塊:負(fù)責(zé)創(chuàng)建 Netlink 套接字,監(jiān)聽(tīng)來(lái)自用戶空間的消息,并處理這些消息 。當(dāng)接收到用戶空間發(fā)送的消息后,內(nèi)核模塊會(huì)根據(jù)消息的內(nèi)容進(jìn)行相應(yīng)的處理,然后將處理結(jié)果返回給用戶空間 。例如,用戶空間發(fā)送一個(gè)獲取系統(tǒng)內(nèi)存使用情況的請(qǐng)求,內(nèi)核模塊接收到后,會(huì)讀取系統(tǒng)內(nèi)存信息,然后將內(nèi)存使用情況作為響應(yīng)消息發(fā)送回用戶空間 。內(nèi)核模塊使用 netlink_kernel_create 函數(shù)創(chuàng)建 Netlink 套接字,并注冊(cè)一個(gè)回調(diào)函數(shù)來(lái)處理接收到的消息 。當(dāng)有消息到達(dá)時(shí),內(nèi)核會(huì)自動(dòng)調(diào)用這個(gè)回調(diào)函數(shù),在回調(diào)函數(shù)中,通過(guò) nlmsg_hdr 和 NLMSG_DATA 等函數(shù)和宏來(lái)解析消息內(nèi)容,并根據(jù)消息類型進(jìn)行相應(yīng)的處理 。如果需要發(fā)送響應(yīng)消息,內(nèi)核模塊會(huì)使用 netlink_unicast 函數(shù)將消息發(fā)送回用戶空間 。
  2. 用戶空間程序:創(chuàng)建 Netlink 套接字,綁定到指定的地址,然后向內(nèi)核發(fā)送消息,并接收內(nèi)核返回的響應(yīng)消息 。用戶空間程序可以根據(jù)實(shí)際需求發(fā)送不同類型的消息,比如配置參數(shù)、查詢系統(tǒng)信息等 。用戶空間程序使用socket函數(shù)創(chuàng)建 Netlink 套接字,使用bind函數(shù)將套接字綁定到一個(gè)特定的地址,包括進(jìn)程 ID 和多播組等信息 。在發(fā)送消息時(shí),首先構(gòu)建一個(gè) Netlink 消息頭和消息體,設(shè)置好消息類型、長(zhǎng)度、序列號(hào)等信息,然后使用sendmsg函數(shù)將消息發(fā)送給內(nèi)核 。接收消息時(shí),使用recvmsg函數(shù)從套接字接收內(nèi)核返回的消息,并根據(jù)消息頭中的信息解析出消息內(nèi)容 。

下面是項(xiàng)目架構(gòu)的示意圖:

+------------------+                +------------------+
| 用戶空間程序     |                | 內(nèi)核模塊         |
|                  |                |                  |
| 1. 創(chuàng)建套接字     |<--------------->| 1. 創(chuàng)建套接字     |
| 2. 綁定地址       |                | 2. 注冊(cè)回調(diào)函數(shù)   |
| 3. 發(fā)送消息       |----消息----->  | 3. 處理消息       |
| 4. 接收消息       |<----響應(yīng)----  | 4. 發(fā)送響應(yīng)消息   |
|                  |                |                  |
+------------------+                +------------------+

通過(guò)這樣的架構(gòu)設(shè)計(jì),用戶空間程序和內(nèi)核模塊之間可以實(shí)現(xiàn)高效、靈活的雙向通信,為各種系統(tǒng)級(jí)應(yīng)用開(kāi)發(fā)提供了堅(jiān)實(shí)的基礎(chǔ) 。

6.2 內(nèi)核模塊實(shí)現(xiàn)

下面是內(nèi)核模塊的實(shí)現(xiàn)代碼,詳細(xì)注釋了每一步的邏輯 。

#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協(xié)議類型,就像給通信通道取個(gè)獨(dú)特的名字
static struct sock *nl_sk = NULL; // 定義一個(gè)Netlink套接字指針,用于后續(xù)操作

// 接收消息的回調(diào)函數(shù),當(dāng)有消息到達(dá)時(shí),內(nèi)核會(huì)調(diào)用這個(gè)函數(shù)
static void nl_recv_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh;
    int pid;
    char *msg;

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

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

    // 回復(fù)用戶空間
    nlh->nlmsg_pid = 0; // 目標(biāo)為用戶空間,因?yàn)閮?nèi)核發(fā)送消息到用戶空間時(shí),pid設(shè)為0
    nlh->nlmsg_type = NLMSG_DONE; // 設(shè)置消息類型為完成,告知用戶空間這是響應(yīng)消息
    strcpy(nlh->nlmsg_data, "Hello from Kernel"); // 設(shè)置響應(yīng)消息內(nèi)容

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

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

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

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

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

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

在這段代碼中,首先定義了一個(gè)自定義的 Netlink 協(xié)議類型NETLINK_TEST 。然后在hello_init函數(shù)中,創(chuàng)建了一個(gè) Netlink 套接字,并注冊(cè)了消息接收回調(diào)函數(shù)nl_recv_msg 。當(dāng)有消息到達(dá)時(shí),nl_recv_msg函數(shù)會(huì)被調(diào)用,它會(huì)解析消息內(nèi)容,打印相關(guān)信息,并向發(fā)送者發(fā)送一個(gè)響應(yīng)消息 。最后,在hello_exit函數(shù)中,釋放 Netlink 套接字資源,確保模塊卸載時(shí)資源被正確回收 。

6.3 用戶空間程序?qū)崿F(xiàn)

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

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

#define NETLINK_TEST 31 // 與內(nèi)核模塊中的協(xié)議類型一致,確保雙方在同一條通信通道上
#define MAX_PLOAD 1024 // 定義最大消息負(fù)載長(zhǎng)度,就像規(guī)定包裹的最大容量

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

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

    // 初始化源地址結(jié)構(gòu)體
    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK; // 設(shè)置協(xié)議族為AF_NETLINK
    src_addr.nl_pid = getpid(); // 設(shè)置源地址的PID為當(dāng)前進(jìn)程ID,就像填寫(xiě)自己的地址
    src_addr.nl_groups = 0; // 不加入多播組

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

    // 初始化目標(biāo)地址結(jié)構(gòu)體,目標(biāo)是內(nèi)核
    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK; // 設(shè)置協(xié)議族為AF_NETLINK
    dest_addr.nl_pid = 0; // 目標(biāo)地址的PID為0,表示內(nèi)核
    dest_addr.nl_groups = 0; // 不加入多播組

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

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

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

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

    // 釋放內(nèi)存,關(guān)閉套接字
    free(nlh);
    close(sock_fd);

    return 0;
}

在這段代碼中,首先創(chuàng)建了一個(gè) Netlink 套接字,并將其綁定到當(dāng)前進(jìn)程的地址 。然后構(gòu)建一個(gè) Netlink 消息,設(shè)置好消息頭和消息內(nèi)容,將消息發(fā)送給內(nèi)核 。接著等待接收內(nèi)核的回復(fù)消息,當(dāng)接收到消息后,打印出內(nèi)核回復(fù)的內(nèi)容 。最后,釋放內(nèi)存資源,關(guān)閉套接字,完成整個(gè)通信過(guò)程 。

6.4 編譯和測(cè)試

①編譯內(nèi)核模塊:創(chuàng)建一個(gè) Makefile 文件,內(nèi)容如下:

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

在終端中執(zhí)行make命令,即可編譯內(nèi)核模塊,生成netlink_kernel.ko文件 。

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

gcc - o netlink_user netlink_user.c

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

③加載模塊并測(cè)試

首先,使用以下命令加載內(nèi)核模塊:

sudo insmod netlink_kernel.ko

然后,運(yùn)行用戶程序:

./netlink_user

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

責(zé)任編輯:武曉燕 來(lái)源: 深度Linux
相關(guān)推薦

2023-10-26 11:39:54

Linux系統(tǒng)CPU

2017-08-16 16:20:01

Linux內(nèi)核態(tài)搶占用戶態(tài)搶占

2025-09-26 02:22:00

2025-04-17 01:44:00

2023-10-17 17:13:14

內(nèi)存程序源碼

2009-12-22 09:11:31

WCF雙向通信

2010-02-23 17:55:24

WCF雙向通信

2020-07-28 08:54:39

內(nèi)核通信Netlink

2009-12-08 11:17:41

WCF雙向通信

2021-12-20 09:53:51

用戶態(tài)內(nèi)核態(tài)應(yīng)用程序

2022-03-25 12:31:49

Linux根文件內(nèi)核

2021-08-31 07:54:24

TCPIP協(xié)議

2021-01-08 05:59:39

Linux應(yīng)用程序Linux系統(tǒng)

2020-11-10 10:00:10

HarmonyOS

2021-09-08 10:21:33

內(nèi)核網(wǎng)絡(luò)包Tcpdump

2021-09-17 11:59:21

tcpdump網(wǎng)絡(luò)包Linux

2009-10-29 09:41:01

Linux內(nèi)核DeviceMappe

2009-12-07 09:31:23

Linux系統(tǒng)調(diào)用表地址

2014-07-17 09:55:23

Linux程序計(jì)時(shí)

2023-01-06 08:04:10

GPU容器虛擬化
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)