用户空间与内核通信(二)

文章:用户空间与内核通信(一)介绍了系统调用(System Call),内核模块参数和sysfs,sysctl函数方式进行用户空间和内核空间的访问。本章节我将介绍使用netlink套接字和proc文件系统实现用户空间对内核空间的访问。

netlink套接字

netlink是一种基于socket的通信机制,用于在用户空间与内核空间之间进行小量数据的及时交互。netlink套接字允许用户空间程序与内核空间程序建立连接,并通过发送和接收消息来进行通信。

内核模块:

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netlink.h>
#include <net/netlink.h>

#define NETLINK_USER 31
#define MSG_LEN 1024

struct sock *nl_sock = NULL;

// 接收用户空间消息的回调函数
static void nl_recv_msg(struct sk_buff *skb) {
    struct nlmsghdr *nlh;
    char msg[MSG_LEN];

    // 从skb中获取消息头
    nlh = nlmsg_hdr(skb);
    if (nlh->nlmsg_len >= sizeof(struct nlmsghdr) &&
        netlink_skb_valid(skb, nlh->nlmsg_len)) {
        // 拷贝消息数据到msg缓冲区
        strncpy(msg, NLMSG_DATA(nlh), MSG_LEN);
        // 打印接收到的消息
        printk(KERN_INFO "Received message from user space: %s\n", msg);
    }
}

// 模块初始化函数
static int __init init_netlink(void) {
    // 创建Netlink套接字
    nl_sock = netlink_kernel_create(&init_net, NETLINK_USER, 0, nl_recv_msg, NULL, THIS_MODULE);
    if (!nl_sock) {
        printk(KERN_ERR "Failed to create netlink socket\n");
        return -1;
    }
    printk(KERN_INFO "Netlink socket created\n");
    return 0;
}

// 模块退出函数
static void __exit exit_netlink(void) {
    if (nl_sock)
        netlink_kernel_release(nl_sock);
    printk(KERN_INFO "Netlink socket released\n");
}

module_init(init_netlink);
module_exit(exit_netlink);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

用户空间应用程序:

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <linux/netlink.h>

#define NETLINK_USER 31
#define MSG_LEN 1024

int main() {
    int sockfd;
    struct sockaddr_nl src_addr, dest_addr;
    struct nlmsghdr *nlh = NULL;
    struct iovec iov;
    struct msghdr msg;

    sockfd = socket(AF_NETLINK, SOCK_RAW, NETLINK_USER);
    if (sockfd < 0) {
        perror("socket");
        return -1;
    }

    memset(&src_addr, 0, sizeof(src_addr));
    src_addr.nl_family = AF_NETLINK;
    src_addr.nl_pid = getpid();  // This is the source's port number

    bind(sockfd, (struct sockaddr*)&src_addr, sizeof(src_addr));

    memset(&dest_addr, 0, sizeof(dest_addr));
    dest_addr.nl_family = AF_NETLINK;
    dest_addr.nl_pid = 0;  // To kernel
    dest_addr.nl_groups = 0;

    nlh = (struct nlmsghdr *)malloc(NLMSG_SPACE(MSG_LEN));
    memset(nlh, 0, NLMSG_SPACE(MSG_LEN));
    nlh->nlmsg_len = NLMSG_SPACE(MSG_LEN);
    nlh->nlmsg_pid = getpid();
    nlh->nlmsg_flags = 0;
    strcpy(NLMSG_DATA(nlh), "Hello from user space");

    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;

    sendmsg(sockfd, &msg, 0);

    close(sockfd);
    free(nlh);

    return 0;
}

编译内核模块:

bash 复制代码
make -C /lib/modules/$(uname -r)/build M=$(pwd) modules

加载内核模块:

bash 复制代码
sudo insmod your_module.ko

编译用户空间程序:

bash 复制代码
gcc user_program.c -o user_program

运行用户空间程序:

bash 复制代码
sudo ./user_program

proc文件系统

proc是一个虚拟文件系统,用于导出内核和进程的状态信息。用户空间程序可以通过读取proc文件系统中的文件来获取内核和进程的信息,也可以通过写入proc文件来向内核发送指令或修改配置。

这些机制为用户空间与内核空间之间的通信提供了灵活和多样化的方式,使得用户程序能够与操作系统内核进行交互,获取系统服务并完成各种任务。

下面的示例,演示了如何使用proc文件系统在用户空间和内核空间之间进行通信。在此示例中,我们将创建一个proc文件,并使用它来向内核发送和接收数据。

首先,让我们创建一个名为"proc_example"的内核模块,该模块将在/proc目录下创建一个名为"proc_example"的文件。用户可以通过读取和写入此文件来与内核通信。

c 复制代码
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/uaccess.h>

#define PROC_ENTRY_NAME "proc_example"
#define BUFFER_SIZE 1024

static struct proc_dir_entry *proc_entry;
static char proc_buffer[BUFFER_SIZE];

// 读取proc文件的回调函数
static ssize_t proc_read(struct file *file, char __user *buffer, size_t count, loff_t *offset) {
    ssize_t len = strlen(proc_buffer);
    if (*offset >= len) {
        return 0; // 已经读取完毕
    }
    if (count > len - *offset) {
        count = len - *offset; // 如果请求的数据超过了剩余的数据量,则只读取剩余的数据量
    }
    if (copy_to_user(buffer, proc_buffer + *offset, count) != 0) {
        return -EFAULT; // 复制数据失败
    }
    *offset += count; // 更新偏移量
    return count;
}

// 写入proc文件的回调函数
static ssize_t proc_write(struct file *file, const char __user *buffer, size_t count, loff_t *offset) {
    if (count >= BUFFER_SIZE) {
        return -EINVAL; // 写入的数据过大
    }
    if (copy_from_user(proc_buffer, buffer, count) != 0) {
        return -EFAULT; // 复制数据失败
    }
    proc_buffer[count] = '\0'; // 添加字符串结束符
    return count;
}

// 模块初始化函数
static int __init init_proc_example(void) {
    proc_entry = proc_create(PROC_ENTRY_NAME, 0666, NULL, &proc_fops);
    if (!proc_entry) {
        printk(KERN_ERR "Failed to create /proc/%s\n", PROC_ENTRY_NAME);
        return -ENOMEM;
    }
    printk(KERN_INFO "/proc/%s created\n", PROC_ENTRY_NAME);
    return 0;
}

// 模块退出函数
static void __exit exit_proc_example(void) {
    if (proc_entry) {
        remove_proc_entry(PROC_ENTRY_NAME, NULL);
        printk(KERN_INFO "/proc/%s removed\n", PROC_ENTRY_NAME);
    }
}

// 定义proc文件操作结构体
static const struct file_operations proc_fops = {
    .read = proc_read,
    .write = proc_write,
};

module_init(init_proc_example);
module_exit(exit_proc_example);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");

编译和加载模块后,你可以在/sys/kernel/debug/proc_example中读取和写入数据。例如,你可以使用cat和echo命令:

bash 复制代码
$ echo "Hello from user space" > /proc/proc_example
$ cat /proc/proc_example
Hello from user space
相关推荐
A小辣椒2 小时前
TShark:Wireshark CLI 功能
linux
A小辣椒6 小时前
TShark:基础知识
linux
AlfredZhao8 小时前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao1 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334661 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪1 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠2 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush42 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5202 天前
Linux 11 动态监控指令top
linux
不会C语言的男孩2 天前
Linux 系统编程 · 第 8 章:进程基础
linux·c语言