字符串转map设备超过255还能加cdvmap里吗

linux驱动(99)
文件位于fs/chr_dev.c
重要的结构体
struct cdev {
struct module *
const struct file_operations *
struct list_head list;
unsigned int
struct char_device_struct {
struct char_device_struct *
unsigned int
unsigned int
char name[64];
struct cdev *
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
struct kobj_map {
struct probe {
struct probe *
unsigned long
struct module *
kobj_probe_t *get;
int (*lock)(dev_t, void *);
} *probes[255];
struct mutex *lock;
struct kobj_map
struct kobj_map里面strcut probe *probe[255]是一个哈希链表数组,如下:
struct kobj_map *cdev_map = kobj_map_init(…)
cdev_alloc\cdev_init -& kobject_init(&cdev-&kobj, &ktype_cdev_default);
cdev_add -& kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
cdev_del -& kobj_unmap(cdev_map, dev, count);
cdev_get -& try_module_get(struct module *);
-& kobject_get(struct kobject *);
cdev_put -& kobject_put(struct kobject *);
-& module_put(struct module *);
没搞明白这个计数到底有什么用处?
struct char_device_struct
如果分配了一个设备号,就会创建struct char_device_struct的对象,并将其添加到chrdevs数组中,,这样通过chrdevs数组,我们就知道分配了哪些设备号。
register_chrdev_region 分配指定的设备号 -& __register_chrdev_region
alloc_chrdev_region 动态分配设备号 -& __register_chrdev_region
以上这两个函数仅仅是注册设备号,如果要和cdev关联起来,还要调用cdev_add
register_chrdev()申请指定的设备号,并且将其注册到字符设备驱动模型中。
cdev与inode、file的关系
cdev-&list下面挂了很多inode
inode-&i_rdev是字符设备号
inode-&i_cdev对应struct cdev,所以可以通过inode找到cdev
int chrdev_open(struct inode *inode, struct file *file)
struct cdev *p = inode-&i_
filp-&f_op = fops_get(p-&ops);
filp-&f_op-&open(inode, file);
参考过以下两篇文章:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:638580次
积分:8869
积分:8869
排名:第2235名
原创:230篇
转载:138篇
评论:110条
(10)(6)(5)(4)(3)(4)(5)(4)(4)(4)(2)(5)(8)(7)(8)(6)(3)(5)(5)(10)(13)(7)(11)(9)(7)(3)(5)(8)(7)(2)(5)(8)(8)(4)(9)(10)(5)(12)(11)(6)(3)(2)(8)(11)(12)(8)(6)(5)(4)(1)(4)(3)(7)(9)(24)(5)
(window.slotbydup = window.slotbydup || []).push({
id: '4740881',
container: s,
size: '200,200',
display: 'inlay-fix'1421人阅读
Linux(62)
字符设备不需要复杂的缓冲策略,也不涉及磁盘高速缓存,处理起来比较容易
字符设备驱动程序由一个cdev结构描述:
struct cdev {
// 每个 cdev 都是一个 kobject
struct module *
// 指向实现驱动的模块
const struct file_operations *
// 操纵这个字符设备文件的方法
struct list_
// 与 cdev 对应的字符设备文件的 inode-&i_devices 的链表头
//设备号,int 类型,高12位为主设备号,低20位为次设备号
// 设备范围号大小
};list字段是双向循环链表的首部,用于收集相同字符设备驱动程序所对应的字符设备文件的索引节点。可能很多设备文件具有相同的设备号,并对应于相同的字符设备。此外,一个设备驱动程序对应的设备号可以是一个范围,而不仅仅是一个号;设备号位于同一范围内的所有设备文件由同一个字符设备驱动程序处理
一个 cdev 一般它有两种定义初始化方式:静态的和动态的。
静态内存定义初始化:
struct cdev my_
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
动态内存定义初始化:
struct cdev *my_cdev = cdev_alloc();
my_cdev-&ops = &
my_cdev-&owner = THIS_MODULE;
两种使用方式的功能是一样的,只是使用的内存区不一样,一般视实际的数据结构需求而定。
struct cdev *cdev_alloc(void)
struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);
INIT_LIST_HEAD(&p-&list);
kobject_init(&p-&kobj, &ktype_cdev_dynamic);
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
memset(cdev, 0, sizeof *cdev); 注1;
INIT_LIST_HEAD(&cdev-&list);
kobject_init(&cdev-&kobj, &ktype_cdev_default);
cdev-&ops =
}由代码可以看到,两个函数功能基本上是一致的,都是初始化内部的kobject,只是cdev_init还给ops进行了初始化。
下面以字符设备的注册注册过程为主线介绍其内部调用及相关结构。
首先看一下设备编号的内部表示
在内核中, dev_t 类型(在 &linux/types.h&中定义)用来持有设备编号 -- 主次部分都包括. 对于 2.6.0 内核, dev_t 是 32 位的量, 12 位用作主编号, 20 位用作次编号,在 &linux/kdev_t.h&中的一套宏定义. 为获得一个 dev_t 的主或者次编号, 使用:
MAJOR(dev_t dev);&
MINOR(dev_t dev);
相反, 如果你有主次编号, 需要将其转换为一个 dev_t, 使用:
MKDEV(int major, int minor);&
好了,进入正题,对照前面 &linux设备驱动模型一字符设备 驱动实例中的init中,下面就是设备号的分配了,有两种方式,register_chrdev_region和alloc_chrdev_region
int register_chrdev_region(dev_t from, unsigned count, const char *name)
struct char_device_struct *
dev_t to = from +
for (n = n & n = next) {
next = MKDEV(MAJOR(n)+1, 0);
if (next & to)
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);
if (IS_ERR(cd))
for (n = n & n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
return PTR_ERR(cd);
}register_chrdev_region接收三个参数:初始设备号(主+次)请求设备号范围大小,以及这个范围内的设备号对应的设备驱动的名称。当次设备号数目过多(count过多)的时候,次设备号可能会溢出到下一个主设备。因此我们在for语句中可以看到,首先得到下一个主设备号(其实也是一个设备号,只不过此时的次设备号为0)并存储于next中。然后判断在from的基础上再追加count个设备是否已经溢出到下一个主设备号。如果没有溢出(next小于to),那么整个for语句就只执行个一次__register_chrdev_region函数;否则当设备号溢出时,会把当前溢出的设备号范围划分为几个小范围,分别调用__register_chrdev_region函数。
如果在某个小范围调用__register_chrdev_region时出现了失败,那么会将此前分配的设备号都释放。
再来看下__register_chrdev_region
static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
int minorct, const char *name)
struct char_device_struct *cd, **
int ret = 0;
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);//配内存并用零来填充(这就是用kzalloc而不是kmalloc的原因)
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/* temporary */
if (major == 0) {//major为0,也就是未指定一个具体的主设备号,需要动态分配
for (i = ARRAY_SIZE(chrdevs)-1; i & 0; i--) {//找合适的位置
if (chrdevs[i] == NULL)
if (i == 0) {
ret = -EBUSY;
major =//若找到后,那么i不仅代表这组设备的主设备号,也代表其在散列表中的关键字
cd-&major =//将参数中的值依次赋给cd变量的对应字段
cd-&baseminor =
cd-&minorct =
strlcpy(cd-&name, name, sizeof(cd-&name));
i = major_to_index(major);//除模255运算
for (cp = &chrdevs[i]; * cp = &(*cp)-&next)//寻找正确的位置
if ((*cp)-&major & major || //主设备号(*(cp)-&major)大于我们所分配的主设备号(major)(有序)
((*cp)-&major == major &&//如果(*cp)结点和cd结点的主设备号相同
(((*cp)-&baseminor &= baseminor) || //前者的次设备号起点比cd结点的大
((*cp)-&baseminor + (*cp)-&minorct & baseminor))))//cd结点的次设备号起点小于(*cp)结点的次设备号的终点
/* Check for overlapping minor ranges.
if (*cp && (*cp)-&major == major) {//主设备号相同时才需要冲突判断
int old_min = (*cp)-&//计算出新老次设备号的范围,
int old_max = (*cp)-&baseminor + (*cp)-&minorct - 1;
int new_min =
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left.
if (new_max &= old_min && new_max &= old_max) {//新范围终点是否在老范围的之间
ret = -EBUSY;
/* New driver overlaps from the right.
if (new_min &= old_max && new_min &= old_min) {//新范围的起点是否在老范围之间
ret = -EBUSY;
//到这里的话说明没有冲突
cd-&next = *//将char_device_struct描述符插入到中途链表中
mutex_unlock(&chrdevs_lock);
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
这里有一个结构char_device_struct,用于注册字符设备驱动,保存已经注册字符驱动的一些信息,如主次设备号,次设备号的数量,驱动的名字等,便于字符设备驱动注册时索引查找
static struct char_device_struct {
/*被255整除后相同的设备号链成一个单向链表*/
struct char_device_struct *
/* 主设备号 */
/* 次设备起始号 */
/* 次设备号范围 */
char name[64];
/* 驱动的名字 */
struct file_operations *
/* 保存文件操作指针,目前没有使用 */
struct cdev *
/* will die */
/*目前没有使用*/
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; /* CHRDEV_MAJOR_HASH_SIZE = 255 */
另外一个函数就是alloc_chrdev_region,它是动态的分配一个主设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
struct char_device_struct *
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd-&major, cd-&baseminor);
}该函数接参数为设备号范围内的初始设备号,范围大小,以及设备驱动的名称,然后调用 __register_chrdev_region。
设备号分配成功了之后就是分配cdev结构了,及初始化,可以直接调用cdev_alloc进行分配和初始化,也可以先分配空间,然后调用cdev_init进行初始化
初始化完成之后 就要调用cdev_add在设备驱动程序模型中注册一个cdev描述符。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
p-&count =
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}它初始化dev和count字段,然后调用kobj_map依次建立设备驱动程序模型的数据结构,把设备号范围复制到设备驱动程序的描述符中。
cdev_map是在chrdev_init时候分配的
void __init chrdev_init(void)
cdev_map = kobj_map_init(base_probe, &chrdevs_lock);
bdi_init(&directly_mappable_cdev_bdi);
}看一下kobj_map结构
struct kobj_map {
struct probe {
struct probe *
/* 散列冲突表中的下一个元素 */
/* 字符设备驱动的设备号,包含主设备号(高12位)和次设备号(低20位) */
/* 次设备号范围 */
struct module *
/* 表明模块的归属,是THIS_MODULE */
kobj_probe_t *
/* 这里可以保存传进来的base_probe函数指针,探测谁这个设备号范围 */
int (*lock)(dev_t, void *);
/* 增加设备号范围内拥有者的引用计数器 */
void */*私有数据*/
} *probes[255];
/* 虽然大小只有255,但采用了链表的形式,可以支持到4096个主设 */
struct mutex *
/* 保存全局互斥锁,用于关键区域的保护 */
};kobj_map_init()函数传进去了两个参数,base_probe函数和chrdevs_lock互斥变量,返回一个struct kobj_map类型的指针。base_probe调用了request_module()函数,用于加载与字符驱动相关的驱动程序,被加载的驱动命名方式是char-major-主设备号-次设备号。request_module()函数,你可以看看该函数上头的英文注释,它最终会调用应用层空间的modprobe命令来加载驱动程序。实际上,没有使用该项功能
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)
struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);
struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);
if ((p == NULL) || (base == NULL)) {
kfree(base);
return NULL;
base-&dev = 1;
base-&range = ~0;/* 初始的范围很大 */
base-&get = base_ /* 保存函数指针 */
for (i = 0; i & 255; i++)
p-&probes[i] =/* 所有指针都指向同一个base */
}回到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;//根据设备号和范围,计数出需要多少个probe结构(range没有超过255的话一个就够了)
unsigned index = MAJOR(dev);
struct probe *p;
if (n & 255)
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);//分配probe结构
if (p == NULL)
return -ENOMEM;
for (i = 0; i & i++, p++) {//初始化赋值
p-&owner =
p-&range =
//把cdev结构保存在了data
mutex_lock(domain-&lock);
for (i = 0, p -= i & i++, p++, index++) {
struct probe **s = &domain-&probes[index % 255];
while (*s && (*s)-&range & range)//保证probe中ranger大的靠后
s = &(*s)-&
p-&next = *s;
mutex_unlock(domain-&lock);
到这里,该字符设备就注册进去了
字符设备驱动注册另外一个经常使用的函数 就是register_chrdev,它的参数为一个主设备号,驱动程序名字以及一个指针fops,它会分配该主设备相关的0~255的次设备号范围并进行注册,建立一个缺省的 cdev 结构. 使用这个接口的驱动必须准备好处理对所有 256 个次编号的 open 调用( 不管它们是否对应真实设备 ), 它们不能使用大于 255 的主或次编号.
static inline int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops)
return __register_chrdev(major, 0, 256, name, fops);
int __register_chrdev(unsigned int major, unsigned int baseminor,
unsigned int count, const char *name,
const struct file_operations *fops)
struct char_device_struct *
struct cdev *
int err = -ENOMEM;
cd = __register_chrdev_region(major, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
cdev = cdev_alloc();
if (!cdev)
goto out2;
cdev-&owner = fops-&
cdev-&ops =
kobject_set_name(&cdev-&kobj, &%s&, name);
err = cdev_add(cdev, MKDEV(cd-&major, baseminor), count);
cd-&cdev =
return major ? 0 : cd-&
kobject_put(&cdev-&kobj);
kfree(__unregister_chrdev_region(cd-&major, baseminor, count));
这个函数和前面讲的驱动注册基本是一致的,只是把一些流程给综合起来了。
再总结一下三个主要的结构:
&&相关文章推荐
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1175255次
积分:15516
积分:15516
排名:第737名
原创:358篇
转载:152篇
译文:60篇
评论:299条
文章:13篇
阅读:19665
文章:46篇
阅读:28547
文章:14篇
阅读:43021
文章:13篇
阅读:86553
(1)(1)(1)(1)(2)(6)(17)(19)(49)(2)(5)(2)(2)(2)(3)(2)(5)(27)(77)(44)(7)(1)(6)(23)(12)(13)(5)(6)(5)(3)(4)(5)(23)(6)(1)(1)(4)(13)(6)(7)(3)(3)(1)(22)(9)(6)(13)(12)(24)(22)(26)(10)&&&&&& 字符设备是linux最常见的一种设备类型,也是相对比较简单的。Linux文件系统的注册是先注册文件系统的类型,比如sysfs、devtmpfs、rootfs等,然后再初始化一个文件系统实例添加进系统中。字符设备的注册其实也是采用这种方式:先向系统注册一个字符设备类型,包括主次设备号,文件操作集等,然后才可能创建一个该类型的字符设备实例添加进系统中,当然这些同类型的实例都是依靠次设备号来区分的。&一、&&&&&&&&& 字符设备注册相关函数1.1&&& 高度封装的函数 @ kernel/fs/char_dev.cstatic inline int register_chrdev(unsigned int major, const char *name,&&&&&&&&&&&&&&&&&&&&&&&&&&& & const struct file_operations *fops)eg: &register_chrdev(INPUT_MAJOR, &input&, &input_fops);第一个参数是该类字符设备的主设备号,例如这里的INPUT_MAJOR是13,那么如果调用该函数注册字符设备的时候传入的是0,表示需要让系统动态分配一个主设备号给该类字符设备,并将这个主设备号返回。每类字符设备的此设备号从0~255,也就是说,每类字符设备最多可存在256个设备实例。&&&&&& &&&&&& static inline void unregister_chrdev(unsigned int major, const char *name)这个函数是注销系统中的一类字符设备,需要传入该类字符设备的主设备号和名字。&&&&&& 1.2&&& 分散调用的函数其上上面的函数都是对接下来要描述的函数的封装,我们注册字符设备的时候可以单独调用下面的函数来一步一步实现。&1.&&&&& 注册或者分配主次设备号如果需要系统动态分配主设备号的话,推荐调用下面的函数来实现:int alloc_chrdev_region(dev_t *dev, &unsigned baseminor, &unsigned count,const char *name)&&&&&&&&&&&&& 第一个参数是dev_t变量的指针,接受该函数即将返回的主次设备号组合;第二个参数baseminor和第三个参数count分别表示最小次设备号和支持的次设备号的个数;最后一个参数是该类字符设备的名字。&&&&&&&&&&&&& eg:alloc_chrdev_region(&fm-&dev_t, 0, 1, FM_NAME); 只支持一个该类型设备实例&&&&&&&&&&&&& 返回0表示成功执行,返回一个负数则是出错。&&&&&&&&&&&&& &&&&&&&&&&&&& 如果主设备号已知,就可以调用以下两种函数来注册:&&&&&&&&&&&&& int register_chrdev_region(dev_t from, unsigned count, const char *name)&&&&&&&&&&&&& static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor,&&&&&&&&&&&&&&&&&&&& & &&&&&&&&&&&&&&&&&&&&&&& &int minorct, const char *name)实际上register_chrdev_region()函数时调用了__register_chrdev_region()函数来实现的,只是返回值更简单明了而已,推荐使用函数register_chrdev_region()。例如:register_chrdev_region(MKDEV(INPUT_MAJOR, 0),255,&input&);&2.&&&&& cdev分配或者初始化struct cdev是字符设备的关键结构体,这一步就是需要分配或者初始化该结构体。如果cedv已经有现成的了,那么就可以直接调用函数cdev_init()来初始化,如果还没有分配cdev的空间那么就需要调用函数cdev_alloc()来分配空间并初始化。&&&&&& struct cdev *cdev_alloc(void);&&&&&& void cdev_init(struct cdev *cdev, const struct file_operations *fops)&&&&&& 使用cdev_alloc()函数的,记得在执行该函数之后需要对cdev-&ops进行初始化,而在cdev_init()中有做这个工作。&&&&&&& 3. 向系统注册这个字符设备类型&&&&&& int cdev_add(struct cdev *p, dev_t dev, unsigned count);&&&&&& eg: cdev_add(cdev, &MKDEV(cd-&major, baseminor), &count);&1.3& MKDE宏&&&&&& 该宏的实现是: #define MKDEV(ma,mi)&&&&& ((ma)&&8 | (mi))&&&&&& 可以看出,linux系统中主设备号最大用24位表示,而次设备号只有256个,也就是说每一种字符设备最多可以有256个设备实例存在。&二、原理实现&&&&&& 以register_chrdev(INPUT_MAJOR, &input&, &input_fops)这个为例。&&&&&& &&&&&& 2.1 字符设备类型注册&&&&&& static inline int register_chrdev(unsigned int major, const char *name,&&&&&&&&&&&&&&&&&&&&&&&&&&& & &&&&&&&&&&&&&&&&&&&&&&& const struct file_operations *fops){&&&&&& &&&&&& return __register_chrdev(major, 0, 256, name, fops);&&&&&&&&&&&&& // 256个次设备号}&&&&&& int __register_chrdev(unsigned int major, unsigned int baseminor,&&&&&&&&&&&&& &&&&& && unsigned int count, const char *name,&&&&&&&&&&&&& &&&& &&&& const struct file_operations *fops){&&&&&& struct char_device_struct *&&&&&& struct cdev *&&&&&& int err = -ENOMEM;&&&&&& &&&&&& // 注册主次设备号&&&&&& cd = __register_chrdev_region(major, baseminor, count, name);&&&&&& …&&&&&& cdev = cdev_alloc();&&&&&& …&&&&&& cdev-&owner = fops-&&&&&&& cdev-&ops = &&&&&&&&&&&&& &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // eg: &input_fops&&&&&& kobject_set_name(&cdev-&kobj, &%s&, name); &&&&& // eg: &input&&&&&&& &&&&&& &&&&&& err = cdev_add(cdev, MKDEV(cd-&major, baseminor), count);&&&&&& …&&&&&& cd-&cdev =&&&&&&& return major ? 0 : cd-&&&&&&& …}&&&&&& &&&&&& 2.1.1 主次设备号注册&&&&&& 这里可以使用第一节中出现的三个函数中alloc_chrdev_region、register_chrdev_region、__register_chrdev_region的任意一个来实现这个,只是在调用时注意入口和出口参数的不同。这里以函数__register_chrdev_region()为例来介绍,在继续之前我们先来聊一下关于这个函数的关键。&&&&&& static struct char_device_struct {&&&&&& &&&&&& struct char_device_struct *&&&&&& &&&&&&&&&&&& &&&&&& un&&&&&& &&&&&&&&&&&& &&&&&& char name[64];&&&&&& &&&&&& struct cdev *} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];&&&&&& @ kernel/include/linux/fs.h&&&&&& #define CHRDEV_MAJOR_HASH_SIZE 255&&&&&& &&&&&& chrdevs是一个大小为255的char_device_struct类型的指针数组,在字符设备的管理中,没有将其当做简单的指针数组来是使用,而是将其作为一个以主设备号对255的余数为键值的hash table在使用,也就是说,这个指针数组的每一个元素(下标为i)如果不为NULL的话,都可以将其作为一个链表来链接那些主设备号是i的倍数的字符设备的char_device_struct结构体(这就是next域的作用)。&&&&&& 那么接下来的这个函数的作用简单的来说,就是在获得chrdevs_lock互斥锁的情况下操作这个chrdevs hash table。具体如何操作,请看下面的源码分析:&&&&&& static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor,&&&&&&&&&&&&&&&&&&&& && &&&&&&&& int minorct, const char *name){&&&&&& struct char_device_struct *cd, **&&&&&& int ret = 0,&&&&&& &&&&&& cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);&&&&&& …&&&&&& mutex_lock(&chrdevs_lock);&&&&&& /* 如果主设备号是0,表示需要系统动态分配一个未占用的主设备号 */&&&&&& if (major == 0) {&&&&&&&&&&&&& for (i = ARRAY_SIZE(chrdevs)-1; i & 0; i--) { // 从chrdevs数组的最后开始动态分配,直到遇到一个未占用的指针数组元素。&&&&&&&&&&&&&&&&&&&& if (chrdevs[i] == NULL)&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& }&&&&&&&&&&&&& if (i == 0) {& // 全部都已被分配,没找到一个可用的。&&&&&&&&&&&&&&&&&&&& ret = -EBUSY;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& }&&&&&&&&&&&&& major =& // 保存分配的主设备号&&&&&&&&&&&&& ret =&&&&&& }&&&&&& // 为结构体char_device_struct各域赋值,保存major,次设备号范围,name。&&&&&& cd-&major =&&&&&& cd-&baseminor =&&&&&& cd-&minorct =&&&&&& strlcpy(cd-&name, name, sizeof(cd-&name));&&&&&& &&&&&& i = major_to_index(major); // major对 255取余数的结果得到查询hash表的键值。&&&&&& /*hash表的每一项,都是一个无头链表,链表中的每一个元素(char_device_struct结构体)是按照字符设备的major从小到大排列;major可以相同,这里依据次设备号大小排列。这里次设备号绝对不能有任何交叉和重叠区域,否则就会注册失败。*/&&&&&& // cp是一个二级指针,*cp是指向char_device_struct的指针,*cp可以是NULL。&&&&&&& for (cp = &chrdevs[i]; * cp = &(*cp)-&next)&&&&&&&&&&&&& if ((*cp)-&major & major || &&&&&&&&&&&&& &&& ((*cp)-&major == major &&&&&&&&&&&&&&& &&&& (((*cp)-&baseminor &= baseminor) ||&&&&&&&&&&&&& &&&&& ((*cp)-&baseminor + (*cp)-&minorct & baseminor))))&&&&&&&&&&&&&&&&&&&&/* 这里查找有些复杂,大致可以分成以下几种情况:1.&&&&& (*cp)-&major & major,表示新注册的major可以插入到链表中,那么将掉过下面的if条件直接将major对应的char_device_struct插入链表。2.&&&&& 上面的for循环没有被break过,而是因为*cp = NULL才退出循环的,这个时候表名新加入的major是当前链表中最大的,这样也会直接跳过接下来的if语句,而将这个major对应的char_device_struct插入链表尾。3.&&&&& (*cp)-&major == major,表示主设备号相等,所以接下来就是要判断次设备号是否会有冲突的可能:(*cp)-&major == major &&(((*cp)-&baseminor &= baseminor) ||&&&&&&&&&&&&&&&&&&& cp)-&baseminor + (*cp)-&minorct & baseminor))&&&&&& 用上面的语句来检查次设备号的冲突情况,举个例子来说:假设当前的*cp对应的字符设备的次设备号范围是{32, 64},那么这里将会检测出新字符设备的baseminor为小于64的情况(小于32或者在32和64之间),因为这两种情况将可能会和原来的次设备号有互相覆盖,所以这两种情况都会进入下面的if条件来进一步检测。&&&&&& 不过这里如果新字符设备的baseminor是大于64的话,那么就不可能发生覆盖现象,所以这种情况是不会break掉上面的for循环,直到遇到链表尾或者baseminor处于{32, 64}的另两个区间内才会退出for循环。New&&&&&&&&&&&& Old&&&&&& & [68,90]&&&&&&&&&&&&&&&&& {32,64}&&& --&&& &&&&{&&&&&&&& }&&& [& ]*/&&&&&&& /* Check for overlapping minor ranges. 进一步检测次设备号是否有交叉覆盖 */&&&&&& if (*cp && (*cp)-&major == major) {&&&&&& // 走到这条分支也不一定会有交叉,所以需要进行下面的比较&&&&&&&&&&&&& int old_min = (*cp)-&&&&&&&&&&&&&& int old_max = (*cp)-&baseminor + (*cp)-&minorct - 1;&&&&&&&&&&&&& int new_min =&&&&&&&&&&&&& int new_max = baseminor + minorct - 1;&&&&&& /*还是接着上面的例子,这里次设备号的分布又会出现以下四种情况:&&&&&&&&&&&&& New&&&&&&&&&&&&&&&&&&&&&& &Old&&&&&& 1. [16,24]&&&&&&&&&&&&&&&&& {32,64}&&&&&&&&&&&&& -- &&&[& ] {&&&&&&&&& }&&&&&& 2. [28,36]&&&&&&&&&&&&&&&&& {32,64}&&&&&&&&&&&&& --&&& [ && {& ]&&&&&& }&&&&&& 3. [40,48]&&&&&&&&&&&&&&&&& {32,64}&&&&&& &&&&&& -- && &&&&&{ &&[& ]&& }&&&&&& 4. [54,70]&&&&&&&&&&&&&&&&& {32,64}&&&&&&&&&&&& &--&&&&&&&& { &&&&&&[ &}& &]*/&&&&&&&&&&&&& /* New driver overlaps from the left.& */&&&&&&&&&&&&& if (new_max &= old_min && new_max &= old_max) {& // 检测出第2、3中情况&&&&&&&&&&&&&&&&&&&& ret = -EBUSY;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& }&&&&&&&&&&&&& /* New driver overlaps from the right.& */&&&&&&&&&&&&& if (new_min &= old_max && new_min &= old_min) {& // 检测出第4中情况&&&&&&&&&&&&&&&&&&&& ret = -EBUSY;&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& }&&&&&&&&&&&&& /* 那剩下的第一种情况就是正常的了 */&&&&&& }&&&&&&& cd-&next = * &// 层层检测下来后进行插入链表操作&&&&&& *cp =&&&&&& mutex_unlock(&chrdevs_lock);& &// 解锁互斥锁&&&&&&out:&&&&&&&&&&&&&&&&&&&&& // 出现错误时候的处理&&&&&& mutex_unlock(&chrdevs_lock);&&&&&& kfree(cd);&&&&&& return ERR_PTR(ret);}&2.1.2 cedv结构体初始化一个特定类型的字符设备用结构体cdev来描述,结构体定义如下:&&&&&& struct cdev {&&&&&& &&&&&&&&&&&& &&&&&& struct module *&&&&&& &&&&&& const struct file_operations *&&&&&& &&&&&& struct list_&&&&&& &&&&&& dev_&&&&&& &&&&&&};struct cdev *cdev_alloc(void){&& &&&&&& struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL);&& &&&&&& if (p) {&&&&&&&&& &&&&&& INIT_LIST_HEAD(&p-&list);&&&&&&&&& &&&&&& kobject_init(&p-&kobj, &ktype_cdev_dynamic);&& &&&&&& }&& &&&&&&}cdev-&owner = fops-&cdev-&ops = &&&&&&&&& // eg: &input_fopskobject_set_name(&cdev-&kobj, &%s&, name); // eg: &input&&&&&&& &&&&&& 2.1.3 向系统添加一个cdev描述的字符设备类型&&&&&& cdev_add(cdev, MKDEV(cd-&major, baseminor), count);int cdev_add(struct cdev *p, dev_t dev, unsigned count){&&&&&& p-&dev =&&&&&&&&&&&&&& // 设备号的base值&&&&&& p-&count =&&&&&&& // 次设备号的个数&&&&&& return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);}////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////kobj_map()是另外一个重点函数,关于这一个知识点,有必要从其init来分析一下。void __init chrdev_init(void){&&&&&& cdev_map = kobj_map_init(base_probe, &chrdevs_lock);&&&&&& bdi_init(&directly_mappable_cdev_bdi);}static struct kobject *base_probe(dev_t dev, int *part, void *data){&&&&&& if (request_module(&char-major-%d-%d&, MAJOR(dev), MINOR(dev)) & 0)&&&&&&&&&&&&& /* Make old-style 2.4 aliases work */&&&&&&&&&&&&& request_module(&char-major-%d&, MAJOR(dev));&&&&&& return NULL;}static struct kobj_map *cdev_struct kobj_map {&&&&&& struct probe {&&&&&&&&&&&&& struct probe *&&&&&&&&&&&&& dev_&&&&&&&&&&&&&&&&&&&&&&&&&& struct module *&&&&&&&&&&&&& kobj_probe_t *&& // &&&&&&&&&&&&& int (*lock)(dev_t, void *);&&&&&&&&&&&&& void *&&&&&& } *probes[255];&& // 大小为255的指针数组&&&&&& struct mutex *};struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock){&&&&&& struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);&&&&&& struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);&&&&&&&&&&&&& if ((p == NULL) || (base == NULL)) {&&&&&&&&&&&&& kfree(p);&&&&&&&&&&&&& kfree(base);&&&&&&&&&&&&& return NULL;&&&&&& }&&&&&&& base-&dev = 1;&&&&&& base-&range = ~0;&&&&&& base-&get = base_ &&&&&& for (i = 0; i & 255; i++)&&&&&&&&&&&&& p-&probes[i] =&&&&&& p-&lock =&&&&&&}实际上cdev_map-&probes这个指针数组也是会作为一个hash表来使用。这个函数kobj_map_init就是对这个hash表的每一项都初始化成执行同一个probe结构体(base指针所指的probe结构体)。从后面的kobj_map()的代码中可以看出,这个probe结构体是处于每一个链表的末尾。////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////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; &// n = 1&&&&&& unsigned index = MAJOR(dev); &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&& // 主设备号&&&&&&&&&&&& struct probe *p;&&&&&& // 该函数常见的用法是对cdev一个一个地进行map&&&&&& if (n & 255)&&&&&&&&&&&&& n = 255;&&&&&&& p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);&&&&&&& if (p == NULL)&&&&&&&&&&&&& return -ENOMEM;&&&&&&& for (i = 0; i & i++, p++) {&&&&&&&&&&&&& p-&owner =&&&&&&&&&&& // eg: NULL&&&&&&&&&&&&& p-&get =&&&&&&&&&&&&&&&&&& // eg: exact_match&&&&&&&&&&&&& p-&lock =&&&&&&&&&&&&&&&&&&& // eg: exact_lock&&&&&&&&&&&&& p-&dev =&&&&&&&&&&&&&& &&&&&& // 主设备号&&&&&&&&&&&&& p-&range =&&&&&&&& &&&&&& // 次设备号个数&&&&&&&&&&&&& p-&data =&&&&&&&&&&&&&&&&&&& // 主设备号对应的cdev结构体&&&&&& }&&&&&& mutex_lock(domain-&lock);&&&&&& // 而这一个循环也只是循环一次,就是为了将上面初始化好的probe函数添加到hash表对应的链表中去。&&&&&& for (i = 0, p -= i & i++, p++, index++) {&&&&&&&&&&&&& struct probe **s = &domain-&probes[index % 255];&&&&&&&&&&&&& while (*s && (*s)-&range & range)&&&&&&&&&&&&&&&&&&&& s = &(*s)-&&&&&&&&&&&&&& p-&next = *s;&&&&&&&&&&&&& *s =&&&&&& }&&&&&& mutex_unlock(domain-&lock);&&&&&& return 0;}&&&&&& 到这里,一个字符设备类型注册就这样结束了,接下来我们看一下,如何添加一个真实的字符设备实例。...
本文已收录于以下专栏:
相关文章推荐
Select函数在Socket编程中还是比较重要的,可是对于初学Socket的人来说都不太爱用Select写程序,他们只是习惯写诸如connect、 accept、recv或recvfrom这样的阻塞...
poll机制分析
          所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数。比如系统调用open、read、write、poll,与之对应的内...
本文转载自:
.cn/s/blog_00gmxo.htmlAndroid安全机制之设备管理 ( 02:20:57)转载...
一、开发环境
1、硬件平台:FS2410
2、主机:Ubuntu 10.10
3、内核版本:Linux 2.6.35
4、交叉编译工具链:arm-none-linux-gnueabi-
之前三节我们采用的查询方式,中断方式和poll方式都是应用程序主动去查询按键状态。那么有没有一种机制可以实现驱动程序主动提醒应用程序按键的状态?答案是肯定的!这就是异步通信机制。
采用异步通信机制来...
一、开发环境
1、硬件平台:FS2410
2、主机:Ubuntu 10.10
3、内核版本:Linux 2.6.35
4、交叉编译工具链:arm-none-linux-gnueabi-
一、查询方式的按键驱动程序
查询方式的按键驱动程序,与LED驱动程序类似,我们来复习一下上节的写好的LED字符设备驱动程序,改写出查询方式的按键驱动程序。首先我们要搭出字符设备驱动程序的框架:
在驱动中注册中断使用request_irq函数。参数依次为:中断号,中断处理函数, 中断触发方式和处理方式, 中断名字, 传递给中断处理函数的参数。
static int key...
他的最新文章
讲师:AI100
讲师:谢梁
您举报文章:
举报原因:
原文地址:
原因补充:
(最多只允许输入30个字)

我要回帖

更多关于 字符串转map 的文章

 

随机推荐