linux cdev详解
来源:互联网 发布:皮卡刻字机端口设置 编辑:程序博客网 时间:2024/06/14 01:03
linux cdev详解
谨以此文纪念过往的岁月
一.前言
以前对于cdev仅仅是知其然,而不知其所以然。在本文中,将深入理解cdev的架构以及具体的实现。
二.真实的cdev
2.1 设备号
搞驱动的都应该知道的东西,在写gpio驱动时,往往会用到以下两个函数。
alloc_chrdev_region --自动分配设备号
register_chrdev_region --分配以设定的设备号。
上面两个函数的调用很简单,当时却没有深入去理解其实现的原理,只知道其采用了hash表,但是具体怎么实现的却不知道。在这里来好好理解一下。上面两个函数的核心是__register_chrdev_region;那来看源码的实现。
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; --CHRDEV_MAJOR_HASH_SIZE = 255
你看这个结构体会发现,其定义了一个指针数组,大小为255.
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(&chrdevs_lock);
/* temporary */
if (major == 0) { --当调用alloc_chrdev_region时,传入的major为0,从表面上看调用alloc_chrdev_region会自动分配设备,但是有一个缺点就是其
分配的设备号只能在255内,而且并没有使用hash表,从而大大减少了linux所支持的设备号数。
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;
}
--以下以register_chrdev_region为例即传入major不为0。
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strncpy(cd->name,name, 64);
i = major_to_index(major); -- major % CHRDEV_MAJOR_HASH_SIZE 这里采用除留余数法来产生地址。
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) --这个是用于处理散列表的冲突。在这里采用拉链法来处理散列冲突。
if ((*cp)->major > major ||((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) { --这里判断从设备号是否出现重合。
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp; --这里是将cd插入链表中。
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
上面是分配设备号的核心函数,上面的描述比较空洞,那来看一个例子。
在两个驱动文件中都采用register_chrdev_region来分配设备号。
A文件.
dev_t devt = MKDEV(506,0);
register_chrdev_region(devt,1,"A");
B文件.
dev_t devt = MKDEV(506,1);
register_chrdev_region(devt,1,"B");
也许有人会奇怪这两个module怎么会用同一个主设备号,有人会认为是在同一个主设备号下有两个从设备,所以会采用同一个主设备号,其实不然,在命令行中输入cat /proc/devices 时会发现竟然有两个主设备号为506的设备,并且这设备号竟然大于255。到此时,你在回去去看cdev 设备号的hash表实现就不太难懂了。其实对于hash表的地址为506%255 = 251,而B的cd 等于 A的cd->next.就是hash出现地址冲突时采用拉链法来解决冲突的。
OK,到此对于cdev的设备号是如何分配的应该很清楚了吧。
2.2 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;
};
cdev_alloc->cdev_init->cdev_add
那就按照上面的顺序来分别解释。
cdev_alloc和cdev_init都比较简单,这里就不说了。在cdev_add中有一个很重要的函数kobj_map。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
其传入的参数cdev_map在刚开始的时候就分配内存并且初始化了。
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; --当rang小于1<<20时,n=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 < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
上面还是一个链表型的数组,用于保存module的信息。至于这些信息的作用哪儿有用,不急,且听我慢慢道来。
三.cdev打开
话说每一个设备在打开的时候都会使用open,而每一个cdev open的时候都会调用chrdev_open这个函数,而上面所说的cdev_add中的一些某明奇妙的东东都会在这里看到。
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev; --以此时p=NULL为例。
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); --查找kobject。这里面就知道kobj_mmap为什么那么做了。
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj); --根据kobj找到cdev
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
inode->i_cindex = idx;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
filp->f_op = fops_get(p->ops); --将file的f_op用cdev自己的ops代替。
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) { --这里将会调用设备自己的open。
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
{
struct kobject *kobj;
struct probe *p;
unsigned long best = ~0UL;
retry:
mutex_lock(domain->lock);
for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { --从链表头开始查询
struct kobject *(*probe)(dev_t, int *, void *);
struct module *owner;
void *data;
if (p->dev > dev || p->dev + p->range - 1 < dev) --还记否上面是根据什么来将probe节点插入到以probes[x]为头的链表中的。
continue;
if (p->range - 1 >= best) --我感觉这个错误时不会发生的。
break;
if (!try_module_get(p->owner)) --判断module是否在内核中,在就增加计数。否则返回0表示module不在kernel中。
continue;
owner = p->owner; --这里就是cdev->owner
data = p->data; --data = cdev
probe = p->get; --在cdev_add中就说明是exact_match
best = p->range - 1; --就是cdev_add中的参数count。
*index = dev - p->dev;
if (p->lock && p->lock(dev, data) < 0) { --p->lock = exact_lock
module_put(owner);
continue;
}
mutex_unlock(domain->lock);
kobj = probe(dev, index, data); --返回时cdev的kobject
static struct kobject *exact_match(dev_t dev, int *part, void *data)
{
struct cdev *p = data;
return &p->kobj;
}
/* Currently ->owner protects _only_ ->probe() itself. */
module_put(owner);
if (kobj)
return kobj;
goto retry;
}
mutex_unlock(domain->lock);
return NULL;
}
四.总结
到此cdev的核心算是理解了,其理解有错之处请各位多多指教。
谨以此文纪念过往的岁月
一.前言
以前对于cdev仅仅是知其然,而不知其所以然。在本文中,将深入理解cdev的架构以及具体的实现。
二.真实的cdev
2.1 设备号
搞驱动的都应该知道的东西,在写gpio驱动时,往往会用到以下两个函数。
alloc_chrdev_region --自动分配设备号
register_chrdev_region --分配以设定的设备号。
上面两个函数的调用很简单,当时却没有深入去理解其实现的原理,只知道其采用了hash表,但是具体怎么实现的却不知道。在这里来好好理解一下。上面两个函数的核心是__register_chrdev_region;那来看源码的实现。
static struct char_device_struct {
struct char_device_struct *next;
unsigned int major;
unsigned int baseminor;
int minorct;
char name[64];
struct cdev *cdev; /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; --CHRDEV_MAJOR_HASH_SIZE = 255
你看这个结构体会发现,其定义了一个指针数组,大小为255.
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(&chrdevs_lock);
/* temporary */
if (major == 0) { --当调用alloc_chrdev_region时,传入的major为0,从表面上看调用alloc_chrdev_region会自动分配设备,但是有一个缺点就是其
分配的设备号只能在255内,而且并没有使用hash表,从而大大减少了linux所支持的设备号数。
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;
}
--以下以register_chrdev_region为例即传入major不为0。
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strncpy(cd->name,name, 64);
i = major_to_index(major); -- major % CHRDEV_MAJOR_HASH_SIZE 这里采用除留余数法来产生地址。
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) --这个是用于处理散列表的冲突。在这里采用拉链法来处理散列冲突。
if ((*cp)->major > major ||((*cp)->major == major &&(((*cp)->baseminor >= baseminor) ||((*cp)->baseminor + (*cp)->minorct > baseminor))))
break;
/* Check for overlapping minor ranges. */
if (*cp && (*cp)->major == major) { --这里判断从设备号是否出现重合。
int old_min = (*cp)->baseminor;
int old_max = (*cp)->baseminor + (*cp)->minorct - 1;
int new_min = baseminor;
int new_max = baseminor + minorct - 1;
/* New driver overlaps from the left. */
if (new_max >= old_min && new_max <= old_max) {
ret = -EBUSY;
goto out;
}
/* New driver overlaps from the right. */
if (new_min <= old_max && new_min >= old_min) {
ret = -EBUSY;
goto out;
}
}
cd->next = *cp; --这里是将cd插入链表中。
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
上面是分配设备号的核心函数,上面的描述比较空洞,那来看一个例子。
在两个驱动文件中都采用register_chrdev_region来分配设备号。
A文件.
dev_t devt = MKDEV(506,0);
register_chrdev_region(devt,1,"A");
B文件.
dev_t devt = MKDEV(506,1);
register_chrdev_region(devt,1,"B");
也许有人会奇怪这两个module怎么会用同一个主设备号,有人会认为是在同一个主设备号下有两个从设备,所以会采用同一个主设备号,其实不然,在命令行中输入cat /proc/devices 时会发现竟然有两个主设备号为506的设备,并且这设备号竟然大于255。到此时,你在回去去看cdev 设备号的hash表实现就不太难懂了。其实对于hash表的地址为506%255 = 251,而B的cd 等于 A的cd->next.就是hash出现地址冲突时采用拉链法来解决冲突的。
OK,到此对于cdev的设备号是如何分配的应该很清楚了吧。
2.2 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;
};
cdev_alloc->cdev_init->cdev_add
那就按照上面的顺序来分别解释。
cdev_alloc和cdev_init都比较简单,这里就不说了。在cdev_add中有一个很重要的函数kobj_map。
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
p->dev = dev;
p->count = count;
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
其传入的参数cdev_map在刚开始的时候就分配内存并且初始化了。
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; --当rang小于1<<20时,n=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 < n; i++, p++) {
p->owner = module;
p->get = probe;
p->lock = lock;
p->dev = dev;
p->range = range;
p->data = data;
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
上面还是一个链表型的数组,用于保存module的信息。至于这些信息的作用哪儿有用,不急,且听我慢慢道来。
三.cdev打开
话说每一个设备在打开的时候都会使用open,而每一个cdev open的时候都会调用chrdev_open这个函数,而上面所说的cdev_add中的一些某明奇妙的东东都会在这里看到。
static int chrdev_open(struct inode *inode, struct file *filp)
{
struct cdev *p;
struct cdev *new = NULL;
int ret = 0;
spin_lock(&cdev_lock);
p = inode->i_cdev; --以此时p=NULL为例。
if (!p) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); --查找kobject。这里面就知道kobj_mmap为什么那么做了。
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj); --根据kobj找到cdev
spin_lock(&cdev_lock);
p = inode->i_cdev;
if (!p) {
inode->i_cdev = p = new;
inode->i_cindex = idx;
list_add(&inode->i_devices, &p->list);
new = NULL;
} else if (!cdev_get(p))
ret = -ENXIO;
} else if (!cdev_get(p))
ret = -ENXIO;
spin_unlock(&cdev_lock);
cdev_put(new);
if (ret)
return ret;
ret = -ENXIO;
filp->f_op = fops_get(p->ops); --将file的f_op用cdev自己的ops代替。
if (!filp->f_op)
goto out_cdev_put;
if (filp->f_op->open) { --这里将会调用设备自己的open。
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
{
struct kobject *kobj;
struct probe *p;
unsigned long best = ~0UL;
retry:
mutex_lock(domain->lock);
for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { --从链表头开始查询
struct kobject *(*probe)(dev_t, int *, void *);
struct module *owner;
void *data;
if (p->dev > dev || p->dev + p->range - 1 < dev) --还记否上面是根据什么来将probe节点插入到以probes[x]为头的链表中的。
continue;
if (p->range - 1 >= best) --我感觉这个错误时不会发生的。
break;
if (!try_module_get(p->owner)) --判断module是否在内核中,在就增加计数。否则返回0表示module不在kernel中。
continue;
owner = p->owner; --这里就是cdev->owner
data = p->data; --data = cdev
probe = p->get; --在cdev_add中就说明是exact_match
best = p->range - 1; --就是cdev_add中的参数count。
*index = dev - p->dev;
if (p->lock && p->lock(dev, data) < 0) { --p->lock = exact_lock
module_put(owner);
continue;
}
mutex_unlock(domain->lock);
kobj = probe(dev, index, data); --返回时cdev的kobject
static struct kobject *exact_match(dev_t dev, int *part, void *data)
{
struct cdev *p = data;
return &p->kobj;
}
/* Currently ->owner protects _only_ ->probe() itself. */
module_put(owner);
if (kobj)
return kobj;
goto retry;
}
mutex_unlock(domain->lock);
return NULL;
}
四.总结
到此cdev的核心算是理解了,其理解有错之处请各位多多指教。
0
上一篇:关于cdev platform以及class的一点见解
下一篇:linux bus总线
相关热门文章
- linux 常见服务端口
- 【ROOTFS搭建】busybox的httpd...
- xmanager 2.0 for linux配置
- 什么是shell
- linux socket的bug??
- 现在的博客积分不会更新了吗?...
- shell怎么读取网页内容...
- ssh等待连接的超时问题...
- curl: (56) Recv failure: Con...
- CACTI 不能安装WINE,怎么办?...
给主人留下些什么吧!~~
评论热议
0 0
- linux cdev详解
- linux cdev详解
- linux cdev详解
- 嵌入式 linux下cdev详解
- linux字符设备 cdev
- include/linux/cdev.h
- linux cdev 理解
- cdev
- cdev
- Linux设备驱动----之一 cdev
- Linux设备驱动----之一 cdev
- Linux 设备驱动--- struct cdev
- linux设备:cdev和kobj_map
- linux设备:cdev和kobj_map
- linux设备:cdev和kobj_map
- linux字符设备驱动 cdev
- linux字符cdev和inode的联系
- linux字符cdev和inode的联系
- C语言符号优先级
- linux内核定时器编程
- linux 等待队列
- linux bus总线
- 关于cdev platform以及class的一点见解
- linux cdev详解
- linux bus总线
- linux class device
- linux device注册
- 学习git笔记,git,最强大的版本管理工具,没有之一。
- 模电的一些零碎问题,不定期更新
- 1.the linux device model--kobject kset
- 2.the linux device model--bus device driver
- LCS LIS LCIS 字符串编辑距离 专题
原创粉丝点击
热门IT博客
热门问题
老师的惩罚
人脸识别
我在镇武司摸鱼那些年
重生之率土为王
我在大康的咸鱼生活
盘龙之生命进化
天生仙种
凡人之先天五行
春回大明朝
姑娘不必设防,我是瞎子
泪痣位置
小泪痣全文免费阅读
泪痣图片
小泪痣无防盗全文
桃花痣和泪痣
小泪痣番外怀孕
泪痣的位置
女生泪痣的正确位置
正确的泪痣的位置图
泪痣的三种位置图解
小泪痣 鹿灵
有泪痣的女人
男人泪痣的位置图片
桃花痣和泪痣的照片
泪痣的正确位置图片
他的小泪痣
有泪痣的人代表什么
桃花痣和泪痣的区别
泪痣的位置与命运图
小泪痣番外二则
什么是泪痣
泪痣好不好
左眼泪痣代表什么
好看的泪痣图片
泪痣的传说
泪痣是什么
长泪痣的女生好不好
泪痣是什么意思
有泪痣的人
泪痣在哪
流泪痣
泪痣好吗
滴泪痣
滴泪痣的位置与命运图
泪目
泪目什么意思
泪目是什么梗
永不消逝的电波全程泪目
泪窝图片
泪窝怎么消除
泪腺