0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【i.MX6ULL】驱动开发10—阻塞&非阻塞式按键检测

码农爱学习 来源:码农爱学习 作者:码农爱学习 2022-05-27 09:08 次阅读

上篇文章:介绍了linux中的五种I/O模型,本篇,就来使用阻塞式I/O非用阻塞式I/O两种方式进行按键的读取实验,并对比之前使用输入捕获和中断法检测的按键程序,查看CPU的使用率是否降低。

1 阻塞I/O方式的按键检测

1.1 阻塞I/O之等待队列

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。

等待队列头使用结构体wait_queue_head_t 表示:

struct __wait_queue_head { 
	spinlock_t       lock; 
	struct list_head task_list; 
}; 

typedef struct __wait_queue_head wait_queue_head_t; 

使用 init_waitqueue_head 函数初始化等待队列头:

/**
 * q: 要初始化的等待队列头
 * return: 无
 */
void init_waitqueue_head(wait_queue_head_t *q) 

当设备不可用的时, 将这些进程对应的等待队列项(wait_queue_t )添加到等待队列里面:

struct __wait_queue { 
	unsigned int      flags; 
    void              *private; 
    wait_queue_func_t func; 
    struct list_head  task_list;
}; 

typedef struct __wait_queue wait_queue_t;

使用宏 DECLARE_WAITQUEUE 定义并初始化一个等待队列项:

DECLARE_WAITQUEUE(name, tsk)

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中:

/**
 * q: 要加入的等待队列头
 * wait:要加入的等待队列项
 * return: 无
 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) 

当设备可以访问以后再将进程对应的等待队列项从等待队列头中删除即可:

/**
 * q: 要删除的等待队列头
 * wait:要删除的等待队列项
 * return: 无
 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait) 

当设备可以使用的时候就要唤醒进入休眠态的进程:

void wake_up(wait_queue_head_t *q) 
void wake_up_interruptible(wait_queue_head_t *q) 

1.2 阻塞I/O程序编写

这里仅介绍与之前按键程序的主要区别。

1.2.1驱动程序

阻塞读取逻辑如下,首先要定义一个等待队列,当按键没有按下时,就要阻塞等待了(将等待队列添加到等待队列头),然后进行行一次任务切换,交出CPU的使用权。等待有按键按下时,会有信号唤醒该等待,并将按键值返回给应用层的程序。

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;
    
    /* 定义一个等待队列 <-------------------------- */
    DECLARE_WAITQUEUE(wait, current);
    
    /* 没有按键按下 <------------------------------ */
    if(atomic_read(&dev->releasekey) == 0)
    {
        /* 将等待队列添加到等待队列头 <------------ */
        add_wait_queue(&dev->r_wait, &wait);
        
        /* 设置任务状态 <-------------------------- */
        __set_current_state(TASK_INTERRUPTIBLE);
        
        /* 进行一次任务切换 <---------------------- */
        schedule();
        
        /* 判断是否为信号引起的唤醒 <-------------- */
        if(signal_pending(current))
        {
            ret = -ERESTARTSYS;
            goto wait_error;
        }
        
        /* 将当前任务设置为运行状态 <-------------- */
        __set_current_state(TASK_RUNNING);
        
        /* 将对应的队列项从等待队列头删除 <-------- */
        remove_wait_queue(&dev->r_wait, &wait);
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* 有按键按下 */
    if (releasekey)
    {
        //printk("releasekey!\r\n");
        if (keyvalue & 0x80)
        {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    }
    else
    {
        goto data_error;
    }
    return 0;

wait_error:
    set_current_state(TASK_RUNNING);           /* 设置任务为运行态 */
    remove_wait_queue(&dev->r_wait, &wait);    /* 将等待队列移除 */
    return ret;
    
data_error:
    return -EINVAL;
}

按键的定时器去抖逻辑中的,读取到按键后,触发唤醒,这里以其中的一个按键为例,其逻辑如下:

void timer1_function(unsigned long arg)
{
    unsigned char value;
    struct irq_keydesc *keydesc;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    keydesc = &dev->irqkeydesc[0];

    value = gpio_get_value(keydesc->gpio); /* 读取IO值 */
    if(value == 1) /* 按下按键 */
    {
        printk("get key1: high\r\n");
        atomic_set(&dev->keyvalue, keydesc->value);
    }
    else /* 按键松开 */
    {
        printk("key1 release\r\n");
        atomic_set(&dev->keyvalue, 0x80 | keydesc->value);
        atomic_set(&dev->releasekey, 1); /* 标记松开按键,即完成一次完整的按键过程 */            
    }
    
    /* 唤醒进程 */
    if(atomic_read(&dev->releasekey))
    {
        wake_up_interruptible(&dev->r_wait);
    }
}

1.2.2 应用程序

应用程序不需要修改,还使用之前的轮询读取的方式,为了在测试时看出阻塞与非阻塞方式的区别,在read函数前后添加打印,如果程序运行正常,会先打印read前一句的打印,直到有按键按下后,read函数才被接触阻塞,read后一句的打印才会打印出。

/* 循环读取按键值数据! */
while(1)
{
    printf("[APP] read begin...\r\n");
    read(fd, &keyvalue, sizeof(keyvalue));
    printf("[APP] read end\r\n");
    if (keyvalue == KEY1VALUE)
    {
        printf("[APP] KEY1 Press, value = %#X\r\n", keyvalue);
    }
    else if (keyvalue == KEY2VALUE)
    {
        printf("[APP] KEY2 Press, value = %#X\r\n", keyvalue);
    }
}

1.2 实验

和之前一样,使用Makefile编译驱动程序和应用程序,并复制到nfs根文件系统中。

开始测试,按如下图,当没有按键按下时,应用程序被阻塞:

pYYBAGKPlZmAX1imAADhTzUjZGc198.png

按键程序在后台运行,此时使用top指令开查看CPU的使用率,可以发现阻塞式按键驱动这种方式,CPU的暂用率几乎为0,虽然按键应用程序中仍实现循环读取的方式,但因平时读取不到按键值,按键应用程序被阻塞住了,CPU的使用权被让出,自然CPU的使用率就降下来了。

poYBAGKPlaGAGg-HAADTTCGipSQ073.png

2 非阻塞I/O方式的按键检测

按键应用程序以非阻塞的方式读取,按键驱动程序也要以非阻塞的方式立即返回。应用程序可以通过select、poll或epoll函数来 查询设备是否可以操作,驱动程序使用poll函数。

2.1 非阻塞I/O之select/poll

select函数原型:

/**
 * nfs: 所要监视的这三类文件描述集合中,最大文件描述符加1
 * readfds: 用于监视指定描述符集的读变化
 * writefds: 用于监视文件是否可以进行写操作
 * exceptfds: 用于监视文件的异常
 * timeout: 超时时间
 * return: 0 超时发生, -1 发生错误, 其他值 可以进行操作的文件描述符个数 
 */
int select(int    nfds,  
           fd_set *readfds,  
           fd_set *writefds, 
           fd_set *exceptfds,  
           struct timeval *timeout) 

其中超时时间使用结构体timeval表示:

struct timeval { 
   long tv_sec;  /* 秒   */ 
   long tv_usec; /* 微妙 */  
}; 

当timeout为NULL的时候就表示无限等待。

poll函数原型:

/**
 * fds: 要监视的文件描述符集合以及要监视的事件,为一个数组
 * nfds: 监视的文件描述符数量
 * timeout: 超时时间,单位为 ms
 * return: 0 超时发生, -1 发生错误, 其他值 可以进行操作的文件描述符个数 
 */
int poll(struct pollfd *fds,  
         nfds_t nfds,  
         nt     timeout) 

2.2 非阻塞I/O程序编写

2.2.1 驱动程序

poll函数处理部分:

unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* 将等待队列头添加到poll_table中 */
    poll_wait(filp, &dev->r_wait, wait);
    
    /* 按键按下 */
    if(atomic_read(&dev->releasekey))
    {
        mask = POLLIN | POLLRDNORM;            /* 返回PLLIN */
    }
    return mask;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .poll = imx6uirq_poll,
};

read函数处理部分:

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char keyvalue = 0;
    unsigned char releasekey = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

    /* 非阻塞访问 */
    if (filp->f_flags & O_NONBLOCK)
    {
        /* 没有按键按下,返回-EAGAIN */
        if(atomic_read(&dev->releasekey) == 0)
        {
            return -EAGAIN;
        }
    }
    /* 阻塞访问 */
    else
    {
        /* 加入等待队列,等待被唤醒,也就是有按键按下 */
        ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
        if (ret)
        {
            goto wait_error;
        }
    }

    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);

    /* 有按键按下 */
    if (releasekey)
    {
        //printk("releasekey!\r\n");
        if (keyvalue & 0x80)
        {
            keyvalue &= ~0x80;
            ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
        }
        else
        {
            goto data_error;
        }
        atomic_set(&dev->releasekey, 0); /* 按下标志清零 */
    }
    else
    {
        goto data_error;
    }
    return 0;

wait_error:
    return ret;
data_error:
    return -EINVAL;
}

2.2.2 应用程序

2.2.2.1 poll方式读取

