下面是一個簡單的字符設別驅動程序的源代碼,本文會分析代碼中涉及到的函數。
#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_initstruct 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。具體過程這裏就不分析了。