您的位置:首页 > 其它

按键驱动回顾

2015-09-22 13:44 288 查看
回顾了自己以前的按键驱动的分析,感觉说得有点乱,上次我主要是逐行分析,然后只是明白各个部分做什么,还是不能从整体上来分析,经验不足,这次我再来回顾一下吧。

以前那篇的地址:/article/1975071.html

———————————————————————————————————————————————————————————————————————

对于驱动的分析,我们首先还是定位到__init函数,(module_init加载的函数,整个程序的入口)

因为我们这个按键驱动是利用了platform总线(简单地说就是将设备和驱动分离开来,一个挂载platform的设备总线上,一个挂载platform的驱动总线上,上面可以挂很多设备,设备和驱动的识别是靠彼此的“名字”----"This module",有了这个总线我们就可以不用为某个特定的设备重新写驱动了,一个驱动就能支持多种设备)所以它里面只有2个关键函数:

platform_device_register(&s3c_button_device)和platform_driver_register(&s3c_button_driver),这2个函数中我们又要重点看后面这个函数。

static struct platform_driver s3c_button_driver = { 
    .probe      = s3c_button_probe, 
    .remove     = s3c_button_remove, 
    .driver     = { 
        .name       = "s3c_button", 
        .owner      = THIS_MODULE, 
    },
};
在platform驱动中probe是最为关键的一个函数,这个函数里面实现了设备的主次设备号的申请,cdev的初始化及注册,当然这个驱动里面还有添加了一个动态创建设备节点的函数,这些过程就是注册一个设备驱动的关键几步。

——————————————————————————————————————————————————————————————————————

小知识:

alloc_chrdev_region(&devno, dev_minor, 1, DEV_NAME); 为动态申请设备号的函数

cdev_init (&(button_device.cdev), &button_fops)和cdev_add (&(button_device.cdev), devno , 1);这2个函数就是实现将设备注册成一个cdev设备加入内核并将驱动的fops和设备联系起来,内核2.6以前貌似是用register_chrdev这个函数来实现的,因为在书中看到过这个函数,所以提了一下,现在普遍都是用cdev了。

———————————————————————————————————————————————————————————————————————

中断

open这个函数中最需要理解的就是中断服务程序

然后我们就可以讲讲按键驱动中的中断了(打断CPU正在做的一件事)

操作系统处理事务的三种方式:DMA>中断>轮训(太浪费CPU内存了)

轮训就比如你正在房间编程,然后你点了外卖,你要一段时间跑过去看有没有人来,这样你就得不停的跑来跑去很麻烦。

中断就是那扇门正好有一个按钮,当外卖来的时候,你听到按钮声就知道来了,这个时候你就放下你手中正在干的事情去拿外卖。

而且每个中断都有一个独一无二的编号,你会知道是哪个中断来了,然后有一个中断服务程序。拿上面的例子来说,你拿了外卖放下编程去吃饭就是一个中断服务程序。

DMA就是当你外卖来的时候你找一个人去帮你拿。(其实DMA中也用到了中断,当DMA帮CPU处理完事务的时候它会跟CPU发送中断,告诉已经做完)

DMA就是直接帮CPU把数据搬到内存中运行,不用CPU再去操作寄存器来进行数据的搬送。



当你按键没按的时候GPIO口就是高电平,按下的时候就是一个低电平。

我们这里用中断来判断按键是否按下,可以看出这个管脚是多功能管脚,当我们把这个管脚设置成中断模式即EINTX的时候,就成了一个功能管脚,而且上面也分析了高电平是没按下,低电平是按下,所以我们设置中断的触发方式就是下降沿触发。

如果我们用轮询来判断的话,这里我们就要把管脚设置成GPIO模式,然后配置成INPUT模式,用一个while循环去读取这个管脚的电平高低,对比一下就知道这样太浪费内存了,所以我们这里用中断来判断是否按键。

*********************************************************************************************************************************************************

在linux内核中用于申请中断的函数是request_irq(),函数原型在Kernel/irq/manage.c中定义:

int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)

irq是要申请的硬件中断号。

handler是向系统注册的中断处理函数,是一个回调函数,中断发生时,系统调用这个函数,dev_id参数将被传递给它。

irqflags是中断处理的属性,若设置了IRQF_DISABLED (老版本中的SA_INTERRUPT,本版zhon已经不支持了),则表示中断处理程序是快速处理程序,快速处理程序被调用时屏蔽所有中断,慢速处理程序不屏蔽;若设置了IRQF_SHARED (老版本中的SA_SHIRQ),则表示多个设备共享中断,若设置了IRQF_SAMPLE_RANDOM(老版本中的SA_SAMPLE_RANDOM),表示对系统熵有贡献,对系统获取随机数有好处。(这几个flag是可以通过或的方式同时使用的)

devname设置中断名称,通常是设备驱动程序的名称 在cat /proc/interrupts中可以看到此名称。

dev_id在中断共享时会用到,一般设置为这个设备的设备结构体或者NULL。

request_irq()返回0表示成功,返回-INVAL表示中断号无效或处理函数指针为NULL,返回-EBUSY表示中断已经被占用且不能共享。

