当前位置:首页>Linux>Linux globalmem 字符设备驱动

Linux globalmem 字符设备驱动

  • 2026-09-06 22:32:37
Linux globalmem 字符设备驱动

一、驱动概述

globalmem 是一款经典的字符设备驱动示例,核心功能是在内核空间模拟一块固定大小的全局内存区域,提供用户空间与内核空间的数据交互接口(读、写、定位、IO控制)。本驱动基于 Linux 6.6 内核编写,适配内核最新接口规范,修正旧版本(如Linux 3.x/4.x)中过时的函数与宏定义,遵循内核编码规范,支持单设备实例,可轻松扩展为多设备实例。

Linux 6.6 内核适配要点:

  • 移除过时的 __devexit、__devinit 宏(内核5.10+已废弃),统一使用 __init、__exit。

  • IO控制接口统一使用 unlocked_ioctl(ioctl 已废弃,内核强制要求)。

  • 内存分配优先使用 kzalloc(自带清0,避免野指针),释放对应使用 kfree,遵循内核内存管理规范。

  • 打印信息规范:使用 pr_info 替代 printk(KERN_INFO),pr_notice 替代 printk(KERN_NOTICE),提升代码可读性与兼容性。

  • 模块参数、设备号申请/释放、cdev 操作接口保持兼容,但需严格遵循内核错误处理流程(避免资源泄漏)。

二、完整驱动代码(globalmem.c)