注意open函数的参数是O_NONBLOCK,即非阻塞访问,并且为了在测试时看出阻塞读取与非阻塞读取的区别,在poll函数前后添加打印,如果程序正常运行,poll函数则不会被阻塞,500ms超时未读取到按键值后会再次循环读取,实际效果就是可以看打一直有打印输出。

    filename = argv[1];
    fd = open(filename, O_RDWR | O_NONBLOCK);    /* 非阻塞访问 */
    if (fd < 0)
    {
        printf("[APP] Can't open file %s\r\n", filename);
        return -1;
    }

    /* 构造结构体 */
    fds.fd = fd;
    fds.events = POLLIN;
    while(1)
    {
        printf("[APP] poll begin... \r\n", data);
        ret = poll(&fds, 1, 500);
        printf("[APP] poll end \r\n", data);
        /* 数据有效 */
        if (ret > 0)
        {
            ret = read(fd, &data, sizeof(data));
            if(ret < 0)
            {
                /* 读取错误 */
            }
            else
            {
                if(data)
                {
                    printf("[APP] key value = %d \r\n", data);
                }
            }     
        }
        /* 超时 */
        else if (ret == 0)
        {
            /* 用户自定义超时处理 */
        }
        /* 错误 */
        else
        {
            /* 用户自定义错误处理 */
        }
    }

2.2.2.2 select方式读取

select方式读取与poll方式类似,都是非阻塞读取,程序类似:

while(1)
{
    FD_ZERO(&readfds);
    FD_SET(fd, &readfds);
    /* 构造超时时间 */
    timeout.tv_sec = 0;
    timeout.tv_usec = 500000; /* 500ms */
    ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
    switch (ret)
    {
            /* 超时 */
        case 0:
            /* 用户自定义超时处理 */
            break;
            /* 错误 */
        case -1:
            /* 用户自定义错误处理 */
            break;
            /* 可以读取数据 */
        default:
            if(FD_ISSET(fd, &readfds))
            {
                ret = read(fd, &data, sizeof(data));
                if (ret < 0)
                {
                    /* 读取错误 */
                }
                else
                {
                    if (data)
                    {
                        printf("key value=%d\r\n", data);
                    }
                }
            }
            break;
    }
}

2.3 实验

2.3.1 poll方式读取

和之前一样,使用Makefile编译驱动程序和应用程序,并复制到nfs根文件系统中。

开始测试,按如下图,当没有按键按下时,应用程序也没有被阻塞,从不断的打印就可以看出应用程序在循环运行。当有按键按下时,能够读取到对应的按键值。

poYBAGKPla2AK2AvAAFXYivs3Nw253.png

按键程序在后台运行,此时使用top指令开查看CPU的使用率,可以发现非阻塞式按键驱动这种方式,CPU的暂用率也几乎为0,虽然按键应用程序中仍实现循环读取的方式,但poll函数有500ms的超时设置,在超时等待的时间里,CPU的使用权也是被让出,所以CPU的使用率也降下来了。

pYYBAGKPlbaAI8KoAADgwZ96jiI026.png

2.3.2 select方式读取

select方式读取与poll方式读取的效果一样。

使用ps指令查看poll方式的按键进行号,使用kill杀带该进程,再运行select方式的按键应用程序:

pYYBAGKPlb2APMsmAAFCfrAxqzU216.png

select非阻塞读取的方式,CPU的暂用率也几乎为0:

poYBAGKPlcOATnPFAADSWuc1oCw810.png

3 总结

本篇使用两种I/O模型进行按键读取:阻塞式I/O非用阻塞式I/O,通过实际的实验,对比两者方式的实际运行效果与主要区别,并查看CPU的占用率,两种方式的CPU使用率都几乎为0。

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 嵌入式
    +关注

    关注

    5063

    文章

    18990

    浏览量

    302500
  • 驱动
    +关注

    关注

    12

    文章

    1822

    浏览量

    85145
  • Linux
    +关注

    关注

    87

    文章

    11216

    浏览量

    208803
  • i.MX6
    +关注

    关注

    1

    文章

    37

    浏览量

    16273
