【代码模板】如何通过netlink从内核态主动发送消息到用户态?(netlink_kernel_create、nlmsg_new、nlmsg_put、netlink_unicast)

摘要

本文通过一个简单的例子介绍了Netlink机制的使用,展示了内核态与用户态之间的异步通信。主要流程包括:内核态驱动注册Netlink用户自定义消息组,用户态程序通过写入字符设备将PID发送给内核态驱动,内核态使用Netlink单播或广播机制通知对应的PID,用户态收到消息后打印出来自内核的信息。

背景

netlink是内核中常用的机制,可以使内核态主动通知用户态并且是异步的操作。本文实现了一个简单的例子介绍netlink的使用机制。主要流程是:内核态驱动注册netlink用户自定义消息组,然后用户态程序通过write字符设备将pid发送给内核态驱动,内核态然后使用netlink单播/广播机制通知到对应的pid,然后用户态收到消息后打印出来自内核的消息

代码与主要流程

内核态程序 test.c

主要流程:
初始化的时候通过netlink_kernel_create创建netlink的用户自定义的netlink消息组。
然后创建字符设备至此写入和ioctl。写入是指定pid,ioctl是触发发送到用户态的netlink中去。

通过nlmsg_new创建一个sk_buffer
然后nlmsg_put初始化skb
然后将需要传送的消息拷贝到mlh的data域段并且设置mlh的msg len(如果不设置,用户态收到的该部分信息不对)
然后调用netlink_unicast单播到某个pid,指定创建的netlink的自定义消息组句柄、需要传送的skb、用户pid、以及flag。也可以组播,组播需要把netlink_unicast换成netlink_broadcast。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/netlink.h>
#include <linux/skbuff.h>
#include <net/netlink.h>
#include <net/net_namespace.h>

#define NETLINK_USER 31
#define DEVICE_NAME "netlink_device"
#define CLASS_NAME "nlclass"
#define IOCTL_CMD 0x12345678 // Must match the command in the user program

static struct sock *nl_sk = NULL;
static dev_t dev_num;
static struct cdev c_dev;
static struct class *cl;
static int user_pid = 0; // Store the user space process PID

