linux设备驱动——字符设备驱动

目录

一、概述

二、字符设备

2.1 字符设备数据抽象

2.1.1 字符设备方法 

2.1.2 cdev接口

2.2 设备号

2.2.1 设备号相关的接口

三、设备节点

四、实例


一、概述

二、字符设备

2.1 字符设备数据抽象

struct cdev {
	struct kobject kobj;
	struct module *owner;
	const struct file_operations *ops;
	struct list_head list;
	dev_t dev;
	unsigned int count;
} __randomize_layout;

抽象结构cdev{},具体看下字段

  1. 继承关系:kobj
  2. 属性:       count,dev,owner
  3. 方法:       ops
  4. 管理:       list

2.1.1 字符设备方法 

file_operations{},放到文件的方法中

2.1.2 cdev接口

  • struct cdev *cdev_alloc(void);

cdev结构的分配可以使用动态或者静态的方法,cdev_alloc对应动态分配,简单看下实现:

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);
	}
	return p;
}

一般的cdev作为父类嵌入到具体的字符设备时,此时cdev是直接嵌入的,无法使用cdev_alloc了,这时候可以随子类一起分配,并调用

cdev_init初始化:

  • void cdev_init(struct cdev *, const struct file_operations *);

cdev初始化完成,要被内核管理起来,通过下面的注册接口:

  • int cdev_add(struct cdev *p, dev_t dev, unsigned count)
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
	int error;

	p->dev = dev;
	p->count = count;

	error = kobj_map(cdev_map, dev, count, NULL, //是通过hash 主设备号保存数据cdev
			 exact_match, exact_lock, p);
	if (error)
		return error;

	kobject_get(p->kobj.parent);

	return 0;
}
  • void cdev_del(struct cdev *p)

2.2 设备号

字符设备通过设备号标识,在内核中通过如下结构表示:

[linux/include/types.h]

typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t		dev_t;

设备号分为主设备号和次设备号,主设备号指定驱动程序,次设备号指定设备在该驱动程序中的哪一个,需要使用下面的宏操作:

#define MINORBITS	20
#define MINORMASK	((1U << MINORBITS) - 1)

#define MAJOR(dev)	((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev)	((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi)	(((ma) << MINORBITS) | (mi))

2.2.1 设备号相关的接口

  • int register_chrdev_region(dev_t from, unsigned count, const char *name)

该函数分配调用者指定的设备号——从from到from+count

int register_chrdev_region(dev_t from, unsigned count, const char *name)
{
	for (n = from; n < to; n = next) {
		next = MKDEV(MAJOR(n)+1, 0); 
		if (next > to)  //从这可以看出,count是可以跨越主设备号的,但是处理上每次最多处理一个主设备号
			next = to;
		cd = __register_chrdev_region(MAJOR(n), MINOR(n),
			       next - n, name);
	}

设备号在内核中是以hash管理的,相关的数据结构:

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个slot,通过major,即主设备号进行hash,而 __register_chrdev_region在指定设备号的情况下,只是进行一个hash的插入操作(片段):

static struct char_device_struct *
__register_chrdev_region(unsigned int major, unsigned int baseminor,
			   int minorct, const char *name)
{
	cd->major = major;
	cd->baseminor = baseminor;
	cd->minorct = minorct;
	strlcpy(cd->name, name, sizeof(cd->name));

	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;

	cd->next = *cp;
	*cp = cd;

}
  • int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)

动态分配主设备号。

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;
}

在__register_chrdev_region对应如下片段:

if (major == 0) {
	ret = find_dynamic_major();
	if (ret < 0) {
		pr_err("CHRDEV \"%s\" dynamic allocation region is full\n", name);
		    goto out;
		}
	major = ret;
}

通过find_dynamic_major看到分配规则:

static int find_dynamic_major(void)
{
	int i;
	struct char_device_struct *cd;

	for (i = ARRAY_SIZE(chrdevs)-1; i >= CHRDEV_MAJOR_DYN_END; i--) { //254开始递减查找
		if (chrdevs[i] == NULL)
			return i;
	}

#define CHRDEV_MAJOR_DYN_EXT_START 511
#define CHRDEV_MAJOR_DYN_EXT_END 384

	for (i = CHRDEV_MAJOR_DYN_EXT_START;
	     i >= CHRDEV_MAJOR_DYN_EXT_END; i--) { //如果没有,则从511-384寻找
		for (cd = chrdevs[major_to_index(i)]; cd; cd = cd->next)
			if (cd->major == i)
				break;

		if (cd == NULL)
			return i;
	}

	return -EBUSY;
}
  • void unregister_chrdev_region(dev_t from, unsigned count)

三、设备节点

设备节点可以静态创建:

mknod /dev/testcdev c 2 0

也可以动态创建:TO DO

四、实例

在实际应用中,cdev处了用于真实驱动之外,还经常被作为一个基本的框架来使用,一些(如misc)利用cdev达到为上层提供更复杂功能的目的。从上面可知,一个字符驱动的基本步骤:

  1. 分配并初始化cdev
  2. 分配设备号
  3. 注册cdev到系统中

misc也是一个字符设备,他使用主设备号10,在misc_init中调用了register_chrdev

register_chrdev(MISC_MAJOR, "misc", &misc_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 *cd;
	struct cdev *cdev;

	cd = __register_chrdev_region(major, baseminor, count, name);

	cdev = cdev_alloc();

	cdev->owner = fops->owner;
	cdev->ops = fops;
	kobject_set_name(&cdev->kobj, "%s", name);

	err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);

}

可以看出就是上面三个步骤的罗列。

misc通过预先向cdev申请一个主设备号为10,次设备号数量为255的设备资源。通过提供misc_register接口提供misc设备注册,每个misc设备可以动态或静态指定次设备号,misc通过链表将misc设备管理起来并通过次设备号区分,当任意misc设备对其方法进行调用时,misc会将其重载成对应的misc设备方法,这样节省了主设备号

  • int misc_register(struct miscdevice *misc)

猜你喜欢

转载自blog.csdn.net/whenloce/article/details/87873628