现在的位置: 首页 > 综合 > 正文

2、Linux下实现按键驱动的几种方式

2018年04月17日 ⁄ 综合 ⁄ 共 4678字 ⁄ 字号 评论关闭

一、查询方式

该方式的缺点很明显:CPU占用资源大

二、中断方式

static dev_t devid;
static struct cdev * keyDev;
static class *key_class;

static DECLARE_WAIT_QUEUE_HEAD(key_waitq);
static volatile int ev_press;

static irqreturn_t gpio_irq_function(int irq, void *dev_id)
{
ev_press = 1;
wake_up_interruptible(&key_waitq);
return IRQ_HANDLED;
}

static int key_open(struct inode *inode, struct file *file)
{
int irq;
gpio_direction_input(IMX_GPIO_NR(2, 3));
/*申请gpio中断*/
irq = gpio_to_irq(IMX_GPIO_NR(2, 3));
request_irq(irq, gpio_irq_function, IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING, "key_irq", NULL);
return 0;
}

static ssize_t key_read(struct file *file, char __user *buf, size_t cnt, loff_t *ppos);
{
int val;
wait_event_interruptible(key_waitq, ev_press);
val = gpio_get_value(IMX_GPIO_NR(2, 3));
copy_to_user(buf, &val, sizeof(val));
ev_press = 0;
return 0;
}

static int key_close(struct inode *inode, struct file *file)
{
free_irq(IMX_GPIO_NR(2, 3), NULL);
return 0;
}

static struct file_operations key_fops={
.owner = THIS_MODULE,
.open   = key_open,
.read   = key_read,
.release = key_close,
};

static int key_init(void)
{
alloc_chrdev_region(&devid, 0, 1, "key");
cdev_init(keyDev, &key_fops);
cdev_add(keyDev, devid, 1);

/*创建设备节点*/
key_class = class_create(THIS_MODULE, "my_key");
device_create(key_class, NULL, devid, NULL, "key");

/*申请GPIO2,3*/
gpio_request(IMX_GPIO_NR(2, 3), "key");

return 0;
}

static void key_exit(void)
{
gpio_free(IMX_GPIO_NR(2, 3));
device_destroy(key_class, devid);
class_destroy(key_class);
cdev_del(keyDev);
unregister_chrdev_region(devid, 1);
}

应用程序查询按键,如果没有键被按下,进入休眠状态;然后中断函数里将它唤醒。这种情况下,只有按键被按下时,CPU才进行处理,大大的节约了CPU的资源。

        两个知识点:

        1、申请中断的函数:int request_irq(unsigned int irq, irq_handler_t handler,unsigned long irqflags, const char *devname, void *dev_id)

      查看注册的中断:cat /proc/interrupts

       2、休眠和唤醒:wait_event_interruptible(button_waitq, ev_press);  和 wake_up_interruptible(&button_waitq);

读一下wait_event_interruptible()的源码,不难发现这个函数先将 当前进程的状态设置成TASK_INTERRUPTIBLE,然后调用schedule(), 而schedule()会将位于TASK_INTERRUPTIBLE状态的当前进程从runqueue 队列中删除。从runqueue队列中删除的结果是,当前这个进程将不再参 与调度,除非通过其他函数将这个进程重新放入这个runqueue队列中,这就是wake_up_interruptible()的作用了。 
由于这一段代码位于一个由condition控制的for(;;)循环中,所以当由 shedule()返回时(当然是被wake_up之后,通过其他进程的schedule()而 再次调度本进程),如果条件condition不满足,本进程将自动再次被设 置为TASK_INTERRUPTIBLE状态,接下来执行schedule()的结果是再次被 从runqueue队列中删除。这时候就需要再次通过wake_up重新添加到 runqueue队列中。 
如此反复,直到condition为真的时候被wake_up. 
可见,成功地唤醒一个被wait_event_interruptible()的进程,需要满足: 
在 1)condition为真的前提下,2) 调用wake_up()。 

