1.阻塞和非阻塞型I/O

1.1 Linux进程状态

Linux定义了五种基本进程状态:

TASK_RUNNING:就绪状态,此状态下的进程已准备好执行,可被调度器选中成为当前运行进程。

TASK_INTERRUPTIBLE:可被信号唤醒的睡眠状态。处于该状态的进程在等待特定资源或事件时暂停执行,当所需资源可用、接收到外部信号或定时器超时时可被唤醒。例如,使用Ctrl+C终止进程时,系统会向目标进程发送SIGINT信号,处于此状态的进程能接收该信号并作出响应,进而终止执行。

TASK_UNINTERRUPTIBLE:不可被信号唤醒的睡眠状态。处于该状态的进程只能在所需资源变为可用时被唤醒。对于此状态的进程,即使发送Ctrl+C等终止信号也无法中断其睡眠,因为该状态下进程无法处理任何外部信号。

TASK_ZOMBIE:僵死状态,表示进程已终止执行并释放了大部分资源,但其进程描述符(task_struct)仍保留在系统中,等待父进程获取其退出状态。

TASK_STOPPED:停止状态,处于该状态的进程已被暂停执行,只有接收到特定信号(如SIGCONT)才能恢复运行。通常由调试操作或作业控制信号触发。

1.2 阻塞与非阻塞的概念

阻塞:在当前设备不可读或不可写时,当前进程程会被挂起。函数只有在得到结果之后才会返回。默认情况下,文件都是以这种方式打开。

非阻塞:非阻塞和阻塞的概念相对应,指在当前设备不可读或不可写时,该函数不会阻塞当前进程程,也是立刻返回。

读写函数是否阻塞是可以用户自己指定:

fd_btn = open(filepath,O_RDWR);                 //默认阻塞打开
fd_btn = open(filepath,O_RDWR | O_NONBLOCK);    //非阻塞方式打开

示例:从标准设备中读取数据,如果不输入进程会阻塞,在read函数中休眠

#include <stdio.h>
#include <unistd.h>
int main(void)
{
	char buf[10];
	read(0,buf,2);					//从标准输入读取2字节数据
	printf("---------------\r\n");
	return 0;
}

read:调用驱动程序文件操作方法的xxx_read函数,如果xxx_read没有实现让进程休眠。应用程序也不可能休眠的。

2.Linux等待队列基础

在Linux软件开发过程中,进程访问硬件设备时常遇到需要等待特定条件满足后才能继续执行的场景,此时设备驱动程序需提供条件判断机制:当条件未满足时使进程进入休眠状态,待条件达成后由内核负责唤醒相应进程。

内核的等待队列机制正是为实现事件驱动的条件等待而设计:需要等待特定事件的进程会将自身注册到对应的等待队列中,同时主动释放处理器控制权(即进入休眠状态);当预设事件触发时,内核会自动唤醒队列中所有处于等待状态的进程。

2.1 等待队列头数据结构

每个等待队列都有一个等待队列头,等待队列头是一个类型为wait_queue_head_t的数据结构。

struct __wait_queue_head {
	spinlock_t		lock;
	struct list_head	task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;		//等待队列头

对于这个结构驱动开发者只需要记得类型名watt_queue_head_t即可,不需要直接访问任何一个成员。

2.2 等待队列的睡眠与唤醒

让一个进程进入条件等待的休眠,只需要调用wait_event就可以进程在指定等待队列头上休眠。(有条件,前提是其等待条件为假)

调用内核提供wake_up这类宏/函数可以把进程从指定的队列唤醒。一般在中断程序中调用,因为进程休眠了是无法自己唤醒自己的,只能由其他进程唤醒或中断唤醒。

进程1:负数读取数据,没有数据时阻塞,休眠,直到有数据才读取数据返回

进程2:负责写数据write

3.等待队列API

等待队列头相关API定义在linux/wait.h文件中。

3.1 静态定义并初始化等待队列头:DECLARE_WAIT_QUEUE_HEAD

头文件

#include <linux/wait.h>

原型

#define DECLARE_WAIT_QUEUE_HEAD(name) \

                     wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

参数

name等待队列头结构变量名

功能

静态定义并初始化等待队列头

示例:

DECLARE_WAIT_QUEUE_HEAD(btn_wq);

展开:wait_queue_head_t btn_wg= WAIT_QUEUE_HEAD_INITIALIZER(btn_wq)

3.2 动态初始化等待队列头:init_waitqueue_head

头文件

#include <linux/wait.h>

原型

#define init_waitqueue_head(q)                          \

       do {                                   \

              static struct lock_class_key __key; \

              __init_waitqueue_head((q), #q, &__key);    \

       } while (0)

参数

q:是wait_queue_head_t指针

功能

运行时动态初始化等待队列头结构

wait_queue_head_t btn_wq;		//全局变量
//在模块初始化函数中动态初始化btn_wg
init_waitqueue_head(&btn_wq);

3.3 不可中断的阻塞等待:wait_event

头文件

#include <linux/wait.h>

原型

#define wait_event(wq, condition)                             \

do {                                                        \

       might_sleep();                                               \

       if (condition)       break;                                       \

       __wait_event(wq, condition);                              \

} while (0)

参数

wq:waitqueue_head_t类型变量,不是指针

condition:休眠的条件,为假时才可以进入休眠

功能

让调用进程进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE),直到condition变成真,被内核唤醒。

注意:调用时要确认condition值是真还是假,如果调用时condition为真,则不会休眠。

3.4 可中断的阻塞等待:wait_event_interruptible

头文件

#include <linux/wait.h>

原型

#define wait_event_interruptible(wq, condition)                            \

({                                                            \

       int __ret = 0;                                          \

       might_sleep();                                               \

       if (!(condition))                                      \

              __ret = __wait_event_interruptible(wq, condition);   \

       __ret;                                                      \

})

参数

wq:waitqueue_head_t类型变量,不是指针

condition:等待的条件,为假时才可以进入休眠

返回

-ERESTARTSYS:被信号中断返回;0:条件为真返回

功能

让调用进程进入可中断的睡眠状态(TASK_INTERRUPTIBLE),直到condition变成真被内核唤醒或被信号打断唤醒。

注意:调用时要确认condition值是真还是假,如果调用时condition为真,则不会休眠。

3.5 带超时功能的不可中断阻塞等待:wait_event_timeout

头文件

#include <linux/wait.h>

原型

#define wait_event_timeout(wq, condition, timeout)

参数

wq :wait_queue_head_t类型变量,不是指针。

condition:等待的条件,为假时才可以进入休眠。

timeout:最长等待时间,单位是时钟节拍。Systick定时器进入一次中断的时间就是一个节拍。

返回

大于0:条件成立返回,值是剩余的时间:0:超时返回

功能

让调用进程进入不可中断的睡眠状态(TASK_UNINTERRUPTIBLE),直到condition变成真被内核唤醒或超时被内核唤醒。

HZ:一秒对应的时钟节拍数量,如:2秒:2*HZ

毫秒级:unsigned long msecs_to_jiffies(const unsigned int m);

微秒级:unsigned long usecs_to_jiffies(const unsigned int u);

1S+200ms+50us转换为时钟节拍:1*HZ+msecs_to_jiffies(200)+usecs_to_jiffies(50)

注意:调用时要确认condition值是真还是假,如果调用时condition为真,则不会休眠。

3.6 带超时功能的可中断阻塞等待:wait_event_interruptible_timeout

头文件

#include <linux/wait.h>

原型

#define wait_event_interruptible_timeout(wq, condition, timeout)

参数

wq :wait_queue_head_t类型变量,不是指针。

condition:等待的条件,为假时才可以进入休眠。

timeout:最长等待时间,单位是时钟节拍。Systick定时器进入一次中断的时间就是一个节拍。

返回

大于0:条件成立返回,值是剩余的时间;

0:超时返回;

-ERESTARTSYS:被信号中断返回。

功能

让调用进程进入可中断的睡眠状态(TASK_UNINTERRUPTIBLE),直到condition变成真被内核唤醒或超时被内核唤醒或被信号中断唤醒。

HZ:一秒对应的时钟节拍数量,如:2秒:2*HZ

毫秒级:unsigned long msecs_to_jiffies(const unsigned int m);

微秒级:unsigned long usecs_to_jiffies(const unsigned int u);

1S+200ms+50us转换为时钟节拍:1*HZ+msecs_to_jiffies(200)+usecs_to_jiffies(50)

注意:调用时要确认condition值是真还是假,如果调用时condition为真,则不会休眠。

3.7 唤醒所有休眠进程:wake_up

头文件

#include <linux/wait.h>

原型

#define wake_up(x)                 __wake_up(x, TASK_NORMAL, 1, NULL)

参数

x:等待队列头结构指针(不是变量,是变量的地址)。

功能

唤醒指定队列上的所有非独占进程和一个独占进程。

补充:

处于休眠状态的进程可分为两种类型:独占型进程和非独占型进程。

独占型进程:在内核中应用极为罕见,整个Linux内核源代码中此类进程的使用实例不超过30处。

wake_up_all:该函数会激活等待队列中的全部进程,无论其是独占型还是非独占型进程。

​​​​​3.8 唤醒可中断的休眠进程:wake_up_interruptible

头文件

#include <linux/wait.h>

原型

#define wake_up_interruptible(x)   __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

参数

x:等待队列头结构指针(不是变量,是变量的地址)。

功能

专用于唤醒带interruptible进入休眠的进程。

4.等待队列编程示例

在原来的按键驱动基础上增加read接口的阻塞和非阻塞功能。

4.1 分析思路

