在上一篇按键驱动编程实验中,虽然我们实现了K2和K3的按键驱动,但是测试程序在运行的时候会一直调用read函数,导致cpu占有率很高,我们看一下测试程序运行的时候cpu的占有率,如下图。
测试程序运行时占了不少cpu资源,所以我们这里用阻塞和非阻塞模式来实现。具体就是说当没有数据的时候,进程就阻塞不占用cpu资源,当有数据的时候再讲进程唤醒。也就是触发中断的时候唤醒进程。我们看一下具体的驱动代码实现。
#include <linux/module.h>
#include <linux/init.h>
#include <linux/of.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/slab.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#include <linux/sched.h>
#define GPX1CON 0x11000c20
#define KEY2_ENTER 28
#define KEY3_ENTER 29
struct key_event{
int code;
int value;
};
struct dev_desc{
void *reg_addr;
int key_major;
unsigned int irqno1;
unsigned int irqno2;
struct class *cls;
struct device *dev;
struct key_event event;
wait_queue_head_t wq_head;//等待队列头
int key_state;//是否有数据
};
struct dev_desc *key_dev;
//中断处理函数
irqreturn_t key2_handler(int irqno, void *dev)
{
unsigned int value;
printk("-----------------%s--------------------\n",__FUNCTION__);
//读取key2的状态
value = readl(key_dev->reg_addr+4) & (0x1<<1);
if(value){
key_dev->event.code = KEY2_ENTER;
key_dev->event.value = 1;
printk("key2 up\n");
}else{
key_dev->event.code = KEY2_ENTER;
key_dev->event.value = 0;
printk("key2 down\n");
}
//表示有数据了,唤醒等待队列,同时设置标志位
wake_up_interruptible(&key_dev->wq_head);
key_dev->key_state = 1;
return IRQ_HANDLED;
}
irqreturn_t key3_handler(int irqno, void *dev)
{
unsigned int value;
printk("-----------------%s--------------------\n",__FUNCTION__);
//读取key3的状态
value = readl(key_dev->reg_addr+4) & (0x1<<2);
if(value){
key_dev->event.code = KEY3_ENTER;
key_dev->event.value = 1;
printk("key3 up\n");
}else{
key_dev->event.code = KEY3_ENTER;
key_dev->event.value = 0;
printk("key3 down\n");
}
//表示有数据了,唤醒等待队列,同时设置标志位
wake_up_interruptible(&key_dev->wq_head);
key_dev->key_state = 1;
return IRQ_HANDLED;
}
ssize_t key_read (struct file *filp, char __user *buf, size_t count, loff_t *fpos)
{
int ret;
printk("-----------------%s--------------------\n",__FUNCTION__);
//如果当前是非阻塞模式且没有数据就立即返回
if(filp->f_flags & O_NONBLOCK && !key_dev->key_state){
return -EAGAIN;
}
//没有数据的时候就休眠(阻塞模式)
wait_event_interruptible(key_dev->wq_head,key_dev->key_state);
//表示有数据
ret = copy_to_user(buf, &key_dev->event, count);
if(ret){
printk("copy_from_user failed\n");
return -EFAULT;
}
memset(&key_dev->event, 0, sizeof(key_dev->event));
key_dev->key_state = 0;
return count;
}
ssize_t key_write (struct file *filp, const char __user *buf, size_t count, loff_t *fpos)
{
printk("-----------------%s--------------------\n",__FUNCTION__);
return 0;
}
int key_open (struct inode *inode, struct file *filp)
{
printk("-----------------%s--------------------\n",__FUNCTION__);
return 0;
}
int key_close (struct inode *inode, struct file *filp)
{
printk("-----------------%s--------------------\n",__FUNCTION__);
return 0;
}
const struct file_operations fops = {
.open = key_open,
.read = key_read,
.write = key_write,
.release = key_close,
};
static int __init key_dev_init(void)
{
struct device_node *np1,*np2;
int err = 0;
printk("-----------------%s--------------------\n",__FUNCTION__);
//分配空间
key_dev = kzalloc(sizeof(struct dev_desc), GFP_KERNEL);
if(key_dev == NULL){
printk("kzalloc failed\n");
return -ENOMEM;
}
//获取key2的设备树节点
np1 = of_find_node_by_path("/key2_node");
if(np1 == NULL){
printk("key2 of_find_node_by_path failed\n");
}
//获取key3的设备树节点
np2 = of_find_node_by_path("/key3_node");
if(np2 == NULL){
printk("key3 of_find_node_by_path failed\n");
}
//获取key2中断号
key_dev->irqno1 = irq_of_parse_and_map(np1,0);
//获取key3中断号
key_dev->irqno2 = irq_of_parse_and_map(np2,0);
//打印中断号
printk("irqno of key2 is %d\n",key_dev->irqno1);
printk("irqno of key3 is %d\n",key_dev->irqno2);
//申请主设备号
key_dev->key_major = register_chrdev(0, "key_drv",&fops);
if(key_dev->key_major < 0){
printk("register_chrdev failed\n");
err = -ENODEV;
goto err_0;
}
//创建设备节点
key_dev->cls = class_create(THIS_MODULE, "key_class");
if(IS_ERR(key_dev->cls)){
printk(KERN_ERR "class_create failed\n");
err = PTR_ERR(key_dev->cls);
goto err_1;
}
key_dev->dev = device_create(key_dev->cls, NULL, MKDEV(key_dev->key_major, 0), NULL, "key");
if(IS_ERR(key_dev->dev)){
printk(KERN_ERR "device_create failed\n");
err = PTR_ERR(key_dev->dev);
goto err_2;
}
//key2申请中断
if(request_irq(key_dev->irqno1,key2_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"key2_irq",NULL)){
printk("key2 request_irq failed\n");
}
//key3申请中断
if(request_irq(key_dev->irqno2,key3_handler,IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING,"key3_irq",NULL)){
printk("key3 request_irq failed\n");
}
//将GPX1CON物理地址映射为虚拟地址reg_addr
key_dev->reg_addr = ioremap(GPX1CON,8);
if(key_dev->reg_addr == NULL){
printk("ioremap error\n");
err = -EFAULT;
goto err_3;
}
//初始化等待队列
init_waitqueue_head(&key_dev->wq_head);
return 0;
err_3:
device_destroy(key_dev->cls,MKDEV(key_dev->key_major, 0));
err_2:
class_destroy(key_dev->cls);
err_1:
unregister_chrdev(key_dev->key_major, "key_drv");
err_0:
kfree(key_dev);
return err;
}
static void __exit key_dev_exit(void)
{
printk("-----------------%s--------------------\n",__FUNCTION__);
//释放地址映射
iounmap(key_dev->reg_addr);
//释放key2中断
free_irq(key_dev->irqno1,NULL);
//释放key3中断
free_irq(key_dev->irqno2,NULL);
//释放设备节点
device_destroy(key_dev->cls,MKDEV(key_dev->key_major, 0));
class_destroy(key_dev->cls);
//释放设备号
unregister_chrdev(key_dev->key_major, "key_drv");
//释放key_dev
kfree(key_dev);
}
module_init(key_dev_init);
module_exit(key_dev_exit);
MODULE_LICENSE("GPL");
紫色部分是采用阻塞模式新增的代码,首先我们在模块入口函数中初始化等待队列init_waitqueue_head(&key_dev->wq_head),我们再定义一个变量来表示是不是有数据产生key_state,这里要初始化为0,作为wait_event_interruptible(key_dev->wq_head,key_dev->key_state)的第二个参数,key_state为0代表条件不成立,所以进程执行wait_event_interruptible的时候就会使当前进程休眠,也就是将当前进程放进我们初始化好的等待队列中,然后我们在两个中断函数中唤醒等待队列,同时设置数据标志位,说明有数据产生,进而唤醒进程。
测试代码不变,还是上一篇的代码。重新编译驱动程序加载进内核,我们再次运行测试程序。然后查看测试程序运行时cpu的占有率。如下图。
发现此时几乎不占cpu什么资源,即使按下按键触发中断也几乎不占用cpu资源。
绿色部分是采用非阻塞模式新增的代码,采用非阻塞模式时,如果没有数据就立即返回报错,当然我们需要在测试代码中增加非阻塞模式的条件判断,看一下测试代码如下图。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define KEY2_ENTER 28
#define KEY3_ENTER 29
struct key_event{
int code;
int value;
};
struct key_event event;
int main()
{
int fd;
fd = open("/dev/key",O_RDWR|O_NONBLOCK);
if(fd < 0){
perror("open");
exit(-1);
}
while(1){
read(fd,&event,sizeof(struct key_event));
if(event.code == KEY2_ENTER){
if(event.value == 1){
printf("user key2 up\n");
}else{
printf("user key2 down\n");
}
}else if(event.code == KEY3_ENTER){
if(event.value == 1){
printf("user key3 up\n");
}else{
printf("user key3 down\n");
}
}else{
}
}
close(fd);
return 0;
}
蓝色部分就是新增的代码,我们在驱动程序的key_read函数中增加一条打印函数名的语句,就可以看到运行测试代码的时候会一直输出这条语句。
说明cpu在不断地轮询,再看看此时cpu的使用率。
可见此时cpu的使用率是相当高的。非阻塞模式不怎么常用,因为cpu会不断地轮询我们这里的key_state状态。通常使用的是阻塞模式。