三、poll机制

poll驱动程序:

static unsigned int key_poll(struct file *fp, poll_table * wait)
{
poll_wait(fp, &key_waitq, wait);
return ev_press ? 0 : POLLIN | POLLRDNORM;
}

poll测试程序:int poll(struct pollfd *fds,nfds_t nfds, int timeout);//返回值是有多少个可读事件

int main(int argc, char **argv)
{
int fd;
unsigned char key_val;
int ret;
struct pollfd fds[1];
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}

fds[0].fd     = fd;
fds[0].events = POLLIN;
while (1)
{
ret = poll(fds, 1, 5000);
if (ret == 0)
{
printf("time out\n");
}
else
{
read(fd, &key_val, 1);
printf("key_val = 0x%x\n", key_val);
}
}
return 0;
}

poll驱动机制分析:Poll函数会调用到fds中对应的poll驱动函数,把进程加入到该驱动定义的一个等待队列中,然后判断返回值。如果不为0,直接返回;如果为0,进程就会休眠timeout这么长的时间;然后再来判断驱动函数poll的返回值,如果有,返回非0,如果没有,则返回0.  
系统调用过程:
sys_poll
  do_sys_poll(ufds, nfds, to);
    poll_initwait(&table);
      table->pt->proc = __pollwait;
    do_poll(nfds, head, &table, end_time);
        poll_table* pt = &wait->pt;//table->pt->proc = __pollwait;
for (;;) {
struct poll_list *walk;
for (walk = list; walk != NULL; walk = walk->next) {
struct pollfd * pfd, * pfd_end;
pfd = walk->entries;
pfd_end = pfd + walk->len;
for (; pfd != pfd_end; pfd++) {
if (do_pollfd(pfd, pt)) { //调用驱动的poll函数-->调用poll_wait把进程放入等待队列,判断poll函数的返回值
count++;
pt = NULL;
}
}
}
/*
* All waiters have already been registered, so don't provide
* a poll_table to them on the next loop iteration.
*/
pt = NULL;
if (!count) {
count = wait->error;
if (signal_pending(current))
count = -EINTR;
}
if (count || timed_out) //退出条件:超时或者发生count非0
break;
/*
* If this is the first loop and we have a timeout
* given, then we convert to ktime_t and set the to
* pointer to the expiry value.
*/
if (end_time && !to) {
expire = timespec_to_ktime(*end_time);
to = &expire;
}
if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))  //休眠
timed_out = 1;
}

四、异步通知机制

异步通知的意思就是,一旦设备就绪,则主动通知应用程序,应用程序 根本就不需要查询设备状态,类似于中断的概念,一个进程收到一个信号与处理器收到一个中断请求可以说是一样的。信号是异步的,一个进程不必通过任何操作来等待信号的到达。

static irqreturn_t buttons_irq(int irq, void *dev_id)
{
kill_fasync (&button_async, SIGIO, POLL_IN); //在适当的位置调用这句话进行通知

return IRQ_RETVAL(IRQ_HANDLED);
}

static int drv_fasync (int fd, struct file *filp, int on)
{
return fasync_helper (fd, filp, on, &button_async);
}

static struct file_operations sencod_drv_fops = {
    .owner   =  THIS_MODULE,   
    .open    =  drv_open,     
    .fasync  =  drv_fasync,
};

app:
void my_signal_fun(int signum)
{
printf("fsnc app\n");
}

int main(int argc, char **argv)
{
unsigned char key_val;
int ret;
int Oflags;
signal(SIGIO, my_signal_fun);
fd = open("/dev/buttons", O_RDWR);
if (fd < 0)
{
printf("can't open!\n");
}

fcntl(fd, F_SETOWN, getpid());

Oflags = fcntl(fd, F_GETFL); 

fcntl(fd, F_SETFL, Oflags | FASYNC);
while (1)
{
sleep(1000);
}
return 0;
}

五、互斥与信号量

六、定时器防抖


抱歉!评论已关闭.