热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux驱动开发六:按键中断+poll机制

基于韦东山视频用于QT6410poll机制分析韦东山2009.12.10所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数。比如系统调用open、read、writ

基于韦东山视频用于QT6410


poll机制分析


韦东山2009.12.10


所有的系统调用,基于都可以在它的名字前加上“sys_”前缀,这就是它在内核中对应的函数。比如系统调用openreadwritepoll,与之对应的内核函数为:sys_opensys_readsys_writesys_poll


 


一、内核框架:


对于系统调用pollselect,它们对应的内核函数都是sys_poll。分析sys_poll,即可理解poll机制。


1.      sys_poll函数位于fs/select.c文件中,代码如下:


asmlinkagelong sys_poll(struct pollfd __user *ufds, unsigned int nfds,


                long timeout_msecs)


{


        s64 timeout_jiffies;


 


        if (timeout_msecs > 0) {


#ifHZ > 1000


            /* We can only overflow if HZ >1000 */


            if (timeout_msecs / 1000 >(s64)0x7fffffffffffffffULL / (s64)HZ)


                timeout_jiffies = -1;


            else


#endif


                timeout_jiffies =msecs_to_jiffies(timeout_msecs);


        } else {


            /* Infinite (<0) or no (0)timeout */


            timeout_jiffies = timeout_msecs;


        }


 


        return do_sys_poll(ufds,nfds, &timeout_jiffies);


}


它对超时参数稍作处理后,直接调用do_sys_poll


 


2.      do_sys_poll函数也位于位于fs/select.c文件中,我们忽略其他代码:


intdo_sys_poll(struct pollfd __user *ufds, unsigned int nfds, s64 *timeout)


{


……


poll_initwait(&table);


……


        fdcount = do_poll(nfds, head,&table, timeout);


……


}


 


poll_initwait函数非常简单,它初始化一个poll_wqueues变量table


poll_initwait> init_poll_funcptr(&pwq->pt, __pollwait); > pt->qproc = qproc;


table->pt->qproc=__pollwait__pollwait将在驱动的poll函数里用到。


 


 


3.      do_sys_poll函数位于fs/select.c文件中,代码如下:


 


static int do_poll(unsigned int nfds, struct poll_list *list,


           struct poll_wqueues *wait, s64 *timeout)


{


01……


02  for (;;){


03……


04                  if(do_pollfd(pfd, pt)) {


05                          count++;


06                          pt = NULL;


07                  }


08……


09      if(count || !*timeout || signal_pending(current))


10          break;


11      count= wait->error;


12      if(count)


13          break;


14


15      if(*timeout <0) {


16          /*Wait indefinitely */


17          __timeout= MAX_SCHEDULE_TIMEOUT;


18      }else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT-1)) {


19          /*


20          * Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in


21          * a loop


22          */


23          __timeout= MAX_SCHEDULE_TIMEOUT - 1;


24          *timeout-= __timeout;


25      }else {


26          __timeout= *timeout;


27          *timeout= 0;


28      }


29


30      __timeout= schedule_timeout(__timeout);


31      if(*timeout >= 0)


32          *timeout+= __timeout;


33  }


34  __set_current_state(TASK_RUNNING);


35  returncount;


36 }


 


分析其中的代码,可以发现,它的作用如下:


    02行可以知道,这是个循环,它退出的条件为:


a.      09行的3个条件之一(count0,超时、有信号等待处理)


count0表示04行的do_pollfd至少有一个成功。


b.      1112行:发生错误


    重点在do_pollfd函数,后面再分析


    30行,让本进程休眠一段时间,注意:应用程序执行poll调用后,如果①②的条件不满足,进程就会进入休眠。那么,谁唤醒呢?除了休眠到指定时间被系统唤醒外,还可以被驱动程序唤醒──记住这点,这就是为什么驱动的poll里要调用poll_wait的原因,后面分析。


 


4.      do_pollfd函数位于fs/select.c文件中,代码如下:


static inline unsigned int do_pollfd(struct pollfd*pollfd, poll_table *pwait)