/* * Linux 6.6 内核 globalmem 字符设备驱动 * 功能:模拟全局内存,支持读写、seek、IO控制(内存清空)、文件私有数据机制 * 遵循 GPLv2 协议,适配内核6.6接口规范 */#include<linux/module.h>#include<linux/fs.h>#include<linux/init.h>#include<linux/cdev.h>#include<linux/slab.h>#include<linux/uaccess.h>#include<linux/ioctl.h>// 设备相关宏定义(Linux 6.6 推荐规范:使用幻数避免IO命令冲突)#define GLOBALMEM_MAGIC  'g'// 幻数(0~0xff,避免与内核现有幻数冲突)#define GLOBALMEM_SIZE   0x1000               // 全局内存大小(4KB)#define MEM_CLEAR        _IO(GLOBALMEM_MAGIC, 0) // IO控制命令:清空内存(无数据传输)#define GLOBALMEM_MAJOR  230                  // 预设主设备号(可通过模块参数修改)#define DEVICE_NAME      "globalmem"// 设备名称// 全局变量声明staticint globalmem_major = GLOBALMEM_MAJOR;  // 主设备号module_param(globalmem_major, int, S_IRUGO);  // 模块参数:允许用户空间修改主设备号MODULE_PARM_DESC(globalmem_major, "Major number of globalmem device (default: 230)");// 设备结构体(封装cdev和内存区域,体现内核面向对象封装思想)structglobalmem_dev {structcdevcdev;// 字符设备核心结构体unsignedchar mem[GLOBALMEM_SIZE];  // 模拟的全局内存区域};staticstructglobalmem_dev *globalmem_devp;// 设备结构体实例指针// 函数声明(驱动核心接口)staticintglobalmem_open(struct inode *inode, struct file *filp);staticintglobalmem_release(struct inode *inode, struct file *filp);staticssize_tglobalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);staticssize_tglobalmem_write(struct file *filp, constchar __user *buf, size_t size, loff_t *ppos);staticloff_tglobalmem_llseek(struct file *filp, loff_t offset, int orig);staticlongglobalmem_ioctl(struct file *filp, unsignedint cmd, unsignedlong arg);// 文件操作结构体(关联驱动核心接口,Linux 6.6 无接口变更)staticconststructfile_operationsglobalmem_fops = {    .owner          = THIS_MODULE,          // 所属模块,避免模块被意外卸载    .llseek         = globalmem_llseek,     // 文件定位接口    .read           = globalmem_read,       // 读接口(用户->内核)    .write          = globalmem_write,       // 写接口(内核->用户)    .unlocked_ioctl = globalmem_ioctl,      // IO控制接口(无大内核锁,内核推荐)    .open           = globalmem_open,       // 设备打开接口    .release        = globalmem_release,    // 设备释放接口};/** * globalmem_open - 设备打开函数 * @inode:inode节点指针(关联设备文件的索引信息) * @filp:文件结构体指针(关联打开的设备文件) * 返回值:0-成功,负数-失败 * 功能:将设备结构体指针赋值给文件私有数据,供后续接口使用 */staticintglobalmem_open(struct inode *inode, struct file *filp){// 将文件私有数据指向设备结构体实例,后续read/write/ioctl可直接获取    filp->private_data = globalmem_devp;return0;  // 打开成功(无额外初始化操作)}/** * globalmem_release - 设备释放函数 * @inode:inode节点指针 * @filp:文件结构体指针 * 返回值:0-成功 * 功能:释放设备资源(本驱动无额外资源,仅返回成功) */staticintglobalmem_release(struct inode *inode, struct file *filp){return0;  // 释放成功}/** * globalmem_read - 设备读函数 * @filp:文件结构体指针 * @buf:用户空间缓冲区指针(存储读取的数据) * @size:用户请求读取的字节数 * @ppos:文件当前读写偏移量指针 * 返回值:成功读取的字节数,0-到达文件末尾,负数-失败 * 功能:将内核空间mem数组的数据拷贝到用户空间缓冲区 */staticssize_tglobalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){unsignedlong p = *ppos;          // 当前偏移量unsignedint count = size;        // 请求读取的字节数int ret = 0;                      // 返回值structglobalmem_dev *dev = filp->private_data;// 获取设备结构体实例// 边界检查:偏移量超出内存大小,返回0(表示到达文件末尾)if (p >= GLOBALMEM_SIZE)return0;// 调整读取字节数:避免超出内存剩余空间if (count > GLOBALMEM_SIZE - p)        count = GLOBALMEM_SIZE - p;// 将内核空间数据拷贝到用户空间(copy_to_user:内核->用户,失败返回未拷贝字节数)if (copy_to_user(buf, dev->mem + p, count)) {        ret = -EFAULT;  // 拷贝失败,返回错误码(坏地址)    } else {        *ppos += count; // 更新读写偏移量        ret = count;    // 返回成功读取的字节数        pr_info("read %u bytes from offset %lu\n", count, p);  // 打印日志(内核日志)    }return ret;}/** * globalmem_write - 设备写函数 * @filp:文件结构体指针 * @buf:用户空间缓冲区指针(存储要写入的数据) * @size:用户请求写入的字节数 * @ppos:文件当前读写偏移量指针 * 返回值:成功写入的字节数,负数-失败 * 功能:将用户空间缓冲区的数据拷贝到内核空间mem数组 */staticssize_tglobalmem_write(struct file *filp, constchar __user *buf, size_t size, loff_t *ppos){unsignedlong p = *ppos;          // 当前偏移量unsignedint count = size;        // 请求写入的字节数int ret = 0;                      // 返回值structglobalmem_dev *dev = filp->private_data;// 获取设备结构体实例// 边界检查:偏移量超出内存大小,返回0(无法写入)if (p >= GLOBALMEM_SIZE)return0;// 调整写入字节数:避免超出内存剩余空间if (count > GLOBALMEM_SIZE - p)        count = GLOBALMEM_SIZE - p;// 将用户空间数据拷贝到内核空间(copy_from_user:用户->内核,失败返回未拷贝字节数)if (copy_from_user(dev->mem + p, buf, count)) {        ret = -EFAULT;  // 拷贝失败,返回错误码(坏地址)    } else {        *ppos += count; // 更新读写偏移量        ret = count;    // 返回成功写入的字节数        pr_info("written %u bytes to offset %lu\n", count, p);  // 打印日志    }return ret;}/** * globalmem_llseek - 文件定位函数 * @filp:文件结构体指针 * @offset:用户请求的偏移量 * @orig:定位基准(0-文件开头,1-当前位置,2-文件末尾) * 返回值:新的偏移量,负数-失败 * 功能:修改文件当前读写偏移量,支持从开头、当前位置定位(Linux 6.6 支持文件末尾定位,本驱动暂不启用) */staticloff_tglobalmem_llseek(struct file *filp, loff_t offset, int orig){loff_t ret = 0;  // 新的偏移量switch (orig) {case0:  // 从文件开头定位(SEEK_SET)// 偏移量为负,非法请求if (offset < 0) {                ret = -EINVAL;break;            }// 偏移量超出内存大小,非法请求if ((unsignedint)offset > GLOBALMEM_SIZE) {                ret = -EINVAL;break;            }            filp->f_pos = (unsignedint)offset;  // 更新文件偏移量            ret = filp->f_pos;break;case1:  // 从当前位置定位(SEEK_CUR)// 偏移后超出内存大小,非法请求if ((filp->f_pos + offset) > GLOBALMEM_SIZE) {                ret = -EINVAL;break;            }// 偏移后为负,非法请求if ((filp->f_pos + offset) < 0) {                ret = -EINVAL;break;            }            filp->f_pos += offset;  // 更新文件偏移量            ret = filp->f_pos;break;default:  // 不支持的定位基准,返回错误            ret = -EINVAL;break;    }return ret;}/** * globalmem_ioctl - IO控制函数 * @filp:文件结构体指针 * @cmd:用户请求的IO控制命令 * @arg:用户传递的参数(本驱动无参数,置0) * 返回值:0-成功,负数-失败 * 功能:处理用户空间发送的IO控制命令(本驱动仅支持MEM_CLEAR:清空内存) */staticlongglobalmem_ioctl(struct file *filp, unsignedint cmd, unsignedlong arg){structglobalmem_dev *dev = filp->private_data;// 获取设备结构体实例// 命令合法性检查:检查幻数和命令序号(避免命令冲突)if (_IOC_TYPE(cmd) != GLOBALMEM_MAGIC)return -EINVAL;if (_IOC_NR(cmd) > 0)return -EINVAL;// 处理具体命令switch (cmd) {case MEM_CLEAR:  // 清空全局内存memset(dev->mem, 0, GLOBALMEM_SIZE);  // 将mem数组清0            pr_info("globalmem: memory cleared successfully\n");break;default:  // 不支持的命令,返回错误return -EINVAL;    }return0;  // 命令执行成功}/** * globalmem_setup_cdev - 初始化cdev结构体并添加到内核 * @dev:设备结构体实例指针 * @index:次设备号索引(单设备为0) * 功能:初始化cdev、关联文件操作结构体、将cdev添加到内核字符设备链表 */staticvoidglobalmem_setup_cdev(struct globalmem_dev *dev, int index){dev_t devno = MKDEV(globalmem_major, index);  // 生成设备号(主设备号+次设备号)// 初始化cdev:关联设备结构体和文件操作结构体    cdev_init(&dev->cdev, &globalmem_fops);    dev->cdev.owner = THIS_MODULE;  // 关联所属模块// 将cdev添加到内核(注册字符设备),次设备号范围:index ~ index+0(单设备)int err = cdev_add(&dev->cdev, devno, 1);if (err) {        pr_notice("globalmem: error %d adding cdev%d\n", err, index);    }}/** * globalmem_init - 驱动初始化函数(模块加载时执行) * 返回值:0-成功,负数-失败 * 功能:申请设备号、分配设备结构体内存、初始化cdev、注册设备 */staticint __init globalmem_init(void){int ret;dev_t devno = MKDEV(globalmem_major, 0);  // 生成设备号(次设备号为0)// 申请设备号:两种方式(预设主设备号/动态分配)if (globalmem_major) {// 预设主设备号,申请指定设备号(次设备号0~0,单设备)        ret = register_chrdev_region(devno, 1, DEVICE_NAME);    } else {// 动态分配设备号(内核自动分配主设备号)        ret = alloc_chrdev_region(&devno, 0, 1, DEVICE_NAME);        globalmem_major = MAJOR(devno);  // 获取动态分配的主设备号    }// 设备号申请失败,返回错误if (ret < 0)return ret;// 分配设备结构体内存(kzalloc:分配+清0,GFP_KERNEL:内核态常规内存分配)    globalmem_devp = kzalloc(sizeof(struct globalmem_dev), GFP_KERNEL);if (!globalmem_devp) {  // 内存分配失败        ret = -ENOMEM;goto fail_malloc;  // 跳转至错误处理,释放已申请的设备号    }// 初始化cdev并添加到内核(单设备,次设备号为0)    globalmem_setup_cdev(globalmem_devp, 0);    pr_info("globalmem: driver initialized successfully\n");    pr_info("globalmem: major number = %d\n", globalmem_major);return0;  // 初始化成功// 错误处理:内存分配失败,释放设备号fail_malloc:    unregister_chrdev_region(devno, 1);    pr_err("globalmem: failed to allocate memory, driver init failed\n");return ret;}/** * globalmem_exit - 驱动退出函数(模块卸载时执行) * 功能:从内核移除cdev、释放设备结构体内存、释放设备号,避免资源泄漏 */staticvoid __exit globalmem_exit(void){// 从内核移除cdev(注销字符设备)    cdev_del(&globalmem_devp->cdev);// 释放设备结构体内存    kfree(globalmem_devp);// 释放设备号    unregister_chrdev_region(MKDEV(globalmem_major, 0), 1);    pr_info("globalmem: driver exited successfully\n");}// 模块加载/卸载入口(Linux 6.6 无接口变更)module_init(globalmem_init);module_exit(globalmem_exit);// 模块信息声明(遵循GPL协议,否则内核拒绝加载)MODULE_AUTHOR("Linux Driver Developer");MODULE_LICENSE("GPL v2");MODULE_DESCRIPTION("Global Memory Character Device Driver for Linux 6.6 Kernel");MODULE_VERSION("1.0");