*****************************************************************************************************************************************************************************************

去抖

为了防止我们人为不小心碰到按键,然后产生了电平差,产生了中断,让CPU误以为我们按了按键,这里我们用到了一个定时器去去抖。

原理就是当产生中断的时候我们先启动我们的定时器,处理定时器的处理函数。(这里就有一个中断的顶部和底部的概念,直到启动定时器都叫中断的顶部,当去处理定时器的处理函数的时候就到了中断的底部了。),定时器定时了一个时间,这个时间一过我们再来判读这个按键是否真的按下。

——————————————————————————————————————————————————————————————————

然后就是read这个函数,当我们用户空间调用read来读取这个按键的状态的时候如果没有按下它有可能是一直等着,也有可能是一直在读,这里就是阻塞访问和非阻塞访问,用户空间read里面的一个参数。

这里我们就用到了一个等待队列的概念

Linux内核里的等待队列机制在做驱动开发时用的非常多,多用来实现阻塞式访问

struct 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;


当ev_press=0的时候我们先将这个队列进入休眠状态,不调用这个进程,让CPU去干别的,一旦我们按键按下,我们就将这个参数设置为1,将这个队列唤醒,让CPU去执行这个进程

static int button_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)//读到的数据保存在缓冲区buf中,count是请求读到的字节
{ 
    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是等待条件,这里判断是否需要唤起等待队列(即按键有没有按下),不需要则往下执行。
    {
         if(file->f_flags & O_NONBLOCK)//O_NONBLOCK是设置为非阻塞模式,这里是判断这个文件是否是非阻塞模式
         {
			 //应用程序若采用非阻塞方式读取则返回错误(因为按键没有按下,如果你用非阻塞的模式去读的话则CPU一直在访问,这样浪费cpu资源)
             dbg_print("read() without block mode.\n");
             return -EAGAIN;
         }
         else
         {
			 //以阻塞方式读取且按键没按下产生,让等待队列进入睡眠
             /* Read() will be blocked here */
             dbg_print("read() blocked here now.\n");
             wait_event_interruptible(pdev->waitq, pdev->ev_press);//当ev_press为真时即返回
         }
    }

    pdev->ev_press = 0;//1为按键按下产生,并清除标识为0,准备给下一次判断用

    for(i=0; i<pdata->nbuttons; i++)
    {
        dbg_print("button[%d] status=%d\n", i, pdev->status[i]);
        status |= (pdev->status[i]<<i); //如果是第一盏灯则左移1位,第二盏左移2位,以此类推用来判断是第几盏灯。
    }

    ret = copy_to_user(buf, (void *)&status, min(sizeof(status), count));//将内核中的按键状态数据拷贝到用户空间给应用程序使用

	/*由于内核空间与用户空间的内存不能直接互访,因此借助函数copy_to_user()完成将内核空间的数据到用户空间的复制,
函数copy_from_user()完成将用户空间数据到内核空间的复制。
	函数原型:unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
	如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
	*to是用户空间的指针,
    *from是内核空间指针,
	n表示从内核空间向用户空间拷贝数据的字节数*/
	
	
    return ret ? -EFAULT : min(sizeof(status), count);
}


在前面的定时器处理程序中有判断按键是否真的按键,按下则会设置ev_press为1唤醒队列进行read将按键缓冲区的数据copy从内核空间到用户空间的缓存空间去,否则还是设置为0继续等待。

—————————————————————————————————————————————————————————————————————————————

大概过程就是 按下按键产生一个中断,启动定时器,延时判断,是否真的按键按下(去抖),然后再唤醒等待队列,将按键缓冲区的数据copy到用户空间。

程序中用到了中断和等待队列,这样是为了应用层不浪费cpu时间,提高效率,至于poll机制的引入是为了如果应用程序使用了非阻塞访问,我们也可以用select这个函数来解决,用它来进行监听,以至于不会让cpu一直在不停的轮询。适合多进程,但是我的就一个单进程的测试程序所以看不出效果.

10月8号,今天又看了一下书:

阻塞与非阻塞IO:当你用户空间read一个设备的时候可以选择是阻塞还是非阻塞,阻塞就是进程就卡在这里当有数据的时候再返回,这个时候CPU不能干其他的事情。

非阻塞就是当read设备的时候如果没有数据,要么事放弃,要么就是不停的查询。这样看起来非阻塞可能比阻塞好一点,实则不然,如果设备驱动不阻塞,则用户想获得设备资源就得不停的路线,这反而会消耗CPU资源,如果在驱动中加入了等待队列,那么我们就可以阻塞访问的时候不太损耗CPU时间。等待队列的机制就是以将这个进程加入等待队列,休眠,CPU去干其他的时候,当有数据的时候再返回,用户并不能感知到,这个唤醒的条件就是中断,因为硬件资源获得的同时往往伴随着一个中断。这样就能解决阻塞访问设备不浪费CPU时间片的方法了。

以上是我对以前按键驱动程序的再次理解!

这个人的分析很到位http://blog.chinaunix.net/uid-103601-id-2961348.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: