【linux驱动】用户空间程序与内核模块交互-- IOCTL和Netlink

创建自定义的IOCTL(输入/输出控制)或Netlink命令以便用户空间程序与内核模块交互涉及几个步骤。这里将分别介绍这两种方法。

一、IOCTL 方法

1. 定义IOCTL命令

在内核模块中,需要使用宏定义你的IOCTL命令。通常情况下,IOCTL命令包括了一个命令编号、请求类型的方向(读/写/两者)以及数据大小:

cpp 复制代码
#include <linux/ioctl.h>

#define MY_IOCTL_TYPE 'x'  // 通常是一个字符

#define MY_IOCTL_CMD1 _IOR(MY_IOCTL_TYPE, 1, my_data_struct)
#define MY_IOCTL_CMD2 _IOW(MY_IOCTL_TYPE, 2, my_data_struct)
// ...

2. 实现ioctl函数

在你的内核模块中,实现ioctl系统调用的函数处理:

cpp 复制代码
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg) {
    my_data_struct data;
    switch (cmd) {
        case MY_IOCTL_CMD1:
            if (copy_from_user(&data, (my_data_struct __user *)arg, sizeof(data)))
                return -EFAULT;
            // 处理MY_IOCTL_CMD1
            break;
        case MY_IOCTL_CMD2:
            // 处理MY_IOCTL_CMD2
            if (copy_to_user((my_data_struct __user *)arg, &data, sizeof(data)))
                return -EFAULT;
            break;
        default:
            return -ENOTTY; // 未知的命令
    }
    return 0; // 成功
}

const struct file_operations fops = {
    .unlocked_ioctl = my_ioctl,
    // 其他的file_operations成员
};

3. 在用户空间调用IOCTL

应用程序使用`ioctl`系统调用与内核模块交流:

cpp 复制代码
#include <sys/ioctl.h>
#include <fcntl.h>

int fd = open("/dev/mydevice", O_RDWR);
my_data_struct data;
// 设置 data
ioctl(fd, MY_IOCTL_CMD2, &data);
// 读取 data
ioctl(fd, MY_IOCTL_CMD1, &data);
close(fd);

在内核模块中,创建并初始化Netlink Socket:

cpp 复制代码
#include <net/sock.h>
struct sock *nl_sk = NULL;

static void nl_recv_msg(struct sk_buff *skb) {
    // 从skb中解析出消息并处理
}

static int __init my_module_init(void) {
    struct netlink_kernel_cfg cfg = {
        .input = nl_recv_msg,
    };
    nl_sk = netlink_kernel_create(&init_net, NETLINK_USER, &cfg);
    if (!nl_sk) {
        pr_err("Error creating socket.\n");
        return -10;
    }

    return 0;
}

2. 实现Netlink消息处理函数

如上所示,`nl_recv_msg`是在用户空间发送消息到内核时调用的接收消息处理函数。处理逻辑根据具体需求实现。

3. 用户空间程序

在用户空间程序中,使用Netlink进行通讯:

cpp 复制代码
#include <sys/socket.h>
#include <linux/netlink.h>

struct sockaddr_nl src_addr, dest_addr;
struct nlmsghdr *nlh = NULL;
int nl_sock;

// 创建Netlink Socket
nl_sock = socket(PF_NETLINK, SOCK_RAW, NETLINK_USER);

// 初始化地址结构
memset(&src_addr, 0, sizeof(src_addr));
src_addr.nl_family = AF_NETLINK;
src_addr.nl_pid = getpid(); // 自进程ID