  • 如何传递用户打开方式
fd_btn = open(filepath,O_RDWR);                 //默认阻塞打开
fd_btn = open(filepath,O_RDWR|O_NONBLOCK);      //非阻塞方式打开

open函数调用时内核会一个structfile结构变量,然后和fd关联,后续的read,write,ioctl系统调用就是传递fd,内核通过fd找到open时创建的file结构变量,把它做为参数传递给驱动的read,write,ioctl接口。file结构存放文件的打开方法。

Linux系统调用:

int open(const char *pathname, int flags);

应用程序:

open(path,O_RDWR|O_NONBLOCK);
struct file {
       ……
       unsigned int              f_flags;         //存储open时用户空间传递的打开方式
       ……
       loff_t                   f_pos;                  //文件读写位置
       ……
}
  • 在read接口中根据用户打开方式编写不同的处理代码
    1. 定义全局的等待队列头,并且初始化。
    2. 如果用户是阻塞方式打开设备:
    3. 如果用户是非阻塞打开:

4.2 具体代码实现

drv-btn.c

//结构体改进程序
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/miscdevice.h>
#include<asm/uaccess.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/gpio.h>
#include<linux/slab.h>

#define LEDS_MINOR	255
#define DEVICE_NAME  "my-buttons" 		// 设备节点名称,将在/dev目录下创建 

#ifndef ARRAYSIZE
#define ARRAYSIZE(a)	(sizeof(a)/sizeof(a[0]))
#endif

//静态定义并且初始化等待队列头
//DECLARE_WAIT_QUEUE_HEAD(btn_wq);

//定义一个等待队列头结构变量
static wait_queue_head_t btn_wq;

//按键数量,在模块初始化函数中进行计算
static int key_size;
//按键缓冲区,一个元素存放一个按键值,'1'表示按下,'0'表示松开
//在模块的初始化函数中分配缓冲区空间
static char *keys_buf;	


//使用面向对象思想设计按键,把一个按键信息进行封装
struct key_info{
	int id;					//按键编号
	int gpio;				//统一的GPIO编号
	unsigned long flags;	//触发方式
	char *name;				//按键名
	int irq;				//中断编号
};

//实例化对象
static struct key_info keys[]={
	[0]={
		.id 	= 0,
		.gpio 	= 5,
		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
		.name 	= "key-0",
	},
	[1]={
		.id 	= 0,
		.gpio 	= 55,
		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
		.name 	= "key-1",
	},	
};

static int key_open (struct inode *pinode, struct file *pfile)
{
    printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
    return 0;    
}

loff_t key_llseek (struct file *pfile, loff_t offset, int whence)
{
    printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
    return 0;    
}

static ssize_t key_read (struct file *pfile, char __user *buf, size_t count, loff_t *offset)    
{
    int ret;
	if(count > key_size)	count = key_size;
    if(count == 0)			return 0;
	
	//准备数据,但是按键数据在中断中实时更新,不需要在这里读取
	//复制数据到用户空间
	ret = copy_to_user(buf, keys_buf, count);
	if(ret)
	{
		printk("error:copy_to_user\r\n");
		ret = -EFAULT;
		goto errot_copy_to_user;
	}
	return count;  // 返回成功读取的字节数  
errot_copy_to_user:
	return ret;
}

int key_release (struct inode *pinode, struct file *pfile)
{
    printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
    return 0;
}

// 文件操作结构体(关联实现的操作函数) 
static const struct file_operations dev_fops = {
    .open = key_open,      // 打开设备 
    .read = key_read,      // 读取设备状态 
	.owner = THIS_MODULE,
};

// 杂项设备定义 
static struct miscdevice key_device = {
    .name = DEVICE_NAME,	// 设备名称(出现在/dev目录下) 
    .minor = LEDS_MINOR, 	// 次设备号(建议使用动态分配) 
    .fops = &dev_fops,		// 关联文件操作函数集 
};

//按键中断函数
//设置了双边触发,按下和松开都会进入这个函数
irqreturn_t btns_irq_handler(int irq,void *devid)
{
	int s;
	struct key_info *pdata = (struct key_info *)devid;
	//printk("id:%d,name:%s,irq:%d\r\n",pdata->id,pdata->name,pdata->irq);

	//检测当前的电平状态
	s = !gpio_get_value(pdata->gpio);
/*
	if(s){
		printk("key press\r\n");
	}
	else{
		printk("key up\r\n");
	}
*/
	keys_buf[pdata->id]='0'+s;	//保存状态
	return IRQ_HANDLED;
}

static int __init my_btn_init(void)
{
    int ret,i;	

	//初始化等待队列头结构变量
	init_waitqueue_head(&btn_wq);
	
	key_size = ARRAY_SIZE(keys);	//计算按键数量

	//分配按键缓冲区
	keys_buf = kzalloc(key_size,GFP_KERNEL);
	if(keys_buf == NULL){
		return -EFAULT;
	}
	
	//循环注册中断
	for(i = 0;i < key_size;i++){
		keys[i].irq = gpio_to_irq(keys[i].gpio);
		if(keys[i].irq < 0)
		{
			printk("error:gpio_to_irq\r\n");
			goto error_gpio_to_irq;  
		}
		printk("irq:%d\r\n",keys[i].irq);
		
		//传递每个按键结构变量地址,发生中断时可以通过参数取得
		ret = request_irq(keys[i].irq, btns_irq_handler, keys[i].flags, keys[i].name, (void *)&keys[i]);
		if(ret < 0){
			printk("error:request_irq\r\n");
			goto error_request_irq;  
		}
	}
    ret = misc_register(&key_device);
    if(ret < 0){
        printk("error:misc_register\r\n");
        goto error_misc_register;  
    }

    return 0;  								// 初始化成功 

error_misc_register:
error_request_irq:
	while(--i >= 0){
		free_irq(keys[i].irq, (void *)&keys[i]);	//注销中断
	}
	
error_gpio_to_irq:
	kfree(keys_buf);		//释放按键缓冲区空间
    return ret; 
}

static void __exit my_btn_exit(void)
{
	int i = key_size;
	while(--i >= 0){
		free_irq(keys[i].irq, (void *)&keys[i]);	//注销中断
	}
	misc_deregister(&key_device);	//注销杂项设备
	kfree(keys_buf);		//释放按键缓冲区空间
}

module_init(my_btn_init);		  							
module_exit(my_btn_exit);  								
MODULE_LICENSE("GPL");										
MODULE_AUTHOR("LIU");    									
MODULE_DESCRIPTION("RK3399 GPIO KEY Control Driver");

app.c

#include<stdio.h>            // 标准输入输出库(printf, perror等)
#include<stdlib.h> 
#include<string.h> 
#include<sys/types.h>        // 系统数据类型定义(如dev_t)
#include<sys/stat.h>         // 文件状态信息(文件模式等)
#include<fcntl.h>            // 文件控制选项(open等)
#include<unistd.h>           // 系统调用封装(lseek, read, write, sleep等)
#include<sys/ioctl.h>        // I/O控制操作(ioctl)
#include<errno.h> 

#define BTN_SIZE	2					// 按键数量
#define DEV_NAME 	"/dev/my-buttons"   // 默认设备名

int main(int argc, char **argv)
{
    int fd,ret, i;                
    const char *devname; 		// 设备路径指针(初始化为默认路径)
    unsigned char pre_buf[BTN_SIZE+1],recv_buf[BTN_SIZE+1];

	memset(pre_buf,'0',BTN_SIZE);
	memset(recv_buf,'0',BTN_SIZE);
	
    if(argc == 1)
        devname = DEV_NAME;
    else if(argc == 2)
        devname= argv[1];
    else {
        printf("Usage:%s [/dev/devname]\r\n", argv[0]);
        return 0;
    }

    //fd = open(devname, O_RDWR);  			// O_RDWR:以读写模式打开
    fd = open(devname, O_RDWR|O_NONBLOCK);  // O_RDWR:以读写模式打开,非阻塞
    if(fd < 0) {
        perror("open");   					// 打印系统错误信息
        printf("fd=%d\r\n", fd);
        return -1;            				// 打开失败退出程序
    }
    printf("fd=%d\r\n", fd);  // 成功打开后输出fd值

    while(1) {
		//printf("非阻塞\r\n");
		ret = read(fd,recv_buf,BTN_SIZE);	//读取按键数据
		if(ret < 0){
			if(errno != EAGAIN){
				perror("read");
				exit(-1);
			}else	continue;
		}

		//只在状态发生变化时候才输出
		for(i = 0;i < BTN_SIZE;i++){
			//分别判断每一个按键状态是否发生变化
			if(recv_buf[i] != pre_buf[i]){
				//更新当前状态为上一次状态
				pre_buf[i] = recv_buf[i];

				//判断这次变化是按下还是松开
				if(pre_buf[i] == '1')
					printf("KEY%d is press!\r\n",i+1);
				else
					printf("KEY%d is up!\r\n",i+1);
			}
		}
    }    
    return 0;
}

现象

drv-btn.c

