( 3) 通过函数 m isc_reg ister注册一个键盘设备, 并分配主设备号和从设备号, 初始化一个环形队列以及定义一个键盘控制的数据结构。其中包括键值、键的状态和长按标志。
应用程序对设备驱动的调用实际是对相应设备文件进行操作, 利用 mknod命令将此节点与对应设备建立联系。
( 4) 通过 init_ w a it queue_head(& sa ts . read _ w a it)初始化读信号量。
4 . 2 打开键盘设备
应用程序打开设备文件时, 会调用驱动中的 OPEN 函数, 此函数会对键盘所用到的行列 GPI O 口进行配置。打开的设备在内 核中通过 file结 构进行标识, 内核 使用 fileopreati on ,通过上面的结构中设备文件操作结构的映射, 来调用驱动中的 kd_c trl_open。接下来要做的是:
( 1) 通过 se m a_ i n it( & kdc- > irq_w ait , 0)初始化在后面用来唤醒后台线程的信号量。
( 2) 调用初始化函数 i n it_pxa_kdc( )来初始化 GPI O口,具体是把 行!的 GPI O 口设为输出模式并设定值为 0 , 把列!GPIO口设为中断模式,下降沿有效。如下所示:
re t = se t_kdc_gp i o( KDC_ROW _PINS , 1 , PI NS_MODE _OUT , 0) ;
ret = set_kdc_gp i o ( KDC _COL _PI NS , 1 , PI NS _ MODE _FALLI NG_I NTTERUPT , 0);
( 3) 以严格的串行方式执行任务的效率并不高, 如果把它们放在后台调度,不管是对它们的函数还是对终端用户进程都能得到较好的响应。所以初始化 GPIO口后,开启一个内核线程 kd_ctrl_thread专门用于处理键盘事件, 其实也就是向系统申请了软硬件资源。为了确保在该线程创建完成,使用 co m pleti on ,在 Linux内核中, co m pletion是一种简单的同步机制,利用 co m pleti on机制可以使两个任务同步。我们利用 i n it_comp l e ti on(& kdc- > i n it_ex it)动态初始化一个线程创建信号量 i n it_ex it , 以及用 wa it_fo r_co m pleti on (& kdc- >i n it_ex it)来等待进程创建完成, 然后在进程创建结束后通过co m plete(& kdc- > i nit_ex it)确定事件已经完成即后台线程创建成功, 继续执行函数 w ait_ for_ comp l e ti on之后的任务。
通过 ret = kerne l_t h read( kd_c trl_ t hread , kdc , CLONE_FS |CLONE_FILES)创建后台线程。
4 . 3 等待键盘事件
后台线程一旦创建和初始化完成, 就会进入一个无条件的 for循 环, 通 过 set _ task _ state ( tsk , TASK _ INTERRUPTIBLE) 将此线程推入可中断睡眠的队列,调用 schedule ti m eou t (H Z/100)来实现 15毫秒的进程挂起。此时让出 CPU,直到中断事件来临或睡眠超过规定时间后再重新执行。线程一旦被唤醒即按照顺序先利用 set_kdc_gp io ( KDC _COL_PI NS , 1 , PI NS _MODE _ENABLEI NTERRUPT, 0) 使 所有列GPI O 口中断, 接着调用 down _ i nterrupti b l e ( & kdc- > irq _wa it): 该函数的作用是获得信号量 irq_wa it , 把 irq_ w a i t的值减掉 1 , 如果信号量 irq_wa it的值非负, 就直接返回,如果获取失败键盘线程将以 TASK_I NTERRUPTIBLE状态进入可中断睡眠,直到下次键盘事件利用信号量 irq_ w a it唤醒此线程才能继续运行。因此,驱动程序在没有按键按下时将阻塞自己的执行,不消耗任何的 CPU资源。
4 . 4 键盘事件发生
一旦有按键事件发生也就是产生一个中断, 则进入中断处理程序 kd_ctr l _ irq( ), 在这个函数中所做的工作如图 2。