bind(nl_sock, (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;   // 对端的ID,0表示内核
dest_addr.nl_groups = 0; // 无组播

// 发送消息到内核

记得在模块中注册Netlink操作,并且在模块退出时释放Netlink Socket,用户空间程序需要负责构造和解码Netlink消息。以上只是一个概述,实现时往往需要处理更多的细节和错误情况。

三、创建一个字符设备让用户空间程序进行读写操作,内核模块可以对这些操作进行响应

在Linux中,字符设备是可以进行按字节流读写操作的设备。创建一个字符设备使得用户空间的程序可以打开、读写、关闭等操作,并使得内核模块能够对这些操作进行响应,通常是通过实现一个设备驱动来完成的。`ioctl`是一个系统调用,用于设备特定的操作,如配置或获取设备信息。在字符设备驱动中实现`ioctl`是可选的,取决于设备是否需要提供额外的设备控制功能。

以下是创建和注册字符设备的基本步骤:

1. 分配设备号:

使用`alloc_chrdev_region`来动态申请主设备号和从设备号,或者使用`register_chrdev_region`如果你希望静态指定设备号。

2. 创建设备类和设备节点:

通常采用`class_create`创建一个设备类,并使用`device_create`创建设备节点。设备节点是用户空间与设备交云的接口,在`/dev/`目录下创建。

3. 初始化`cdev`结构:

`cdev`结构代表字符设备的内核结构。使用`cdev_init`来初始化`cdev`结构,并关联该结构与之前定义的文件操作函数集合。

4. 添加 cdev 到内核中:

使用`cdev_add`将`cdev`结构添加到内核中,设备就会变为活跃状态,用户空间就可以访问它了。

5. 实现文件操作函数:

定义一个包含`open`、`release`、`read`、`write`等操作的`file_operations`结构,这样用户空间应用就可以通过系统调用来操作驱动。

例如,以下代码段演示了以上步骤的基本框架:

cpp 复制代码
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>

static int major;
static struct class *my_class;
static struct cdev my_cdev;

static int my_open(struct inode *inode, struct file *file)
{
    // 打开设备的代码
    return 0;
}

static ssize_t my_read(struct file *file, char __user *buf, size_t count, loff_t *f_pos)
{
    // 读取设备的代码
    return 0; // 返回读取的字节数
}

static ssize_t my_write(struct file *file, const char __user *buf, size_t count, loff_t *f_pos)
{
    // 写入设备的代码
    return count; // 返回写入的字节数
}

static int my_release(struct inode *inode, struct file *file)
{
    // 关闭设备的代码
    return 0;
}

static const struct file_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .read = my_read,
    .write = my_write,
    .release = my_release,
    // 如果你需要使用ioctl,则在这里添加.unlocked_ioctl = my_ioctl,
};

static int __init my_init(void)
{
    dev_t dev_id;
    
    // 1. 分配设备号
    if (alloc_chrdev_region(&dev_id, 0, 1, "my_device") < 0) {
        return -1;
    }
    major = MAJOR(dev_id);
    
    // 2. 创建设备类和设备节点
    my_class = class_create(THIS_MODULE, "my_device_class");
    device_create(my_class, NULL, dev_id, NULL, "mydevice");
    
    // 3. 初始化cdev结构
    cdev_init(&my_cdev, &my_fops);
    
    // 4. 添加cdev到内核中
    if (cdev_add(&my_cdev, dev_id, 1) < 0) {
        unregister_chrdev_region(dev_id, 1);
        return -1;
    }
    
    return 0;
}

static void __exit my_exit(void)
{
    dev_t dev_id = MKDEV(major, 0);
    
    // 5. 从系统中删除cdev
    cdev_del(&my_cdev);
    
    // 销毁设备节点和设备类
    device_destroy(my_class, dev_id);
    class_destroy(my_class);
    
    // 释放设备号
    unregister_chrdev_region(dev_id, 1);
}

module_init(my_init);
module_exit(my_exit);

MODULE_LICENSE("GPL");

在`my_fops`中,你可以实现`.unlocked_ioctl`(或`.ioctl`,取决于内核版本)来响应`ioctl`调用。请注意,这只是一个简单的框架。在实际的驱动实现中,你将需要填充这些函数,处理错误情况,并且可能需要处理并发控制和同步问题。

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