Linux内核修炼之字符设备分析二(源码分析)

====本文系本站原创,欢迎转载! 转载请注明出处:http://blog.csdn.net/yyplc==== 

继上篇,本篇结合源码分析

cdev数据结构:

struct cdev {
	struct kobject kobj;  //kobject实体
	struct module *owner;
	const struct file_operations *ops;  //大家熟悉的file_operations结构
	struct list_head list;              //list用于设备管理的链表
	dev_t dev;                          //设备号
	unsigned int count;                 //设备的连续次设备号的数量(范围)
};

与cdev相关的函数如下(源码在fs/char_dev.c实现):

void cdev_init(struct cdev *, const struct file_operations *); //
struct cdev *cdev_alloc(void);   //返回cdev实例
void cdev_put(struct cdev *p);   //减少cdev实例中kobject的引用计数,也就是kref的值
int cdev_add(struct cdev *, dev_t, unsigned); //
void cdev_del(struct cdev *);    //删除一个cdev实例
static int chrdev_open(struct inode *inode, struct file *filp); //打开一个设备时调用,这个函数只是内部使用(static)
下面我们选一些对我们有用的函数来了解,什么叫有用?所谓有用就是写驱动时有用的。
//作用:初始化一个cdev结构,包括kobj,关联fops函数。
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
	memset(cdev, 0, sizeof *cdev);
	INIT_LIST_HEAD(&cdev->list);
	kobject_init(&cdev->kobj, &ktype_cdev_default); //初始化cdev->kobj
	cdev->ops = fops; //关联cdev的fops
}
//作用:为系统添加一个cdev实例。ldd3上说"常常 count 是 1, 但是有多个设备号对应于一个特定的设备的情形.
//例如, 设想 SCSI 磁带驱动, 它允许用户空间来选择操作模式(例如密度), 通过安排多个次编号给每一个物理设备."
//但我们用register_chr_dev时,看到的是count = 256
先看看struct kobj_map cdev_map的结构:
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;
};
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);
}
kobj_map()会创建一个probe对象,然后将其插入cdev_map中的某一项中,并关联probe->data指向cdev,probe->range = count;
//打开char_dev时,都打开chrdev_open,因为在进入驱动的自定义的file_operations前已定义下面:
const struct file_operations def_chr_fops = {
	.open = chrdev_open,
};
//作用:根据设备号(cdev_map ,inode->i_rdev),通过kobj_lookup()来获取kobject,再通过获取cdev,最后获取关联的fops操作函数
static int chrdev_open(struct inode *inode, struct file *filp); 
除以上cdev操作函数以外,下面我们看看字符设备相关函数:可以分成两类,注册函数和注销函数
注册函数:
//通过主设备号,静态注册一个char设备,其中包括cdev_alloc(),cdev_add()过程
int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops);
//根据设备号,注册一段设备号,from是设备号,当为0时就是动态注册,count是“多少个来连续次设备号”
int register_chrdev_region(dev_t from, unsigned count, const char *name);
//动态分配设备号,dev是输出的设备号
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
//其实上面三个函数的实现都是调用这个函数的
static struct char_device_struct *__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
注销函数:
void unregister_chrdev(unsigned int major, const char *name);
static struct char_device_struct * __unregister_chrdev_region(unsigned major, unsigned baseminor, int minorct);
看看它们的调用过程:
register_chrdev(major, name, fops)--> __register_chrdev_region(major, 0, 256, name)-->cdev = cdev_alloc()
-->cdev_add(cdev, MKDEV(cd->major, 0), 256);
alloc_chrdev_region(dev, baseminor, count, name)-->__register_chrdev_region(0, baseminor, count, name)-->
*dev = MKDEV(cd->major, cd->baseminor);
register_chrdev_region(from, count, name)--> __register_chrdev_region(MAJOR(from), MINOR(from)), next-from, name);
由于以上3个函数都通过调用
__register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
先看字符设备的数据结构:
内核中所有已分配的字符设备编号都记录在一个名为 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 cdev *cdev;                  // 指向字符设备驱动程序描述符的指针
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];
定义一个指针chardevs数组,大小为CHRDEV_MAJOR_HASH_SIZE,注意,数组中的元素是一个地址,其中
#define CHRDEV_MAJOR_HASH_SIZE 255
指针数组常适用于指向若干字符串,这样使字符串处理更加灵活方便。
注意,内核并不是为每一个字符设备号定义一个 char_device_struct 结构,而是为一组对应同一个字符设备驱动的设备编号范围定义一个 
char_device_struct 结构。chrdevs 散列表的大小是 255,散列算法是把每组字符设备编号范围的主设备号以 255 取模插入相应的散列桶中。
同一个散列桶中的字符设备编号范围是按起始次设备号递增排序的。
函数 __register_chrdev_region() 主要执行以下步骤:
1. 分配一个新的 char_device_struct 结构,并用 0 填充。
2. 如果申请的设备编号范围的主设备号为 0,那么表示设备驱动程序请求动态分配一个主设备号。动态分配主设备号的原则是从散列表的最后一个桶向前寻找,那个桶是空的,主设备号就是相应散列桶的序 号。所以动态分配的主设备号总是小于 255,如果每个桶都有字符设备编号了,那动态分配就会失败。
3. 根据参数设置 char_device_struct 结构中的初始设备号,范围大小及设备驱动名称。
4. 计算出主设备号所对应的散列桶,为新的 char_device_struct 结构寻找正确的位置。同时,如果设备编号范围有重复的话,则出错返回。
5. 将新的 char_device_struct 结构插入散列表中,并返回 char_device_struct 结构的地址。

现在我们通过分析这个函数,来知道内核是怎么注册设备号的。
注册代码:
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,一个char_device_struct内存空间,并初始化为0
	cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
	if (cd == NULL)
		return ERR_PTR(-ENOMEM);


	mutex_lock(&chrdevs_lock);


	/* temporary */
	if (major == 0) {  //动态分配
		for (i = ARRAY_SIZE(chrdevs)-1; i > 0; i--) {//从254开始,也就是第255个开始
			if (chrdevs[i] == NULL)  
				break;
		}


		if (i == 0) { //因为主设备号为0,内核定义为UNNAMED_MAJOR,弃之不用
			ret = -EBUSY;
			goto out;
		}
		major = i;  //找到一个没用过的主设备号来用
		ret = major;
	}
//找到major后,给cd赋值
	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));


	i = major_to_index(major);//求余:i= major % CHRDEV_MAJOR_HASH_SIZE


	for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next) //根据主设备号,确定正确的次设备号的范围(位置),注意有可能‘回转‘的情况,所以后面会检测
		if ((*cp)->major > major ||             //如果是一个没用过的主设备号,(*cp)->next =NULL
		    ((*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; //指向下一个设备的char设备结构
	*cp = cd;       //把当前cd的地址保存到chrdevs[i]中
	mutex_unlock(&chrdevs_lock);
	return cd;   //返回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)//在chardevs[]中找到已注册的cd结构位置
		if ((*cp)->major == major &&
		    (*cp)->baseminor == baseminor &&
		    (*cp)->minorct == minorct)
			break;
	if (*cp) {//从chardevs[]中剔除cd结构,并将当前的chardevs[]指向cd的下一个char结构
		cd = *cp;
		*cp = cd->next; 
	}
	mutex_unlock(&chrdevs_lock);
	return cd;  //返回找到的cd
}

你可能感兴趣的:(数据结构,struct,File,Module,null,linux内核)