Linux驱动:register_chrdev_region、 alloc_chrdev_region
dev_t 类型
定义:dev_t 是设备号的类型,本质是 unsigned int,高 12 位为主设备号,低 20 位为次设备号。
MAJOR(dev_t dev); // 从 dev_t 中提取主设备号
MINOR(dev_t dev); // 从 dev_t 中提取次设备号
MKDEV(int major, int minor); // 根据主/次设备号创建 dev_t
静态分配设备号:register_chrdev_region
int register_chrdev_region(dev_t from, unsigned count, const char *name);
功能:向内核注册一段连续的设备号(主设备号需手动指定)。
参数:
from:起始设备号(通过 MKDEV 创建)。
count:连续设备号的数量。
name:设备名(显示在 /proc/devices 中)。
返回值:
成功:返回 0。
失败:返回负错误码(如 -EBUSY 表示主设备号已被占用)。
cdev_put
cdev_put 的核心作用是减少字符设备结构的引用计数。当引用计数降为 0 时,内核会自动释放该字符设备结构占用的内存。
引用计数:
每个 struct cdev 都有一个内部引用计数,用于跟踪有多少地方正在使用该设备结构。当计数为 0 时,内核认为该设备不再被使用,可以安全释放。
void cdev_put(struct cdev *p);
p:指向要减少引用计数的字符设备结构(struct cdev)的指针。
必须在 cdev_del() 之后调用 cdev_put(),以确保正确管理引用计数
初始化 struct cdev:cdev_init
void cdev_init(struct cdev *cdev, const struct file_operations *fops);
功能:初始化 cdev 结构体,关联文件操作函数。
参数:
cdev:指向 struct cdev 的指针。
fops:指向 file_operations 结构体的指针(定义设备的读写等操作)。
static struct file_operations my_fops = {.owner = THIS_MODULE,.open = my_open,.read = my_read,.write = my_write,.release = my_release,
};struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops);
my_cdev.owner = THIS_MODULE; // 设置模块所有者
添加字符设备到系统:cdev_add
int cdev_add(struct cdev *p, dev_t dev, unsigned count);
功能:将初始化后的 cdev 注册到内核,使其与设备号关联。
参数:
p:指向 struct cdev 的指针。
dev:关联的起始设备号。
count:设备号数量(需与 register_chrdev_region 的 count 一致)。
返回值:
成功:返回 0。
失败:返回负错误码(如 -EBUSY 表示设备号已被使用)。
从系统移除字符设备:cdev_del
功能:从内核中移除 cdev,解除设备号与 cdev 的关联。
参数:
p:指向要移除的 struct cdev 的指针。
注意事项:
必须在模块卸载时调用,否则会导致内核资源泄漏。
调用后,设备号仍需通过 unregister_chrdev_region 释放。
释放设备号:unregister_chrdev_region
void unregister_chrdev_region(dev_t from, unsigned count);
功能:释放之前通过 register_chrdev_region 注册的设备号。
参数:
from:起始设备号(需与注册时一致)。
count:设备号数量(需与注册时一致)。
module_test.c
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/uaccess.h> // copy_to_user, copy_from_user
#include <linux/kernel.h>
#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h> #define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT#define rGPJ0CON *((volatile unsigned int *) GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *) GPJ0DAT)#define BUFFER_SIZE 100
#define MYMAJOR 250
#define MYNAME "mychartest"int mymajor;
static char buffer[BUFFER_SIZE];static dev_t dev_num; // 设备号(主设备号+次设备号)
static struct cdev my_cdev; // 字符设备结构int ctrled(int sta)
{if(sta){rGPJ0CON = 0x11111111;//rGPJ0DAT =((1<<3) | (1<<4) | (1<<5)) //灭1rGPJ0DAT =((1<<3) | (0<<4) | (0<<5)) ;//亮0printk(KERN_INFO "rGPJ0CON = %p\n",rGPJ0CON);printk(KERN_INFO "rGPJ0DAT = %d\n",rGPJ0DAT);printk(KERN_INFO "GPJ0CON = %p\n",GPJ0CON);printk(KERN_INFO "GPJ0DAT = %p\n",GPJ0DAT);}else{rGPJ0DAT =((0<<3) | (1<<4) | (1<<5)); //灭}}
// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 读取设备函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_read\n");// 确保用户缓冲区足够大if (count < sizeof(unsigned int)) {return -EINVAL;}// 读取寄存器值unsigned int reg_value = rGPJ0DAT;printk(KERN_INFO "reg_value = %d\n",reg_value);printk(KERN_INFO "sizeof(reg_value) = %d\n",sizeof(reg_value));if (copy_to_user(buf, ®_value, sizeof(reg_value))) {return -EFAULT;}// 更新文件位置*f_pos += sizeof(reg_value);// 返回实际传输的字节数return sizeof(reg_value);
}// 写入设备函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_write\n");memset(buffer,0,sizeof(buffer));if (copy_from_user(buffer, buf, count)) {return -EFAULT;}if(!strcmp(buffer,"1")){printk(KERN_INFO "1\n");ctrled(1);}else if(!strcmp(buffer,"0")){printk(KERN_INFO "0\n");ctrled(0);}return 0;
}// 定义file_operations结构体实例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一样,都这样写.open = my_open,.release = my_release,.write = my_write,.read = my_read,};// 模块安装函数
static int __init chrdev_init(void)
{ int ret;printk(KERN_INFO "chrdev_init helloworld init\n");// 静态分配设备号(使用预先定义的主设备号 MYMAJOR)dev_num = MKDEV(MYMAJOR, 0); // 创建设备号(主设备号=MYMAJOR,次设备号=0)// 注册设备号范围(请求使用该主设备号,次设备号从0开始,数量为1)unsigned count = 1;ret = register_chrdev_region(dev_num, count, MYNAME);if (ret < 0) {printk(KERN_ERR "Failed to register char device region (major=%d)\n", MYMAJOR);return ret;}printk(KERN_INFO "Registered major number: %d\n", MYMAJOR);// 初始化 cdev 结构cdev_init(&my_cdev, &my_fops);my_cdev.owner = THIS_MODULE;// 注册字符设备到内核ret = cdev_add(&my_cdev, dev_num, 1);if (ret < 0) {printk(KERN_ERR "Failed to add char device\n");unregister_chrdev_region(dev_num, 1); // 释放已注册的设备号return ret;}printk(KERN_INFO "chrdev_init helloworld successs... mymajor = %d\n", MYMAJOR);
}// 模块下载函数
static void __exit chrdev_exit(void)
{// 移除字符设备cdev_del(&my_cdev);cdev_put(&my_cdev); // 减少 cdev 引用计数(静态分配需调用)// 释放设备号unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "chrdev_exit helloworld exit\n");}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
alloc_chrdev_region
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
功能:向内核请求分配一段连续的字符设备号(动态分配主设备号,固定起始次设备号)。
参数:
dev:输出参数,用于存储分配的第一个设备号(通过 MAJOR() 和 MINOR() 解析)。
baseminor:起始次设备号(通常为 0)。
count:需要分配的设备号数量(例如,若需创建 3 个设备,则 count=3)。
name:设备名(显示在 /proc/devices 中,通常为驱动名)。
返回值:
成功:返回 0,分配的设备号存入 *dev。
失败:返回负错误码(如 -ENFILE 表示设备号用尽)。
使用alloc_chrdev_region自动分配设备号
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/uaccess.h> // copy_to_user, copy_from_user
#include <linux/kernel.h>
#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h> #define GPJ0CON S5PV210_GPJ0CON
#define GPJ0DAT S5PV210_GPJ0DAT#define rGPJ0CON *((volatile unsigned int *) GPJ0CON)
#define rGPJ0DAT *((volatile unsigned int *) GPJ0DAT)#define BUFFER_SIZE 100
#define MYMAJOR 250
#define MYNAME "mychartest"int mymajor;
static char buffer[BUFFER_SIZE];static dev_t dev_num; // 设备号(主设备号+次设备号)
static struct cdev my_cdev; // 字符设备结构int ctrled(int sta)
{if(sta){rGPJ0CON = 0x11111111;//rGPJ0DAT =((1<<3) | (1<<4) | (1<<5)) //灭1rGPJ0DAT =((1<<3) | (0<<4) | (0<<5)) ;//亮0printk(KERN_INFO "rGPJ0CON = %p\n",rGPJ0CON);printk(KERN_INFO "rGPJ0DAT = %d\n",rGPJ0DAT);printk(KERN_INFO "GPJ0CON = %p\n",GPJ0CON);printk(KERN_INFO "GPJ0DAT = %p\n",GPJ0DAT);}else{rGPJ0DAT =((0<<3) | (1<<4) | (1<<5)); //灭}}
// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 读取设备函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_read\n");// 确保用户缓冲区足够大if (count < sizeof(unsigned int)) {return -EINVAL;}// 读取寄存器值unsigned int reg_value = rGPJ0DAT;printk(KERN_INFO "reg_value = %d\n",reg_value);printk(KERN_INFO "sizeof(reg_value) = %d\n",sizeof(reg_value));if (copy_to_user(buf, ®_value, sizeof(reg_value))) {return -EFAULT;}// 更新文件位置*f_pos += sizeof(reg_value);// 返回实际传输的字节数return sizeof(reg_value);
}// 写入设备函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_write\n");memset(buffer,0,sizeof(buffer));if (copy_from_user(buffer, buf, count)) {return -EFAULT;}if(!strcmp(buffer,"1")){printk(KERN_INFO "1\n");ctrled(1);}else if(!strcmp(buffer,"0")){printk(KERN_INFO "0\n");ctrled(0);}return 0;
}// 定义file_operations结构体实例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一样,都这样写.open = my_open,.release = my_release,.write = my_write,.read = my_read,};// 模块安装函数
static int __init chrdev_init(void)
{ int ret;printk(KERN_INFO "chrdev_init helloworld init\n");// 动态分配设备号(主设备号自动分配,次设备号从0开始,分配1个)ret = alloc_chrdev_region(&dev_num, 0, 1, "my_device");if (ret < 0) {printk(KERN_ERR "Failed to allocate char device number\n");return ret;}// 打印分配的主设备号和次设备号printk(KERN_INFO "Allocated major: %d, minor: %d\n", MAJOR(dev_num), MINOR(dev_num));// 初始化 cdev 结构cdev_init(&my_cdev, &my_fops);my_cdev.owner = THIS_MODULE;// 注册字符设备到内核ret = cdev_add(&my_cdev, dev_num, 1);if (ret < 0) {printk(KERN_ERR "Failed to add char device\n");unregister_chrdev_region(dev_num, 1); // 释放已注册的设备号return ret;}printk(KERN_INFO "chrdev_init helloworld successs... mymajor = %d\n", MYMAJOR);
}// 模块下载函数
static void __exit chrdev_exit(void)
{// 移除字符设备cdev_del(&my_cdev);// 释放设备号unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "chrdev_exit helloworld exit\n");}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息
cdev_alloc
struct cdev *cdev_alloc(void);
功能:动态分配并初始化一个 struct cdev 对象,返回指向该对象的指针。
返回值:
成功:返回指向 struct cdev 的指针。
失败:返回 NULL(内存分配失败)。
#include <linux/module.h> // module_init module_exit
#include <linux/init.h> // __init __exit
#include <linux/fs.h>
#include <linux/uaccess.h> // copy_to_user, copy_from_user
#include <linux/kernel.h>
#include <mach/gpio-bank.h>
#include <mach/regs-gpio.h>
#include <linux/cdev.h> #define MYNAME "mychartest"static dev_t dev_num; // 设备号(主设备号+次设备号)
struct cdev *my_cdev;// 打开设备函数
static int my_open(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device opened\n");return 0;
}// 释放设备函数
static int my_release(struct inode *inode, struct file *filp) {printk(KERN_INFO "Device released\n");return 0;
}// 读取设备函数
static ssize_t my_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_read\n");return 0;
}// 写入设备函数
static ssize_t my_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) {printk(KERN_INFO "my_write\n");return 0;
}// 定义file_operations结构体实例
static const struct file_operations my_fops = {.owner = THIS_MODULE,//全部一样,都这样写.open = my_open,.release = my_release,.write = my_write,.read = my_read,
};// 模块安装函数
static int __init chrdev_init(void)
{ int ret;printk(KERN_INFO "chrdev_init helloworld init\n");my_cdev = cdev_alloc(); // 动态分配if (!my_cdev) {return -ENOMEM;}// 设置文件操作my_cdev->ops = &my_fops;my_cdev->owner = THIS_MODULE;// 动态分配设备号(主设备号自动分配,次设备号从0开始,分配1个)ret = alloc_chrdev_region(&dev_num, 0, 1, "my_device");if (ret < 0) {printk(KERN_ERR "Failed to allocate char device number\n");return ret;}// 打印分配的主设备号和次设备号printk(KERN_INFO "Allocated major: %d, minor: %d\n", MAJOR(dev_num), MINOR(dev_num));// 注册字符设备到内核ret = cdev_add(my_cdev, dev_num, 1);if (ret < 0) {printk(KERN_ERR "Failed to add char device\n");unregister_chrdev_region(dev_num, 1); // 释放已注册的设备号return -1;}return 0; // 添加返回值
}// 模块下载函数
static void __exit chrdev_exit(void)
{// 移除字符设备cdev_del(my_cdev);// 释放设备号unregister_chrdev_region(dev_num, 1);printk(KERN_INFO "chrdev_exit helloworld exit\n");}module_init(chrdev_init);
module_exit(chrdev_exit);// MODULE_xxx这种宏作用是用来添加模块描述信息
MODULE_LICENSE("GPL"); // 描述模块的许可证
MODULE_AUTHOR("aston"); // 描述模块的作者
MODULE_DESCRIPTION("module test"); // 描述模块的介绍信息
MODULE_ALIAS("alias xxx"); // 描述模块的别名信息