{


……


            if(file->f_op && file->f_op->poll)


                    mask= file->f_op->poll(file, pwait);


……


}


 


可见,它就是调用我们的驱动程序里注册的poll函数。


 


二、驱动程序:


驱动程序里与poll相关的地方有两处:一是构造file_operation结构时,要定义自己的poll函数。二是通过poll_wait来调用上面说到的__pollwait函数,pollwait的代码如下:


staticinline void poll_wait(struct file * filp, wait_queue_head_t * wait_address,poll_table *p)


{


        if (p && wait_address)


            p->qproc(filp, wait_address, p);


}


p->qproc就是__pollwait函数,从它的代码可知,它只是把当前进程挂入我们驱动程序里定义的一个队列里而已。它的代码如下:


staticvoid __pollwait(struct file *filp, wait_queue_head_t *wait_address,


                        poll_table *p)


{


        struct poll_table_entry *entry =poll_get_entry(p);


        if (!entry)


            return;


        get_file(filp);


        entry->filp = filp;


        entry->wait_address = wait_address;


        init_waitqueue_entry(&entry->wait,current);


        add_wait_queue(wait_address,&entry->wait);


}


 


执行到驱动程序的poll_wait函数时,进程并没有休眠,我们的驱动程序里实现的poll函数是不会引起休眠的。让进程进入休眠,是前面分析的do_sys_poll函数的30行“__timeout = schedule_timeout(__timeout)”。


poll_wait只是把本进程挂入某个队列,应用程序调用poll > sys_poll> do_sys_poll > poll_initwaitdo_poll > do_pollfd >我们自己写的poll函数后,再调用schedule_timeout进入休眠。如果我们的驱动程序发现情况就绪,可以把这个队列上挂着的进程唤醒。可见,poll_wait的作用,只是为了让驱动程序能找到要唤醒的进程。即使不用poll_wait,我们的程序也有机会被唤醒:chedule_timeout(__timeout),只是要休眠__time_out这段时间。


 


现在来总结一下poll机制:


1. poll > sys_poll > do_sys_poll >poll_initwaitpoll_initwait函数注册一下回调函数__pollwait,它就是我们的驱动程序执行poll_wait时,真正被调用的函数。


 


2.接下来执行file->f_op->poll,即我们驱动程序里自己实现的poll函数


  它会调用poll_wait把自己挂入某个队列,这个队列也是我们的驱动自己定义的;


  它还判断一下设备是否就绪。


 


3.如果设备未就绪,do_sys_poll里会让进程休眠一定时间


 


4.进程被唤醒的条件有2:一是上面说的“一定时间”到了,二是被驱动程序唤醒。驱动程序发现条件就绪时,就把“某个队列”上挂着的进程唤醒,这个队列,就是前面通过poll_wait把本进程挂过去的队列。


 


5.如果驱动程序没有去唤醒进程,那么chedule_timeout(__timeou)超时后,会重复23动作,直到应用程序的poll调用传入的时间到达。

简化过程:

应用程序调用poll,内核调用sys_poll
app:poll
kernel:sys_poll
 do_sys_poll(....,timeout_jiffies)
  poll_initwait(&table);
   init_poll_funcptr(&pwq->pt,__pollwait); >talb->qproc=__pollwait
  do_poll(nfds,head,&table,timeout)
   for(;;)
   {
    for(;pfd != pfd_end;pfd ++){//查询多个驱动
     if(do_pollfd(pfd,pt)){ >mask = file->f_op->poll(file,pwait);return mask;
        //驱动的poll:
        __pollwait(filp,&button_waitq);
        把当前进程挂到button_waitq队列里去

      count++;//如果驱动的poll返回非0值,那么count++
      pt = NULL;
     }
    }

    //break的条件:count非0,超时,有信号在等待
    if(count || !*timeout || signal_pending(current))
     break;

    //休眠__timeout 
    __timeout = schedule_timeout(__timeout);
   }

代码:

forth_drv.c

/*
*功能:中断驱动,在按键驱动基础上修改,原理图见按键驱动,增加poll机制
*/
#include
#include
#include
#include
#include
#include
#include
#include

#include

#include
#include
//#include
//#include
#include


