傳統 hotplug 機制透過呼叫用戶空間程式、環境變數傳遞參數,效率較低。現代 Linux 核心 (2.6+) 採用 Netlink Socket (協議 NETLINK_KOBJECT_UEVENT) 實現核心空間與用戶空間非同步高效通訊。本文提供完整 C 語言範例,示範如何建立 Netlink 監聽器即時接收 U 盤插拔等 uevent 事件。
NETLINK_KOBJECT_UEVENT:專門傳遞 kobject 狀態變更事件 (裝置增減、綁定/解綁驅動等)。#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <sys/un.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <linux/types.h>
#include <linux/netlink.h>
#include <errno.h>
#define UEVENT_BUFFER_SIZE 2048
static int init_hotplug_sock(void) {
struct sockaddr_nl snl;
const int buffersize = 16 * 1024 * 1024; // 16MB 接收緩衝,防止事件丟失
int retval;
memset(&snl, 0, sizeof(struct sockaddr_nl));
snl.nl_family = AF_NETLINK;
snl.nl_pid = getpid(); // 使用進程 PID 作為端口 ID
snl.nl_groups = 1; // 加入多播群組 1 (uevent)
int hotplug_sock = socket(PF_NETLINK, SOCK_DGRAM, NETLINK_KOBJECT_UEVENT);
if (hotplug_sock == -1) {
perror("error getting socket");
return -1;
}
// 設定接收緩衝區大小 (需 CAP_NET_ADMIN 權限)
if (setsockopt(hotplug_sock, SOL_SOCKET, SO_RCVBUFFORCE, &buffersize, sizeof(buffersize)) < 0) {
perror("setsockopt SO_RCVBUFFORCE failed");
// 非致命,繼續使用預設緩衝
}
retval = bind(hotplug_sock, (struct sockaddr *)&snl, sizeof(struct sockaddr_nl));
if (retval < 0) {
perror("bind failed");
close(hotplug_sock);
return -1;
}
return hotplug_sock;
}
int main(int argc, char *argv[]) {
int hotplug_sock = init_hotplug_sock();
if (hotplug_sock == -1) return EXIT_FAILURE;
printf("Listening for kernel uevents... (Ctrl+C to quit)\n");
while (1) {
char buf[UEVENT_BUFFER_SIZE * 2] = {0};
ssize_t len = recv(hotplug_sock, &buf, sizeof(buf), 0);
if (len > 0) {
// 核心發送的 uevent 為多行 "KEY=VALUE" 格式,以雙 \0 結尾
printf("--- Event (%zd bytes) ---\n%s\n", len, buf);
} else if (len == -1) {
perror("recv failed");
break;
}
}
close(hotplug_sock);
return EXIT_SUCCESS;
}
gcc -o uevent_listener uevent_listener.c
sudo ./uevent_listener
需 root 權限 (設定 SO_RCVBUFFORCE 及綁定 Netlink)。
--- Event (312 bytes) ---
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1d.7/usb2/2-4/2-4:1.0/host6/target6:0:0/6:0:0:0/block/sdb
SUBSYSTEM=block
DEVNAME=sdb
DEVTYPE=disk
MAJOR=8
MINOR=16
SEQNUM=1234
...
--- Event (298 bytes) ---
ACTION=add
DEVPATH=/devices/pci0000:00/0000:00:1d.7/usb2/2-4/2-4:1.0/host6/target6:0:0/6:0:0:0/block/sdb/sdb1
SUBSYSTEM=block
DEVNAME=sdb1
DEVTYPE=partition
MAJOR=8
MINOR=17
SEQNUM=1235
...
| 字段 | 含義 |
|---|---|
ACTION | add (插入/裝置出現)、remove (拔出/裝置消失)、change (屬性變更) |
SUBSYSTEM | 子系統類別:block、usb、scsi、net 等 |
DEVNAME | /dev/ 下的裝置節點名 (如 sdb, sdb1) |
DEVPATH | sysfs 中裝置路徑,唯一標識裝置層級關係 |
ID_FS_TYPE | 檔案系統類型 (如 vfat, ext4) - 需 udev 規則生成 |
ID_VENDOR, ID_MODEL | USB 廠商/產品名稱 |
# 簡易解析邏輯 (Python 示範)
import subprocess, os
sock = init_netlink_socket() # 同 C 邏輯
while True:
data = sock.recv(65536).decode()
env = dict(line.split('=',1) for line in data.split('\n') if '=' in line)
if env.get('ACTION') == 'add' and env.get('SUBSYSTEM') == 'block' and env.get('DEVTYPE') == 'partition':
dev = '/dev/' + env['DEVNAME']
fstype = subprocess.check_output(['blkid', '-o', 'value', '-s', 'TYPE', dev]).decode().strip()
if fstype:
mp = '/media/usb_' + env['DEVNAME']
os.makedirs(mp, exist_ok=True)
subprocess.call(['mount', '-t', fstype, dev, mp])
print(f"Mounted {dev} ({fstype}) to {mp}")
手寫 Netlink 監聽器適合嵌入式/無 udev 環境或特殊需求。標準桌面/伺服器系統建議:
/etc/udev/rules.d/99-usb-automount.rules 規則匹配裝置屬性。systemd-mount 或自訂腳本處理掛載/卸載。Netlink Socket 提供了核心事件直達用戶空間的高效通道。掌握 NETLINK_KOBJECT_UEVENT 監聽與解析技術,能開發出輕量級的裝置監控工具、自動化備份觸發器或嵌入式系統熱插拔管理邏輯,深入理解 Linux 裝置模型運作機制。