三、Makefile(Linux 6.6 内核编译适配)

编写适配 Linux 6.6 内核的 Makefile,确保驱动能够正确编译生成 .ko 模块(需指定内核源码路径)。

# Linux 6.6 内核 globalmem 驱动 Makefile# 说明:需修改 KERNELDIR 为本地 Linux 6.6 内核源码路径# 内核源码路径(请根据实际情况修改)KERNELDIR ?= /usr/src/linux-6.6.0# 当前驱动源码目录PWD := $(shell pwd)# 模块名称MODULE_NAME := globalmem# 编译规则:默认编译模块obj-m := $(MODULE_NAME).o# 编译命令:进入内核源码目录,执行模块编译all:$(MAKE) -C $(KERNELDIR) M=$(PWD) modules# 清理编译生成的文件clean:$(MAKE) -C $(KERNELDIR) M=$(PWD) clean    rm -f *.ko.cmd *.mod.cmd *.o.cmd *.symvers

四、代码解析(Linux 6.6 适配重点)

1. 内核接口适配

Linux 6.6 内核对字符设备驱动的核心接口无重大变更,但废弃了部分旧接口,本驱动主要做了以下适配:

  • 打印函数:使用 pr_info、pr_notice、pr_err 替代 printk 带日志级别,这些函数是内核推荐的打印接口,自动关联模块信息,日志输出更规范。

  • IO控制命令:使用内核推荐的 _IO 宏生成命令码,通过幻数(GLOBALMEM_MAGIC)避免不同设备驱动的命令冲突,替代了旧版本中直接定义命令为 0x1 的不规范方式。

  • 废弃宏移除:移除了 __devexit、__devinit 等内核 5.10+ 已废弃的宏,统一使用 __init(模块加载初始化)、__exit(模块卸载清理)。

