精品国产人成在线_亚洲高清无码在线观看_国产在线视频国产永久2021_国产AV综合第一页一个的一区免费影院黑人_最近中文字幕MV高清在线视频

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。

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

    關注

    5043

    文章

    18801

    瀏覽量

    298355
  • 驅動
    +關注

    關注

    12

    文章

    1785

    瀏覽量

    84890
  • Linux
    +關注

    關注

    87

    文章

    11122

    瀏覽量

    207877
  • i.MX6
    +關注

    關注

    1

    文章

    37

    瀏覽量

    16251
收藏 人收藏

    評論

    相關推薦

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

    本篇主要介紹了i.MX6ULL按鍵檢測的使用,主要的知識點是設備樹的修改,以及GPIO的輸入配置與高低電平的讀取。
    的頭像 發表于 05-24 09:11 ?5927次閱讀
    <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 ?3725次閱讀
    <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 ?705次閱讀

    移植NXP官方linux 5.4內核到i.MX6ULL開發

    本文描述移植NXP官方 linux 5.4 內核到i.MX6ULL開發板。
    發表于 12-19 11:10 ?1892次閱讀

    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 ?1374次閱讀
    飛凌<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 ?1054次閱讀
    基于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 ?2834次閱讀
    【<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 ?1008次閱讀
    【北京迅為】<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 ?677次閱讀
    基于<b class='flag-5'>i.MX6ULL</b>的掉電<b class='flag-5'>檢測</b>設計與軟件測試