之前的文章上面说到了关于使用两种方法来获取不同的键盘值,具体的文章可以参看以下文章:
嵌入式Linux驱动开发(三)——字符设备驱动之查询的方法获取键盘值
嵌入式Linux驱动开发(四)——字符设备驱动之中断方法以及中断方法获取键盘值
前情回顾:
再开始明天的内容之前,先简单review一下,我们都用了哪些方案来获取键盘值,她们的特征都是哪些。只有不断地理清了思路,我们才才能更好的理解,为什么会出现这么多的解决方案,当遇见问题的时侯,才可以对症下药。
第一种方案,也是最粗鲁的一种,在应用层通过一个死循环的read函数,来不断地查看底层read函数,在底层我们并未做阻塞,每次应用层read,底层就会给下层应用返回值。这么嵌入式驱动 linux,这个简单粗鲁的形式,其实是最不合理的,由于read函数不断地在读取,致使CPU的使用率被死循环占用了99%,这基本上是灾难式的解决方案。其实简单,而且代价也是显而易见的。
为了增加CPU的使用率,这么就要防止使用死循环不断地来从底层获取信息,这么,也就形成了两种解决思路,也分别就是第二篇文章中的两种解决方案了。
第二种方案,直接放弃了下层应用read的操作,全部交给底层,通过中断来实现。(request_irq和free_irq函数来注册和卸载中断)只要键盘按下,步入中断模式linux手机,调用底层的中断处理函数,来将键盘信息进行复印。其实下层应用没有哪些影响,然而,这样的方案也并不是我们想要的,由于,底层相对稳定的部份,参与了他不该参与的部份——具体中断怎么解决。这样或许也是灾难性的,莫非由于下层应用程序的需求发生了改变,还须要更改底层的驱动程序吗?!这似乎要把从用户到应用层开发再到底层应用开发人员给烦死。并且这个方案似乎不能解决需求,但它也并不是一无是处的,正是由于他的存在,才出现了第三种解决的方案。
第三种方案嵌入式驱动 linux,实际从底层解决思路的角度来看,是通过睡眠的方法解决的,从下层来看是通过阻塞来解决的。也就,下层应用通过调用了阻塞式的read函数(这也是大部份read函数的使用场景),假如没有数据返回来,就让read阻塞在这儿,下层应用可以放心的死循环来调用read,何必害怕它会不停地调用底层而花费大量资源。对于底层怎样来实现阻塞式的read呢?虽然也很简单是通过睡眠的方法来实现的。
staticDECLARE_WAIT_QUEUE_HEAD(wait_queue);
wake_up_interruptible(&wait_queue);
wait_event_interruptible(wait_queue,condition);
通过以上的函数,来实现底层应用的睡眠功能,结合第二种方案,在下层read的时侯直接让驱动睡眠,而不是返回。通过注册中断处理函数,在其中,来唤起并通过copytouser函数来把数据返回到下层应用的read函数中,进而实现了下层的阻塞功能。不但可以测量到中断的具体发生情况,同时也防止了CPU的高使用率的发生。
通过以上一系列的方式,初步实现了获取键盘值的需求,然而,还不能否满足我们的所有需求,例如,最后的解决方案是将read阻塞在了那儿,这不一定能满足我们所有的需求,为此,也诞生了以下的方案——poll机制。
延续之前文章中提及的最后一种方案,仍然阻塞在那儿可能会限制我们的需求,这么,是否可以通过一种方案,让他不要始终阻塞,而是阻塞一段时间,假如没有变化就先让它返回,不要始终阻塞在哪里。
对于Linux应用开发来说,poll机制的使用和open、read函数的使用长治小异,只须要调用poll函数即可。关于这个函数的具体使用方式,可以通过manpoll来查看具体的函数使用技巧。
poll函数的原型如下:
#include
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
按照man指南的信息,可以在应用层实现poll函数和read函数的调用。
#include
#include
#include
#include
#include
#include
int main()
{
//打开设备文件
int fd = open("/dev/poll_eint", O_RDWR);
if(fd < 0)
{
printf("open errorn");
return -1;
}
#if 0
/*
poll 函数所接收的第一个参数,该结构体的内容
*/
struct pollfd {
int fd; /* file descriptor 文件描述符*/
short events; /* requested events 输入参数*/
short revents; /* returned events 输出参数,事件发生后由内核来修改*/
};
#endif
//创建 struct polld 结构体,并为其中的成员赋值
struct pollfd fds[1];
fds[0].fd = fd; //指定文件描述
fds[0].events = POLLIN;//有数据可以读的时候返回
int ret = 0; //poll 函数的返回值,依据返回值判断是否有数据可读
unsigned char value = 0; //读取的按键值
//循环查询
while(1)
{
// int poll(struct pollfd *fds, nfds_t nfds, int timeout);
/*
fds:struct pollfd 数组,其中管理了文件描述以及输入参数
ndfs: 需要通过 poll 机制管理的文件描述符数量
timeout:超时时间,单位为毫秒
*/
//调用 poll 函数
ret = poll(fds, 1, 5000);
//如果返回值为0,打印超时
if(!ret)
printf("timeout.....%dnn", ret);
else{
//返回值不为0,说明有数据可以读取,通过 read 来读取数据并打印
read(fd, &value, 1);
printf("value: %xt ret: %dn", value, ret);
}
}
return 0;
}
以上就是一个简单的通过poll查询的应用程序示例代码。并且,Linux系统是怎样来实现poll函系统,驱动程序怎样来写是接出来要讨论的话题。
和open一样,下层调用open函数,步入内核空间,调用sys_open系统调用,在逐层向上调用,仍然到调用到驱动程序中file_operations中注册的open函数为止。poll函数也同样,按照内核源代码qq linux,具体poll函数的调用原理如下。
应用层通过调用: poll函数
进入到内核空间的系统调用: sys_poll(位于/linux/sys_poll.h 文件中)
do_sys_poll(...., timeout_jiffies)
poll_initwait(&table)
init_poll_funcptr(&pwq->pt, __pollwait)
pt->qproc = qproc //相当于table->qproc = __pollwait
do_poll(nfds, head, &table, timeout)
for(;;) //死循环
if(do_pollfd(pfd, pt))
//do_pollfd中,调用mask = file->f_op->poll(file, pwait); return mask;
//实际此时就会调用到驱动程序中的 poll 函数
p->qproc(filp, wait_address, p);//驱动程序中的 poll 函数,需要调用 poll_wait 函数,poll_wait 函数中执行了这个条语句
__pollwait()//把当前进程挂到队列中去管理,并不休眠
count++; //如果驱动的poll返回非0值,那么count++
pt = NULL;
if(count || !*timeout || signal_pending(current))
break; //break的三个条件:count非0;超时;有信号等待处理
//如果条件不成立,会进入到休眠状态
//驱动的poll:p->qproc(filp, wait_address, p) 把当钱进程挂到wait_address队列中去
__timeout = schedule_timeout(__timeout) //休眠__timeout的时间
按照以上的调用路线可以看见其中有一句f_op->poll(....),可以看出这个句话实际是从之前我们很熟悉的structfile_operations中取出了poll的函数表针,但是添加到一个超时队列中来进行调用管理的。
这么,我们只须要瞧瞧具体的poll函数原型是怎样样的也就可以来写poll机制的驱动了。
struct file_operations {
// .........
unsigned int (*poll) (struct file *, struct poll_table_struct *);
//.........
};
驱动程序使用poll机制以及中断机制来获取键盘值
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define EINT_PIN_COUNT 4
static const char* dev_name = "poll_eint";
static volatile unsigned int major = 0;
static struct class* poll_class;
static struct class_device* poll_class_device;
struct pin_desc
{
unsigned int pin;
unsigned int value;
};
//%kernel%includeasm-armarchirqs.h
//#define IRQ_EINT0 S3C2410_IRQ(0) /* 16 */
//中断号数组
static const int eints[EINT_PIN_COUNT] =
{
IRQ_EINT0,
IRQ_EINT2,
IRQ_EINT11,
IRQ_EINT19
};
static struct pin_desc pins[4] =
{
{S3C2410_GPF0, 0x1},
{S3C2410_GPF2, 0x2},
{S3C2410_GPG3, 0x3},
{S3C2410_GPG11, 0x4},
};
unsigned int status = 0;
unsigned int condition = 0;
unsigned char value = 0;
static DECLARE_WAIT_QUEUE_HEAD(wait_queue); //初始化中断等待序列
//中断处理函数
static irqreturn_t irq_handler(int irq, void *dev_id)
{
//获取按键值,如果按下则与0x80
struct pin_desc* desc = (struct pin_desc*) dev_id;
status = s3c2410_gpio_getpin(desc->pin);
if(status)
value = desc->value | 0x80;
else
value = desc->value;
//从睡眠队列中唤醒
wake_up_interruptible(&wait_queue);
condition = 1;
return 0;
}
static ssize_t poll_read (struct file *file, char __user *buff, size_t size, loff_t *ppos)
{
//read 的时候直接进入睡眠状态,有中断发生的时候在中断处理函数中唤醒
printk(".........readnn");
wait_event_interruptible(wait_queue, condition);
condition = 0;
//唤醒后,讲数据发送到用户空间
printk("read.........nn");
copy_to_user(buff, &value, 1);
return 0;
}
static unsigned int poll_poll (struct file *file, struct poll_table_struct *pts)
{
unsigned int mask = 0;
printk("poll<<<<<<<<<<<<<>>>>>>>>>>>>>nn");
//如果按键被按下,返回非零值,以便应用层判断poll 返回值
if(condition)
mask |= POLLIN | POLLRDNORM;
return mask;
}
static int poll_open (struct inode *inode, struct file *file)
{
// 在 open 的时候注册按键中断
int i;
for(i = 0; i < EINT_PIN_COUNT; ++i){
request_irq(eints[i], irq_handler, IRQT_BOTHEDGE, dev_name, &pins[i]);
}
printk("interrupt registern");
return 0;
}
static int poll_release (struct inode *inode, struct file *file)
{
//释放之前注册的中断
//void free_irq(unsigned int irq, void *dev_id)
int i = 0;
for(;i < EINT_PIN_COUNT; ++i){
free_irq(eints[i], &pins[i]);
}
printk("button releasedn");
return 0;
}
struct file_operations poll_fops =
{
.owner = THIS_MODULE,
.open = poll_open,
.read = poll_read,
.poll = poll_poll,
.release = poll_release,
};
static int __init poll_init(void)
{
major = register_chrdev(major, dev_name, &poll_fops);
poll_class = class_create(THIS_MODULE, dev_name);
poll_class_device = class_device_create(poll_class, NULL, MKDEV(major, 0), NULL, dev_name);
printk("initn");
return 0;
}
static void __exit poll_exit(void)
{
unregister_chrdev(major, dev_name);
class_device_unregister(poll_class_device);
class_destroy(poll_class);
printk("exitn");
}
module_init(poll_init);
module_exit(poll_exit);
MODULE_AUTHOR("Ethan Lee ");
MODULE_LICENSE("GPL");
通过以上的代码,就可以实现通过poll机制和中断的方法来实现不完全阻塞的状态来查询键盘值了。
这么以上的方式实际都是通过应用层查询的方法来实现了具体的键盘值获取任务,是不是还有其他的形式来获取键盘值呢?
假定把应用程序主动查看的方法称作一个人通过不断地打电话的方法来寻问快件是否送达,这么,是不是可以让驱动程序来给应用程序打电话,通知他状态发生了变化呢?
这个其实是可以的,肯定须要用到进程间的通信方法。