2. 核心功能模块

  • 设备结构体封装:将 cdev(字符设备核心)和 mem(全局内存)封装在 struct globalmem_dev 中,体现内核面向对象的封装思想,便于后续扩展(如添加多设备实例、中断处理等)。

  • 文件私有数据:在 globalmem_open 中,将设备结构体指针赋值给 filp->private_data,后续 read、write、ioctl 等接口可直接通过该指针获取设备实例,无需全局变量遍历,提升效率且便于多设备扩展。

  • 设备号管理:支持两种设备号申请方式(预设主设备号/动态分配),动态分配可避免主设备号冲突,适配多驱动共存场景;模块卸载时,严格释放设备号和内存,避免资源泄漏。

  • 数据交互安全:使用 copy_to_user(内核->用户)和 copy_from_user(用户->内核)进行数据拷贝,这两个函数会检查用户空间地址合法性,避免内核崩溃,是内核用户空间与内核空间数据交互的标准接口。

3. 错误处理规范

Linux 6.6 内核对驱动错误处理要求更严格,本驱动遵循以下规范:

  • 设备号申请失败时,直接返回错误码,不进行后续操作。

  • 内存分配失败时,跳转至 fail_malloc 标签,释放已申请的设备号,避免资源泄漏。

  • IO控制命令、文件定位等操作,先进行合法性检查(如偏移量、命令幻数),非法请求返回对应错误码(-EINVAL、-EFAULT),符合内核错误处理规范。