 //结构体改进程序
#include<linux/module.h>
#include<linux/fs.h>
#include<linux/miscdevice.h>
#include<asm/uaccess.h>
#include<linux/interrupt.h>
#include<linux/irq.h>
#include<linux/gpio.h>
#include<linux/slab.h>
#include<linux/wait.h>

#define LEDS_MINOR	255
#define DEVICE_NAME  "my-buttons" // 设备节点名称,将在/dev目录下创建 

#ifndef ARRAYSIZE
#define ARRAYSIZE(a)	(sizeof(a)/sizeof(a[0]))
#endif

//按键动作发生标志
static int btn_action = 0;
//静态定义并且初始化等待队列头
//DECLARE_WAIT_QUEUE_HEAD(btn_wq);

//定义一个等待队列头结构变量
static wait_queue_head_t btn_wq;

//按键数量,在模块初始化函数中进行计算
static int key_size;
//按键缓冲区,一个元素存放一个按键值,'1'表示按下,'0'表示松开
//在模块的初始化函数中分配缓冲区空间
static char *keys_buf;	


//使用面向对象思想设计按键,把一个按键信息进行封装
struct key_info{
	int id;					//按键编号
	int gpio;				//统一的GPIO编号
	unsigned long flags;	//触发方式
	char *name;				//按键名
	int irq;				//中断编号
};

//实例化对象
static struct key_info keys[]={
	[0]={
		.id 	= 0,
		.gpio 	= 5,
		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
		.name 	= "key-0",
	},
	[1]={
		.id 	= 1,
		.gpio 	= 54,
		.flags 	= IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING,
		.name 	= "key-1",
	},	
};

static int key_open (struct inode *pinode, struct file *pfile)
{
    printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
    return 0;    
}

loff_t key_llseek (struct file *pfile, loff_t offset, int whence)
{
    printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
    return 0;    
}

static ssize_t key_read (struct file *pfile, char __user *buf, size_t count, loff_t *offset)    
{
    int ret;
	if(count > key_size)	count = key_size;
    if(count == 0)			return 0;

	//判断是否有数据
	//判断用户的打开方式
	if(btn_action == 0){
		if(pfile->f_flags & O_NONBLOCK){
			return -EAGAIN;
		}
		//休眠直到condition 为真,被内核唤醒
		//wait_event(btn_wq,btn_action); 
		ret = wait_event_interruptible(btn_wq,btn_action);
		if(ret < 0){
			printk("it was interrupted by a signal\r\n");
			return ret;
		}
		
	}

	printk("--------****--------\r\n");
	
//	ret = wait_event_interruptible(btn_wq,btn_action);//休眠直到condition 为真,被内核唤醒或收到信号
//	if(ret)
//		return ret;
	
	//准备数据,但是按键数据在中断中实时更新,不需要在这里读取
	//复制数据到用户空间
	ret = copy_to_user(buf, keys_buf, count);
	if(ret){
		printk("error:copy_to_user\r\n");
		ret = -EFAULT;
		goto errot_copy_to_user;
	}
	//能运行这里说明有数据
	btn_action = 0;	//已经读取新数据,清新数据标志
	
	return count;  // 返回成功读取的字节数  
errot_copy_to_user:
	return ret;
}

int key_release (struct inode *pinode, struct file *pfile)
{
    printk("line:%d,%s is call\n",__LINE__,__FUNCTION__);
    return 0;
}

// 文件操作结构体(关联实现的操作函数) 
static const struct file_operations dev_fops = {
    .open = key_open,      // 打开设备 
    .read = key_read,      // 读取设备状态 
	.owner = THIS_MODULE,
};

// 杂项设备定义 
static struct miscdevice key_device = {
    .name = DEVICE_NAME,	// 设备名称(出现在/dev目录下) 
    .minor = LEDS_MINOR, 	// 次设备号(建议使用动态分配) 
    .fops = &dev_fops,		// 关联文件操作函数集 
};

//按键中断函数
//设置了双边触发,按下和松开都会进入这个函数
irqreturn_t btns_irq_handler(int irq,void *devid)
{
	int s;
	struct key_info *pdata = (struct key_info *)devid;
	//printk("id:%d,name:%s,irq:%d\r\n",pdata->id,pdata->name,pdata->irq);

	//检测当前的电平状态
	s = !gpio_get_value(pdata->gpio);

	keys_buf[pdata->id]='0'+s;	//保存状态

	btn_action = 1;		//表示现在有新的按键动作发生
		
	wake_up(&btn_wq);	//通知内核去指定的等待队列上查询,看是否有进程条件成立,唤醒进程
	
	return IRQ_HANDLED;
}

static int __init my_btn_init(void)
{
    int ret,i;	

	//初始化等待队列头结构变量
	init_waitqueue_head(&btn_wq);
	
	key_size = ARRAY_SIZE(keys);	//计算按键数量

	//分配按键缓冲区
	keys_buf = kzalloc(key_size,GFP_KERNEL);
	if(keys_buf == NULL)
		return -EFAULT;
	
	//循环注册中断
	for(i = 0;i < key_size;i++){
		keys[i].irq = gpio_to_irq(keys[i].gpio);
		if(keys[i].irq < 0){
			printk("error:gpio_to_irq\r\n");
			goto error_gpio_to_irq;  
		}
		printk("irq:%d\r\n",keys[i].irq);
		
		//传递每个按键结构变量地址,发生中断时可以通过参数取得
		ret = request_irq(keys[i].irq, btns_irq_handler, keys[i].flags, keys[i].name, (void *)&keys[i]);
		if(ret < 0){
			printk("error:request_irq\r\n");
			goto error_request_irq;  
		}
	}
    ret = misc_register(&key_device);
    if(ret < 0){
        printk("error:misc_register\r\n");
        goto error_misc_register;  
    }
    return 0;  								

error_misc_register:
error_request_irq:
	while(--i >= 0){
		free_irq(keys[i].irq, (void *)&keys[i]);	//注销中断
	}
	
error_gpio_to_irq:
	kfree(keys_buf);		//释放按键缓冲区空间
    return ret; 
}

static void __exit my_btn_exit(void)
{
	int i = key_size;
	while(--i >= 0){
		free_irq(keys[i].irq, (void *)&keys[i]);	//注销中断
	}
	misc_deregister(&key_device);	//注销杂项设备
	kfree(keys_buf);		//释放按键缓冲区空间
}

module_init(my_btn_init);		  								
module_exit(my_btn_exit);  									
MODULE_LICENSE("GPL");											
MODULE_AUTHOR("LIU");    									
MODULE_DESCRIPTION("RK3399 GPIO KEY Control Driver"); 			

app.c

#include<stdio.h>              // 标准输入输出库(printf, perror等)
#include<stdlib.h> 
#include<string.h> 
#include<sys/types.h>          // 系统数据类型定义(如dev_t)
#include<sys/stat.h>           // 文件状态信息(文件模式等)
#include<fcntl.h>              // 文件控制选项(open等)
#include<unistd.h>             // 系统调用封装(lseek, read, write, sleep等)
#include<sys/ioctl.h>          // I/O控制操作(ioctl)
#include<errno.h> 

#define BTN_SIZE	1					// 按键数量
#define DEV_NAME 	"/dev/my-buttons"  	// 默认设备名

int main(int argc, char **argv)
{
    int fd,ret, i;                
    const char *devname; 		// 设备路径指针(初始化为默认路径)
    unsigned char pre_buf[BTN_SIZE+1],recv_buf[BTN_SIZE+1];

	memset(pre_buf,'0',BTN_SIZE);
	memset(recv_buf,'0',BTN_SIZE);
	
    if(argc == 1)
        devname = DEV_NAME;
    else if(argc == 2)
        devname= argv[1];
    else {
        printf("Usage:%s [/dev/devname]\r\n", argv[0]);
        return 0;
    }

    fd = open(devname, O_RDWR);  	// O_RDWR:以读写模式打开
    if(fd < 0) {
        perror("open");   			// 打印系统错误信息
        printf("fd=%d\r\n", fd);
        return -1;            		// 打开失败退出程序
    }
    printf("fd=%d\r\n", fd);  		// 成功打开后输出fd值

    while(1) {
		ret = read(fd,recv_buf,BTN_SIZE);	//读取按键数据
		if(ret < 0){
			if(errno != EAGAIN){
				perror("read");
				exit(-1);
			}else	continue;
		}

		//只在状态发生变化时候才输出
		for(i = 0;i < BTN_SIZE;i++){
			//分别判断每一个按键状态是否发生变化
			if(recv_buf[i] != pre_buf[i]){
				//更新当前状态为上一次状态
				pre_buf[i] = recv_buf[i];

				//判断这次变化是按下还是松开
				if(pre_buf[i] == '1')
					printf("KEY%d is press!\r\n",i+1);
				else
					printf("KEY%d is up!\r\n",i+1);
			}
		}
    }    
    return 0;
}

现象

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