其实上一篇博客的写法就是非阻塞模型,感兴趣可以参考一下~
传统方式和设备树方式获取中断_灵魂之Ca的博客-CSDN博客
前言:
框架:字符设备驱动框架
什么是IO模型呢?
IO顾名思义就是输入输出,对于linux驱动而言,输入输出就是file_operations里的read和write操作。至于模型,这个词听起来牛批,其实就是io读写的不同模式,比如非阻塞io,从应用程序的角度(驱动是服务于应用的),无论有没有read到数据,都会返回一个值,程序不会卡在原地;而阻塞io,就是应用程序,没有read到数据就会卡在原地不动(后面我们会知道其实是这个应用程序的read所对应的驱动被吊起来了,既不进行下去,也不返回,造成了应用程序卡在那里的情况~)
其他的io模型还有poll多路监听模型,异步io模型。
听起来好像把应用程序卡住不是好事,但是呢从cpu资源的角度,有些占用资源的空操作(如没有数据还一直读取),就应该吊起来。如下如图,如果应用程序是while读取按键数据并且是以非阻塞的IO模型,那么cpu资源的占用是非常炸烈的。
如下:
运行非阻塞IO模型的按键驱动和应用程序,可见应用程序的资源占用达到了惊人的24%,然而它只是一个小小的readkey程序而言。
而用阻塞IO模型(在驱动中,驱动read--判断没有数据就将驱动进程挂起,当有按键中断时写语句将驱动进程唤醒从而回应循环read的应用进程,将数据返回过去)这样的模型,cpu资源占用就很小很小!因为没有数据的时候驱动进程没有任何操作,顶多cpu也就执行应用程序的read这一条代码而已,没有调用到驱动程序一系列代码。
可见,这样不仅能有正常的读取按键功能,而且cpu资源占用率也很低。
代码思路:
阻塞io模型 即没有资源时把app进程挂起,有资源了再唤醒app进程来读
实现思路:0.初始化等待队列 1.将驱动进程加入等待队列 2.将驱动进程状态设为TASK_INTERRUPTIBLE 3.使之休眠
有数据时:4.中断唤醒驱动进程
以下是代码~和相关注释:
代码:
hw_charDev_describe.h
//仍然先是描述外设的结构体
//#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/wait.h>
struct key_event{
int code; //表示按键的内涵:home/esc/Q/W/E/R/T/ENTER
int value; //按下1,抬起0
unsigned char state; //按键状态 有没有按
};
struct hw_describe{
char *dev_name; //外设名字
unsigned int dev_major; //外设设备号
struct class *hw_class; //外设设备类型
struct device *dev; //外设设备节点
struct cdev mycdev; //注册cdev到系统
struct device_node *mynode; //设备节点
unsigned int phyreg_base; //外设寄存器物理基地址
volatile unsigned int *virreg_base; //外设寄存器虚拟基地址
unsigned int regmap_size; //ioremap的长度,一个寄存器是4,即4Byte,32位
int mygpio; //gpio号
int irqno; //中断号
//外设类型
struct key_event mykey_event;
//IO操作 实现阻塞IO的关键--等待队列
wait_queue_head_t wq_head;
};
6th_charDev_pendingApp.c
//阻塞io模型 即没有资源时把app进程挂起,有资源了再唤醒app进程来读
//实现思路:1.将驱动进程加入等待队列 2.将驱动进程状态设为TASK_INTERRUPTIBLE 3.使之休眠
//以上三步都由函数 wait_event_interruptible 直接完成
//有数据时:4.中断唤醒驱动进程
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("LCH");
#include "hw_charDev_describe.h"
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <asm/io.h>
#include <linux/gpio.h>
#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
#include <linux/uaccess.h>
#include <uapi/linux/input-event-codes.h>
struct hw_describe *key_dev;//用于描述硬件外设
unsigned char key_state = 0;//0表示没有按下,1表示有按下
/*打开操作*/
static int chardevnode_open(struct inode *inode, struct file *file){
printk(KERN_EMERG "chardevnode_open is success!\n");
return 0;
}
/*关闭操作*/
static int chardevnode_release(struct inode *inode, struct file *file){
printk(KERN_EMERG "chardevnode_release is success!\n");
return 0;
}
/*IO操作*/
static long chardevnode_ioctl(struct file *file, unsigned int cmd, unsigned long arg){
// printk(KERN_EMERG "chardevnode_ioctl is success! cmd is %ld,arg is %ld \n",cmd,arg);
return 0;
}
ssize_t chardevnode_read(struct file *file, char __user *buf, size_t count, loff_t *f_ops){
//用户要读,ret = copy_to_user(buf用户的,给用户数据的指针,count);ret!=0是不正常的
int ret;
//如果是非阻塞模型,并且没有数据,就返回一个出错码
//否则作阻塞模型处理
if(file->f_flags&O_NONBLOCK && !key_dev->mykey_event.state){
return -EAGAIN;
}
//判断有无数据,没有就让驱动进程休眠 参数2 1:不等待 0:等待
wait_event_interruptible(key_dev->wq_head,key_dev->mykey_event.state);
ret = copy_to_user(buf,&key_dev->mykey_event,count);
if(ret>0){
printk(KERN_EMERG "copy_to_user error!\n");
return -EFAULT;
}
memset(&key_dev->mykey_event,0,sizeof(key_dev->mykey_event));
key_dev->mykey_event.state=0;
return 0;
}
ssize_t chardevnode_write(struct file *file, const char __user *buf, size_t count, loff_t *f_ops){
//用户写进来,ret = copy_from_user(存数据的指针,buf用户写进来的,count);ret!=0是不正常的
return 0;
}
struct file_operations fops ={
.open = chardevnode_open,
.release = chardevnode_release,
.unlocked_ioctl = chardevnode_ioctl,
.read = chardevnode_read,
.write = chardevnode_write,
};
static irqreturn_t keyirq_handler(int irqno,void *devid){
int value;
printk("receive a interrupt 9!\n");
// 4.1非设备树方式
// value = *(key_dev->virreg_base+1)&(0x1<<1);
// 4.2设备树方式
value = gpio_get_value(key_dev->mygpio);
if(value==1){ //没按下 == key_event.value = 0
key_dev->mykey_event.code = KEY_ENTER;
key_dev->mykey_event.value = 0;
printk("key up!\n");
}
else if(value==0){ //按键按下
key_dev->mykey_event.code = KEY_ENTER;
key_dev->mykey_event.value = 1;
printk("key down!\n");
}
//有数据了,唤醒驱动进程~
wake_up_interruptible(&key_dev->wq_head);
key_dev->mykey_event.state = 1;
return IRQ_HANDLED;
}
static int __init charDev_init(void)
{
int ret =0 ;
printk(KERN_EMERG "charDev_init\n");
//0.给外设对象分配空间
key_dev = kmalloc(sizeof(struct hw_describe),GFP_KERNEL);
if(key_dev ==NULL){
printk(KERN_EMERG "kmallock error\n");
return(-ENOMEM);
}
key_dev->dev_name = "mykey";
//1.申请主设备号 设备号是MKDEV(key_dev->dev_major,自选)
key_dev->dev_major=register_chrdev(0,key_dev->dev_name,&fops);
if(key_dev->dev_major<0)
{
printk(KERN_EMERG "register_chrdev error\n");
ret = -ENODEV;
goto err_0;
}
else
{
printk(KERN_EMERG "dev_major is %d\n",key_dev->dev_major);
}
//2.注册设备到系统 /proc/devices
cdev_init(&(key_dev->mycdev),&fops);
(key_dev->mycdev).owner = THIS_MODULE;
(key_dev->mycdev).ops = &fops;
ret = cdev_add(
&(key_dev->mycdev),MKDEV(key_dev->dev_major,0),1);
if(ret){
printk(KERN_EMERG "cdev_add is fail! %d\n",ret);
goto err_1;
}else{
printk(KERN_EMERG "cdev_add is success!\n");
}
//3.生成设备节点
key_dev->hw_class = class_create(THIS_MODULE,"charDev_class");
if(IS_ERR(key_dev->hw_class))
{
printk(KERN_EMERG "class_create error\n");
ret = PTR_ERR(key_dev->hw_class);
goto err_2;
}
key_dev->dev=device_create(key_dev->hw_class,NULL,MKDEV(key_dev->dev_major,0),NULL,key_dev->dev_name);//设备节点名字
if(IS_ERR(key_dev->dev))
{
printk(KERN_EMERG "device_create error\n");
ret = PTR_ERR(key_dev->dev);
goto err_3;
}
else
{
printk(KERN_EMERG "dev_name is %s\n",key_dev->dev_name);
}
//4.获取硬件资源
/*
// 4.1直接从原理图得到硬件信息
//key GM_INT9--GPX1_1--0x11000C20
key_dev->regmap_size = 4;
key_dev->phyreg_base = 0x11000C20 ; //寄存器基地址
//+0是CON +1是DAT +2是PUD +3是DRV
key_dev->virreg_base = ioremap(key_dev->phyreg_base,4*key_dev->regmap_size);//映射4个寄存器
if(key_dev->virreg_base==NULL){
printk(KERN_EMERG "ioremap error\n");
ret = -ENOMEM;
goto err_4;
}
//配置GPX1-1为输入
*(key_dev->virreg_base) &= (~(0xF<<4)); //清除4-7位
*(key_dev->virreg_base) |=((0x0)<<4); //将4-7位置为0x1
//初始化GPX1-1为高电平,即和原理图一样
*(key_dev->virreg_base+1) &= (~(0x1<<1)); //清除第1位
*(key_dev->virreg_base+1) |=((0x1)<<1); //将第1位置为0x1,即高电平
//初始化GPX1-1为上拉,即和原理图一样
*(key_dev->virreg_base+2) &= (~(0x3<<2)); //清除【3:2】位
*(key_dev->virreg_base+2) |=((0x3)<<2); //将其置为0x3,即上拉
*/
// 4.2设备树方式获得硬件信息
// /mykey compatible = "gpio-key"
key_dev->mynode = of_find_node_by_name(NULL,"mykey");
if(key_dev->mynode==NULL){
printk(KERN_EMERG "of_find_node_by_name error\n");
ret = -ENOMEM;
goto err_4;
}
key_dev->mygpio = of_get_named_gpio_flags(key_dev->mynode,"gpios",0,NULL);
if (!gpio_is_valid(key_dev->mygpio))
printk("gpio isn't valid\n");
else printk("gpio num=%d",key_dev->mygpio);
ret= gpio_request(key_dev->mygpio, "gpios");
if(ret!=0){
printk(KERN_EMERG "gpio_request error\n");
ret = -ENOMEM;
goto err_5;
}
gpio_direction_input(key_dev->mygpio);//初始化为输入模式
//获取中断号--申请中断
key_dev->irqno = of_irq_get(key_dev->mynode,0);
ret = request_irq(key_dev->irqno,keyirq_handler,IRQ_TYPE_EDGE_BOTH,"keyirq",NULL);
if(ret==0){ //返回0说明正确
printk(KERN_EMERG "key_dev->irqno is %d\n",key_dev->irqno);
}
//阻塞io:初始化等待队列头
init_waitqueue_head(&key_dev->wq_head);
return 0;
err_5:
gpio_free(key_dev->mygpio);
err_4:
device_destroy(key_dev->hw_class,MKDEV(key_dev->dev_major,0));
err_3:
class_destroy(key_dev->hw_class);
err_2:
unregister_chrdev(key_dev->dev_major,key_dev->dev_name);
err_1:
cdev_del(&(key_dev->mycdev));
err_0:
kfree(key_dev);
return(ret);
}
static void __exit charDev_exit(void)
{
free_irq(key_dev->irqno,NULL);
gpio_free(key_dev->mygpio);
//iounmap(key_dev->virreg_base);
device_destroy(key_dev->hw_class,MKDEV(key_dev->dev_major,0));
class_destroy(key_dev->hw_class);
cdev_del(&(key_dev->mycdev));
unregister_chrdev(key_dev->dev_major,key_dev->dev_name);
kfree(key_dev);
}
module_init(charDev_init);
module_exit(charDev_exit);
app_readkey_pending.c
其实和上一个是一样的
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <unistd.h>
struct key_event{
int code; //表示按键的内涵:home/esc/Q/W/E/R/T/ENTER
int value; ///按下1,抬起0
unsigned char state; //按键状态 有没有按
};
//打开设备节点 传入cmd ,
int main(int argc ,char **argv){
int fd,res,digital,readNum;
struct key_event mykey;
char *readSWDigitalNode="/dev/mykey";
unsigned char readBuffer;
//打开节点
fd = open(readSWDigitalNode,O_RDWR);
if(fd>0)printf("app open readSWDigitalNode success\n");
else {
printf("app open readSWDigitalNode err\n");
return -1;
}
while(1){
readNum = read(fd, (void *)(&mykey),sizeof(struct key_event));//读取1B的数据
if(mykey.state==1){
if(mykey.value==1){ //按下1,抬起0
printf("app key down\n");
}
else if(mykey.value==0){
printf("app key up\n");
}
mykey.state=0;
}
}
close(fd);
return ;
}
完~