#include
#include
#include


int major;
const char *major_name = "forth_drv";
const char *minor_name = "button";

static struct class *forthdrv_class;
static struct class_device *forthdrv_class_dev;

volatile unsigned long *gpncon = NULL;
volatile unsigned long *gpndat = NULL;

//声明一个按键的等待队列
static DECLARE_WAIT_QUEUE_HEAD(button_waitq);

/* 中断事件标志, 中断服务程序将它置1,forth_drv_read将它清0 */
static volatile int ev_press = 0;

struct pin_desc{
unsigned int pin;
unsigned int key_val;
};

/* 键值: 按下时, 0x01, 0x02, 0x03, 0x04 */
/* 键值: 松开时, 0x81, 0x82, 0x83, 0x84 */
static unsigned char key_val;

/*
* K1,K2,K3,K4对应GPN0,GPGN1,GPN2,GPN3
*/
/*
struct pin_desc pins_desc[4] = {
{S3C64XX_GPN0_EINT0, 0x01},
{S3C64XX_GPN1_EINT1, 0x02},
{S3C64XX_GPN2_EINT2, 0x03},
{S3C64XX_GPN3_EINT3, 0x04},
};
struct pin_desc pins_desc[4] = {
{0, 0x01},
{1, 0x02},
{2, 0x03},
{3, 0x04},
};
*/
struct pin_desc pins_desc[4] = {
{S3C64XX_GPN(0), 0x01},
{S3C64XX_GPN(1), 0x02},
{S3C64XX_GPN(2), 0x03},
{S3C64XX_GPN(3), 0x04},
};

/*
* 确定按键值
*中断处理程序,并置标志位为1,唤醒等待队列上等待的进程,注册用户中断处理函数,设置触发方式为双边沿触发
*/
static irqreturn_t button_irq(int irq,void *dev_id)
{
struct pin_desc * pindesc = (struct pin_desc *)dev_id;
unsigned int pinval;

//pinval = ioread32(S3C64XX_GPNDAT);
//pinval = gpio_get_value(S3C64XX_GPN(pindesc->pin));/* 读取中断引脚的状态 */
pinval = gpio_get_value(pindesc->pin);/* 读取中断引脚的状态 */

/* 确定按键值 */
if (pinval)
{
/* 松开 */
key_val = 0x80 | pindesc->key_val;
}
else
{
/* 按下 */
key_val = pindesc->key_val;
}

ev_press = 1; /* 表示中断发生了 */
wake_up_interruptible(&button_waitq); /* 唤醒休眠的进程 */


return IRQ_RETVAL(IRQ_HANDLED);
}

/* open函数 */
static int forth_drv_open(struct inode *inode,struct file *file)
{
/* 配置GPN0、1、2、3为输入(配置为0) */
//*gpncon &=~((0x3<<(0*2)) | (0x3<<(1*2)) | (0x3<<(2*2)) | (0x3<<(3*2)));//清零寄存器
request_irq(IRQ_EINT(0),button_irq,IRQ_TYPE_EDGE_BOTH,"K1",&pins_desc[0]);
request_irq(IRQ_EINT(1),button_irq,IRQ_TYPE_EDGE_BOTH,"K2",&pins_desc[1]);
request_irq(IRQ_EINT(2),button_irq,IRQ_TYPE_EDGE_BOTH,"K3",&pins_desc[2]);
request_irq(IRQ_EINT(3),button_irq,IRQ_TYPE_EDGE_BOTH,"K4",&pins_desc[3]);
//request_irq(IRQ_EINT(4),button_irq,IRQ_TYPE_EDGE_BOTH,"K4",1);
return 0;
}

/* read函数 */
static ssize_t forth_drv_read(struct file *file,char __user *buf,size_t size,loff_t *ppos)
{
if(size != 1)
return -EINVAL;

/* 如果没有按键动作, 休眠 */
wait_event_interruptible(button_waitq, ev_press);

/* 如果有按键动作, 返回键值 */
copy_to_user(buf, &key_val, 1);
ev_press = 0;//重新清0

return 1;
}