五、驱动编译与测试(Linux 6.6 环境)

1. 编译前提

  • 安装 Linux 6.6 内核源码(需与当前系统内核版本一致)。

  • 安装内核编译依赖工具:sudo apt install gcc make linux-headers-6.6.0(Ubuntu/Debian 系统)。

  • 修改 Makefile 中的 KERNELDIR,指向本地 Linux 6.6 内核源码路径。

2. 编译驱动

# 进入驱动源码目录cd /path/to/globalmem-driver# 执行编译make# 编译成功后,生成 globalmem.ko 模块文件

3. 加载与测试驱动

# 加载驱动模块(需root权限)sudo insmod globalmem.ko# 查看模块是否加载成功lsmod | grep globalmem# 查看设备号(确认主设备号为230或动态分配的号码)cat /proc/devices | grep globalmem# 创建设备节点(主设备号230,次设备号0)sudo mknod /dev/globalmem c 230 0# 修改设备节点权限(允许普通用户读写)sudo chmod 666 /dev/globalmem# 测试驱动功能# 1. 写数据到设备echo"hello linux 6.6 globalmem" > /dev/globalmem# 2. 读设备数据(验证写功能)cat /dev/globalmem# 3. 清空设备内存(IO控制命令)sudo ioctl /dev/globalmem $(echo -n -e "\x01\x67\x00\x00\x00\x00\x00\x00")# 4. 再次读设备数据(验证清空功能,应无输出)cat /dev/globalmem# 卸载驱动模块sudo rmmod globalmem# 删除设备节点sudo rm /dev/globalmem

六、多设备实例扩展(可选)

基于本驱动,可轻松扩展为支持多个 globalmem 设备实例(修改以下部分即可,适配 Linux 6.6 内核):

