register_chrdev_region()
或alloc_chrdev_region()
来注册一个字符设备的设备号,大家都知道Linux是通过设备号来找到相应的驱动程序的,所以你要注册字符设备的时候,需要一个设备号或系统为你指定一个设备号。register_chrdev_region()
是注册指定设备号的字符设备,这个如果注册设备号已经存在则有可能会失败,而alloc_chrdev_region()
是系统分配一个未使用的设备号然后再注册.cdev_add()
函数,把该设备号相应的文件操作file_operation
文件操作入口表添加到内核的散列表里面,其中file_operation
是需要在驱动里面实现的。然后使用命令mknod
,创建与该字符设备(设备号)对应的设备节点,当有用户程序打开这个设备并对其操作时候,系统会调用kobj_lookup()
函数,根据设备编号就可以找到 cdev
结构变量,从而取出其中的 ops (file_operation)
字段,跳到相应的驱动入口去执行。cdev_del()
函数把散列表里面的数据清除,再调用unregister_chrdev_region()
.alloc_register_region()
register_chrdev_region()
字符设备编号
内核中所有已分配的字符设备编号都记录在一个名为 chrdevs
散列表里。该散列表中的每一个元素是一个char_device_struct
结构。
static struct char_device_struct {
struct char_device_struct *next; // 指向散列冲突链表中的下一个元素的指针
unsigned int major; // 主设备号
unsigned int baseminor; // 起始次设备号
int minorct; // 设备编号的范围大小
char name[64]; // 处理该设备编号范围内的设备驱动的名称
struct file_operations *fops;
struct cdev *cdev; // 指向字符设备驱动程序描述符的指针
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
内核用dev_t
类型 (dev_t
是一个32位的数,12位表示主设备号,20为表示次设备号。
在实际使用中,是通过
(dev_t)–>主设备号、次设备号 | MAJOR(dev_t dev) ,MINOR(dev_t dev) |
---|---|
主设备号、次设备号–>(dev_t) | MKDEV(int major,int minor) |
加载内核模块的时候,会可以cat /proc/devices
里面看到你注册的设备和主设备号。
该函数的作用是,注册一个注定设备号的字符驱动。注册一组设备编号范围(即一个 char_device_struct 结构)
函数原型
int register_chrdev_region(dev_t from, unsigned count, const char *name); //指定设备编号
from: 要分配的设备编号范围的初始值(次设备号常设为0);
count: 连续编号范围.
name: 编号相关联的设备名称. (/proc/devices);
若有重合部分,则
register_chrdev_region
返回-EBUSY,表示系统正忙,申请的设备号已被占用;若无重合部分,则register_chrdev_region
返回0,表示成功
函数 register_chrdev_region()
主要执行以下步骤:
char_device_struct
结构,并用 0 填充。char_device_struct
结构中的初始设备号,范围大小及设备驱动名称。char_device_struct
结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。char_device_struct
结构插入散列表中,并返回 char_device_struct
结构的地址。int alloc_chrdev_region(dev_t *dev,unsigned int firstminor,unsigned int count,char *name);
firstminor: 是请求的最小的次编号;
count: 是请求的连续设备编号的总数;
name: 为设备名,返回值小于0表示分配失败。
返回的设备号存放在 *dev中。
然后通过major=MMOR(dev)
获取主设备号。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数
*cdev:cdev
结构体。
*fops: file_operations指针。
cdev.owner = THIS_MODULE;
cdev.ops = &fops;
cdev设置完成,通知内核struct cdev的信息(在执行这步之前必须确定你对struct cdev的以上设置已经完成!)
对于已经知道了主设备号,添加设备就用
int cdev_add(struct cdev *dev,dev_t num,unsigned int count);
第一个参数是设备,第二个参数是设备号,第三个参数是要注册的次设备数目
如果是动态申请的设备号,就用
cdev_add(struct cdev *dev, MKDEV(mem_major, minor), MEMDEV_NR_DEVS);
第一个参数是设备,第二个参数是设备号,第三个参数是要注册的次设备数目。
完成这一步之后,你已经注册了字符设备,但是你还不能对它进行操作(打开,关闭读,写等),你需要使用mknod
命令手动的去生成一个设备号对应的索引节点(设备节点)。
在Linux中使用cdev
结构体描述字符设备,cdev
结构体定义:
struct cdev {
struct kobject kobj;
struct module *owner;
const struct file_operations *ops;
struct list_head list;
dev_t dev;
unsigned int count;
};
kobj
一个嵌入在该结构中的内核对象。它用于该数据结构的一般管理(是一个重要的数据结构,后在后面对其进行详细的介绍)。
owner
指向提供驱动程序的模块。
ops
是一组文件操作,实现了与硬件通信的具体操作。
dev
指定了设备号。
count
表示与该设备关联的从设备的数目。
list
用来实现一个链表,其中包含所有表示该设备的设备特殊文件的inode。
静态:
struct cdev my_cdev;
cdev_init(&my_cdev,&fops);
my_cdev.owner = THIS_MODULE;
动态(就是通过kmalloc去申请cdev结构):
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops= &my_fops;
my_cdev.owner= THIS_MODULE;
注册:
int cdev_add(struct cdev *dev,dev_t num,unsigned int count);
num是设备号,count经常取1
注销:
void cdev_del(struct cdev *dev);
void cdev_del(struct cdev *p);
unregister_chrdev_region( dev_t dev,MEMDEV_NR_DEVS) //第以个参数是设备号,第二个参数是要注册的次设备数目
手动创建
加载驱动后去查看/proc/devices
文件中查看它的主设备号.
通过mknod
命令去手动创建,例如:mknod /dev/hello c 250 0,/dev/hello
为设备节点名字,c代表字符设备,250和0代表它的主次设备号。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
###b 2. 写open,write,read等函数
static int seconds_drv_open(struct inode *inode,struct file *file)
{
//printk("seconds_drv_open\n");
/* 配置gpf4,5,6引脚为输出 */
return 0;
}
static ssize_t seconds_drv_write(struct file *file,const char __user *buf,size_t count,loff_t *ppos)
{
//printk("seconds_drv_write\n");
return 0;
}
static struct file_operations seconds_drv_fops={
.owner = THIS_MODULE,
.open = seconds_drv_open,
.write = seconds_drv_write,
};
static int seconds_drv_init(void)
{
register_chrdev_region(num_dev, number_minor_num, DEVICE_NAME);
return 0;
}
static void seconds_drv_exit(void)
{
unregister_chrdev_region(MKDEV(major_num, minor_num),number_minor_num);
}
module_init(seconds_drv_init);
module_exit(seconds_drv_exit);
MODULE_LICENSE("GPL");
//内核模块传参数
//让内核传进来空闲的主、次设备号
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
char01.c
/*包含初始化宏定义的头文件,代码中的module_init和module_exit在此文件中*/
#include
/*包含初始化加载模块的头文件,代码中的MODULE_LICENSE在此头文件中*/
#include
/*定义module_param module_param_array的头文件*/
#include
/*定义module_param module_param_array中perm的头文件*/
#include
/*三个字符设备函数*/
#include
/*MKDEV转换设备号数据类型的宏定义*/
#include
/*定义字符设备的结构体*/
#include
/*分配内存空间函数头文件*/
#include
/*包含函数device_create 结构体class等头文件*/
#include
#define DEVICE_NAME "chardevicenode"
#define number_minor_num 2
#define MAJOR_NUM 0
#define MINOR_NUM 0
#define DEVICE_SIZE 3000
MODULE_LICENSE("Dual BSD/GPL");
/*声明是开源的,没有内核版本限制*/
MODULE_AUTHOR("iTOPEET_dz");
/*声明作者*/
/*主设备号*/
int major_num = MAJOR_NUM;
/*次设备号*/
int minor_num = MINOR_NUM;
/*因为有两个次设备,把cdev设备封装成一个结构体*/
struct reg_dev{
char * data;
unsigned long size;
struct cdev dev_cdev;
};
struct reg_dev *mydevice;
//定义一个class
static struct class *myclass;
static void reg_cdev_init(struct reg_dev * dev, int index)
{
int err;
/*偏移量,有不止一个设备*/
int devno = MKDEV(major_num,minor_num+index);
/*设备初始化*/
cdev_init(&dev->dev_cdev, &my_fops);
dev->dev_cdev.owner = THIS_MODULE;
dev->dev_cdev.ops = &my_fops;
/*注册设备*/
err = cdev_add(&dev->dev_cdev,devno,1);
if(err){
printk(KERN_EMERG "cdev_add %d is fail! %d\n",index,err);
}
else{
printk(KERN_EMERG "cdev_add %d is success!\n",index);
}
}
//file_operations
struct file_operations my_fops = {
.owner = THIS_MODULE,
};
//内核模块传参数
module_param(major_num, int, S_IRUSR);
module_param(minor_num, int, S_IRUSR);
//初始化
static int scdev_init(void)
{
int ret = 0, i;
dev_t num_dev;
printk(KERN_EMERG "module_arg1 is %d\n", major_num);
printk(KERN_EMERG "module_arg2 is %d\n", minor_num);
if(major_num){
//获得dev_t类型的设备号
num_dev = MKDEV(major_num, minor_num);
//静态申请设备号
ret = register_chrdev_region(num_dev, number_minor_num, DEVICE_NAME);
}else{
/*动态注册设备号*/
ret = alloc_chrdev_region(&num_dev, minor_num, number_minor_num, DEVICE_NAME);
/*获得主设备号*/
major_num = MAJOR(num_dev);
printk(KERN_EMERG "alloc_chrdev_region request is %d\n", major_num);
}
if(ret < 0){
printk(KERN_EMERG "register_chrdev_region request %d is failed\n", major_num);
}
/*设备初始化*/
for(i=0;i<number_minor_num;i++){
/*注册设备到系统*/
reg_cdev_init(&mydevice[i], i);
}
printk(KERN_EMERG "scdev_init!\n");
/*打印信息,KERN_EMERG表示紧急信息*/
return 0;
fail:
/*注销设备号*/
unregister_chrdev_region(MKDEV(major_num, minor_num),number_minor_num);
printk(KERN_EMERG "kmalloc is fail!\n");
return ret;
}
static void scdev_exit(void)
{
int i;
printk(KERN_EMERG "scdev WORLD exit!\n");
/*除去字符设备*/
for(i=0;i<number_minor_num;i++){
cdev_del(&(mydevice[i].dev_cdev));
}
/*释放内存*/
kfree(mydevice);
unregister_chrdev_region(MKDEV(major_num, minor_num),number_minor_num);
}
module_init(scdev_init);
module_exit(scdev_exit);
测试文件,char01_test.c
#include
#include
#include
#include
/////////////////////////////////////////////////
int main(int argc, char **argv)
{
int fd;
fd = open("/dev/chardevicenode", O_RDONLY);
if (fd < 0)
{
printf("open /dev/chardevicenode failed!/n");
printf("%s/n", strerror(errno));
return -1;
}
printf("open /dev/chardevicenode ok!/n");
close(fd);
return 0;
}