红尘有梦txt全集下载:对linux字符设备的理解(整体架构)

来源:百度文库 编辑:偶看新闻 时间:2024/05/05 03:03:49

主要数据结构:

struct cdev {
struct kobject kobj; //内嵌的kobj,对象管理用
struct module *owner; //模块
const struct file_operations *ops; //文件操作
struct list_head list;
dev_t dev; //起始设备号
unsigned int count; //设备个数,一个cdev可以表示几个设备号
};
//通过i节点可以判断设备是字符设备还是块设备,并得到设备号,得到设备号找到kobject,在通过kobject找到cdev
//怎么通过设备号找到kobject的,可以通过下面这个数据结构和相关的函数实现
struct kobj_map { //对kobject进行管理,方便查找等操作 
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;
};
可以将这个数据结构,分成两部分
struct probe {
struct probe *next; //指向下一个probe,这些probe具有相同的主设备号
dev_t dev; //起始设备号
unsigned long range; //多少个设备号
struct module *owner; //模块
kobj_probe_t *get; //获得kobject方法
int (*lock)(dev_t, void *);//加锁
void *data; //数据
} ;
struct kobj_map { 
struct probe *probes[255]; //255个指向probe的指针,和255个主设备对用
struct mutex *lock; //锁,对这个结构操作时,先得到,保证互斥访问
};
先来分析这个结构的用法,具体看和它相关的几个函数:

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) //为设备号位dev,范围为range的设备申请probe结构,并加入domain中进行管理
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; //根据设备号,和范围计数出需要多少个probe结构,一个probe最多能包含255个设备号
unsigned index = MAJOR(dev); //
unsigned i;
struct probe *p;

if (n > 255)
n = 255;

p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL); //申请所需要的probe

if (p == NULL)
return -ENOMEM;

for (i = 0; i < n; i++, p++) { //循环初始化每个probe
p->owner = module;
p->get = probe; 
p->lock = lock;
p->dev = dev;
p->range = range; (range ?,每个都是range大小?,对!!!)
p->data = data;
}
mutex_lock(domain->lock); //要操作domain了,互斥操作
for (i = 0, p -= n; i < n; i++, p++, index++) { //p-=n?,上以操作改变了此值,将probe循环的放入合适的位置
struct probe **s = &domain->probes[index % 255];
while (*s && (*s)->range < range) //保证probe链表中range越大位置越靠后
s = &(*s)->next;
p->next = *s;
*s = p; //插入
}
mutex_unlock(domain->lock); //操作完了
return 0;
}
//疑问???
//每个domain定义了255个probe的指针指向一个链表,每个链表代表主设备号相同的设备,那么这个列表上的range加起来可能大于255个次设备号??,
//继续往下看,看完后就明白了,
void kobj_unmap(struct kobj_map *domain, dev_t dev, unsigned long range) //这个函数完成和上面函数相反的过程
{
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; //计数要去掉的probe的个数
unsigned index = MAJOR(dev);
unsigned i;
struct probe *found = NULL;

if (n > 255)
n = 255;

mutex_lock(domain->lock);
for (i = 0; i < n; i++, index++) {
struct probe **s;
for (s = &domain->probes[index % 255]; *s; s = &(*s)->next) {
struct probe *p = *s;
if (p->dev == dev && p->range == range) { //如果注册时候的设备号dev和range都相同说明找到了
*s = p->next;
if (!found)
found = p; //因为申请的时候是连续,而且第一个肯定是在前面
break;
}
}
}
mutex_unlock(domain->lock);
kfree(found); //释放资源
}

//关键函数,在domain中查找设备号dev的kobject
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)
continue;
if (p->range - 1 >= best)
break;
if (!try_module_get(p->owner))
continue;
//如果执行到这里说明dev_t包含在p中
owner = p->owner; //得到注册时的参数
data = p->data;
probe = p->get;
best = p->range - 1; 
*index = dev - p->dev;
if (p->lock && p->lock(dev, data) < 0) {
module_put(owner);
continue;
}
mutex_unlock(domain->lock);
kobj = probe(dev, index, data); //通过注册的函数得到kobj
/* Currently ->owner protects _only_ ->probe() itself. */
module_put(owner);
if (kobj)
return kobj;//返回kobj
goto retry;
}
mutex_unlock(domain->lock);//解锁
return NULL;
}

//初始化,并返回
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);
int i;

if ((p == NULL) || (base == NULL)) {
kfree(p);
kfree(base);
return NULL;
}

base->dev = 1;
base->range = ~0;
base->get = base_probe;
for (i = 0; i < 255; i++) //初始化所有的链表头
p->probes[i] = base;
p->lock = lock;
return p;
}

接着看,字符设备是怎么使用kobj_map管理kobject的:
chrdev_init()调用
--》cdev_map = kobj_map_init(base_probe, &chrdevs_lock);
//cdev_map是个全局的变量,赋值管理所有的字符设备的kobject
i 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);
//exact_match获取kobject,exact_lock枷锁,执行exact_match的参数为p即这个设备的cdev指针,通过这个指针就可以找到对应kobject了
}
//上面用的的两个函数
static struct kobject *exact_match(dev_t dev, int *part, void *data)
{
struct cdev *p = data;
return &p->kobj;
}

