资料介绍
在linux系统中,很多驱动是字符型驱动,有些是直接编译集成在内核中,另一些是单独编译成“。ko”动态加载的。其实字符驱动只是个外壳,用于内核与应用程序间通信,无非是调用open,release,read,write和ioctl等例程。所以根据应用不同,字符驱动能会调用其他驱动模块,如i2c、spi和v4l2等,于是字符驱动还可分WDT驱动、RTC驱动和MTD驱动等。所以在分析其他驱动模块之前有必要好好分析下字符设备驱动模型。本篇文章要讲的就是字符设备驱动模型,也就是字符设备驱动是怎么注册和注销的,怎么生成设备节点的,怎么和应用程序关联的,例程调用具体如何实现的等等。
一、字符设备驱动的注册和注销
对于写过linux-2.6内核(本文采用linux-2.6.18内核)字符驱动的程序员来说,对下面这段程序的形式肯定不陌生。int result;
/*
* Register the driver in the kernel
* Dynmically get the major number for the driver using
* alloc_chrdev_region function
*/
result = alloc_chrdev_region( 0, 1,“testchar”);
/* if it fails return error */
if (result dev = 1;
base-》range = ~0; /*初始的范围很大*/
base-》get = base_probe; /*保存函数指针*/
for (i = 0; i probes[i] = base; /*所有指针都指向同一个base */
p-》lock = lock;
return p;
}。
复制代码该函数只是分配了一个结构体struct kobj_map,并做了初始化,保存了函数指针base_probe和全局锁lock。
下面就按照驱动注册流程一个个解析这些例程调用吧。首先是alloc_chrdev_region()函数,解析它之前,先看看结构体(定义了255个结构体指针),static struct char_device_struct {
/*被255整除后相同的设备号链成一个单向链表*/
struct char_device_struct *next;
unsigned int major; /*主设备号*/
unsigned int baseminor; /*次设备起始号*/
int minorct; /*次设备号范围*/
char name[64]; /*驱动的名字*/
struct file_operations *fops; /*保存文件操作指针,目前没有使用*/
struct cdev *cdev; /* will die */ /*目前没有使用*/
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; /* CHRDEV_MAJOR_HASH_SIZE = 255 */
复制代码它的作用仅仅是用于注册字符设备驱动,保存已经注册字符驱动的一些信息,如主次设备号,次设备号的数量,驱动的名字等,便于字符设备驱动注册时索引查找。
alloc_chrdev_region()函数很简单,通过调用__register_chrdev_region()来实现,通过英语注释你也可以明白,这个函数有两个作用,一是,如果主设备号为0,则分配一个最近的主设备号,返回给调用者;二是,如果主设备号不为0,则占用好该主设备号对应的位置,返回给调用者。如下,static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock( /*这下看到了吧,加锁,就允许你一个人进来*/
/* temporary */
if (major == 0) { /*如果主设备号为零,则找一个最近空闲的号码分配*/
for (i = ARRAY_SIZE(chrdevs)-1; i 》 0; i——) {
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
/*这些不用说你懂的*/
cd-》major = major;
cd-》baseminor = baseminor;
cd-》minorct = minorct;
strncpy(cd-》name,name, 64);
i = major_to_index(major);
/*如果主设备号不为0,则占用好该主设备号对应的位置*/
for (cp = *cp; cp =
if ((*cp)-》major 》 major ||
((*cp)-》major == major (*cp)-》baseminor 》= baseminor))
break;
if (*cp (*cp)-》major == major
(*cp)-》baseminor next = *cp;
*cp = cd;
mutex_unlock( /*开锁,队列里的下一个人可以进来了*/
return cd;
out:
mutex_unlock(
kfree(cd);
return ERR_PTR(ret);
}
复制代码接着是cdev_init()函数,先说说cdev的结构体,struct cdev {
struct kobject kobj; /*不多解释了,看看鄙人前面写的文章吧*/
struct module *owner; /*模块锁定和加载时用得着*/
const struct file_operations *ops; /*保存文件操作例程结构体*/
struct list_head list; /* open时,会将其inode加到该链表中,方便判别是否空闲*/
dev_t dev; /*设备号*/
unsigned int count;
};
复制代码cdev结构体把字符设备驱动和文件系统相关联,后面解析字符设备驱动怎样运行的时候会详谈。
cdev_init()函数如下,void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(
cdev-》kobj.ktype = /*卸载驱动时会用到,别急,后面详讲*/
kobject_init(
cdev-》ops = fops; /*用户写的字符设备驱动fops就保存在这了*/
}。
复制代码你也看到了,该函数就是对变量做了初始化,关于kobject的解析,建议你看看鄙人博客上写的《Linux设备模型浅析之设备篇》和《Linux设备模型浅析之驱动篇》两篇文章,这里就不详谈了。
用户的fops,在本文中是test_fops,一般形式是这样的,
static const struct file_operations test_fops = {
。owner = THIS_MODULE,
。open = test_fops_open,
。release = test_fops_release,
。ioctl = test_fops_ioctl,
。read = test_fops_read,
。write = test_fops_write,
};
复制代码
接着又调用了函数cdev_add(),这个函数又调用了kobj_map()函数,其作用就是分配一个struct probe结构体,填充该结构体中的变量并将其加入到全局的cdev_map中,说白了,就是分个一亩三分田给该字符设备驱动,并做好标记,放到主设备号对应的地方,等主人下次来找的时候能找到(使用kobj_lookup()函数,后面会讲到)。该函数是这样的,int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n 》 255)
n = 255;
/*分配了一亩三分田*/
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
/*填充些私有的东西*/
for (i = 0; i owner = module;
p-》get = probe; /*是exact_match ()函数,获取cdev结构体的kobject指针*/
p-》lock = lock; /*是exact_lock()函数,增加引用*/
p-》dev = dev;
p-》range = range;
p-》data = data; /* cdev保存到p-》data中*/
}
mutex_lock(domain-》lock);
/*将这一亩三分田加到主设备号对应的位置上去*/
for (i = 0, p -= n; i probes[index % 255];
while (*s (*s)-》range next;
p-》next = *s;
*s = p;
}
mutex_unlock(domain-》lock);
return 0;
}
复制代码接下来有class_create()函数和class_device_create()函数,前者生成一个名字为“testchar”的class,后者作用就是在/dev目录下生成设备节点,当然,需要uevent和UDEVD的支持,具体可见鄙人博客上的文章《Linux设备模型浅析之uevent篇》。
顺带说下register_chrdev()函数,其也是注册字符设备驱动,只不过是封装好的,包含了所有前面讲的注册步骤——分配一个设备号,由一个主设备号和255个次设备号组成。如下,int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
cd = __register_chrdev_region(major, 0, 256, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc(); /*这个有点不一样,动态分配的,不是调用者提供*/
if (!cdev)
goto out2;
cdev-》owner = fops-》owner;
cdev-》ops = fops;
kobject_set_name( “%s”, name);
for (s = strchr(kobject_name( s; s = strchr(s, ‘/’))
*s = ‘!’;
err = cdev_add(cdev, MKDEV(cd-》major, 0), 256);
if (err)
goto out;
cd-》cdev = cdev;
return major ? 0 : cd-》major;
out:
kobject_put(
out2:
kfree(__unregister_chrdev_region(cd-》major, 0, 256));
return err;
}
一、字符设备驱动的注册和注销
对于写过linux-2.6内核(本文采用linux-2.6.18内核)字符驱动的程序员来说,对下面这段程序的形式肯定不陌生。int result;
/*
* Register the driver in the kernel
* Dynmically get the major number for the driver using
* alloc_chrdev_region function
*/
result = alloc_chrdev_region( 0, 1,“testchar”);
/* if it fails return error */
if (result dev = 1;
base-》range = ~0; /*初始的范围很大*/
base-》get = base_probe; /*保存函数指针*/
for (i = 0; i probes[i] = base; /*所有指针都指向同一个base */
p-》lock = lock;
return p;
}。
复制代码该函数只是分配了一个结构体struct kobj_map,并做了初始化,保存了函数指针base_probe和全局锁lock。
下面就按照驱动注册流程一个个解析这些例程调用吧。首先是alloc_chrdev_region()函数,解析它之前,先看看结构体(定义了255个结构体指针),static struct char_device_struct {
/*被255整除后相同的设备号链成一个单向链表*/
struct char_device_struct *next;
unsigned int major; /*主设备号*/
unsigned int baseminor; /*次设备起始号*/
int minorct; /*次设备号范围*/
char name[64]; /*驱动的名字*/
struct file_operations *fops; /*保存文件操作指针,目前没有使用*/
struct cdev *cdev; /* will die */ /*目前没有使用*/
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; /* CHRDEV_MAJOR_HASH_SIZE = 255 */
复制代码它的作用仅仅是用于注册字符设备驱动,保存已经注册字符驱动的一些信息,如主次设备号,次设备号的数量,驱动的名字等,便于字符设备驱动注册时索引查找。
alloc_chrdev_region()函数很简单,通过调用__register_chrdev_region()来实现,通过英语注释你也可以明白,这个函数有两个作用,一是,如果主设备号为0,则分配一个最近的主设备号,返回给调用者;二是,如果主设备号不为0,则占用好该主设备号对应的位置,返回给调用者。如下,static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
{
struct char_device_struct *cd, **cp;
int ret = 0;
int i;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock( /*这下看到了吧,加锁,就允许你一个人进来*/
/* temporary */
if (major == 0) { /*如果主设备号为零,则找一个最近空闲的号码分配*/
for (i = ARRAY_SIZE(chrdevs)-1; i 》 0; i——) {
if (chrdevs[i] == NULL)
break;
}
if (i == 0) {
ret = -EBUSY;
goto out;
}
major = i;
ret = major;
}
/*这些不用说你懂的*/
cd-》major = major;
cd-》baseminor = baseminor;
cd-》minorct = minorct;
strncpy(cd-》name,name, 64);
i = major_to_index(major);
/*如果主设备号不为0,则占用好该主设备号对应的位置*/
for (cp = *cp; cp =
if ((*cp)-》major 》 major ||
((*cp)-》major == major (*cp)-》baseminor 》= baseminor))
break;
if (*cp (*cp)-》major == major
(*cp)-》baseminor next = *cp;
*cp = cd;
mutex_unlock( /*开锁,队列里的下一个人可以进来了*/
return cd;
out:
mutex_unlock(
kfree(cd);
return ERR_PTR(ret);
}
复制代码接着是cdev_init()函数,先说说cdev的结构体,struct cdev {
struct kobject kobj; /*不多解释了,看看鄙人前面写的文章吧*/
struct module *owner; /*模块锁定和加载时用得着*/
const struct file_operations *ops; /*保存文件操作例程结构体*/
struct list_head list; /* open时,会将其inode加到该链表中,方便判别是否空闲*/
dev_t dev; /*设备号*/
unsigned int count;
};
复制代码cdev结构体把字符设备驱动和文件系统相关联,后面解析字符设备驱动怎样运行的时候会详谈。
cdev_init()函数如下,void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
memset(cdev, 0, sizeof *cdev);
INIT_LIST_HEAD(
cdev-》kobj.ktype = /*卸载驱动时会用到,别急,后面详讲*/
kobject_init(
cdev-》ops = fops; /*用户写的字符设备驱动fops就保存在这了*/
}。
复制代码你也看到了,该函数就是对变量做了初始化,关于kobject的解析,建议你看看鄙人博客上写的《Linux设备模型浅析之设备篇》和《Linux设备模型浅析之驱动篇》两篇文章,这里就不详谈了。
用户的fops,在本文中是test_fops,一般形式是这样的,
static const struct file_operations test_fops = {
。owner = THIS_MODULE,
。open = test_fops_open,
。release = test_fops_release,
。ioctl = test_fops_ioctl,
。read = test_fops_read,
。write = test_fops_write,
};
复制代码
接着又调用了函数cdev_add(),这个函数又调用了kobj_map()函数,其作用就是分配一个struct probe结构体,填充该结构体中的变量并将其加入到全局的cdev_map中,说白了,就是分个一亩三分田给该字符设备驱动,并做好标记,放到主设备号对应的地方,等主人下次来找的时候能找到(使用kobj_lookup()函数,后面会讲到)。该函数是这样的,int kobj_map(struct kobj_map *domain, dev_t dev, unsigned long range,
struct module *module, kobj_probe_t *probe,
int (*lock)(dev_t, void *), void *data)
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n 》 255)
n = 255;
/*分配了一亩三分田*/
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
/*填充些私有的东西*/
for (i = 0; i owner = module;
p-》get = probe; /*是exact_match ()函数,获取cdev结构体的kobject指针*/
p-》lock = lock; /*是exact_lock()函数,增加引用*/
p-》dev = dev;
p-》range = range;
p-》data = data; /* cdev保存到p-》data中*/
}
mutex_lock(domain-》lock);
/*将这一亩三分田加到主设备号对应的位置上去*/
for (i = 0, p -= n; i probes[index % 255];
while (*s (*s)-》range next;
p-》next = *s;
*s = p;
}
mutex_unlock(domain-》lock);
return 0;
}
复制代码接下来有class_create()函数和class_device_create()函数,前者生成一个名字为“testchar”的class,后者作用就是在/dev目录下生成设备节点,当然,需要uevent和UDEVD的支持,具体可见鄙人博客上的文章《Linux设备模型浅析之uevent篇》。
顺带说下register_chrdev()函数,其也是注册字符设备驱动,只不过是封装好的,包含了所有前面讲的注册步骤——分配一个设备号,由一个主设备号和255个次设备号组成。如下,int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;
cd = __register_chrdev_region(major, 0, 256, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc(); /*这个有点不一样,动态分配的,不是调用者提供*/
if (!cdev)
goto out2;
cdev-》owner = fops-》owner;
cdev-》ops = fops;
kobject_set_name( “%s”, name);
for (s = strchr(kobject_name( s; s = strchr(s, ‘/’))
*s = ‘!’;
err = cdev_add(cdev, MKDEV(cd-》major, 0), 256);
if (err)
goto out;
cd-》cdev = cdev;
return major ? 0 : cd-》major;
out:
kobject_put(
out2:
kfree(__unregister_chrdev_region(cd-》major, 0, 256));
return err;
}
下载该资料的人也在下载
下载该资料的人还在阅读
更多 >
- Linux总线、设备、驱动模型的探究 7次下载
- 基于评分矩阵与评论文本的深度学习模型 58次下载
- 基于改进和声搜索算法的深度置信网络模型 5次下载
- 基于预训练模型和长短期记忆网络的深度学习模型 19次下载
- 基于深度学习的图像修复模型及实验对比 20次下载
- 深度模型中的优化与学习课件下载 3次下载
- 基于规范化函数的深度金字塔模型算法 14次下载
- Linux设备驱动的模型摘抄 8次下载
- 《Linux设备驱动开发详解》第6章、字符设备驱动 23次下载
- VxWorks设备驱动之字符设备驱动详解 7次下载
- Linux设备驱动开发之字符设备驱动编程分析 1次下载
- 基于linux系统的字符设备驱动研究与设计_王森 1次下载
- linux设备驱动基本原理及设备驱动的实现 123次下载
- 嵌入式Linux字符设备驱动的设计与应用
- 嵌入式Linux字符设备驱动的设计与应用
- 深度学习中的模型权重 385次阅读
- 深度学习模型训练过程详解 423次阅读
- 深度学习的模型优化与调试方法 432次阅读
- nuere-简单小巧快速的字符串解析库 460次阅读
- 大模型部署框架FastLLM实现细节解析 1170次阅读
- 深度解析如何管控SMT回流焊炉温曲线 1452次阅读
- i.MX6ULL|字符设备驱动开发实践 564次阅读
- i.MX6ULL|字符设备驱动流程深究 635次阅读
- 关于Linux字符设备基本类型 1476次阅读
- 描述u-boot驱动模型的数据结构 1412次阅读
- 基于Unrolling的深度方法及应用 4993次阅读
- 基于PXA255开发板外围字符设备的嵌入式Linux字符设备驱动设计与应用 1138次阅读
- 模型驱动深度学习的标准流程与学习方法解析 4820次阅读
- 基于Linux的设备号的构成、分配、释放的详细解析 4726次阅读
- tensorflow 训练模型之目标检测入门知识与案例解析 1.6w次阅读
下载排行
本周
- 1TPS54202H降压转换器评估模块用户指南
- 1.02MB | 7次下载 | 免费
- 2华瑞昇CR215芯片数字万用表原理图
- 0.21 MB | 1次下载 | 3 积分
- 3SMD LED选型目录 灯珠手册
- 5.36 MB | 1次下载 | 免费
- 4Tiva C系列DK-TM4C129X入门指南
- 1.69MB | 1次下载 | 免费
- 5电涌保护器(SPD)的选择、安装以及装配
- 4.57 MB | 1次下载 | 免费
- 6时源芯微EMC前车灯案例
- 458.59 KB | 1次下载 | 免费
- 7bq25890、bq25892双级联充电器EVM(PWR692)用户指南
- 2.02MB | 1次下载 | 免费
- 8TPS54426降压转换器评估模块用户指南
- 847.7KB | 次下载 | 免费
本月
- 1ADI高性能电源管理解决方案
- 2.43 MB | 156次下载 | 免费
- 22024PMIC市场洞察
- 2.23 MB | 63次下载 | 免费
- 3开关电源设计原理手册
- 1.83 MB | 30次下载 | 免费
- 4智能门锁原理图
- 0.39 MB | 28次下载 | 免费
- 5OAH0428最新规格书(中文)
- 2.52 MB | 15次下载 | 7 积分
- 6ST7789V2单芯片控制器/驱动器英文手册
- 3.07 MB | 9次下载 | 1 积分
- 7LTH7充电电路和锂电池升压5V输出电路原理图
- 0.04 MB | 7次下载 | 免费
- 8TPS54202H降压转换器评估模块用户指南
- 1.02MB | 7次下载 | 免费
总榜
- 1matlab软件下载入口
- 未知 | 935119次下载 | 10 积分
- 2开源硬件-PMP21529.1-4 开关降压/升压双向直流/直流转换器 PCB layout 设计
- 1.48MB | 420061次下载 | 10 积分
- 3Altium DXP2002下载入口
- 未知 | 233084次下载 | 10 积分
- 4电路仿真软件multisim 10.0免费下载
- 340992 | 191367次下载 | 10 积分
- 5十天学会AVR单片机与C语言视频教程 下载
- 158M | 183334次下载 | 10 积分
- 6labview8.5下载
- 未知 | 81581次下载 | 10 积分
- 7Keil工具MDK-Arm免费下载
- 0.02 MB | 73807次下载 | 10 积分
- 8LabVIEW 8.6下载
- 未知 | 65987次下载 | 10 积分
评论
查看更多