<acronym id="s8ci2"><small id="s8ci2"></small></acronym>
<rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
<acronym id="s8ci2"></acronym>
<acronym id="s8ci2"><center id="s8ci2"></center></acronym>
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模型進行按鍵讀?。?strong>阻塞式I/O和非用阻塞式I/O,通過實際的實驗,對比兩者方式的實際運行效果與主要區別,并查看CPU的占用率,兩種方式的CPU使用率都幾乎為0。

聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • 嵌入式
    +關注

    關注

    4994

    文章

    18380

    瀏覽量

    290383
  • 驅動
    +關注

    關注

    11

    文章

    1723

    瀏覽量

    84431
  • Linux
    +關注

    關注

    87

    文章

    11011

    瀏覽量

    206915
  • i.MX6
    +關注

    關注

    1

    文章

    36

    瀏覽量

    16233
收藏 人收藏

    評論

    相關推薦

    i.MX6ULL 驅動開發7—按鍵輸入捕獲與GPIO輸入配置與高低電平讀取

    本篇主要介紹了i.MX6ULL按鍵檢測的使用,主要的知識點是設備樹的修改,以及GPIO的輸入配置與高低電平的讀取。
    的頭像 發表于 05-24 09:11 ?5493次閱讀
    <b class='flag-5'>i.MX6ULL</b> <b class='flag-5'>驅動</b><b class='flag-5'>開發</b>7—<b class='flag-5'>按鍵</b>輸入捕獲與GPIO輸入配置與高低電平讀取

    Linux設備驅動中的阻塞阻塞I/O

    :Wake_up_interruptible(&amp;q);輪詢操作輪詢的概念與作用使用阻塞I/O的應用程序通常會使用select()和poll()系統調用查詢是否可對設備進行無
    發表于 02-21 10:53

    阻塞阻塞I/O

    :Wake_up_interruptible(&amp;q); 輪詢操作 輪詢的概念與作用使用阻塞I/O的應用程序通常會使用select()和poll()系統調用查詢是否可對設備進
    發表于 07-09 08:19

    i.MX6UL/i.MX6ULL開發常見問題】單獨編譯內核,uboot生成很多文件,具體用哪一個?

    i.MX6UL/i.MX6ULL開發常見問題》基于米爾電子 i.MX6UL/i.MX6ULL產品(V.
    發表于 07-01 17:50

    迅為I.MX6ULL終結者開發板支持JTAG調試

    的硬件環境1、迅為-i.MX6ULL終結者開發板一塊2、JLNK V9下載器一個3、JLINK V9轉換板一個(2.54mm轉2.0mm)1.2 搭建開發環境1.2.1 安裝JLINK V9
    發表于 05-06 14:09

    I.MX6ULL終結者開發板裸機仿真jlink調試

    I.MX6ULL‘終結者’開發板預留了JTAG仿真接口,并給出了開發文檔,可以實現在JLINK仿真器條件下的單步跟蹤、斷點調試等功能,使得開發研究i
    發表于 07-07 10:56

    迅為i.MX6ULL開發板資料下載,讓Linux學習更輕松

    ` 本帖最后由 平常心0 于 2020-9-23 18:09 編輯 迅為電子的 i.MX6ULL 核心板分為工業級和商業級兩種。提供的接口是郵票孔方式。開發板資料下載鏈接:鏈接:https
    發表于 09-23 18:07

    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

    阻塞如何讀取矩陣按鍵?

    阻塞如何讀取矩陣按鍵?
    發表于 01-17 08:17

    關于i.MX6ULL配置GPIO

    處理器,它的GPIO外設應該如何配置呢?今天小編就將通過飛凌嵌入的OKMX6ULL-S開發板來為大家詳細介紹。一、i.MX6ULL處理器的GPIO配置
    發表于 08-05 10:37

    I.MX6ULL無法枚舉USB2514是為什么?

    你好目前,I.MX6ULL開發存在一些問題。其中之一是OTG USB2無法正常掛載USB2514,無法正確枚舉下游設備,只顯示設備id。usb設計要注意什么。
    發表于 04-03 06:55

    I.MX6ULL UART傳輸問題求解

    I.MX6ULL UART傳輸問題
    發表于 04-21 08:09

    線程邊界路由器i.mx6ull otbr-agent處于活動狀態是什么原因造成的?怎么解決?

    我關注 https://github.com/nxp-imx/meta-matter “如何在目標上設置 OpenThread 邊界路由器”以在 i.mx6ull(定制板, EVK)上運行線程邊界
    發表于 05-31 06:37
    亚洲欧美日韩精品久久_久久精品AⅤ无码中文_日本中文字幕有码在线播放_亚洲视频高清不卡在线观看
    <acronym id="s8ci2"><small id="s8ci2"></small></acronym>
    <rt id="s8ci2"></rt><rt id="s8ci2"><optgroup id="s8ci2"></optgroup></rt>
    <acronym id="s8ci2"></acronym>
    <acronym id="s8ci2"><center id="s8ci2"></center></acronym>