static int exact_lock(dev_t dev, void *data)
{
struct cdev *p = data;
return cdev_get(p) ? 0 : -1;
}

//通过上面的数据结构能找到设备号对应的cdev了,但这还不够,我没还需要对驱动进行管理,比如申请设备号??所以有了下面一个数据结构

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[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) { //如果major=0,表示申请一个可用的,不等于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);

for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //
if ((*cp)->major > major ||
((*cp)->major == major &&
(((*cp)->baseminor >= baseminor) ||
((*cp)->baseminor + (*cp)->minorct > baseminor))))
break; 
//所有的char_device_struct都是以设备号排序,的所以找的char_device_struct结构是满足要求的给的设备号的结构
/* 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;//将申请的设备号加入到找的char_device_struct的前面,这样保证了从小到大的排序
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}

static struct char_device_struct *
__unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct)
{
struct char_device_struct *cd = NULL, **cp;
int i = major_to_index(major);

mutex_lock(&chrdevs_lock); 
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //找到满足要求的char_device_struct结构边删除掉
if ((*cp)->major == major &&
(*cp)->baseminor == baseminor &&
(*cp)->minorct == minorct)
break;
if (*cp) { 
cd = *cp;
*cp = cd->next;
}
mutex_unlock(&chrdevs_lock);
return cd;
}

int register_chrdev_region(dev_t from, unsigned count, const char *name) //申请设备号
{
struct char_device_struct *cd;
dev_t to = from + count;
dev_t n, next;

for (n = from; n < to; n = next) { 
next = MKDEV(MAJOR(n)+1, 0); //下一主设备号
if (next > to)
next = to;
cd = __register_chrdev_region(MAJOR(n), MINOR(n),
next - n, name);//调用前面的函数
if (IS_ERR(cd))
goto fail;
}
return 0;
fail:
to = n;
for (n = from; n < to; n = next) {
next = MKDEV(MAJOR(n)+1, 0);
kfree(__unregister_chrdev_region(MAJOR(n), MINOR(n), next - n));
}
return PTR_ERR(cd);
}

int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)//动态的分配设备号
{
struct char_device_struct *cd;
cd = __register_chrdev_region(0, baseminor, count, name);
if (IS_ERR(cd))
return PTR_ERR(cd);
*dev = MKDEV(cd->major, cd->baseminor);
return 0;
}

int register_chrdev(unsigned int major, const char *name,
const struct file_operations *fops) //注册字符设备,256次设备
{
struct char_device_struct *cd;
struct cdev *cdev;
char *s;
int err = -ENOMEM;

cd = __register_chrdev_region(major, 0, 256, name); // 申请主设备号位major的所有设备号
if (IS_ERR(cd))
return PTR_ERR(cd);

cdev = cdev_alloc(); //分配一个cdev结构
if (!cdev)
goto out2;

cdev->owner = fops->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name); //设置kobj的名字
for (s = strchr(kobject_name(&cdev->kobj),'/'); 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(&cdev->kobj);
out2:
kfree(__unregister_chrdev_region(cd->major, 0, 256));
return err;
}
void unregister_chrdev(unsigned int major, const char *name)
{
struct char_device_struct *cd;
cd = __unregister_chrdev_region(major, 0, 256);
if (cd && cd->cdev)
cdev_del(cd->cdev);
kfree(cd);
}//完成上面相反的工作

//这个函数描述了怎么讲字符设备和设相关的操作连接到一起在看他之前我们来看
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{ //这时对一个i节点的初始化
inode->i_mode = mode;
if (S_ISCHR(mode)) { //
inode->i_fop = &def_chr_fops; 如果发现时字符设备就给它赋值def_chr_fops,打开i节点的时候调用
inode->i_rdev = rdev;
.....
}

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; 
if (!p) { //第一次打开p为NULL
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); //根据i节点上的设备号找到kobject
if (!kobj)
return -ENXIO;
new = container_of(kobj, struct cdev, kobj); //根据kobject找到cdev
spin_lock(&cdev_lock);
/* Check i_cdev again in case somebody beat us to it while
we dropped the 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); 
if (!filp->f_op)
goto out_cdev_put;

if (filp->f_op->open) { //若果给定的fop有open函数则调用它
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}

return 0;

out_cdev_put:
cdev_put(p);
return ret;
}

这样整个流程字符设备的整体架构就清楚了。关于kobject、kset、class、bus、driver、device需要慢慢梳理。

总结:
字符设备用法
首先申请字符设备号 有两个函数
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,const char *name)//动态的分配设备号
int register_chrdev_region(dev_t from, unsigned count, const char *name) //指定设备号

然后调用 
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
还用很多复杂的函数如 cdev_alloc等等。。。。
这是字符设备的核心部分,其实对于驱动开发更多的还是在kops操作函数的实现上。