/* * Linux 6.6 内核 globalmem 字符设备驱动(多设备实例版) * 功能:支持5个独立设备实例,每个实例拥有独立全局内存,支持读写、seek、IO控制 * 遵循 GPLv2 协议,适配内核6.6接口规范,可直接编译使用 */#include<linux/module.h>#include<linux/fs.h>#include<linux/init.h>#include<linux/cdev.h>#include<linux/slab.h>#include<linux/uaccess.h>#include<linux/ioctl.h>#include<linux/container_of.h>  // 多设备必备,用于通过cdev获取设备实例// 设备相关宏定义(多设备适配)#define GLOBALMEM_MAGIC  'g'// 幻数(避免IO命令冲突)#define GLOBALMEM_SIZE   0x1000               // 单个设备全局内存大小(4KB)#define MEM_CLEAR        _IO(GLOBALMEM_MAGIC, 0) // IO控制命令:清空内存#define GLOBALMEM_MAJOR  230                  // 预设主设备号(可通过模块参数修改)#define DEVICE_NAME      "globalmem"// 设备名称前缀#define DEVICE_NUM       5                    // 多设备实例数量(5个,次设备号0~4)// 全局变量声明staticint globalmem_major = GLOBALMEM_MAJOR;  // 主设备号module_param(globalmem_major, int, S_IRUGO);  // 模块参数:允许修改主设备号MODULE_PARM_DESC(globalmem_major, "Major number of globalmem device (default: 230)");// 设备结构体(多设备适配,每个实例独立)structglobalmem_dev {structcdevcdev;// 字符设备核心结构体unsignedchar mem[GLOBALMEM_SIZE];  // 单个设备的独立全局内存int dev_idx;               // 设备索引(0~4,区分不同设备实例)};staticstructglobalmem_dev *globalmem_devp;// 设备结构体数组指针(存储多个实例)// 函数声明(驱动核心接口,多设备通用)staticintglobalmem_open(struct inode *inode, struct file *filp);staticintglobalmem_release(struct inode *inode, struct file *filp);staticssize_tglobalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos);staticssize_tglobalmem_write(struct file *filp, constchar __user *buf, size_t size, loff_t *ppos);staticloff_tglobalmem_llseek(struct file *filp, loff_t offset, int orig);staticlongglobalmem_ioctl(struct file *filp, unsignedint cmd, unsignedlong arg);// 文件操作结构体(多设备通用,无修改)staticconststructfile_operationsglobalmem_fops = {    .owner          = THIS_MODULE,    .llseek         = globalmem_llseek,    .read           = globalmem_read,    .write          = globalmem_write,    .unlocked_ioctl = globalmem_ioctl,    .open           = globalmem_open,    .release        = globalmem_release,};/** * globalmem_open - 设备打开函数(多设备适配) * 核心:通过container_of获取当前打开的设备实例,避免全局变量遍历 */staticintglobalmem_open(struct inode *inode, struct file *filp){// 通过inode的i_cdev成员,反向获取对应的设备结构体实例(多设备关键)structglobalmem_dev *dev = container_of(inode->i_cdev, structglobalmem_dev, cdev);    filp->private_data = dev;  // 绑定设备实例到文件私有数据    pr_info("globalmem: device %d opened successfully\n", dev->dev_idx);return0;}/** * globalmem_release - 设备释放函数(多设备通用) */staticintglobalmem_release(struct inode *inode, struct file *filp){structglobalmem_dev *dev = filp->private_data;    pr_info("globalmem: device %d released successfully\n", dev->dev_idx);return0;}/** * globalmem_read - 设备读函数(多设备通用,操作当前设备实例的内存) */staticssize_tglobalmem_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){unsignedlong p = *ppos;unsignedint count = size;int ret = 0;structglobalmem_dev *dev = filp->private_data;// 获取当前设备实例if (p >= GLOBALMEM_SIZE)return0;if (count > GLOBALMEM_SIZE - p)        count = GLOBALMEM_SIZE - p;if (copy_to_user(buf, dev->mem + p, count)) {        ret = -EFAULT;    } else {        *ppos += count;        ret = count;        pr_info("globalmem: device %d read %u bytes from offset %lu\n", dev->dev_idx, count, p);    }return ret;}/** * globalmem_write - 设备写函数(多设备通用,操作当前设备实例的内存) */staticssize_tglobalmem_write(struct file *filp, constchar __user *buf, size_t size, loff_t *ppos){unsignedlong p = *ppos;unsignedint count = size;int ret = 0;structglobalmem_dev *dev = filp->private_data;// 获取当前设备实例if (p >= GLOBALMEM_SIZE)return0;if (count > GLOBALMEM_SIZE - p)        count = GLOBALMEM_SIZE - p;if (copy_from_user(dev->mem + p, buf, count)) {        ret = -EFAULT;    } else {        *ppos += count;        ret = count;        pr_info("globalmem: device %d written %u bytes to offset %lu\n", dev->dev_idx, count, p);    }return ret;}/** * globalmem_llseek - 文件定位函数(多设备通用) */staticloff_tglobalmem_llseek(struct file *filp, loff_t offset, int orig){loff_t ret = 0;structglobalmem_dev *dev = filp->private_data;switch (orig) {case0:  // SEEK_SET:从文件开头定位if (offset < 0 || (unsignedint)offset > GLOBALMEM_SIZE) {                ret = -EINVAL;break;            }            filp->f_pos = (unsignedint)offset;            ret = filp->f_pos;break;case1:  // SEEK_CUR:从当前位置定位if ((filp->f_pos + offset) > GLOBALMEM_SIZE || (filp->f_pos + offset) < 0) {                ret = -EINVAL;break;            }            filp->f_pos += offset;            ret = filp->f_pos;break;default:            ret = -EINVAL;break;    }    pr_info("globalmem: device %d seek to offset %lu\n", dev->dev_idx, ret);return ret;}/** * globalmem_ioctl - IO控制函数(多设备通用,清空当前设备实例内存) */staticlongglobalmem_ioctl(struct file *filp, unsignedint cmd, unsignedlong arg){structglobalmem_dev *dev = filp->private_data;// 命令合法性检查if (_IOC_TYPE(cmd) != GLOBALMEM_MAGIC || _IOC_NR(cmd) > 0)return -EINVAL;switch (cmd) {case MEM_CLEAR:memset(dev->mem, 0, GLOBALMEM_SIZE);            pr_info("globalmem: device %d memory cleared successfully\n", dev->dev_idx);break;default:return -EINVAL;    }return0;}/** * globalmem_setup_cdev - 初始化单个cdev并添加到内核(多设备循环调用) * @dev:单个设备实例指针 * @index:设备索引(次设备号,0~4) */staticvoidglobalmem_setup_cdev(struct globalmem_dev *dev, int index){dev_t devno = MKDEV(globalmem_major, index);    dev->dev_idx = index;  // 绑定设备索引    cdev_init(&dev->cdev, &globalmem_fops);    dev->cdev.owner = THIS_MODULE;int err = cdev_add(&dev->cdev, devno, 1);if (err) {        pr_notice("globalmem: error %d adding device %d\n", err, index);    } else {        pr_info("globalmem: device %d cdev initialized successfully\n", index);    }}/** * globalmem_init - 驱动初始化函数(多设备适配,申请多个设备号和实例) */staticint __init globalmem_init(void){int ret, i;dev_t devno = MKDEV(globalmem_major, 0);// 申请DEVICE_NUM个设备号(次设备号0~4)if (globalmem_major) {        ret = register_chrdev_region(devno, DEVICE_NUM, DEVICE_NAME);    } else {        ret = alloc_chrdev_region(&devno, 0, DEVICE_NUM, DEVICE_NAME);        globalmem_major = MAJOR(devno);    }if (ret < 0) {        pr_err("globalmem: failed to allocate device numbers\n");return ret;    }// 分配DEVICE_NUM个设备实例的内存(数组形式)    globalmem_devp = kzalloc(sizeof(struct globalmem_dev) * DEVICE_NUM, GFP_KERNEL);if (!globalmem_devp) {        ret = -ENOMEM;goto fail_malloc;    }// 循环初始化每个设备实例for (i = 0; i < DEVICE_NUM; i++) {        globalmem_setup_cdev(globalmem_devp + i, i);    }    pr_info("globalmem: %d devices initialized successfully, major number = %d\n", DEVICE_NUM, globalmem_major);return0;fail_malloc:    unregister_chrdev_region(devno, DEVICE_NUM);    pr_err("globalmem: failed to allocate memory, driver init failed\n");return ret;}/** * globalmem_exit - 驱动退出函数(多设备适配,释放所有实例资源) */staticvoid __exit globalmem_exit(void){int i;dev_t devno = MKDEV(globalmem_major, 0);// 循环移除每个设备的cdev,释放资源for (i = 0; i < DEVICE_NUM; i++) {        cdev_del(&(globalmem_devp + i)->cdev);        pr_info("globalmem: device %d cdev removed\n", i);    }    kfree(globalmem_devp);  // 释放设备实例数组内存    unregister_chrdev_region(devno, DEVICE_NUM);  // 释放所有设备号    pr_info("globalmem: all devices exited successfully\n");}// 模块加载/卸载入口module_init(globalmem_init);module_exit(globalmem_exit);// 模块信息声明MODULE_AUTHOR("Linux Driver Developer");MODULE_LICENSE("GPL v2");MODULE_DESCRIPTION("Multi-instance Global Memory Character Device Driver for Linux 6.6 Kernel");MODULE_VERSION("1.0");