static unsigned forth_drv_poll(struct file *file,poll_table *wait)
{
unsigned int mask = 0;

/* 不会立即休眠 只是将进程挂到key_waitq队列, 并不会休眠 */
poll_wait(file,&button_waitq,wait);

if(ev_press)//如果没有中断产生
mask |= POLLIN | POLLRDNORM;

//printk("\t*******************\n\tpoll\n\t***********************\n");
return mask;
}
//主要是卸载用户中断处理程序
int forth_drv_close(struct inode* inode,struct file* file)
{
free_irq(IRQ_EINT(0),&pins_desc[0]);
free_irq(IRQ_EINT(1),&pins_desc[1]);
free_irq(IRQ_EINT(2),&pins_desc[2]);
free_irq(IRQ_EINT(3),&pins_desc[3]);

return 0;
}
/* 结构体 */
static struct file_operations forth_drv_fops={
.owner = THIS_MODULE,/* 这是一个宏,推向编译模块时自动创建到__this_module变量 */
.open = forth_drv_open,
.read = forth_drv_read,
.release = forth_drv_close,
.poll = forth_drv_poll,
};


/* 入口函数 */
static int forth_drv_init(void)
{
major = register_chrdev(0,major_name,&forth_drv_fops);
if(major <0)
{
printk("Can't register major number\n");
return major;
}

/* 自动生成设备节点机制,获取系统消息,cat /proc/class会发现 first_drv */
forthdrv_class = class_create(THIS_MODULE, major_name);
/* register your own device in sysfs, and this will cause udev to create corresponding device node */
forthdrv_class_dev = device_create( forthdrv_class, NULL, MKDEV(major, 0),NULL, minor_name );

/* 地址映射 */
gpncon = (volatile unsigned long *)ioremap(0x7F008830,16);
gpndat = gpncon + 1;//指针加1

return 0;
}

/* 出口函数 */
static void forth_drv_exit(void)
{
unregister_chrdev(major,major_name);
device_unregister(forthdrv_class_dev);
class_destroy(forthdrv_class);
iounmap(gpncon);
}

module_init(forth_drv_init);
module_exit(forth_drv_exit);
MODULE_LICENSE("GPL");


Makefile:

obj-m := forth_drv.o

KER_DIR := /work/work/linux/


all:
$(MAKE) -C $(KER_DIR) M=`pwd` modules
#cp first_drv.ko /work/tftpboot/
cp forth_drv.ko /work/nfsroot/
echo $^

clean:
make -C $(KER_DIR) M=`pwd` modules clean


test.c

/*
*功能:在一定时间内没有按键中断,也返回
*/
#include
#include
#include
#include
#include


/*test
*/

int main(int argc,char *argv[])
{
int fd;
int ret ;
char key_vals;
struct pollfd fds[1];//"1"表示只查询一个中断设备

fd = open("/dev/button",O_RDWR);
if(fd <0)
{
printf("can't opent /dev/button\n");
return 0;
}

fds[0].fd = fd;
fds[0].events = POLLIN;

while(1)
{
ret = poll(fds, 1, 5000);//5秒超时时间 poll(fds, 1, 5000);"1"表示查询一个文件
if(0 == ret)
{
printf("timeout !\n");
}
else
{
read(fd,&key_vals,1);
printf("key_vals = 0x%x\n",key_vals);
}
}

return 0;

}



