~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
《常识》
¥应用程序----->系统内核----->设备驱动----->硬件设备
¥设备驱动既是系统内核的下属,又是硬件设备的老大。
¥在inux系统中用一个文件来代表一个设备。这个文件就叫设备文件。设备驱动的责任是将应用程序对设备文件
的打开、读、写、定位等操作转化为对硬件设备的打开、读、写、定位等操作。而对于任何硬件设备,应用程序
只需利用这些基本操作就可以完全控制它!
¥编写linux设备驱动需要的知识结构:
1、40%的设计模式相关知识。设计模式是系统内核限定的,做别人的下属就得按照别人的规矩办事。
2、30%的内核工作原理相关知识。内核是你领导,领会领导意图才能把事情办好。
3、30%的硬件相关知识。控制好硬件是你的的本质工作,你得把你的小弟管理好.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
***********************************************************华丽的分割线***************************************************************
一、设备文件
-------------------------------------------------------------------------------------------------------
什么是设备文件?设备文件有什么用?
linux设备文件就是一个实实在在看得见的文件(什么是文件?一个txt文本就是一个文件、一个word文档就是一个文件、一张图片就是一个文件),只不过这个文件是“设备类型”,它是用来代表一个设备的,一般在设备驱动加载之后创建,在设备驱动卸载后移除。有了设备文件,如果想对设备进行等操作。只需对设备文件进行等操作即可。这是如何实现的呢?就是通过驱动程序实现的:设备文件操作----->系统内核----->设备驱动----->硬件设备设备文件也是一个文件,c库中对文件的操作包括打开、写、读、定位等,分别是通过fopen、fwrite、fread、fseek等c库函数实现的。设备驱动的作用就是将 这些对设备文件的打开、读、写、定位等操作转化为对硬件设备的打开、读、写、定位等操作。那么仅仅是实现对设备进行打开、读、写、定位等操作就能完全控制好它么?对于能与微控制器接口的设备都可以!!!!!!
------------------------------------------------------------------------------------------------------
如何创建设备文件?
有两种方法:1.手工创建 mknod方法:mknok filename type major minor【major-主设备号;minor-次设备号;type-设备类型可取:c(字符设备),b(块设备)】执行此命令之后就可以看到在当前目录下生成了一个名为filename 的文件。2.自动创建: 暂时不知
创建设备文件需要用到主次设备号,那么什么是主次设备号呢?
***********************************************************华丽的分割线***************************************************************
二、主次设备号:
------------------------------------------------------------------------------------------------------
什么是主次设备号?主次设备号的作用是什么?
设备号,就是系统为设备分配的一个编号。在/dev目录下-ll,可以看到有每个设备文件都有两个号,他们就是主次设备号。
主设备号是用来标识与设备文件(什么是设备文件?上面讲过)相连的驱动程序,主设备号用来反映设备类型;次设备号被驱动程序用来辨别操作的是哪个设备,次设备号用来区分同类型的设备。设备文件需要设备号才能创建;设备驱动也需要设备号才能装载。
设备文件正是通过主设备号找到它的驱动;设备驱动正是利用次设备号才知道他要操作的具体是哪个设备。
------------------------------------------------------------------------------------------------------
内核中如何描述设备号?
内核中用 dev_t 类型来描述,其实质是是unsigned int 32位整数,其中高12位为主设备号,低20位为次设备号用宏 MAJOR(dev_t dev)解出主设备号,MINOR(dev_t dev)分解出次设备号。
------------------------------------------------------------------------------------------------------
linux内核如何分配主次设备号?
静态申请:1.根据/documentation/devices.txt,确定一个没有使用的主设备号
2.使用register_chrdev_region函数注册设备号:int register_chrdev_region(dev_t from,
unsigned count,const char *name)【from-希望使用的设备号;count- 望申请使用的设备号数目;
nanme-shebeiming(体现在/proc/devices)】
静态注册的缺点是移植时容易发生冲突,但是简单。
动态分配:使用allo_chrdev_region分配设备号:int allo_chrdev_region(dev_t *dev,unsigned baseminor,
unsigned count,const char name)【dev 非配到的设备号(是获取到得,不用填);baseminor-起始次设备号
;count-需要分配的设备号数目;name-设备名】
------------------------------------------------------------------------------------------------------
如何注销设备号?
设备号是宝贵的资源,都应该在不再使用它们时释放这些设备号
void unregister_chrdev_region(dev_t from,unsigned cout|):释放从from开始的count个设备号
***********************************************************华丽的分割线***************************************************************
三、字符设备驱动中的三种重要数据结构
------------------------------------------------------------------------------------------------------
哪三种?有什么作用
Struct inode,struct file_operation,Struct file,他们是linux内核定义的。很显然,他们就是c语言中的结构体类型。
这里需要区分下“结构体类型”与“结构体变量”:结构体类型相当于生产产品的“模子”,而“结构体变量”就是用这些模子生产出来的产品。不同的模子生产出来的产品的样式不同:生产人的模子造出来的是长两只手两条腿一个脑袋的人,生产马的模子造出来的是四条腿一个脑袋的马。同一个模子可以用来生产很多产品,同一个结构体类型可以定义很多变量。
linux内核为什么要定义这些“模子”呢?linux内核定义他们是为了描述不同的类。不同的类具有不同的特征,需要用不同的结构去描述:人长两只手两条腿一个脑袋,马是长四条腿一个脑袋,鸡长两条腿一个脑袋。
------------------------------------------------------------------------------------------------------
Struct inode结构用来描述什么?(这个模子用来生产什么的?)重要成员有哪些?
作用:struct inode结构是用来描述设备文件属性的,它的内容由内核来填写的。每创建一个设备文件时,就会定义一个struct inode结构体类型的变量(用这个模子造一个产品),用来存放该文件的位置,设备号等物理信息。
重要成员:dev_t i_rdev:设备号。在创建设备文件时,把命令中的设备号赋给它。
struct cdev *i_cdev:struct cdev 也是一个模子,他是用来描述一类设备的,用这个模子生产出来的变量都是用来描述设备一类的,它的内容是由驱动程序来填写的。那i_cdev变量描述的是哪类设备的呢?i_cdev变量描述的就是inode结构变量所描述的这个设备文件的想关联的这类设备。设备驱动程序在创建的时候会定义一个struct cdev结构的变量,把自己相关的信息的值赋给cdev变量的成员,在设备驱动装载的时候它会被注册到内核中。 当设备文件没有被打开的时候structinode中的i_cdev变量值为NULL;当设备文件被打开时,内核就会在已注册到内核中的所有struct cdev 变量中找出与当前打开的设备文件具有相同主设备号的那个,然后把这个struct cdev变量的指针赋给i_cdev。具体的过程需要进行内核分析。由此可见,设备文件 是通过设备号与设备驱动关联起来的。
------------------------------------------------------------------------------------------------------
Struct file_peration结构用来描述什么的?重要成员有哪些?
作用:Struct file_peration用来描述能在设备上进行的操作,它的内容需要驱动程序来填写的。它是一个函数指针(指向函数的指针!!!)的集合。结构中的成员指向驱动函数中的函数,这些函数实现一个特定的操作,对于不支持的操作保留为NULL。实际是一张对应表,把应用程序中的操作转换成驱动程序操作。内核为什么只定义这些操作呢?因为这些操作足以完全控制任何一个硬件设备。驱动程序只需完成这些操作的是实现,其他的问题交给应用程序来处理。每个驱动程序都会定义一个struct file_operation类型的变量,并把编写好的操作函数(读写打开等函数)的指针赋给这个结构变量的成员,而这个变量的指针会被赋给 在驱动程序定义的struct cdev类型的变量,然后随struct cdev一同注册到内核中。
重要成员:struct module *owner:它并不是一个操作,它是指向“拥有”该结构的内核模块的指针,内核使用这个字段以避免在模块的操作正在使用时被卸载。
loff_t (*llseek)(struct file *,loff_t ,int):llseek方法用来修改文件当前读写位置,并且返回新位置
ssize_t (*read)(struct file *,char_ _user * ,size_t,loff_t*):用来从设备中读取数据
ssize_t (*write)(struct file *,char_ _user * ,size_t,loff_t*):用来向设备中写取数据
int (*open)(struct inode*,struct file *):一般为对设备文件执行的第一个操作,但不是必须的。
------------------------------------------------------------------------------------------------------
Struct file结构用来描述什么?重要成员有哪些?
作用:struct file结构用来描述打开的文件,存放与文件操作有关的数据,它的内容由内核填写的。每个文件(当然包括设备文件)在打开的时候,内核都会为他创建一个struct file类型的变量,存放与该文件相关的操作。
重要成员:loff_t f_pos:文件读写的位置(loff是内核定义的一种数据类型,其实是整型)
struct file_operations *f_op:对的,就是上面刚讲的!!!!!!struct file又是如何获得这个成员的值的呢?文件在打开的时候,内核会根据设备号(设备号从哪儿来的呢?从文件的对应inode结构变量中找到)找到在内核中找到具有相同设备号的那个驱动,从而找到指向指向驱动file_operation变量的指针,具体的过程很复杂,设计到内核分析。以后再说。
***********************************************************华丽的分割线***************************************************************
四、字符设备的注册
------------------------------------------------------------------------------------------------------
字符设备在内核中是如何被描述的?
在linux2.6内核中,字符设备使用struct cdev结构来描述的,一个cdev描述的是一类设备,而这类设备可以有很多个,在设备注册的时候需要告诉这类设备关联的设备的个数。实质上在内核中字符设备的真正模型是struct probe结构。在内核中有一个probe结构体数组,具有255个元素,每个元素代表一个驱动,probe结构中当然包含cdev结构。至于说cdev结构是字符设备的描述,我想是因为cdev结构能够唯一的描述一个字符设备,并不代表内核中就是采用这个结构来描述字符设备。因此,应该把“设备”和“设备模型”两个概念分别开来。
------------------------------------------------------------------------------------------------------
字符设备驱动是如何注册的?
分为以下三个步骤:
前提是你在驱动中首先定义了一个cdev类型变量和一个file_operation类型变量。
1、给cdev变量分配内存:使用cdev_alloc函数完成,struct cdev *cdec_alloc(void),目的是为cdev变量分配动态内存。当然,如果你把它定义成静态变量,不用再用这个
函数专门再分配内存,因为他已经有了。
2、初始化cdev变量:使用cdev_init函数完成,void cdev_init(struct cdev *cdev,const struct file_operations *fops)【cdev-待初始化的cdev结构;fops-设备对应的操作 函数集(上面讲到得)】,初始化的作用暂时还不清楚。
3、注册cdev:使用cdev_add函数完成,int cdev_add(struct cdev *p,dev_t dev,unsigned count)【p-待添加到内核的字符设备结构;dev-设备号;count-添加的设备个数)】。cdev_add()用来将设备驱动添加到设备驱动模型(上面经过设备驱动模型)中。前面讲过,内核中有一个probe结构体数组,他有255个元素,每个元素代表一个驱动,因此cdev_add()的作用实际上是将设备驱动struct cdev *p,添加到这个结构体数组的某一项中去。
***********************************************************华丽的分割线***************************************************************
五、字符设备的操作
------------------------------------------------------------------------------------------------------
字符设备的操作指的是什么?
就是struct file_operation中包含的那些方法(函数),这些方法的实现也正是驱动程序的本职任务。
------------------------------------------------------------------------------------------------------
字符设备的基本操作包括哪些?
就是上面讲的struct file_operation中的那些重要成员。
open:int (*open)(struct inode *,struct file *),一般为设备的第一个操作,可以不实现,如果该项是NULL,
设备的打开操作永远成功。
release:void (*release)(struct inode *,struct file *),当文件关闭时调用这个操作,这个操作也可以不实现。
read:ssize_t (*read)(struct file *,char__user *,size_t,loff_t *),从设备中读数据
write:ssize_t(*write)(struct file *,char__user *,size_t,loff_t *),向设备写数据。
lseek::off_t(*llseek)(structfile *,loff_t,int),修改文件的当前读写位置,并将新位置座位返回值。
要注意的是,上述操作函数的接口(什么是接口?在这里就是函数的参数的类型!)是不能被修改的!因为这是内核规定的标准接口。这些就是驱动程序需要实现的操作!每种
操作对对应着一种系统调用!
上述函数被调用的时候,它的那些参数自然是系统函数(属于内核)传递给它的,那系统内核又是怎么知道这些参数的呢?我们知道在这些函数被调用之前,驱动已经被加载到
内核之中了(module_init()已经执行了),也就是说设备驱动cdev已经注册到内核之中了!所以内核当然知道这些参数的信息了。
------------------------------------------------------------------------------------------------------
驱动程序(内核模块)的open方法如何实现?
open方法是驱动程序用来为以后的操作完初始化准备工作的:初始化设备(寄存器设置),标明次设备号等等。open方
法的原型如下:
int (*open)(struct inode *inode,struct file *filp)
函数名可以改,函数参数的类型一定不能改!为什么呢?上面提到过,open方法对应着“系统调用(什么是系统调用?)的open函数(此open非彼open),系统调用的
open函数有标准的格式,所以这个open方法应该保持着对应,不然你的驱动函数显然不能被系统函数正常调用。
------------------------------------------------------------------------------------------------------
read和write方法的原型又是什么?
ssize_t read(struct file *filp,char__user *buff,size_t count,loff_t *offp)
ssize_t write(struct file *filp, const char__user *buff,size_t count,loff_t *offp)
其中buff是用户空间(什么是用户空间和内核空间?参看,参看操作系统原理)的指针,内核代码不能直接引用其中
的内容!!!(为什么?暂时不懂)。然而驱动程序(位于内核空间,属于内核代码)又必须访问用户空间的缓冲去以完成自己的工作,为了确保安全,必须使用内核提供的
专用函数来完成。例如:
int long copy_to_user(void _ _user *to,const void *from,unsigned long count);
int long copy_from_user(void *to,const void _ _user *from,unsigned long count)
***********************************************************华丽的分割线***************************************************************
六、字符设备的注销
------------------------------------------------------------------------------------------------------
为什么要注销设备?
因为不需要再使用。。。。。做事要有头有尾。
如何注销字符设备?
void cdev_del(struct cdev *cdev)
***********************************************************华丽的分割线***************************************************************
本人享有博客文章的版权,转载请标明出处:http://blog.csdn.net/qingyu2431
linux字符设备驱动学习笔记1
最新推荐文章于 2024-09-02 13:20:38 发布