多设备实例使用说明(配套上述完整代码)

  1. 编译方式:与单设备版一致,直接使用原Makefile编译,无需修改(Makefile兼容单/多设备)。

  2. 创建设备节点:加载驱动后,需为5个设备实例分别创建节点(次设备号0~4),命令如下:

    # 依次创建5个设备节点,对应设备0~4sudo mknod /dev/globalmem0 c 230 0sudo mknod /dev/globalmem1 c 230 1sudo mknod /dev/globalmem2 c 230 2sudo mknod /dev/globalmem3 c 230 3sudo mknod /dev/globalmem4 c 230 4# 修改所有节点权限,允许普通用户读写sudo chmod 666 /dev/globalmem*
  3. 测试说明:每个设备节点对应独立的全局内存,操作互不干扰,示例:

    # 向设备0写入数据echo"device 0 test data" > /dev/globalmem0# 从设备0读取数据(正常输出写入内容)cat /dev/globalmem0# 向设备1写入不同数据echo"device 1 test data" > /dev/globalmem1# 从设备0读取(仍为原有内容,不受设备1操作影响)cat /dev/globalmem0# 清空设备0内存sudo ioctl /dev/globalmem0 $(echo -n -e "\x01\x67\x00\x00\x00\x00\x00\x00")# 再次读取设备0(无输出,清空成功)cat /dev/globalmem0
  4. 卸载驱动:与单设备版一致,执行sudo rmmod globalmem即可,卸载后建议删除所有设备节点。

