按键:按下去我们get到low电平以此来判断用户输入,可以把它看作是一个输入设备。
通常在51单片机上,我们可以写一个while,然后不停的轮询,判断某个gpio口被拉低了,我们便认为按键被按下了。另外一种是中断。当有中断触发时,程式会跳转到中断处理函数里面,我们在中断函数里面实现功能!
但是如果放到linux中,我们该如何去实现,这便是一个问题。
1.笔记
等待队列:
在Linux内核中等待队列有很多用途,可用于中断处理、进程同步及定时。我们在这里只说,进程经常必须等待某些事件的发生。等待队列实现了在事件上的条件等待: 希望等待特定事件的进程把自己放进合适的等待队列,并放弃控制全。因此,等待队列表示一组睡眠的进程,当某一条件为真时,由内核唤醒它们。
struct __wait_queue_head {
spinlock_t lock;
struct list_head task_list;
};
#define __WAIT_QUEUE_HEAD_INITIALIZER(name) { \
.lock = __SPIN_LOCK_UNLOCKED(name.lock), \
.task_list = { &(name).task_list, &(name).task_list } }
#define DECLARE_WAIT_QUEUE_HEAD(name) \
wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)
//资讯网上查找到的
//当应用程序需要进行对多文件读写时,若某个文件没有准备好,则系统会处于读写阻塞的状态,并影响了其他文件的读写。为了避免这种情//况,在必须使用多输入输出流又不想阻塞在它们任何一个上的应用程序常将非阻塞 I/O 和 poll(System V)、select(BSD //Unix)、 epoll(linux2.5.45开始)系统调用配合使用。当poll函数返回时,会给出一个文件是否可读写的标志,应用程序根据不同//的标志读写相应的文件,实现非阻塞的读写。这些系统调用功能相同: 允许进程来决定它是否可读或写一个或多个文件而不阻塞。这些调用//也可阻塞进程直到任何一个给定集合的文件描述符可用来读或写。这些调用都需要来自设备驱动中poll 方法的支持,poll返回不同的标//志,告诉主进程文件是否可以读写。
/**
* enum irqreturn
* @IRQ_NONE interrupt was not from this device
* @IRQ_HANDLED interrupt was handled by this device
* @IRQ_WAKE_THREAD handler requests to wake the handler thread
*/
enum irqreturn {
IRQ_NONE = (0 << 0),
IRQ_HANDLED = (1 << 0),
IRQ_WAKE_THREAD = (1 << 1),
};
typedef enum irqreturn irqreturn_t;
struct timer_list {
/*
* All fields that change during normal runtime grouped to the
* same cacheline
*/
struct list_head entry;//用来将多个定时器连接成一条双向循环队列。
unsigned long expires;//:指定定时器到期的时间
struct tvec_base *base;
//指向一个可执行函数。当定时器到期时,内核就执行function所指定的函数。
void (*function)(unsigned long);
//data域被内核用作function函数的调用参数。
unsigned long data;
int slack;
#ifdef CONFIG_TIMER_STATS
int start_pid;
void *start_site;
char start_comm[16];
#endif
#ifdef CONFIG_LOCKDEP
struct lockdep_map lockdep_map;
#endif
};
//jiffies是Linux内核中的一个全局变量,用来记录自系统启动以来产生的节拍的总数。启动时,内核将该变量初始化为0,此后,每次时//钟中断处理程序都会增加该变量的值。因为一秒内时钟中断的次数等于HZ,所以jiffies一秒内增加的值也就为HZ。系统运行时间以秒为//单位计算,就等于jiffies/HZ。
//节拍率HZ是通过静态预处理定义的,在系统启动时按照HZ值对硬件进行设置。体系结构不同,HZ值就不同,在i386体系结构在include// /asm-i386/param.h中定义如下:
// #define HZ 1000
//每秒钟时钟中断1000次,也就是说,在1秒里jiffies会被增加1000。因此jiffies + 2 * HZ表示推后2秒钟。
struct timer_list my_timer;
init_timer(&my_timer);// 初始化定时器
my_timer.expires = jiffies + delay; //超时时间
my_timer.data = 0; //.function的参数
my_timer.function = my_function; //超时执行函数
add_timer(&my_timer); //激活定时器
a. init_timer(struct timer_list*):定时器初始化函数;
b. add_timer(struct timer_list*):往系统添加定时器;
c. mod_timer(struct timer_list *, unsigned long jiffier_timerout):修改定时器的超时时间为jiffies_timerout;
d. timer_pending(struct timer_list *):定时器状态查询,如果在系统的定时器列表中则返回1,否则返回0;
e. del_timer(struct timer_list*):删除定时器。
setup_timer(struct timer_list, function,data); //初始化timer并赋值func和data
如果一个要被del_timer函数删除的timer对象已经被调度执行(内核源码称这种定时器状态为inactive),函数将直接返回0,否则函数将通过detach_timer将该定时器对象从队列中删除。在多处理器的SMP系统中,del_timer_sync函数要完成的任务除了同del_timer一样从定时器队列中删除一个定时器对象外,还会确保当函数返回时系统中没有任何处理器正在执行定时器对象上的定时器函数,而如果只是调用del_timer,那么当函数返回时,被删除的定时器对象的定时器函数可能正在其他处理器上运行。
/**
* wait_event_interruptible - sleep until a condition gets true
* @wq: the waitqueue to wait on
* @condition: a C expression for the event to wait for
*
* The process is put to sleep (TASK_INTERRUPTIBLE) until the
* @condition evaluates to true or a signal is received.
* The @condition is checked each time the waitqueue @wq is woken up.
*
* wake_up() has to be called after changing any variable that could
* change the result of the wait condition.
*
* The function will return -ERESTARTSYS if it was interrupted by a
* signal and 0 if @condition evaluated to true.
*/
#define wait_event_interruptible(wq, condition)
这个函数先将当前进程的状态设置成TASK_INTERRUPTIBLE,然后调用schedule(),而schedule()会将位于TASK_INTERRUPTIBLE状态的当前进程从runqueue队列中删除。从runqueue队列中删除的结果是,当前这个进程将不再参
与调度,除非通过其他函数将这个进程重新放入这个runqueue队列中
poll:
poll调用之后,kernel针对每个driver进入其相应的poll函数。poll_wait负责将当前进程放入wait_queue,(对于每个driver,每个queue,申请wait_queue_t, 放入相应的queue,由kernel完成),但是现在并不阻塞current进程,直到所有的driver最后都没有合适的mask的时候,阻塞poll系统调用,当有信号将当前进程唤醒后,说明某一条件满足了。阻塞结束,返回将current从wait_queue挪出来
常量 说明
POLLIN 普通或优先级带数据可读
POLLRDNORM 普通数据可读
POLLRDBAND 优先级带数据可读
POLLPRI 高优先级数据可读
POLLOUT 普通数据可写
POLLWRNORM 普通数据可写
POLLWRBAND 优先级带数据可写
POLLERR 发生错误
POLLHUP 发生挂起
POLLNVAL 描述字不是一个打开的文件
坦白讲:这个还真是有点难了,我本以为很简单,结果查了好多资料,还是看不懂!
实际代码:
/*
* linux/drivers/char/smart210_buttons.c
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/sched.h>
#include <linux/poll.h>
#include <linux/irq.h>
#include <asm/irq.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <mach/hardware.h>
#include <linux/platform_device.h>
#include <linux/cdev.h>
#include <linux/miscdevice.h>
#include <linux/gpio.h>
#include <mach/map.h>
#include <mach/gpio.h>
#include <mach/regs-clock.h>
#include <mach/regs-gpio.h>
#define DEVICE_NAME "buttons"
//这里构造了一个按键的结构体,包括管脚,number号,名字,和定时器结构
struct button_desc {
int gpio;
int number;
char *name;
struct timer_list timer;
};
static struct button_desc buttons[] = {
{ S5PV210_GPH2(0), 0, "KEY0" },
{ S5PV210_GPH2(1), 1, "KEY1" },
{ S5PV210_GPH2(2), 2, "KEY2" },
{ S5PV210_GPH2(3), 3, "KEY3" },
{ S5PV210_GPH3(0), 4, "KEY4" },
{ S5PV210_GPH3(1), 5, "KEY5" },
{ S5PV210_GPH3(2), 6, "KEY6" },
{ S5PV210_GPH3(3), 7, "KEY7" },
};
static volatile char key_values[] = {
'0', '0', '0', '0', '0', '0', '0', '0'
};
/*这里初始化,并声明一个等待队列*/
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
static volatile int ev_press = 0;
/*按键定时器的处理函数*/
static void smart210_buttons_timer(unsigned long _data)
{
struct button_desc *bdata = (struct button_desc *)_data;
int down;
int number;
unsigned tmp;
tmp = gpio_get_value(bdata->gpio);
/* active low */
down = !tmp;
printk("KEY %d: %08x\n", bdata->number, down);
number = bdata->number;
if (down != (key_values[number] & 1)) {
key_values[number] = '0' + down;
ev_press = 1;
wake_up_interruptible(&button_waitq);//唤醒按键等待队列
}
}
/*按键中断程序*/
static irqreturn_t button_interrupt(int irq, void *dev_id)
{
struct button_desc *bdata = (struct button_desc *)dev_id;
/*更新定时器的timeout值*/
mod_timer(&bdata->timer, jiffies + msecs_to_jiffies(40));
return IRQ_HANDLED;
}
static int smart210_buttons_open(struct inode *inode, struct file *file)
{
int irq;
int i;
int err = 0;
for (i = 0; i < ARRAY_SIZE(buttons); i++) {
if (!buttons[i].gpio)
continue;
//初始化定时器,并对定时器赋func 和data
setup_timer(&buttons[i].timer, smart210_buttons_timer,
(unsigned long)&buttons[i]);
//每个管脚申请中断
irq = gpio_to_irq(buttons[i].gpio);
err = request_irq(irq, button_interrupt, IRQ_TYPE_EDGE_BOTH,
buttons[i].name, (void *)&buttons[i]);
if (err)
break;
}
if (err) {
i--;
for (; i >= 0; i--) {
if (!buttons[i].gpio)
continue;
irq = gpio_to_irq(buttons[i].gpio);
disable_irq(irq);
free_irq(irq, (void *)&buttons[i]);
//从定时器队列中删除一个定时器,并且确保它的func没有在执行中
del_timer_sync(&buttons[i].timer);
}
return -EBUSY;
}
ev_press = 1;
return 0;
}
static int smart210_buttons_close(struct inode *inode, struct file *file)
{
int irq, i;
for (i = 0; i < ARRAY_SIZE(buttons); i++) {
if (!buttons[i].gpio)
continue;
irq = gpio_to_irq(buttons[i].gpio);
free_irq(irq, (void *)&buttons[i]);
del_timer_sync(&buttons[i].timer);
}
return 0;
}
static int smart210_buttons_read(struct file *filp, char __user *buff,
size_t count, loff_t *offp)
{
unsigned long err;
if (!ev_press) {
/*无按键操作时,若文件打开方式为O_NONBLOCK,非阻塞,直接返回——EAGAIN,否则*/
//当前进程休眠在等待队列中,直到新的数据到来,唤醒等待队列上的进程,才返回
//如果是非阻塞访问时,ev_press 不成立直接 return,相反,如果是阻塞访问,
//那么便等待 wait队列被外部按键中断唤醒。
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
else
wait_event_interruptible(button_waitq, ev_press);
//将 button_waitq放入等待队列,等待 直到ev_press 为ture时,且wake_up,退出等待队列
}
ev_press = 0;
/*内核空间资料拷贝到用户空间*/
err = copy_to_user((void *)buff, (const void *)(&key_values),
min(sizeof(key_values), count));
return err ? -EFAULT : min(sizeof(key_values), count);
}
/*poll方式,这里的驱动可以阻塞访问,也可以非阻塞访问,具体需要看测试程序如何访问设备文件*/
static unsigned int smart210_buttons_poll( struct file *file,
struct poll_table_struct *wait)
{
unsigned int mask = 0;
poll_wait(file, &button_waitq, wait);
if (ev_press)
mask |= POLLIN | POLLRDNORM;/*key is press down,IO is readable*/
return mask;
}
static struct file_operations dev_fops = {
.owner = THIS_MODULE,
.open = smart210_buttons_open,
.release = smart210_buttons_close,
.read = smart210_buttons_read,
.poll = smart210_buttons_poll,
};
static struct miscdevice smart210_buttons = {
.minor = MISC_DYNAMIC_MINOR,
.name = DEVICE_NAME,
.fops = &dev_fops,
};
static int __init button_dev_init(void)
{
int ret;
ret = misc_register(&smart210_buttons);
printk(DEVICE_NAME"\tinitialized\n");
return ret;
}
static void __exit button_dev_exit(void)
{
misc_deregister(&smart210_buttons);
}
module_init(button_dev_init);
module_exit(button_dev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("FriendlyARM Inc.");
测试程序:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>
#include <errno.h>
int main(void)
{
int buttons_fd;
char buttons[8] = {'0', '0', '0', '0', '0', '0', '0', '0'};
//测试程序采用阻塞方式访问设备
buttons_fd = open("/dev/buttons", 0);
if (buttons_fd < 0) {
perror("open device buttons");
exit(1);
}
for (;;) {
char current_buttons[8];
int count_of_changed_key;
int i;
if (read(buttons_fd, current_buttons, sizeof current_buttons) != sizeof current_buttons) {
perror("read buttons:");
exit(1);
}
//printf("read file ok\n");
for (i = 0, count_of_changed_key = 0; i < sizeof buttons / sizeof buttons[0]; i++) {
if (buttons[i] != current_buttons[i]) {
buttons[i] = current_buttons[i];
printf("%skey %d is %s", count_of_changed_key? ", ": "", i, buttons[i] == '0' ? "up" : "down");
count_of_changed_key++;
}
}
if (count_of_changed_key) {
printf("\n");
}
}
close(buttons_fd);
return 0;
}
这边文件打开为默认方式,即不是非阻塞方式。驱动程序中smart210_buttons_poll这个函数没有用。
打开文件中,各个key启动对应key的irq中断,当有key按下或者弹起时,触发中断,触发中断后,中断处理函数更新对应key的定时器中断的timeout时间为40ms之后,40ms之后对应key的定时器产生中断,调用定时器的中断处理函数,此函数将等待队列唤醒,read函数从内核空间获得有效的数据,这边的主要目的是为了按键消抖。
本例子包含内容太多,所以会额外多一篇文章再研究