Linux设备驱动(二)—— 字符设备驱动(一)

字符设备驱动程序(一)


概念


linux系统从千千万万设备中提取它们的共性,将这些设备分成3大类:字符设备,块设备,网络设备。
字符设备是前面提到的这三类设备中最常见的设备,比如生活中大家常见的键盘、鼠标、触摸屏等都属于字符设备。所以掌握字符设备驱动框架是每个驱动程序员所必须的。

应用程序与内核、驱动的关系


u-boot 启动内核
内核 启动应用程序
应用 读写文件、获取键值

用点灯程序为例
应用程序,直接调用open read write 来调用驱动中的led_open、led_read、led_write
字符驱动架构
在这里插入图片描述
1.首选上层应用程序调用open函数打开/dev目录下的一个文件 xxx(这个文件就是“设备文件”)
在这里插入图片描述

2.open()是库里面的函数,所以库里面的一部分代码会被调用
在这里插入图片描述

3.这时候库里面会执行异常指令,执行这些异常指令最后导致的结果就是产生“系统调用”进入内核,这个时候从用户空间进入了内核空间
在这里插入图片描述
** 4、进入内核后,内核会接受到这个异常并开始处理,处理的结果是找到对应的驱动程序**
在这里插入图片描述
5、找到对应的驱动后,驱动程序中的xxx_open()函数就会被调用。因为驱动程序是在内核里面执行的,它的运行权限就比较大,所以在驱动的open()函数就可以对硬件设备进行操作了。
在这里插入图片描述

根据上面的5个步骤,最终open()函数和xxx_open()函数对应起来。
上述是open()函数调用的过程,open一个文件之后,紧接着就会调用read() write() close()等函数,他们的调用过程和open()函数是一样的。

驱动框架中的问题

1.C库是怎么进入内核呢?open,read,write实现的实质是什么?

执行swi val(汇编指令),引发一个异常,相当于中断一样。当发生这个异常的时候,就会进入内核的异常处理函数里面。
2.在库中执行一段异常指令就会通过系统调用进入内核,内核是怎么找到对应的驱动程序的呢?**
上层的open函数调用之后是怎么找到具体的驱动程序并且调用该驱动程序中的open函数的——设备文件中的主从设备号。所以我们从这里也得出了一个非常重要的结论:主从设备在跟设备文件绑定的同时也跟具体的驱动程序绑定在一起

设备文件:从文字上看就是设备的文件,这类文件就是专门给硬件设备服务的文件,而且这类文件都存在/dev目录下,每当我们向内核中加一个设备驱动程序,这时候就会在相应的/dev目录下生成相应的设备文件总结起来:设备文件就是跟驱动程序对应起来的,是上层应用访问驱动的接口。下面我们就来看一下这个设备文件的属性:

  1. 访问权限之前的字母是b或c,分别表示块设备和字符设备。

  2. 设备文件没有文件长度,而增加了另外的两个值,分别是主设备号和从设备号。二者共同形成一个唯一的号码,内核可由此查找对应的设备驱动程序。

  3. 之所以给设备文件分配名称,是因为用户更容易记忆符号名而不是数字,但名称无法表示设备文件的实际功能,这主要是通过主从设备号表示一个设备的,设备文件所处的目录也与其功能不相干。

设备文件中非常重要的3个属性:
1.设备类型:c和b,c代表这个设备文件对应的设备驱动程序是一个字符设备驱动程序,b代表这个设备文件对应的设备驱动程序是一个块设备驱动程序。

2.—主设备号(范围:1~254):标识一类驱动程序。通过前面这个图我们可以看出主设备号204是标识串口这一类驱动程序;主设备号31标识mtd这一类驱动程序。那这里就有一个问题了,我们知道一台电脑上不止一个串口,这多个串口的驱动程序都是用主设备号204来标识,那我们怎么从中找到某一个具体的串口驱动程序呢,这个时候就需要我们的从设备号了。

3.从设备号(范围:0~255):对应一类驱动程序中某一个具体的驱动程序。当我们通过204这个主设备号找到串口这一类驱动程序后,再通过从设备号找到具体的某一个串口的驱动程序。

3.系统调用接口的作用(system call interface)

在异常处理函数里面,根据发生异常的原因,调用不同的处理函数。

例如:使用open函数,则传进来的值为val1(swi val1);使用read函数,则传进来的值为val2(swi val2);使用write函数,则传进来的值为val3(write val3)。内核里的系统调用接口(system callinterface),会根据传进来不同的值,去调用sys_open、sys_read和sys_write。


总结

Sytem call interface系统调用接口是 决定 使用哪一个函数(read(),write(),open()……)

VFS虚拟文件系统 是 决定 调用哪一个驱动程序的open()函数(led_open()、memdev_open()……)

file_operations
file_operations:是一个函数指针的集合

  • 应用程序和VFS之间的接口是系统调用,而VFS与磁盘文件系统以及普通设备之间的接口是file_operations结构体成员函数;file_operations结构体中成员函数是字符设备驱动与内核的接口,是用户空间对Linux进行系统调用最终的落实者,这个结构体包含对文件打开,关闭,读写,控制的一系列成员函数。
  • 由于字符设备的上层没有磁盘文件系统,所以字符设备的file_operations成员函数就直接由设备驱动提供了,file_operations正是字符设备驱动的核心。
  • 而对于块设备而言,ext2,fat,jffs2等文件系统中会实现对VFS的file_operations成员函数,设备驱动层将看不到file_operations的存在。磁盘文件系统和设备驱动会将磁盘上文件的访问最终转换成对磁盘上柱面和扇区的访问。

file_operations成员

static const  struct  file_operations  XXX_fops =
{
	.owner  =  THIS_MODULE,
	.llseek  =  XXX_llseek,
	.open  =  XXX _open,
	.read  =  XXX _read,
	.write =   XXX _write,
	.ioctl    =     XXX _ioctl,
	.release  =  XXX _release,
};

struct module *owner
第一个 file_operations 成员根本不是一个操作; 它是一个指向拥有这个结构的模块的指针. 这个成员用来在它的操作还在被使用时阻止模块被卸载. 几乎所有程序中, 它被简单初始化为 THIS_MODULE, 一个在 <linux/module.h> 中定义的宏.

cloff_t (*llseek) (struct file *, loff_t, int); 

文件定位函数,合法时返回文件的当前位置,不合法返回-EINVAL

第一个参数为file指针,第二个为请求偏移量,第三个为文件定位的起始地址:一般为0或1,0表示文件开头,1表示当前位置

ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);

读函数,利用copy_to_user()函数让内核读取用户空间的数据,并返回访问的字节数

ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 

写函数利用copy_from_user()函数让用户向文件写入数据,并返回写入的字节数

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

执行I/O控制命令

int (*open) (struct inode *, struct file *);

打开文件

(7)int (*release) (struct inode *, struct file *) ;

关闭文件


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值