多设备核心适配要点

  • 新增DEVICE_NUM宏定义,控制设备实例数量,可根据需求修改(如改为3个、10个)。
  • 设备结构体新增dev_idx成员,用于区分不同设备实例,便于日志输出和调试。
  • 使用container_of函数,通过inode的i_cdev成员反向获取设备实例,替代单设备的全局变量直接引用,是多设备驱动的核心技巧。
  • 设备号申请、cdev初始化、资源释放均采用循环方式,批量处理所有设备实例,避免重复代码。

此代码可直接替换单设备版代码,编译后即可实现多设备功能,完全适配Linux 6.6内核,无任何兼容性问题。

七、注意事项

  • 主设备号冲突:若预设主设备号(230)已被其他驱动占用,可通过模块参数修改,或使用动态分配设备号(不指定主设备号,内核自动分配)。

  • 内核版本匹配:本驱动基于 Linux 6.6 内核编写,若用于其他内核版本(如5.10+),需微调打印函数和宏定义,核心功能可兼容;低于5.10的内核需修改IO控制接口和废弃宏。

  • 权限问题:设备节点默认仅root用户可读写,测试时可修改节点权限(chmod 666 /dev/globalmem),实际应用中需根据需求设置合理权限。

  • 资源释放:模块卸载前,需确保设备已关闭(无进程占用),否则会导致卸载失败,可通过 lsof /dev/globalmem 查看占用进程,关闭后再卸载模块。

最新文章

随机文章