在上一章实现了简单的按键驱动,但是我们通过insmod到内核,按下按键以后,查看CPU资源运行情况,发现就是一个简单的按键驱动竟然用了20%多的资源,tiny4412可是一个4核的cpu!!!
为神马会出现这种情况,我们来分析一下并且改进它
单纯的用按键测试驱动,并不会出现资源占用过大,但是用了app去测试却发现有这种情况
在app中
有一个while函数,里面有一个read函数,无论两个if语句是否成立,都会反复执行循环,调用read函数,所以就会有这样一个情况,系统资源被反复调用read函数浪费
所以,我们判断,当有数据的时候才read,没有数据时候就不读,一直阻塞在那里
所以在这里我们用到阻塞模型
代码在上一节代码稍作改动即可!
如何判断没有数据时候就不read呢?
在驱动read函数中我们进行判断,如果有数据就copy_to_user,没有数据就讲该进程上下文休眠
我们通过按键描述结构体中的state来判断,1代表按下,有数据。0代表没有按下,没有数据,进行休眠。
所以在按键按下时候,我们需要在中断服务函数irqreturn_t key_handle_t中唤醒进程
在唤醒函数wake_up_interruptible中需要一个参数,等待队列头
在之前的休眠进程函数中我们也用到了等待队列头这个参数,其实这个是相当于将休眠的进程将入到一个队列排队,之后再将这些队列给依次唤醒
所以我们需要在结构体中定义这个等待队列头
wait_queue_head_t key_hand_queue;
当驱动被装在进内核时候就应该进这个等待的队列初始化
init_waitqueue_head(&my_key.key_hand_queue);//初始化等待队列头
改动后的驱动代码如下:
#include<linux/init.h>
#include<linux/module.h>
#include<linux/device.h>
#include<linux/fs.h>
#include <asm/uaccess.h>
#include<linux/interrupt.h> //中断注册注销头文件
#include<linux/gpio.h> //gpio相关的头文件
#include<linux/cdev.h>
#include <linux/string.h>
#include<linux/sched.h>
struct key_event{
int code;//按键类型
int value;//按键状态,按下或者抬起( 0 / 1 )
};
struct key_dsc{
int major;
struct cdev *key_cdev;
struct class *key_class;
struct device *key_dev;
int irq;//中断号
unsigned long flag;
struct key_event my_event;
wait_queue_head_t key_hand_queue;
int key_state;
};
#define device_name "qin_key"
#define class_name "qin_class"
#define KEY_ENTER 28
struct key_dsc my_key;
ssize_t key_read(struct file *file, const char __user *buffer, size_t count, loff_t *fpos){
// ****************read函数目的是将案按键的数组传到应用层,kbuf[4] ************
//没有数据将会休眠进行到这一步,不会再往下执行
wait_event_interruptible(my_key.key_hand_queue,my_key.key_state);//将进程休眠,用my_ke中的state状态判断
//有数据将会继续往下执行
if(copy_to_user(buffer,&my_key.my_event,sizeof(struct key_event)))//copy_to_user 返回零代表copy失败,1代表成功
{
printk("\n copy fail!! \n");
}
memset(&my_key.my_event,0,sizeof(struct key_event));
my_key.key_state=0;//本次数据传送完需要将状态置0,继续休眠
return count;
}
ssize_t key_write (struct file *file, const char __user *buf, size_t count, loff_t *fpos){
printk("\n key_ is write!! \n");
}
int key_open (struct inode *inode, struct file *file){
printk("\n key_ is open!! \n");
return 0;
}
int key_close(struct inode *inode, struct file *file){
printk("\n key_ is close!! \n");
}
irqreturn_t key_handle_t(int irq,void *dev_id){
int dn=0;
dn=gpio_get_value(EXYNOS4_GPX3(2));//根据这个函数获取GPX3_2的按键状态
if(!dn)
{
printk("\n key down !! \n");
my_key.my_event.code=KEY_ENTER;
my_key.my_event.value=1;
}
else
{
printk("\n key up !! \n");
my_key.my_event.code=KEY_ENTER;
my_key.my_event.value=0;
}
wake_up_interruptible(&my_key.key_hand_queue);//唤醒进程
my_key.key_state=1;//此时有数据,将状态置1
return IRQ_HANDLED;
}
static struct file_operations myfops={
.owner=THIS_MODULE,
.open=key_open,
.release=key_close,
.write=key_write,
.read=key_read,
};
static int __init mykey_init(void)
{
my_key.irq=0;
my_key.major=250;
my_key.flag=IRQF_DISABLED |
IRQF_TRIGGER_FALLING |
IRQF_TRIGGER_RISING;//设置属性,上升,下降沿触发
my_key.irq=gpio_to_irq(EXYNOS4_GPX3(2));//获取中断号
request_irq(my_key.irq,key_handle_t,my_key.flag,"key1",NULL);//将这个按键注册到内核,这样才能识别
if(register_chrdev(my_key.major,"qin",&myfops))//返回值为1则失败
{
printk("\nregister is faile !\n");
}
else
{
printk("\nregister is ok !\n");
}
my_key.key_class=class_create(THIS_MODULE,"class");
my_key.key_dev=device_create(my_key.key_class,NULL,MKDEV(my_key.major,0),NULL,"qin");
printk("\nI am key_dev\n");
my_key.key_state=0;
init_waitqueue_head(&my_key.key_hand_queue);//初始化等待队列头
return 0;
}
static int __exit mykey_exit(void)
{
free_irq(my_key.irq,NULL); //注销key1中断
device_destroy(my_key.key_class,MKDEV(my_key.major,0));//注销dev
class_destroy(my_key.key_class);//注销class
unregister_chrdev(my_key.major,"qin");
printk("\n bye bye~~~~~~~\n");
return 0;
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
app不需要做改动
总结:
此源代码在上一个的修改有
#include<linux/sched.h> 这里添加的函数需要用到这个头文件
1.wait_queue_head_t key_hand_queue;
结构体中定义等待队列头,定义按键中的state用于之后判断是否将进程休眠,
因为结构体没有初始化所以需要在入口函数进行置0,一开始是没有数据的
my_key.key_state=0;
2.init_waitqueue_head(&my_key.key_hand_queue);//初始化等待队列头
在init入口函数中初始化等待队列头
3.wait_event_interruptible(my_key.key_hand_queue,my_key.key_state);//将进程休眠,用my_ke中的state状态判断
在read函数中判断是是否将进程休眠
4.当有数据来的时候(按键按下),在irqreturn_t key_handle_t(int irq,void *dev_id)函数中进行唤醒和状态置1
wake_up_interruptible(&my_key.key_hand_queue);//唤醒进程
my_key.key_state=1;//此时有数据,将状态置1
*/