static void send_to_user(void) {
   
   
    struct sk_buff *skb;
    char msg[] = "Hello from kernel!";
    int size = NLMSG_SPACE(strlen(msg) + 1); // including null terminator

    if (user_pid == 0) {
   
   
        printk(KERN_ERR "User PID not set\n");
        return;
    }

    skb = nlmsg_new(size, GFP_KERNEL);
    if (!skb) {
   
   
        printk(KERN_ERR "Failed to allocate new skb\n");
        return;
    }

    struct nlmsghdr *nlh = nlmsg_put(skb, 0, 0, NLMSG_DONE, strlen(msg) + 1, 0);
    if (!nlh) {
   
   
        nlmsg_free(skb);
        printk(KERN_ERR "Failed to put nlmsghdr\n");
        return;
    }
    memcpy(NLMSG_DATA(nlh), msg, strlen(
<think>我们首先需要了解netlink是一种用于内核与用户空间通信的机制。用户态程序通过创建netlink socket,与内核模块进行通信。 步骤: 1. 用户态程序:创建netlink socket,绑定地址,然后发送请求消息到内核,并接收回复。 2. 内核模块:需要注册一个netlink操作集,处理用户态的请求。 用户态代码示例(简化版): 注意:这里假设内核模块使用的netlink协议号为自定义的(例如NETLINK_TEST,需要与内核一致),通常使用NETLINK_USERSOCK(31)以上的值。 用户态程序的主要步骤: a. 创建socket:socket(AF_NETLINK, SOCK_RAW, netlink_family) b. 填充本地地址(源地址)并绑定 c. 填充目标地址(内核地址,pid=0表示内核) d. 填充要发送消息(包括netlink消息头和自定义数据) e. 发送消息 f. 接收回复 这里我们以查询数据为例,假设我们定义了一种消息格式,用户态发送一个查询请求,内核返回数据。 注意:netlink消息需要按照一定的格式,通常包括struct nlmsghdr和自定义数据。 示例代码用户态): */ #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 MSG_TYPE_QUERY 0x11 // 最大负载长度 #define MAX_PAYLOAD 1024 int main(int argc, char *argv[]) { int sock_fd; struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct iovec iov; struct msghdr msg; int ret; // 创建netlink socket sock_fd = socket(AF_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; src_addr.nl_pid = getpid(); // 当前进程的pid作为源地址 src_addr.nl_groups = 0; // 单播 // 绑定socket ret = bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); if (ret < 0) { perror("bind"); close(sock_fd); return -1; } // 准备发送消息 nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MAX_PAYLOAD)); if (!nlh) { perror("malloc"); close(sock_fd); return -1; } memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); // 设置netlink消息头 nlh->nlmsg_len = NLMSG_SPACE(MAX_PAYLOAD); nlh->nlmsg_pid = getpid(); // 发送者的pid nlh->nlmsg_flags = 0; // 自定义消息:这里我们假设消息体是一个字符串,但实际可能是结构体等 // 消息类型放在nlmsg_type字段,也可以放在负载里,这里我们放在nlmsg_type nlh->nlmsg_type = MSG_TYPE_QUERY; // 可以在这里填充负载数据,本例中我们只是发送一个查询请求,所以负载为空 // 如果有数据,可以拷贝到NLMSG_DATA(nlh)指向的区域 // strcpy(NLMSG_DATA(nlh), "Some data"); // 填充目标地址(内核) memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; // 0表示内核 dest_addr.nl_groups = 0; // 单播 // 设置iovec和msghdr iov.iov_base = (void *)nlh; iov.iov_len = nlh->nlmsg_len; 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; // 发送消息 ret = sendmsg(sock_fd, &msg, 0); if (ret < 0) { perror("sendmsg"); free(nlh); close(sock_fd); return -1; } printf("Sent query to kernel\n"); // 接收回复 memset(nlh, 0, NLMSG_SPACE(MAX_PAYLOAD)); ret = recvmsg(sock_fd, &msg, 0); if (ret < 0) { perror("recvmsg"); free(nlh); close(sock_fd); return -1; } // 打印接收到的消息 printf("Received message from kernel: %s\n", (char *)NLMSG_DATA(nlh)); free(nlh); close(sock_fd); return 0; } /* 内核模块部分不是本问题的重点,但为了完整性,简要描述: 1. 内核模块需要定义一个struct netlink_kernel_cfg,并设置.input接收回调函数。 2. 使用netlink_kernel_create创建netlink socket。 3. 在接收回调函数中,解析用户态发来的消息,根据消息类型(如MSG_TYPE_QUERY)处理,并回复。 注意:以上用户态代码仅为示例,实际使用时需要更严谨的错误处理和消息解析。 最后,根据要求,提出相关问题: */ §§相关问题§§ 1. 在用户态程序中,如何确保netlink消息的接收不会阻塞? 2. 内核模块中如何处理并发到达的多个netlink消息? 3. 除了netlink,还有哪些其他方式可以实现用户态内核态的数据交换? 4. 在netlink通信中,如何保证消息的完整性和可靠性? </think>### Linux下C语言用户态程序通过Netlink向内核模块查询数据的实现 **核心步骤与代码示例:** 1. **用户态程序实现** 创建Netlink Socket,发送查询请求并接收内核响应: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <linux/netlink.h> #define NETLINK_TEST 31 // 自定义协议号(>16) # MAX_PLOAD 1024 // 最大负载长度 int main() { int sock_fd; struct sockaddr_nl src_addr, dest_addr; struct nlmsghdr *nlh = NULL; struct msghdr msg; struct iovec iov; // 1. 创建Netlink Socket sock_fd = socket(AF_NETLINK, SOCK_RAW, NETLINK_TEST); memset(&src_addr, 0, sizeof(src_addr)); src_addr.nl_family = AF_NETLINK; src_addr.nl_pid = getpid(); // 用户态PID作为地址标识 bind(sock_fd, (struct sockaddr*)&src_addr, sizeof(src_addr)); // 2. 构造查询请求消息 nlh = (struct nlmsghdr*)malloc(NLMSG_SPACE(MAX_PLOAD)); memset(nlh, 0, NLMSG_SPACE(MAX_PLOAD)); nlh->nlmsg_len = NLMSG_SPACE(MAX_PLOAD); nlh->nlmsg_pid = getpid(); nlh->nlmsg_flags = 0; strcpy(NLMSG_DATA(nlh), "QUERY_DATA"); // 自定义请求内容 // 3. 发送请求到内核 memset(&dest_addr, 0, sizeof(dest_addr)); dest_addr.nl_family = AF_NETLINK; dest_addr.nl_pid = 0; // 0表示内核 dest_addr.nl_groups = 0; // 单播 iov.iov_base = (void*)nlh; iov.iov_len = nlh->nlmsg_len; msg.msg_name = (void*)&dest_addr; msg.msg_namelen = sizeof(dest_addr); msg.msg_iov = &iov; msg.msg_iovlen = 1; sendmsg(sock_fd, &msg, 0); // 4. 接收内核响应 memset(nlh, 0, NLMSG_SPACE(MAX_PLOAD)); recvmsg(sock_fd, &msg, 0); printf("Kernel response: %s\n", (char*)NLMSG_DATA(nlh)); close(sock_fd); free(nlh); return 0; } ``` 2. **内核模块关键处理**(需在内核模块中实现): ```c // 内核模块Netlink回调函数示例 static void nl_recv_msg(struct sk_buff *skb) { struct nlmsghdr *nlh = nlmsg_hdr(skb); char *payload = NLMSG_DATA(nlh); if (strcmp(payload, "QUERY_DATA") == 0) { // 构造响应数据 char *resp = "Kernel data: XYZ"; struct sk_buff *skb_out = nlmsg_new(strlen(resp), 0); nlh = nlmsg_put(skb_out, 0, 0, NLMSG_DONE, strlen(resp), 0); NETLINK_CB(skb_out).dst_group = 0; // 单播 strncpy(nlmsg_data(nlh), resp, strlen(resp)); // 发送用户态 nlmsg_unicast(nl_sk, skb_out, nlh->nlmsg_pid); } } ``` **关键点说明:** - **协议号选择**:`NETLINK_TEST` 需与内核模块一致(16-31为用户自定义范围) - **消息结构**:`struct nlmsghdr` 必须包含长度、PID、标志位 - **内核交互**:用户态通过`sendmsg`/`recvmsg`,内核用`nlmsg_unicast`响应 - **PID匹配**:内核通过消息中的`nlmsg_pid`将响应返回给正确进程 **编译运行:** ```bash # 用户态程序 gcc user_program.c -o user_query ./user_query # 输出示例:Kernel response: Kernel data: XYZ ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值