推荐阅读
  • 深入解析Linux下的I/O多路转接epoll技术
    本文深入解析了Linux下的I/O多路转接epoll技术,介绍了select和poll函数的问题,以及epoll函数的设计和优点。同时讲解了epoll函数的使用方法,包括epoll_create和epoll_ctl两个系统调用。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 本文介绍了Python高级网络编程及TCP/IP协议簇的OSI七层模型。首先简单介绍了七层模型的各层及其封装解封装过程。然后讨论了程序开发中涉及到的网络通信内容,主要包括TCP协议、UDP协议和IPV4协议。最后还介绍了socket编程、聊天socket实现、远程执行命令、上传文件、socketserver及其源码分析等相关内容。 ... [详细]
  • 树莓派Linux基础(一):查看文件系统的命令行操作
    本文介绍了在树莓派上通过SSH服务使用命令行查看文件系统的操作,包括cd命令用于变更目录、pwd命令用于显示当前目录位置、ls命令用于显示文件和目录列表。详细讲解了这些命令的使用方法和注意事项。 ... [详细]
  • 深入理解Kafka服务端请求队列中请求的处理
    本文深入分析了Kafka服务端请求队列中请求的处理过程,详细介绍了请求的封装和放入请求队列的过程,以及处理请求的线程池的创建和容量设置。通过场景分析、图示说明和源码分析,帮助读者更好地理解Kafka服务端的工作原理。 ... [详细]
  • Android工程师面试准备及设计模式使用场景
    本文介绍了Android工程师面试准备的经验,包括面试流程和重点准备内容。同时,还介绍了建造者模式的使用场景,以及在Android开发中的具体应用。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • MySQL数据库锁机制及其应用(数据库锁的概念)
    本文介绍了MySQL数据库锁机制及其应用。数据库锁是计算机协调多个进程或线程并发访问某一资源的机制,在数据库中,数据是一种供许多用户共享的资源,如何保证数据并发访问的一致性和有效性是数据库必须解决的问题。MySQL的锁机制相对简单,不同的存储引擎支持不同的锁机制,主要包括表级锁、行级锁和页面锁。本文详细介绍了MySQL表级锁的锁模式和特点,以及行级锁和页面锁的特点和应用场景。同时还讨论了锁冲突对数据库并发访问性能的影响。 ... [详细]
  • STL迭代器的种类及其功能介绍
    本文介绍了标准模板库(STL)定义的五种迭代器的种类和功能。通过图表展示了这几种迭代器之间的关系,并详细描述了各个迭代器的功能和使用方法。其中,输入迭代器用于从容器中读取元素,输出迭代器用于向容器中写入元素,正向迭代器是输入迭代器和输出迭代器的组合。本文的目的是帮助读者更好地理解STL迭代器的使用方法和特点。 ... [详细]
  • 本文分享了一个关于在C#中使用异步代码的问题,作者在控制台中运行时代码正常工作,但在Windows窗体中却无法正常工作。作者尝试搜索局域网上的主机,但在窗体中计数器没有减少。文章提供了相关的代码和解决思路。 ... [详细]
  • 本文介绍了使用PHP实现断点续传乱序合并文件的方法和源码。由于网络原因,文件需要分割成多个部分发送,因此无法按顺序接收。文章中提供了merge2.php的源码,通过使用shuffle函数打乱文件读取顺序,实现了乱序合并文件的功能。同时,还介绍了filesize、glob、unlink、fopen等相关函数的使用。阅读本文可以了解如何使用PHP实现断点续传乱序合并文件的具体步骤。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • Python瓦片图下载、合并、绘图、标记的代码示例
    本文提供了Python瓦片图下载、合并、绘图、标记的代码示例,包括下载代码、多线程下载、图像处理等功能。通过参考geoserver,使用PIL、cv2、numpy、gdal、osr等库实现了瓦片图的下载、合并、绘图和标记功能。代码示例详细介绍了各个功能的实现方法,供读者参考使用。 ... [详细]
  • 重入锁(ReentrantLock)学习及实现原理
    本文介绍了重入锁(ReentrantLock)的学习及实现原理。在学习synchronized的基础上,重入锁提供了更多的灵活性和功能。文章详细介绍了重入锁的特性、使用方法和实现原理,并提供了类图和测试代码供读者参考。重入锁支持重入和公平与非公平两种实现方式,通过对比和分析,读者可以更好地理解和应用重入锁。 ... [详细]
  • Centos下安装memcached+memcached教程
    本文介绍了在Centos下安装memcached和使用memcached的教程,详细解释了memcached的工作原理,包括缓存数据和对象、减少数据库读取次数、提高网站速度等。同时,还对memcached的快速和高效率进行了解释,与传统的文件型数据库相比,memcached作为一个内存型数据库,具有更高的读取速度。 ... [详细]
author-avatar
平凡特产小店
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有