分类: LINUX
2012-08-24 10:05:45
按键驱动可以有两种方式实现:一种是查询法,一种是中断法。查询法的确定非常耗资源,在一个高效的系统中,不应使用查询法来获取按键值,优点是程序代码简单。故一般只在bootloader中使用这种方式。中断法优点是不耗资源,驱动时程序实现起来较复杂。
中断法采用的设计思想是:应用程序来读按键的键值,如果没有按键按下的时候,进程休眠,当有按键按下的时候在中断程序中唤醒进程。
按键驱动的实现步骤:
1.定义一个等待队列
2.在模块加载函数中使用request_irq函数注册中断,request_irq函数中有五个参数,分别是中断号,中断处理函数,中断的触发方式,名字,和传入中断函数的参数(该参数在共享中断中用于区分是哪一个触发的中断)。
3.在read函数中使用wait_event_interruptible函数有条件休眠进程,该休眠方式是可中断休眠。wait_event_interruptible(button_waitq, ev_press); 当ev_press为真时,立即返回,当ev_press为假时,进入可中断休眠。并将该进程挂载button_waitq定义的等待队列上。
4.在中断处理函数中使用wake_up_interruptible函数唤醒进程。
wake_up_interruptible(&button_waitq);
处在于中断处理程序是在中断上下文中运行的,它的行为受到某些限制:
1.不能向用户空间发送或接受数据
2.不能使用可能引起阻塞的函数
3.不能使用可能引起调度的函数
一些命令:
cat /proc/interrupts 查看当前注册的中断
exec 5
关闭设备文件exec 5<&- 关闭之前打开5指向的设备文件。
对于应用程序来说:可以使用去读按键键值,没有按键按下就休眠。也可以让应用程序一直休眠,当有按键按下的时候,在驱动的中断处理函数中给应用程序发信号,唤醒应用程序,让它来读取数据。这是就用到了file_operations中的.fasync函数。
在驱动中.fasync函数实现很简单,如下:
static struct fasync_struct * fapp;
static int buttons_fasync(int fd, struct file *filp, int on)
{
return fasync_helper(fd, filp, on, &fapp);
}
在中断处理函数中,当有按键按下,给应用程序发信号。
使用函数kill_fasync(&fapp, SIGIO, POLL_IN);
在应用程序中:
A.使用函数signal(SIGIO, my_signal_fun)注册信号处理函数my_signal_fun
B.调用fcntl(fd, F_SETOWN, getpid()); // 告诉内核,发给谁,即将进程的pid告诉内核
C.Oflags = fcntl(fd, F_GETFL);
D.fcntl(fd, F_SETFL, Oflags | FASYNC); // 改变fasync标记,最终会调用到驱动的faync > fasync_helper:初始化/释放fasync_struct
E.在信号处理函数中,读取按键值
信号量
信号量(semaphore)是用于保护临界区的一种常用方法,只有得到信号量的进程才能执行临界区代码。
当获取不到信号量时,进程进入休眠等待状态。
定义信号量
struct semaphore sem;
初始化信号量
void sema_init (struct semaphore *sem, int val);
void init_MUTEX(struct semaphore *sem);//初始化为0
static DECLARE_MUTEX(button_lock); //定义互斥锁
获得信号量
void down(struct semaphore * sem);
int down_interruptible(struct semaphore * sem);
int down_trylock(struct semaphore * sem);
释放信号量
void up(struct semaphore * sem);
阻塞操作
是指在执行设备操作时若不能获得资源则挂起进程,直到满足可操作的条件后再进行操作。
被挂起的进程进入休眠状态,被从调度器的运行队列移走,直到等待的条件被满足。
非阻塞操作
进程在不能进行设备操作时并不挂起,它或者放弃,或者不停地查询,直至可以进行操作为止。
fd = open("...", O_RDWR | O_NONBLOCK);
按键是一个机械结构,在按下的时候难免会发生机械抖动,为了解决这一个问题,我们引入了内核定时器,这样来实现按键的去抖,对于内核定时器的使用,总价为以下几点:
1. 定义一个struct timer_list结构;
2. 使用init_timer函数初始化定时器
3. 设置超时函数,即timer_list结构中.function成员
4. 使用add_timer函数添加定时器
5. 使用mod_timer重新设置定时器超时时间
在入口函数中添加了定时器,在出口函数中应该删除定时器,使用del_timer函数。
为了提供统一的接口,在内核中实现了一套input子系统,实现这个操作的步骤:
1. 分配一个struct input_dev结构,使用input_allocate_device函数实现
2. 设置能产生那类事件,使用set_bit函数实现
3. 能参数这类事件中的哪些事件,使用set_bit函数实现
4. 注册input_dev结构
5. 在发生中断的时候使用input_event函数上报事件(对于按键来说,有按下弹起两种事件),上报完后使用input_sync再上报同步事件
Input系统代码:
static struct input_dev *buttons_dev;
在入口函数中:
/* 分配一个input_dev结构 */
buttons_dev = input_allocate_device();
/* 设置 input_dev结构*/
set_bit(EV_KEY, buttons_dev->evbit); // 能产生按键类事件
set_bit(KEY_L, buttons_dev->keybit); // 能产生按键事件中,键值为L的事件
在中断函数中:
/* 上报产生了键值为pin_pd->keyval的按键松开时的事件 */
input_event(buttons_dev, EV_KEY, pin_pd->keyval, 0);
/* 上报产生了键值为pin_pd->keyval的按键按下时的事件 */
input_event(buttons_dev, EV_KEY, pin_pd->keyval, 1);
/* 上报完事件后应上报一个同步信号告诉内核,已上报完 */
input_sync(buttons_dev);
以上内容全是本人凭个人理解写出的:如有错误请多包涵,若能指出,本人将万分感激。