首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 相關知識

Linux Netlink 通信機制詳解:用戶態與內核態雙向交互實戰

     編輯:PHP 相關知識

什麼是 Netlink

Netlink 是 Linux 內核與用戶態進程雙向通信的標準機制,基於 BSD Socket(AF_NETLINK)。優於 /proc、ioctl 的單向限制,支持全雙工、異步、多播(總線模式)、內核主動推送。

核心數據結構

sockaddr_nl — Netlink 地址

struct sockaddr_nl {
    sa_family_t nl_family;  // AF_NETLINK
    unsigned short nl_pad;  // 填充 0
    __u32 nl_pid;           // 端口 ID(通常為進程 PID)
    __u32 nl_groups;        // 多播組掩碼(每 bit 代表一組)
};
  • nl_pid:唯一標識一個 Netlink 通道,內核為 0,用戶態通常用 getpid()。
  • nl_groups:加入多播組掩碼,bind() 時設置。

nlmsghdr — Netlink 消息頭

struct nlmsghdr {
    __u32 nlmsg_len;   // 整個消息長度(含頭)
    __u16 nlmsg_type;  // 消息類型
    __u16 nlmsg_flags; // 標誌位
    __u32 nlmsg_seq;   // 序列號
    __u32 nlmsg_pid;   // 發送進程 PID
};

消息體通過 NLMSG_DATA(nlh) 獲取。控制消息類型:NLMSG_NOOP、NLMSG_ERROR、NLMSG_DONE、NLMSG_OVERRUN。

用戶態編程模型(以自定義協議 NETLINK_TEST=25 為例)

方式一:sendmsg/recvmsg(通用)

int sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST);
// 綁定本地地址
struct sockaddr_nl src = { .nl_family = AF_NETLINK, .nl_pid = 100 };
bind(sock_fd, (struct sockaddr*)&src, sizeof(src));
// 構造消息
struct nlmsghdr *nlh = malloc(NLMSG_SPACE(MAX_PAYLOAD));
nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD);
nlh->nlmsg_pid = 100;
nlh->nlmsg_flags = 0;
strcpy(NLMSG_DATA(nlh), "Hello kernel!");
// 目標地址(內核 pid=0)
struct sockaddr_nl dst = { .nl_family = AF_NETLINK, .nl_pid = 0 };
struct iovec iov = { .iov_base = nlh, .iov_len = nlh->nlmsg_len };
struct msghdr msg = { .msg_name = &dst, .msg_namelen = sizeof(dst), .msg_iov = &iov, .msg_iovlen = 1 };
sendmsg(sock_fd, &msg, 0);
// 接收回復
recvmsg(sock_fd, &msg, 0);
printf("Reply: %s\n", (char*)NLMSG_DATA(nlh));

關鍵點:三處 PID 的含義

位置代碼作用
bind 地址src.nl_pid = 100註冊本進程接收通道,如信箱門牌號
目標地址dst.nl_pid = 0指定接收方(0=內核),如收信地址
消息頭nlh->nlmsg_pid = 100告知內核回復地址,如信封上的寄件人地址

若三者不一致會導致無法收到回復或發送給錯誤進程。

方式二:sendto/recvfrom(簡化)

sendto(sock_fd, nlh, nlh->nlmsg_len, 0, (struct sockaddr*)&dst, sizeof(dst));
recvfrom(sock_fd, nlh, MAX_PAYLOAD, 0, NULL, NULL);

仍需 bind() 註冊本地 pid,否則內核無法回復。

內核態 API 核心函數

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

// 單播發送
int netlink_unicast(struct sock *ssk, struct sk_buff *skb, u32 pid, int nonblock);

// 多播發送
int netlink_broadcast(struct sock *ssk, struct sk_buff *skb, u32 pid, u32 group, gfp_t allocation);

// 釋放
void netlink_kernel_release(struct sock *sk);

內核接收回調示例

void nl_data_ready(struct sk_buff *skb) {
    struct nlmsghdr *nlh = nlmsg_hdr(skb);
    char *data = NLMSG_DATA(nlh);
    u32 pid = nlh->nlmsg_pid; // 獲取發送進程 pid
    // 處理數據...
    // 回復用戶態
    struct sk_buff *skb_out = alloc_skb(NLMSG_SPACE(MAX), GFP_KERNEL);
    struct nlmsghdr *nlh_out = nlmsg_put(skb_out, 0, 0, 0, MAX, 0);
    NETLINK_CB(skb_out).pid = 0; // 內核 pid
    memcpy(NLMSG_DATA(nlh_out), "Reply from kernel", 18);
    netlink_unicast(nl_sk, skb_out, pid, MSG_DONTWAIT);
}

同步/異步特性與避坑指南

  • 用戶→內核同步:sendmsg 會阻塞直到內核回調 input() 返回。回調中耗時操作會卡死用戶進程(D 狀態)。
  • 內核→用戶異步:內核發送數據入隊列,用戶態 recvmsg 非阻塞讀取。
  • pid 不能為 0:用戶態 bind 時 pid=0 會與內核衝突,導致死循環掛機。
  • 同一協議不可重復 bind:同一 NETLINK_TEST 下 pid 唯一,嘗試雙進程 bind 會報 Address already in use,SO_REUSEADDR 無效。
  • 內核主動推送:雙方約定 pid,內核直接 netlink_unicast 即可,無需用戶態先發起。

總結

Netlink 為內核與用戶態提供了靈活、高效的雙向通信通道,掌握 sockaddr_nl、nlmsghdr、sendmsg/recvmsg 與內核 netlink_kernel_create、netlink_unicast 核心 API,即可實現路由管理、監控事件訂閱、容器網絡配置等高級場景。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved