这里写代码片内核在fs.h文件中为驱动人员提供了一个现成的注册字符设备的接口register_chrdev,并在这里定义了它
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 *cd;
struct cdev *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->owner;
cdev->ops = fops;
kobject_set_name(&cdev->kobj, "%s", name);
err = cdev_add(cdev, MKDEV(cd->major, baseminor), count);
if (err)
goto out;
cd->cdev = cdev;
return major ? 0 : cd->major;
}
这个函数比上一个多了两个参数,baseminor是该字符设备的起始次设备号,count表示该设备占用多少个次设备号,在这里起始次设备号为0,次设备号个数为256,在内核中是通过一个字符设备驱动的主设备号和次设备号来对它们进行区别和管理,因此内核为字符设备驱动提供了注册设备号的接口,如上先是调用__register_chrdev_region(major, baseminor, count, name);注册设备号
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) {//如果主设备号为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;
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;
/* 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;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
}
进来这个函数先分配一个结构struct char_device_struct *cd,在内核中用这么一个结构来描述一个设备驱动,这个结构在内核的描述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];
可见在描述这个结构的同时也定义了一个用来管理字符设备驱动的指针数组chrdevs,CHRDEV_MAJOR_HASH_SIZE为255,这就跟最大次设备号255对应上了,即一个主设备号最多只能有256个次设备号。然后判断主设备号是否为0,如果是0则从从字符设备管理指针数组中找出空的一项,并把该项的序号作为主设备号,再就是判断该次设备号在该主设备号中是否已被使用,未使用返回该字符设备的描述结构表示为该设备驱动分配设备号成功,看回上面,除了调用__register_chrdev_region分配设备号外,又分配另外一个结构struct cdev,该结构用来跟字符设备驱动的文件操作函数集进行绑定用的,并调用cdev_add添加进去,如果注册成功返回该驱动的主设备号。由上可知,注册一个字符设备驱动大致分为两步:
1、调用__register_chrdev_region注册设备号
2、分配一个struct cdev *结构,初始化该结构,并调用cdev_add添加进去
在最上面可以到,调用register_chrdev注册的字符设备驱动,虽然主设备号可以自动分配或者手动赋值,但是对于注册的该设备驱动,起始次设备号固定为0,占用256个次设备号,在实际应用中,往往不需要占用这么多的次设备号,以造成系统资源的浪费,因此内核又提供了两个接口供外部调用来自己注册设备号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),前者用来自动分配一个设备号,后者用来注册一个自己定义的设备号。在linux应用层中,是通过设备文件来对设备进行操作,因此只是注册设备显然是不行的,另外还需要生成一个设备文件来供上层应用访问。在linux中,创建一个设备驱动文件可调用struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, …)来创建一个设备文件,可见创建一个设备文件需要struct class *class结构、它的parent、设备号、自定义的一个数据结构(可以为空)、最后还可以传一个可变参数进来,内核提供了创建struct class *class结构的接口class_create。到此编写一个字符设备驱动程序归结为以下步骤:
1、注册一个设备号(可用接口alloc_chrdev_region,register_chrdev_region)
2、主设备号跟文件操作函数集的绑定(cdev_add) 注:以上两步可用register_chrdev代替(次设备号固定)
3、创建一个struct class结构(class_create)
4、创建设备驱动文件(device_create)