字符设备驱动
1.1理解什么是驱动
我们根据这张图来分析一个软件系统的层次关系。
1首先app1等应用层的应用程序使用库提供的open函数等,这些函数可以代表打开led设备文件。
2库根据open函数传入的参数执行swi指令,这条指令会引起CPU异常,进入内核。
3内核的一场处理函数会根据这些参数找到相应的驱动程序,返回一个文件句柄给库,进而返回给应用程序。
4应用程序的得到句柄之后,使用库函数提供的write或ioclt函数发出控制命令。
5库根据write或ioclt函数传入的参数执行swi指令,这条指令会引起CPU异常进入内核。
6内核的异常处理函数会根据这些参数调用驱动程序的相关函数,点亮LED。实际上内核和驱动程序没有界限,驱动程序最终还是要编译进内核的,只不过有些可能是动态加载。
在这强调一下驱动程序只是一个工具,他的功能已经被设计好之后,不能通过应用层来修改,只能进行变化,调节,要修改的话需要重新编译驱动程序。
1.2驱动的分类和开发步骤
这张图中从应用层到底层硬件之间的映射就是驱动的框架,通过这个框架来完成对底层的控制。
一.驱动程序的分类:可以分为三种,字符设备character device 块设备 block device 网络接口 network interface
二.
三.驱动程序开发的步骤
一般来说编译设备驱动程序的大致流程如下。
1查看原理图,数据手册,了解设备的操作方法。
2在内核中找到相近的驱动程序,参照它进行开发。
3实现驱动程序的初始化,比如向内核注册这个驱动程序,这样应用程序传入文件名时候,内核才能找到相应的驱动程序。
4设计所要实现的功能,比如open,close,read,write等函数。
5需要实现中断服务的设备驱动需要实现中断服务。
6编译该驱动程序到内核,或者用insmod命令加载驱动程序。
7最后测试程序。
四.驱动程序的加载和卸载
1.3字符设备驱动的开发(基础)
下面我们通过控制led灯为切入点进行驱动程序的编写。
我们通过这个结构体告诉内核我们要注册给内核的函数。
然后通过这个init函数进行初始化,我们通过register_chrdev把上面的结构体完整的注册进去,上面两步骤就完成了函数的注册,当然这个init函数也需要用module_int申明一下。
此外还要记上GPL协议。
卸载的话与此相反,这里我们就不说了。
最后我们将已经注册进去的函数进行编译,把想要实现的功能放进去,从而实现我们的闪灯效果。
下面看一下我们的应用程序。
这个应用程序就特别短了。在第14行打开设备文件的时候就会执行驱动程序里的open程序,在应用程序里进行判断开关后将val的地址传送到write函数,之后再驱动函数write中获取传送进来的值进行对寄存器的操作,从而实现开关灯。这样就体现的应用程序无法操作底层,只有通过调用驱动来进行底层访问,这也就是驱动的作用。Rmmod卸载驱动,insmod 是加载驱动。
1.3字符设备驱动的开发(中断基理)
一.中断的处理流程
我们用中断方式来控制按键的程序来进行分析。
二.源码分析
首先我们看一下模块的初始化函数和卸载函数
在内容上没什么特别要说的。驱动程序的核心还是在于file_operations结构体。
static struct file_operations sencod_drv_fops = {
.owner = THIS_MODULE, /* 这是一个宏,推向编译模块时自动创建的__this_module变量 */
.open = third_drv_open,
.read = third_drv_read,
.release = third_drv_close,
};
这里告诉了内核要注册哪些函数。经过注册之后我们就可以使用它了。
static int third_drv_open(struct inode *inode, struct file *file)
{
/* 配置GPF0,2为输入引脚 */
/* 配置GPG3,11为输入引脚 */
request_irq(IRQ_EINT0, buttons_irq, IRQT_BOTHEDGE, "S2", &pins_desc[0]);
request_irq(IRQ_EINT2, buttons_irq, IRQT_BOTHEDGE, "S3", &pins_desc[1]);
request_irq(IRQ_EINT11, buttons_irq, IRQT_BOTHEDGE, "S4", &pins_desc[2]);
request_irq(IRQ_EINT19, buttons_irq, IRQT_BOTHEDGE, "S5", &pins_desc[3]);
return 0;}
static irqreturn_t buttons_irq(int irq, void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;
pinval = s3c2410_gpio_getpin(pindesc->pin);
if (pinval)
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}
ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */
return IRQ_RETVAL(IRQ_HANDLED);
}
当应用程序打开设备文件的时候进入这个函数,从而进行中断处理。Close函数与此相反,他的作用是释放已经注册的中断。最终我们通过read函数将参数传入到应用程序中去,实现中断方式的按键驱动。
应用程序如图所示。
三.简略介绍驱动中的其他重要函数作用。
Poll机制
我们在这加一个poll的函数,他的作用就是避免read等函数一直执行读的状态,限制在一定时间内如果完成不到任务就进行下一个动作如睡眠等。
异步通知
这里fasync函数完成了异步通知的作用:他可以将一个程序的参数或结果传入到另外一个函数中去。如下图
通过上图的这一个机制,我们就可以实现应用程序被动的接受数据,这样应用程序就可以节省很多空间来做其他的,耗费的资源也少。
四.同步互斥阻塞
这句话的意思就是同一个时刻只能有一个应用程序使用驱动,其他的需要用到驱动程序的应用程序需要等待。这个功能属于标志位的判断,不是单独一个函数实现的,这个就麻烦了,这里我们先知道有这一个东西吧,明白她的原理和作用。
1.3块设备驱动和网络设备驱动的大致了解
现场学习
2018 年2 月 8 日
刘朋
链接: https://pan.baidu.com/s/17uZ0ZyZ28ttSn-NVlN18Pw 密码: e6m5