Linux字符设备驱动框架
2013-10-23 10:08
471 查看
linux字符设备驱动编写内容:
(1)分配主设备号
(2)填充file_operations
(3)主设备号和file_operations关联
(4)创建一个类
(5)在该类下创建一个设备
(6)设置驱动入口函数和出口函数
一、字符驱动重要结构体
1.1.字符设备结构体
1.2.设备操作函数集
需要实现设备的相应功能,只要给上面的函数指针赋值即可
1.3.设备号分配记录结构体
该指针数组一共255项;每项保存了一个主设备号的使用情况,相同主设备号的设备采用链表方式管理
1.4.设备号和设备关联结构体
当从chrdevs数组中选出一个可用的设备号后,进行注册的实质:将该设备号和cdev结构体相关联,然后放到一个数组中去,这个数组就是cdev_map
二、驱动实现函数
2.1申请设备号fs/char_dev.c
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
参数:major主设备号;为0就由系统自动分配
参数:baseminor起始次设备号
参数:minorct次设备号的个数
参数:name设备的名字/proc/devices可以看见
返回值:返回一个可用的char_device_struct结构体
2.2填充file_operations:cdev_init
cdev = cdev_alloc();
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:cdev设备
参数:fops函数操作集
2.3设备号和设备绑定
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:p指向新构建的字符设备
参数:dev申请的设备号
参数:count次设备号范围
返回值:成功返回1否则返回错误
2.4创建设备类
include/linux/device.h
#define class_create(owner, name)
参数:owner设备的持有者
参数:name类名字
返回值:新建的类
2.5类下创建设备
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
参数:class类名
参数:parent设备父对象
参数:devt设备号
参数:drvdata
参数:fmt
返回值:返回一个struct device
调用class_create和*device_create函数将使得linux文件系统/dev下自动产生新的设备节点
2.6驱动入口和出口函数
当一个函数使用module_init(函数);那么该驱动加载时它将被首先调用
当一个函数使用module_exit(函数);那么该驱动卸载时它将被最后调用
三、字符驱动实例
四、字符设备驱动内部机理
4.1 设备驱动注册细节
图:设备号管理图
4.2 设备驱动调用细节
当应用层调用open函数打开一个设备时将进入内核态:
执行的函数有:sys_open->do_sys_open一系列函数,最后会调用到chrdev_open,在该函数内部将调用kobj_lookup在cdev_map中根据设备号找到它相关联的那个cdev设备,
然后通过这个设备的里面的fops就可以找到操作设备的函数了。
参考文章:
http://www.cnblogs.com/mr-raptor/archive/2011/03/22/2347683.html
http://blog.chinaunix.net/uid-26921272-id-3422993.html
http://blog.chinaunix.net/uid-27106528-id-3321715.html
http://www.cnblogs.com/armlinux/archive/2010/09/12/2396919.html
http://liu1227787871.blog.163.com/blog/static/20536319720128280344914/
(1)分配主设备号
(2)填充file_operations
(3)主设备号和file_operations关联
(4)创建一个类
(5)在该类下创建一个设备
(6)设置驱动入口函数和出口函数
一、字符驱动重要结构体
1.1.字符设备结构体
include/linux/cdev.h struct cdev { struct kobject kobj; //kobject设备模型基础 struct module *owner; const struct file_operations *ops;//硬件的相关操作函数 struct list_head list; dev_t dev; //字符设备起始设备号 unsigned int count; //次设备范围号大小 };
1.2.设备操作函数集
include/linux/fs.h struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);//设备读函数 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);//设备写函数 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); int (*readdir) (struct file *, void *, filldir_t); unsigned int (*poll) (struct file *, struct poll_table_struct *);//设备poll函数 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);//设备控制函数 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); long (*compat_ioctl) (struct file *, unsigned int, unsigned long); int (*mmap) (struct file *, struct vm_area_struct *);//设备映射函数 int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); int (*release) (struct inode *, struct file *); int (*fsync) (struct file *, struct dentry *, int datasync); int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); int (*check_flags)(int); int (*flock) (struct file *, int, struct file_lock *); ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); int (*setlease)(struct file *, long, struct file_lock **); };
需要实现设备的相应功能,只要给上面的函数指针赋值即可
1.3.设备号分配记录结构体
fs/char_dev.c 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];
该指针数组一共255项;每项保存了一个主设备号的使用情况,相同主设备号的设备采用链表方式管理
1.4.设备号和设备关联结构体
当从chrdevs数组中选出一个可用的设备号后,进行注册的实质:将该设备号和cdev结构体相关联,然后放到一个数组中去,这个数组就是cdev_map
drivers/base/map.c 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; //指向cdev } *probes[255]; struct mutex *lock; }; fs/char_dev.c static struct kobj_map *cdev_map;
二、驱动实现函数
2.1申请设备号fs/char_dev.c
static struct char_device_struct * __register_chrdev_region(unsigned int major, unsigned int baseminor,int minorct, const char *name)
参数:major主设备号;为0就由系统自动分配
参数:baseminor起始次设备号
参数:minorct次设备号的个数
参数:name设备的名字/proc/devices可以看见
返回值:返回一个可用的char_device_struct结构体
2.2填充file_operations:cdev_init
cdev = cdev_alloc();
void cdev_init(struct cdev *cdev, const struct file_operations *fops)
参数:cdev设备
参数:fops函数操作集
2.3设备号和设备绑定
int cdev_add(struct cdev *p, dev_t dev, unsigned count)
参数:p指向新构建的字符设备
参数:dev申请的设备号
参数:count次设备号范围
返回值:成功返回1否则返回错误
2.4创建设备类
include/linux/device.h
#define class_create(owner, name)
参数:owner设备的持有者
参数:name类名字
返回值:新建的类
2.5类下创建设备
struct device *device_create(struct class *class, struct device *parent,dev_t devt, void *drvdata, const char *fmt, ...)
参数:class类名
参数:parent设备父对象
参数:devt设备号
参数:drvdata
参数:fmt
返回值:返回一个struct device
调用class_create和*device_create函数将使得linux文件系统/dev下自动产生新的设备节点
2.6驱动入口和出口函数
当一个函数使用module_init(函数);那么该驱动加载时它将被首先调用
当一个函数使用module_exit(函数);那么该驱动卸载时它将被最后调用
三、字符驱动实例
/*内核linux-2.6.30.4 TQ2440*/ #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/init.h> #include <linux/delay.h> #include <linux/poll.h> #include <linux/irq.h> #include <asm/irq.h> #include <linux/interrupt.h> #include <asm/uaccess.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/platform_device.h> #include <linux/cdev.h> #include <linux/miscdevice.h> #include <asm/io.h> static struct class *firstdrv_class; static struct device *firstdrv_class_dev; volatile unsigned long *gpbcon = NULL; volatile unsigned long *gpbdat = NULL; //设备驱动对应的open函数 static int first_drv_open(struct inode *inode, struct file *file) { /*LED1,LED2,LED4对应GPB5、GPB6、GPB7、GPB8 配置GPB5,6,7,8为输出 */ *gpbcon &= ~((0x3<<(5*2)) | (0x3<<(6*2)) | (0x3<<(7*2)) | (0x3<<(8*2))); *gpbcon |= ((0x1<<(5*2)) | (0x1<<(6*2)) | (0x1<<(7*2)) | (0x1<<(8*2))); return 0; } //设备驱动对应的read函数 static ssize_t first_drv_write(struct file *file, const char __user *buf, size_t count, loff_t * ppos) { int val; copy_from_user(&val, buf, count); if (val == 1) *gpbdat &= ~((1<<5) | (1<<6) | (1<<7) | (1<<8));//点亮LED else *gpbdat |= (1<<5) | (1<<6) | (1<<7) | (1<<8);//熄灭LED return 0; } //填充file_operations static struct file_operations first_drv_fops = { .owner = THIS_MODULE, .open = first_drv_open, .write = first_drv_write, }; int major; static int first_drv_init(void) { major = register_chrdev(250, "first_drv", &first_drv_fops); //内部调用了__register_chrdev_region;cdev_alloc;cdev_add firstdrv_class = class_create(THIS_MODULE, "firstdrv"); firstdrv_class_dev = device_create(firstdrv_class, NULL, MKDEV(major, 0), NULL, "xyz"); /* /dev/xyz */ gpbcon = (volatile unsigned long *)ioremap(0x56000010, 16); gpbdat = gpbcon + 1; return 0; } static void first_drv_exit(void) { unregister_chrdev(major, "first_drv"); device_unregister(firstdrv_class_dev); class_destroy(firstdrv_class); iounmap(gpbcon); } module_init(first_drv_init);//驱动入口函数 module_exit(first_drv_exit);//驱动出口函数 MODULE_LICENSE("GPL");
四、字符设备驱动内部机理
4.1 设备驱动注册细节
//字符设备注册函数 int register_chrdev(unsigned int major, const char *name,const struct file_operations *fops) { struct char_device_struct *cd; struct cdev *cdev; char *s; int err = -ENOMEM; cd = __register_chrdev_region(major, 0, 256, name);//在数组chrdevs中找到可用的设备号 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);//设置cedv内嵌的kobj成员 for (s = strchr(kobject_name(&cdev->kobj),'/'); s; s = strchr(s, '/')) *s = '!'; err = cdev_add(cdev, MKDEV(cd->major, 0), 256);//将设备cedv放到cdev_map对应项中 if (err) goto out; cd->cdev = cdev; return major ? 0 : cd->major; out: kobject_put(&cdev->kobj); out2: kfree(__unregister_chrdev_region(cd->major, 0, 256)); return err; } //字符设备设备号管理(分配)函数 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); //如果主设备号是0,那么就在chrdevs中找个未使用的设备号给他 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; } //填充好设备号结构体cd 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; out: mutex_unlock(&chrdevs_lock); kfree(cd); return ERR_PTR(ret); }
图:设备号管理图
//cdev设备和设备号管理函数 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);//将cdev类型的p保存到cdev_map中 } 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) { unsigned n = MAJOR(dev + range - 1) - MAJOR(dev) + 1; unsigned index = MAJOR(dev); unsigned i; struct probe *p; if (n > 255) n = 255; p = kmalloc(sizeof(struct probe) * n, GFP_KERNEL); if (p == NULL) return -ENOMEM; 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; } mutex_lock(domain->lock);//将struct probe插入到链表的方式和上面的方法一样 for (i = 0, p -= n; i < n; i++, p++, index++) { struct probe **s = &domain->probes[index % 255]; while (*s && (*s)->range < range) s = &(*s)->next; p->next = *s; *s = p; } mutex_unlock(domain->lock); return 0; }
4.2 设备驱动调用细节
当应用层调用open函数打开一个设备时将进入内核态:
执行的函数有:sys_open->do_sys_open一系列函数,最后会调用到chrdev_open,在该函数内部将调用kobj_lookup在cdev_map中根据设备号找到它相关联的那个cdev设备,
然后通过这个设备的里面的fops就可以找到操作设备的函数了。
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); kobj = kobj_lookup(cdev_map, inode->i_rdev, &idx); //根据设备的设备号来在cdev_map中查找 if (!kobj) return -ENXIO; new = container_of(kobj, struct cdev, kobj);//取得设备cdev 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 = p = new; inode->i_cindex = idx; 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; filp->f_op = fops_get(p->ops);//将文件描述符的f_op指针赋值为cdev里面的fops if (!filp->f_op) goto out_cdev_put; if (filp->f_op->open) { ret = filp->f_op->open(inode,filp);//调用设备的open函数 if (ret) goto out_cdev_put; } return 0; out_cdev_put: cdev_put(p); return ret; } struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index) { struct kobject *kobj; struct probe *p; unsigned long best = ~0UL; retry: mutex_lock(domain->lock); for (p = domain->probes[MAJOR(dev) % 255]; p; p = p->next) { struct kobject *(*probe)(dev_t, int *, void *); struct module *owner; void *data; if (p->dev > dev || p->dev + p->range - 1 < dev) continue; if (p->range - 1 >= best) break; if (!try_module_get(p->owner)) continue; owner = p->owner; data = p->data; probe = p->get; best = p->range - 1; *index = dev - p->dev; if (p->lock && p->lock(dev, data) < 0) { module_put(owner); continue; } mutex_unlock(domain->lock); kobj = probe(dev, index, data); /* Currently ->owner protects _only_ ->probe() itself. */ module_put(owner); if (kobj) return kobj; goto retry; } mutex_unlock(domain->lock); return NULL; }
参考文章:
http://www.cnblogs.com/mr-raptor/archive/2011/03/22/2347683.html
http://blog.chinaunix.net/uid-26921272-id-3422993.html
http://blog.chinaunix.net/uid-27106528-id-3321715.html
http://www.cnblogs.com/armlinux/archive/2010/09/12/2396919.html
http://liu1227787871.blog.163.com/blog/static/20536319720128280344914/
相关文章推荐
- linux 字符设备驱动框架
- linux设备驱动--globalmem字符设备框架分析
- 一步步理解linux字符设备驱动框架(转)
- linux ------ 字符设备驱动框架
- Linux驱动(三)字符设备驱动框架
- linux驱动字符设备框架
- linux分类驱动对字符设备框架压力的卸载
- linux 字符设备驱动框架
- linux分类驱动对字符设备框架压力的卸载
- Linux 字符设备驱动框架详细介绍
- 一步步理解linux字符设备驱动框架
- Linux字符设备驱动框架
- Linux字符设备驱动框架
- linux设备驱动--globalmem字符设备框架分析
- Linux字符设备驱动框架
- linux驱动开发之字符设备框架 -调用过程分析
- 11-S3C2440驱动学习(七)嵌入式linux-字符设备的另一种写法及RTC驱动程序分析和字符设备驱动框架总结
- linux字符设备驱动框架理解
- linux驱动开发之字符设备框架 - 实例
- linux字符设备驱动框架(一)