收藏 人收藏

    评论

    相关推荐

    i.MX6ULL 驱动开发7—按键输入捕获与GPIO输入配置与高低电平读取

    本篇主要介绍了i.MX6ULL按键检测的使用,主要的知识点是设备树的修改,以及GPIO的输入配置与高低电平的读取。
    的头像 发表于 05-24 09:11 6105次阅读
    <b class='flag-5'>i.MX6ULL</b> <b class='flag-5'>驱动</b><b class='flag-5'>开发</b>7—<b class='flag-5'>按键</b>输入捕获与GPIO输入配置与高低电平读取

    i.MX6ULL嵌入Linux开发1-uboot移植初探

    本系列教程以i.MX6ULL处理器的ARM开发板为实验基础,学习记录嵌入Linux开发的各种知识与经验,主要内容包括嵌入Linux移植,
    的头像 发表于 03-07 08:57 3838次阅读
    <b class='flag-5'>i.MX6ULL</b>嵌入<b class='flag-5'>式</b>Linux<b class='flag-5'>开发</b>1-uboot移植初探

    使用i.MX6ULL开发板进行Linux根文件系统的完善

    上一篇推文讲了怎么移植根文件系统,并在i.MX6ULL开发板中运行起来,但是会出现一些提示,现在来进行根文件的完善。
    发表于 10-17 11:13 759次阅读

    移植NXP官方linux 5.4内核到i.MX6ULL开发

    本文描述移植NXP官方 linux 5.4 内核到i.MX6ULL开发板。
    发表于 12-19 11:10 1996次阅读

    i.MX6UL/i.MX6ULL开发常见问题】单独编译内核,uboot生成很多文件,具体用哪一个?

    i.MX6UL/i.MX6ULL开发常见问题》基于米尔电子 i.MX6UL/i.MX6ULL产品(V.
    发表于 07-01 17:50

    I.MX6ULL终结者开发板裸机仿真jlink调试

    I.MX6ULL‘终结者’开发板预留了JTAG仿真接口,并给出了开发文档,可以实现在JLINK仿真器条件下的单步跟踪、断点调试等功能,使得开发研究i
    发表于 07-07 10:56

    i.MX6ULL开发板硬件资源

    迅为i.MX6ULL 终结者开发板硬件资源非常丰富,几乎将 i.MX6ULL 芯片的所有资源都扩展引出到底板上了,底板提供了丰富的外设接口,开发板的尺寸是 190mm*125mm,充分
    发表于 12-29 06:18

    i.MX6ULL核心板资源

    操作系统镜像&amp;amp;烧写工具提供资料提供相关的 BSP 源代码、文件系统源代码其它默认配置i.MX6ULL、512MB DDR3、4GB EMMC可选配置i.MX6ULL、2
    发表于 07-12 17:50

    初识 i.MX6ULL 寄存器

    裸机开发_L1_汇编LED实验0. 本节目标1. 硬件层电路2. 初识 i.MX6ULL 寄存器2.1 i.MX6ULL 时钟控制寄存器2.2 i.MX6ULL IO复用寄存器2.3
    发表于 12-20 07:13

    《Linux设备驱动开发详解》第8章、Linux设备驱动中的阻塞阻塞IO

    《Linux设备驱动开发详解》第8章、Linux设备驱动中的阻塞阻塞IO
    发表于 10-27 11:35 9次下载
    《Linux设备<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>详解》第8章、Linux设备<b class='flag-5'>驱动</b>中的<b class='flag-5'>阻塞</b>与<b class='flag-5'>非</b><b class='flag-5'>阻塞</b>IO

    飞凌i.MX6ULL开发板的评测,再次进阶拥有更高的性价比

    处理器MCIMX6Y2开发设计,采用先进的ARMCortex-A7内核,运行速度高达800MHz。i.MX6ULL应用处理器包括一个集成的电源管理模块,降低了外接电源的复杂性,并简化了上电时序。
    发表于 10-27 11:55 1461次阅读
    飞凌<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>开发</b>板的评测,再次进阶拥有更高的性价比

    基于NXP i.MX6ULL处理器的FETMX6ULL-C核心板

    合作伙伴,飞凌不负美誉,基于i.MX6ULL匠心打造的FETMX6ULL-S核心板一经问世便好评不断,且已有数百家来自工业、医疗、电力、物联网等行业的用户采用此款核心板快速完成了整机产品的开发上市。
    发表于 04-11 15:05 1130次阅读
    基于NXP <b class='flag-5'>i.MX6ULL</b>处理器的FETMX<b class='flag-5'>6ULL</b>-C核心板

    i.MX6ULL驱动开发4——点亮LED(寄存器版)

    本篇主要介绍了如何通过操作寄存器来点亮i.MX6ULL开发板上的led,通过编写LED对应的驱动程序和应用程序,实现程序设计的分层。
    的头像 发表于 05-21 21:26 2930次阅读
    【<b class='flag-5'>i.MX6ULL</b>】<b class='flag-5'>驱动</b><b class='flag-5'>开发</b>4——点亮LED(寄存器版)

    【北京迅为】i.MX6ULL开发板移植 Debian 文件系统

    【北京迅为】i.MX6ULL开发板移植 Debian 文件系统
    的头像 发表于 02-10 15:34 1103次阅读
    【北京迅为】<b class='flag-5'>i.MX6ULL</b><b class='flag-5'>开发</b>板移植 Debian 文件系统

    基于i.MX6ULL的掉电检测设计与软件测试

    基于i.MX6ULL的掉电检测设计与软件测试基于i.MX6ULL平台设计实现掉电检测功能,首先选择一路IO,利用IO电平变化触发中断,在编写驱动
    的头像 发表于 11-09 10:40 801次阅读
    基于<b class='flag-5'>i.MX6ULL</b>的掉电<b class='flag-5'>检测</b>设计与软件测试