当时在学习按键驱动时,便觉得其中有几个点是比较难理解的,在整体上自己感到并没有很好地掌握。现在经过了一段时间地学习,再将按键驱动里的疑难点加以理解之后,总结记录一下。
关于驱动代码之前已经给出,点击:这里,但只是关于代码语句的注释,下面将从功能块上着重分析这个驱动。
也许现在提起中断,都可以联想到这个图:
中断,本质上就是一种特殊的信号,由某个设备发个CPU,CPU接到这个信号后,操作系统负责处理由这个中断带来的信息。中断是随时可以发生的,不同的设备也对应着不同的中断。这时就需要一个标识——中断号。具体地说叫:中断请求(IRQ)线。
在响应中断时,内核会执行一个函数——中断服务程序。中断服务程序是被内核调用来响应中断的,而它们运行于我们称之为中断上下文的特殊上下文中。
回到按键里具体的代码:
static irqreturn_t s3c_button_intterupt(int irq,void *de_id)
{
int i;
int found = 0;
struct s3c_button_platform_data *pdata = button_device.data;
for(i=0; inbuttons; i++)
{
if(irq == pdata->buttons[i].nIRQ)
{
found = 1;
break;
}
}
if(!found) /* An ERROR interrupt */
return IRQ_NONE;
/* Only when button is up then we will handle this event */
if(BUTTON_UP == button_device.status[i])
{
button_device.status[i] = BUTTON_UNCERTAIN; //未决状态
mod_timer(&(button_device.timers[i]), jiffies+TIMER_DELAY_DOWN);//启动定时器
}
return IRQ_HANDLED;
}
static void button_timer_handler(unsigned long data)
{
struct s3c_button_platform_data *pdata = button_device.data;
int num =(int)data;
int status = s3c2410_gpio_getpin( pdata->buttons[num].gpio );
if(LOWLEVEL == status)
{
if(BUTTON_UNCERTAIN == button_device.status[num]) /* Come from interrupt */
{
//dbg_print("Key pressed!\n");
button_device.status[num] = BUTTON_DOWN;
printk("%s pressed.\n", pdata->buttons[num].name);
/* Wake up the wait queue for read()/poll() */
button_device.ev_press = 1;
wake_up_interruptible(&(button_device.waitq));
}
/* Cancel the dithering */
mod_timer(&(button_device.timers[num]), jiffies+TIMER_DELAY_UP);
}
else
{
//dbg_print("Key Released!\n");
button_device.status[num] = BUTTON_UP;
}
return ;
}
乍一看好像有两个中断服务程序?这就牵扯到一个重要的知识点:中断的上半部和下半部。
上半部:接受到一个中断,它就立即执行,但只做严格时限的工作,这些工作都是在所有中断被禁止的情况下完成的。同时,能够被允许稍后完成的工作推迟到下半部(bottom half)去,此后,下半部会被执行。
下半部:这才是中断发生后真正要进行处理的地方。这里采用了定时器的方式实现,所以button_timer_handler
函数里才是判断按键行为的地方。
对于这个例子,我们找了一个定时器来帮助处理中断。总的来说,从中断服务程序获得了具体的设备号且为有效按键中断之后就由定时器来全盘接手。在超时之后,进入定时器原本设定好的定时器中断处理程序button_timer_handler
中正式的处理这个中断请求。也就是说中断处理程序s3c_button_intterupt()
只是确认按键是否有效,真正处理中断的程序是定时器里面的button_timer_handler( )
稍后会在button_open
函数中看一看是怎么回事。
这里还有一个知识点:消抖。按键毕竟是一种外设,因此可能会由于外界的因素或硬件问题而引起电平状态突然改变(毛刺,时间极短)。对按键来说,它可能会认为这是有效的中断触发(下降沿),这不是我们所希望的。因此,我们同样用了定时器。按下按键,但先设置为未决定状态,同时延时一下,根据在这一小段的延迟时间里,是不是仍然还保持着按下的状态,如果还是,才认为“真正”按下了按键,否则会认为是毛刺。
小结:
1)中断快速响应,快速返回。一旦产生中断,CPU会停止做的事情,来处理你的这个中断,在处理中断时是把别的中断屏蔽掉的,别的中断是不能响应的(但有中断优先级的话会优先处理级别高的中断,如在STM32单片机上,中断能够嵌套),所以不应该在中断服务程序里有sleep等这类处理出现。
2)Linux实现下半部的机制:
软中断:
1、软中断是在编译期间静态分配的。
2、最多可以有32个软中断。
3、软中断不会抢占另外一个软中断,唯一可以抢占软中断的是中断处理程序。
4、可以并发运行在多个CPU上(即使同一类型的也可以)。所以软中断必须设计为可重入的函数(允许多个CPU同时操作), 因此也需要使用自旋锁来保护其数据结构。
5、目前只有两个子系统直接使用软中断:网络和SCSI。
tasklet:
1、tasklet是使用两类软中断实现的:HI_SOFTIRQ和TASKLET_SOFTIRQ。
2、可以动态增加减少,没有数量限制。
3、同一类tasklet不能并发执行。
4、不同类型可以并发执行。
5、大部分情况使用tasklet。
工作队列:
1、由内核线程去执行,换句话说总在进程上下文执行。
2、可以睡眠,阻塞。
3)中断下半部可由:软件定时器,tasklet, 等待队列,内核线程等实现。
参考:http://blog.csdn.net/lizuobin2/article/details/54837680
看open里的部分代码,可以看到借助了定时器:
static int button_open(struct inode *inode, struct file *file)
{
…… ……
for(i=0; inbuttons; i++) //初始化button信息
{
/* Initialize all the buttons status to UP */
pdev->status[i] = BUTTON_UP; //按键未按下
/* Initialize all the buttons' remove dithering timer */
setup_timer(&(pdev->timers[i]), button_timer_handler, i);//设置定时器及调用函数。把每个最初的定时器赋予超时便返回到指定的处理函数的功能
/* Set all the buttons GPIO to EDGE_FALLING interrupt mode */
s3c2410_gpio_cfgpin(pdata->buttons[i].gpio, pdata->buttons[i].setting);//配置成中断模式
irq_set_irq_type(pdata->buttons[i].nIRQ, IRQ_TYPE_EDGE_FALLING);//下降沿触发中断
/* Request for button GPIO pin interrupt */
result = request_irq(pdata->buttons[i].nIRQ, s3c_button_intterupt, IRQF_DISABLED, DEV_NAME, (void *)i);//注册给内核,一旦发生pdata->buttons[i].nIRQ中断号的中断之后,调用s3c_button_intterupt中断处理程序
}
…… ……
在button_open
函数里其实已经涉及到了:
static int button_open(struct inode *inode, struct file *file)
{
…… ……
init_waitqueue_head(&(pdev->waitq)); //初始化等待队列头
…… ……
}
我们在定义button_device
结构体的时候已经在里面定义了这个变量:
struct button_device //定义一个button_device的结构体
{
unsigned char *status; /* The buttons Push down or up status */
struct s3c_button_platform_data *data; /* The buttons hardware information data */
struct timer_list *timers; /* The buttons remove dithering timers */
wait_queue_head_t waitq; /* Wait queue for poll()*/
volatile int ev_press; /* Button pressed event */
struct cdev cdev;
struct class *dev_class;
} button_device;
参考:http://www.cnblogs.com/watson/p/3543320.html
而在button_timer_handler
函数中:
/* Wake up the wait queue for read()/poll() */
button_device.ev_press = 1;
wake_up_interruptible(&(button_device.waitq));
ev_press
是一个按键按下而发生的标识,按下,设为1。此时唤醒等待队列让设备进行读取。ev_press
参数值也用来设置工作模式,选定阻塞或者是非阻塞模式。
看到button_read
读取按键信息这个函数里:
static int button_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
struct button_device *pdev = file->private_data;
struct s3c_button_platform_data *pdata;
int i, ret;
unsigned int status = 0;
pdata = pdev->data;
dbg_print("ev_press: %d\n", pdev->ev_press);
if(!pdev->ev_press) //ev_press为按键标识符,即按下时才为1.结合下面等待队列程序
{
if(file->f_flags & O_NONBLOCK)
{
dbg_print("read() without block mode.\n");// O_NONBLOCK是设置为非阻塞模式
return -EAGAIN;//若没有按键按下,还采用非阻塞模式则会一直浪费CPU的时间等待。
}
else
{
/* Read() will be blocked here */
dbg_print("read() blocked here now.\n");
wait_event_interruptible(pdev->waitq, pdev->ev_press); //在阻塞模式读取且没按键按下则让等待队列进入睡眠,直到ev_press为1
}
}
pdev->ev_press = 0;//清除标识,准备下一次
…… ……
}
这里我们要知道的:
1)阻塞操作: 是指在执行设备操作时,若不能获得资源,则挂起进程直到满足可操作的条件后再进行操作,被挂起的进程会进入休眠状态,被从调度器的运行队列中移除,直到条件被满足。
2)非阻塞操作: 是指在执行设备操作时,若不能获得资源,并不挂起,或者放弃他或者不停地的查询,直到可运行。
而我们驱动中等待队列使用的则是阻塞的方式,首先申请一个等待队列,一旦阻塞该进程就进入休眠将资源让给其他进程的,然后直到满足条件再唤醒等待队列,重新执行程序。而之前,可以看到是在button_timer_handler
中被唤醒,也就是在处理中断时进行。
在这里我的理解是,我们用户运行了按键的应用程序,在open
时设置了定时器、配置成中断模式、初始化了等待队列头;在read
要获取按键信息时,若我们没进行按下按键(ev_press=0
)的操作,这个进程就挂起进入休眠;按下了按键(ev_press=1
)时,也就是进入了中断时,等待队列被唤醒,得到了资源这个进程就再执行下去。read
将按键的状态保存到status
,并将按键信息通过copy_to_user
发送到用户空间。
驱动中的poll函数,其实就是对应到用户空间我们使用的select或poll系统调用,调用select或poll实际上进到驱动里就是调的poll。我以前写的button实例就是用到了select。
static unsigned int button_poll(struct file *file, poll_table * wait)
{
struct button_device *pdev = file->private_data;
unsigned int mask = 0;
poll_wait(file, &(pdev->waitq), wait);//添加队列到等待队列中
if(pdev->ev_press)
{
mask |= POLLIN | POLLRDNORM; /* The data aviable */
}
return mask;
}
/*fops*/
static struct file_operations button_fops = {
.owner = THIS_MODULE,
.open = button_open,
.read = button_read,
.poll = button_poll,
.release = button_release,
};
用法:先调用宏FD_ZERO
将指定的fd_set
清零,然后调用宏FD_SET
将需要测试的fd加入fd_set
,接着调用函数select测试fd_set
中的所有fd,最后用宏FD_ISSET
检查某个fd在函数select调用后,相应位是否仍然为1。
具体可以参考:http://blog.csdn.net/lingfengtengfei/article/details/12392449
到此就列出了当初学习按键驱动时感觉比较难懂的地方,或许还是经验不够,对一些地方的分析还不是很到位。但是还是希望以它为基础,逐渐提高自己的理解和完善学习。