Linux下字符设备驱动程序的结构
2011-10-21 23:09
295 查看
首先,希望可以明确一下概念:
1、 要说的是,驱动,必然是与内核紧密相连的底层程序;并且接口对用户是不可见的。所以你要想到的是如何把驱动和内核联系起来,第一个就想到了模块(modules),模块就像一个载体,一个容器,通过它,会把你写好的程序插入(装载)到内核可见的区域,从而使内核感知到你的驱动的存在,然后用户空间才能通过系统调用的形式联系到驱动,从而完成它的任务,所以你首要了解模块。
2、 驱动位于OS之下,为OS提供硬件操作(当然驱动也有可能不是针对硬件的)的逻辑和底层抽象的封装,所以他要上下兼顾,上边必须符合OS的驱动调用接口,下边要处理好硬件的操作。
3、 Linux比较特殊,把所有的设备都抽象成了文件,这样的话操作接口会比较统一,并且给开发也带来了很大的方便。Linux下设备文件有三种:字符设备、块设备、网络设备。
简单的介绍一下做一个驱动(字符设备驱动)需要做哪些:
1、 表征设备存在的结构体cdev:
整个过程都是围绕着这个结构体在进行。对他的各元素赋值并通过模块加载到内核即可。我们逐一的说明一下。
对于,kobject的初始化是在cdev_init里边完成的,它在Fs/Char_dev.c :
这里连带着把kobject、list_head、file_operations也都做了赋值或初始化;这里做为参数你要有一个file_operations来用于赋值,,而file_operations包含了对该字符设备进行操作的所有可能用到的函数(接口)。从而就引出了file_operations,它是整个字符驱动的核心,也是要把所有处理逻辑绑定的对象:
这里是个简单的例子,我们要做的就是实现那些等号后边的函数,并对其赋值。下边是file_operations的原型,很完备的接口,只用实现你用到的就可以了(在include/linux/fs.h中):
到这里,通过cdev的初始化,我们已经把你写的逻辑和cdev联系起来了(通过file_operations),然后通过:
cdev.owner = THIS_MODULE;
直接复制把cdev和当前模块联系了起来。然后是通过:
dev_tdevno =MKDEV(222,0);
用MKDEV宏生成一个主设备号222,此设备号0的dev_t类型的设备号变量devno,之后是把设备号和设备结构体cdev关联起来,这样设备才能间接找到它的处理函数,用这个来完成关联:
cdev_add(&dev->cdev,devno,1);
well,设备结构体准备好了,和具体的设备号也约定好了(这样属于该类型的设备会直接来找它进行具体的处理),另外,也和当前模块挂接好了,还差一步就是让内核知道它的存在,用下边的函数:
register_chrdev_region(devno,1,"globalmem");
把当前设备注册到内核,这个和cdev没有关系,只是对虚拟设备globalmem进行内核的注册,而他们联系的纽带就是devno。当然对应的还有注销函数:
unregister_chrdev_region(devno,1);
他们一般是成对出现的。
这样就差不多了,整个过程及原理也就是这样了,更详细的,可以好好品读一下Linux驱动开发详解的第六章。讲得非常详细。另外看一下另外一篇blog,【Linux下第一个驱动程序】,是一个实例,并附有大量说明,这样应该可以建立一个初步的概念。然后就是循循渐进了,哈哈。
【本文doc文档下载】
。
。
1、 要说的是,驱动,必然是与内核紧密相连的底层程序;并且接口对用户是不可见的。所以你要想到的是如何把驱动和内核联系起来,第一个就想到了模块(modules),模块就像一个载体,一个容器,通过它,会把你写好的程序插入(装载)到内核可见的区域,从而使内核感知到你的驱动的存在,然后用户空间才能通过系统调用的形式联系到驱动,从而完成它的任务,所以你首要了解模块。
2、 驱动位于OS之下,为OS提供硬件操作(当然驱动也有可能不是针对硬件的)的逻辑和底层抽象的封装,所以他要上下兼顾,上边必须符合OS的驱动调用接口,下边要处理好硬件的操作。
3、 Linux比较特殊,把所有的设备都抽象成了文件,这样的话操作接口会比较统一,并且给开发也带来了很大的方便。Linux下设备文件有三种:字符设备、块设备、网络设备。
简单的介绍一下做一个驱动(字符设备驱动)需要做哪些:
1、 表征设备存在的结构体cdev:
struct cdev{ struct kobject kobj; /* 内嵌的kobject对象*/ struct module *owner; /*所属模块*/ struct file_operations *ops; /*文件操作结构体*/ struct list_head list; dev_t dev; /*设备号,为32 位,其中高12 位为主设备号,低20 位为次设备号*/ unsigned int count; };
整个过程都是围绕着这个结构体在进行。对他的各元素赋值并通过模块加载到内核即可。我们逐一的说明一下。
对于,kobject的初始化是在cdev_init里边完成的,它在Fs/Char_dev.c :
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof *cdev); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
这里连带着把kobject、list_head、file_operations也都做了赋值或初始化;这里做为参数你要有一个file_operations来用于赋值,,而file_operations包含了对该字符设备进行操作的所有可能用到的函数(接口)。从而就引出了file_operations,它是整个字符驱动的核心,也是要把所有处理逻辑绑定的对象:
static const struct file_operations globalmem_fops={ .owner = THIS_MODULE, .read = globalmem_read, .write = globalmem_write, .open = globalmem_open, .release = globalmem_release, };
这里是个简单的例子,我们要做的就是实现那些等号后边的函数,并对其赋值。下边是file_operations的原型,很完备的接口,只用实现你用到的就可以了(在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 *); 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 **); };
到这里,通过cdev的初始化,我们已经把你写的逻辑和cdev联系起来了(通过file_operations),然后通过:
cdev.owner = THIS_MODULE;
直接复制把cdev和当前模块联系了起来。然后是通过:
dev_tdevno =MKDEV(222,0);
用MKDEV宏生成一个主设备号222,此设备号0的dev_t类型的设备号变量devno,之后是把设备号和设备结构体cdev关联起来,这样设备才能间接找到它的处理函数,用这个来完成关联:
cdev_add(&dev->cdev,devno,1);
well,设备结构体准备好了,和具体的设备号也约定好了(这样属于该类型的设备会直接来找它进行具体的处理),另外,也和当前模块挂接好了,还差一步就是让内核知道它的存在,用下边的函数:
register_chrdev_region(devno,1,"globalmem");
把当前设备注册到内核,这个和cdev没有关系,只是对虚拟设备globalmem进行内核的注册,而他们联系的纽带就是devno。当然对应的还有注销函数:
unregister_chrdev_region(devno,1);
他们一般是成对出现的。
这样就差不多了,整个过程及原理也就是这样了,更详细的,可以好好品读一下Linux驱动开发详解的第六章。讲得非常详细。另外看一下另外一篇blog,【Linux下第一个驱动程序】,是一个实例,并附有大量说明,这样应该可以建立一个初步的概念。然后就是循循渐进了,哈哈。
【本文doc文档下载】
。
。
相关文章推荐
- 第12课第4.2节 字符设备驱动程序之中断方式的按键驱动_Linux中断处理结构
- Linux 2.6中的字符设备驱动程序结构
- 第12课第4.1节 字符设备驱动程序之中断方式的按键驱动_Linux异常处理结构
- 一个简单的演示用的Linux字符设备驱动程序
- Linux--字符设备驱动结构框图
- Linux设备驱动程序学习(1)-字符设备驱动程序
- Linux 字符设备驱动结构(四)—— file_operations 结构体知识解析
- LINUX字符设备驱动程序实例(scull)
- linux字符驱动程序结构
- Linux设备驱动程序学习(5)-高级字符驱动程序操作〔(2)阻塞型I/O和休眠〕
- 从零开始写linux字符设备驱动程序(一)(基于友善之臂tiny4412开发板)
- Linux设备驱动程序学习(1)--字符设备驱动
- Linux 2.6 字符设备驱动程序
- Linux设备驱动程序——高级字符驱动程序操作(poll机制)
- Linux字符设备驱动程序的设计(初级)
- 嵌入式Linux驱动学习之路(二十一)字符设备驱动程序总结和块设备驱动程序的引入
- linux字符设备驱动程序框架(老方法)
- linux设备驱动程序 字符设备驱动程序
- Linux字符设备驱动程序的编写框架
- 实验题目:实现嵌入式Linux系统下的字符设备驱动程序(报告)