下面是一个简单的字符设别驱动程序的源代码,本文会分析代码中涉及到的函数。
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
MODULE_AUTHOR("XXX Technologies GmbH");
MODULE_LICENSE("GPL");
#define CORDLESS_MAJOR 240
static char dev0_name[] = "char_device0";
static char dev1_name[] = "char_device1";
static char dev2_name[] = "char_device2";
static dev_t dev0_dev;
static dev_t dev1_dev;
static dev_t dev2_dev;
static struct cdev dev0_cdev;
static struct cdev dev1_cdev;
static struct cdev dev2_cdev;
static int
dev0_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arq)
{
printk("dev0_ioctl\n");
return 0;
}
static int
dev0_read(struct file *filp, char __user *buf, size_t count_want,loff_t *f_pos)
{
printk("dev0_read\n");
return 0;
}
static int
dev0_open(struct inode *inode, struct file *filp)
{
printk("dev0_open\n");
return 0;
}
static int
dev0_release(struct inode *inode, struct file *filp)
{
printk("dev0_release\n");
return 0;
}
static struct file_operations dev0_fops = {
.owner = THIS_MODULE,
.ioctl = dev0_ioctl,
.read = dev0_read,
.open = dev0_open,
.release = dev0_release,
};
static int
dev1_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arq)
{
printk("dev1_ioctl\n");
return 0;
}
static int
dev1_read(struct file *filp, char __user *buf, size_t count_want,loff_t *f_pos)
{
printk("dev1_read\n");
return 0;
}
static int
dev1_open(struct inode *inode, struct file *filp)
{
printk("dev1_open\n");
return 0;
}
static int
dev1_release(struct inode *inode, struct file *filp)
{
printk("dev1_release\n");
return 0;
}
static struct file_operations dev1_fops = {
.owner = THIS_MODULE,
.ioctl = dev1_ioctl,
.read = dev1_read,
.open = dev1_open,
.release = dev1_release,
};
static int
dev2_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arq)
{
printk("dev2_ioctl\n");
return 0;
}
static int
dev2_read(struct file *filp, char __user *buf, size_t count_want,loff_t *f_pos)
{
printk("dev2_read\n");
return 0;
}
static int
dev2_open(struct inode *inode, struct file *filp)
{
printk("dev2_open\n");
return 0;
}
static int
dev2_release(struct inode *inode, struct file *filp)
{
printk("dev2_release\n");
return 0;
}
static struct file_operations dev2_fops = {
.owner = THIS_MODULE,
.ioctl = dev2_ioctl,
.read = dev2_read,
.open = dev2_open,
.release = dev2_release,
};
static int __init
char_dev_test_init(void)
{
dev0_dev = MKDEV(CORDLESS_MAJOR,0);
register_chrdev_region(dev0_dev,1,dev0_name);
dev1_dev = MKDEV(CORDLESS_MAJOR,1);
register_chrdev_region(dev1_dev,1,dev1_name);
dev2_dev = MKDEV(CORDLESS_MAJOR,2);
register_chrdev_region(dev2_dev,1,dev2_name);
cdev_init(&dev0_cdev,&dev0_fops);
cdev_init(&dev1_cdev,&dev1_fops);
cdev_init(&dev2_cdev,&dev2_fops);
dev0_cdev.owner = THIS_MODULE;
cdev_add(&dev0_cdev,dev0_dev,1);
dev1_cdev.owner = THIS_MODULE;
cdev_add(&dev1_cdev,dev1_dev,1);
dev2_cdev.owner = THIS_MODULE;
cdev_add(&dev2_cdev,dev2_dev,1);
return 0;
}
static void __exit
char_dev_test_exit(void)
{
cdev_del(&dev0_cdev);
unregister_chrdev_region(dev0_dev,1);
cdev_del(&dev1_cdev);
unregister_chrdev_region(dev1_dev,1);
cdev_del(&dev2_cdev);
unregister_chrdev_region(dev2_dev,1);
}
module_init(char_dev_test_init);
module_exit(char_dev_test_exit);
编译成ko文件并加载后,用mknod命令创建三个字符设备文件
# mknod /dev/char_device0 c 240 0
# mknod /dev/char_device1 c 240 1
# mknod /dev/char_device2 c 240 2
然后运行cat 命令,会得到以下结果
# cat /dev/char_device0
[ 272.210000] dev0_open
[ 272.210000] dev0_read
[ 272.210000] dev0_release
# cat /dev/char_device1
[ 274.030000] dev1_open
[ 274.030000] dev1_read
[ 274.040000] dev1_release
# cat /dev/char_device2
[ 275.050000] dev2_open
[ 275.050000] dev2_read
[ 275.070000] dev2_release
从代码中可以看出该字符设备程序主要涉及三个变量:设备名(char),设备号(dev_t),字符设备结构(cdev).
字符设备初始化过程也分为三个步骤:
1.首先构造设备号,再将设备号、设备名等信息添加到全局变量chrdevs[]数组中第major%255个元素所指向的链表中。dev0_dev = MKDEV(CORDLESS_MAJOR,0);
register_chrdev_region(dev0_dev,1,dev0_name);
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);
}
register_chrdev_region()函数表示从from开始,注册count个设备,设备名是name。
之所以有个for循环的原因是每个设备号下面可以有1<<8或者1<<20个次设备号,如果
起始设备号from+加上想要注册的设备数count大于下一个主设备号组成的设备号,就需要
分两次进行注册。如果将该例子中的
register_chrdev_region(dev0_dev,1,dev0_name);
改为
register_chrdev_region(dev0_dev,256,dev0_name);
那么需要循环两次进行注册(假设一个主设备号下面最多有255个次设备号)。
下面分析__register_chrdev_region()函数
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;
/*分配一个char_device_struct结构*/
cd = kzalloc(sizeof(struct char_device_struct), GFP_KERNEL);
if (cd == NULL)
return ERR_PTR(-ENOMEM);
mutex_lock(&chrdevs_lock);
/*如果主设备号为0,表示由系统自动分配设备号*/
/* temporary */
if (major == 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;
}
/*设置刚才分配的char_device_struct结构*/
cd->major = major;
cd->baseminor = baseminor;
cd->minorct = minorct;
strlcpy(cd->name, name, sizeof(cd->name));
/*由major确定将要添加到的链表在chrdevs[]中的什么位置*/
i = major_to_index(major);
/*从链表头开始查找*/
for (cp = &chrdevs[i]; *cp; cp = &(*cp)->next)
{
/*如果找到的major大于要添加的major或者是其他情况,
*表示找到要插入的位置。
*第一次注册时,cp为NULL。
*如果之前注册的设备号都小的话,cp也为NULL。
*也就是说链表是按照设备号的大小链接的,小的在前面
*/
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;
}
}
/*将刚才分配到的char_device_struct结构插入链表*/
cd->next = *cp;
*cp = cd;
mutex_unlock(&chrdevs_lock);
return cd;
out:
mutex_unlock(&chrdevs_lock);
kfree(cd);
return ERR_PTR(ret);
}
可以改变代码中register_chrdev_region()函数的第二个参数来验证设备号重叠的情况。
2.初始化cdev,注册操作函数集
cdev_init(&dev0_cdev,&dev0_fops);
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
{
/*清零*/
memset(cdev, 0, sizeof *cdev);
/*初始化list,open设备时会将设备文件索引节点inode->i_devices
*连接到该list上,从而建立设备文件和cdev结构之间的关联
*/
INIT_LIST_HEAD(&cdev->list);
/*初始化kobj,*/
kobject_init(&cdev->kobj, &ktype_cdev_default);
/*设置操作函数集,当应用程序调用open,read,write等函数时,这些函数最终后调用
*fops里相应的函数
*/
cdev->ops = fops;
}
其中的kobject_init(&cdev->kobj, &ktype_cdev_default)是初始化cdev结构中的kobj变量。
cdev中含有kobj变量的一个主要原因是想利用kobj里的ref引用计数。
全局变量ktype_cdev_default里的cdev_default_release()函数会在cdev_del()中调用(引用计数减为0时)。
cdev_del()->kobject_put()->kobject_release()->kobject_cleanup()->cdev_default_release()
3.注册cdev
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
{
/*将设备号保存到cdev结构中*/
p->dev = dev;
p->count = count;
/*映射到cdev_map[]数组中*/
return kobj_map(cdev_map, dev, count, NULL, exact_match, exact_lock, p);
}
cdev_map是在chrdev_init()中初始化的。
void __init chrdev_init(void)
{
cdev_map = kobj_map_init(base_probe, &chrdevs_lock);
bdi_init(&directly_mappable_cdev_bdi);
}
先看下kobj_map_init
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;
};
struct kobj_map *kobj_map_init(kobj_probe_t *base_probe, struct mutex *lock)
{
/*分配一个kobj_map结构*/
struct kobj_map *p = kmalloc(sizeof(struct kobj_map), GFP_KERNEL);
/*分配一个probe结构*/
struct probe *base = kzalloc(sizeof(*base), GFP_KERNEL);
int i;
if ((p == NULL) || (base == NULL)) {
kfree(p);
kfree(base);
return NULL;
}
/*设置probe结构*/
base->dev = 1;
base->range = ~0;
base->get = base_probe;
/*将kobj_map结构中的probep[]数组中的255个元素都指向刚分配的probe结构*/
for (i = 0; i < 255; i++)
p->probes[i] = base;
/*设置kobj_map结构中的锁,以后要操作该结构,都需先获取锁*/
p->lock = lock;
return p;
}
kobj_map_init()是将probep[]数组中的255个元素都指向同一个probe结构。
而kobj_map()是将probep[]数组中的某个元素指向新的probe结构。也可以说成将一个新的包含有字符设备信息的
probe结构注册到probep[]数组中。
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结构,注册多次*/
unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1;
/*确定注册到probep[]数组第几个元素中*/
unsigned index = MAJOR(dev);
unsigned i;
struct probe *p;
if (n > 255)
n = 255;
/*分配probe结构*/
p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL);
if (p == NULL)
return -ENOMEM;
/*将字符设备信息保存到probe中,open字符设备时,会用到这些信息*/
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;//指向cdev结构,里面有fops操作函数集
}
mutex_lock(domain->lock);
for (i = 0, p -= n; i < n; i++, p++, index++) {
struct probe **s = &domain->probes[index % 255];
/*按range从小到大的顺序,将probe链接到probes[index % 255]指向的链表中*/
while (*s && (*s)->range < range)
s = &(*s)->next;
p->next = *s;
*s = p;
}
mutex_unlock(domain->lock);
return 0;
}
字符设备初始化就结束了,主要难点是dev_t设备号的获取和kobj的映射。
最后,分析下char_open函数。
在之前分析mknod()的文章中提到,用mknod创建字符设备文件时,会将文件的inode结构中的
i_fop.open函数设置为chrdev_open()。当应用程序调用open函数打开字符设备文件时会调用
char_open(),char_open()最终会调用cdev结构中fops.open函数。本例中就是dev0_open(),
dev1_open(),dev2_open()等函数。
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) {
struct kobject *kobj;
int idx;
spin_unlock(&cdev_lock);
/*从cdev_map中找到cdev结构中kobj变量*/
kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx);
if (!kobj)
return -ENXIO;
/*根据kobj找到代表字符设备的cdev结构*/
new = container_of(kobj, struct cdev, kobj);
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指向cdev结构*/
inode->i_cdev = p = new;
/*将inode->i_devices添加到cdev->list中*/
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;
/*获取字符设备自己的fops操作函数集*/
filp->f_op = fops_get(p->ops);
if (!filp->f_op)
goto out_cdev_put;
/*执行open函数*/
if (filp->f_op->open) {
ret = filp->f_op->open(inode,filp);
if (ret)
goto out_cdev_put;
}
return 0;
out_cdev_put:
cdev_put(p);
return ret;
}
kobj_lookup()函数先从cdev_map.probe[]数组中找到之前注册的cdev结构,然后返回cdev结构中的kobj。
具体过程这里就不分析了。