Linux設備驅動--輪詢操作


注:本文是《Linux設備驅動開發詳解:基於最新的Linux 4.0內核 by 宋寶華 》一書學習的筆記,大部分內容為書籍中的內容。

書籍可直接在微信讀書中查看:Linux設備驅動開發詳解:基於最新的Linux4.0內核-宋寶華-微信讀書 (qq.com)

字符設備指那些必須以串行順序依次進行訪問的設備,如觸摸屏、磁帶驅動器、鼠標等。對於用戶而言,使用文件系統的操作接口open()、close()、read()、write()等進行訪問。

Linux設備驅動-輪詢操作

如果應用程序以非阻塞的方式訪問設備,設備驅動程序需要提供非阻塞的處理方式,也就是輪詢。

1 簡介

在用戶態程序中,可使用select()、poll()、epoll()系統調用來查詢是否可對設備進行無阻塞的訪問。通過select()和poll()實現的是I/O多路復用功能。

I/O 多路復用允許我們同時檢查多個文件描述符,看其中任意一個是否可執行I/O操作。我們可以在普通文件、終端、偽終端、管道、FIFO、套接字以及一些其他類型的字符型設備上使用 select()和 poll()來檢查文件描述符。這兩個系統調用都允許進程要么一直等待文件描述符成為就緒態,要么在調用中指定一個超時時間。

在內核中,設備驅動中的poll()會被用戶態的select()和poll()調用。

2 應用程序中的輪詢編程

2.1 select()函數

應用程序中使用最廣泛的系統調用是select()函數,原型為:

系統調用select()會一直阻塞,直到一個或多個文件描述符集合成為就緒態。

/* According to POSIX.1-2001 */
#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

/* 返回值:0:表示超時發生,但是沒有任何文件描述符可以進行操作;-1:發生錯誤;其他值:可以進行操作的文件描述符個數 */
int select(int nfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);

參數nfds、readfds、writefds、exceptfds指定了select()要檢查的文件描述符集合。

參數readfds:是用來檢測輸入是否就緒的文件描述符集合,也就是這些文件是否可以讀取,只要這些集合里有一個文件可以讀取,select就會返回一個大於0的值表示文件可以讀取,如果沒有 文件可以讀取。則會根據timeout參數來判斷是否超時。如果設置為NULL,表示不關心任何文件的讀變化。

參數writefds:是用來檢測輸出是否就緒的文件描述符集合,是否可以進行寫操作。

參數exceptfds:是用來檢測異常情況是否發生的文件描述符集合。

參數nfds:必須設為比3個文件描述符集合中所包含的最大文件描述符號還要大1。

所有關於文件描述符集合的操作都是通過四個宏來完成的:FD_ZERO(),FD_SET(),FD_CLR()以及FD_ISSET()。

void FD_CLR(int fd, fd_set *set);  	/* 將文件描述符fd從fdset所指向的集合中移除 */
int  FD_ISSET(int fd, fd_set *set);	/* 如果文件描述符fd是fdset所指向的集合中的成員,返回true。 */
void FD_SET(int fd, fd_set *set);	/* 將文件描述符fd添加到由fdset所指向的集合中 */
void FD_ZERO(fd_set *set);			/* 將fdset所指向的集合初始化為空 */

參數timeout:用來設定select()阻塞的時間上限,struct timeval數據結構定義如下:

struct timeval {
	int tv_sec;		/* 秒 */
	int tv_usec;	/* 微秒 */
};

timeout的兩個域都設置為0,此時select()不會阻塞,只是簡單的輪詢指定的文件描述符集合,看其中是否有就緒的文件描述符並立即返回。

select()多路復用如下:

image-20220130140213539

第一次進行讀寫時,若任何一個文件滿足讀寫要求,select()就直接返回;

第二次進行select()時,沒有文件滿足讀寫要求,select()的進程阻塞且睡眠。

select函數使用示例:

int main(void)
{
    int ret, fd;
    fd_set read_fs;
    struct timeval timeout;
    
    fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞訪問 */
    
    FD_ZERO(&read_fs);		/* 清除read_fs */
    FD_SET(fd, &read_fs);	/* 將fd添加到read_fs */
    
    timeout.tv_sec = 0;
    timeout.tv_usec = 500000; /* 500ms */
    
    ret = select(fd + 1, &read_fs, NULL, NULL, &timeout);
    switch (ret) {
        case 0:	/* 超時 */
            printf("timeout\r\n");
            break;
        case -1: /* 錯誤 */
            printf("error!\r\n");
            break;
        default: /* 可以讀取數據 */
            if (FD_ISSET(fd, &read_fs)) { /* 判斷fd是否為文件描述符 */
                /* 使用read函數讀取數據 */
            }
            break;
    }
    
    return 0;
}

2.2 poll()函數

select()函數能夠監視的文件描述符數量一般為1024個。poll函數沒有最大文件描述符限制。

poll()中提供一列文件描述符,並在每個文件描述符上標明感興趣的事件。

poll()函數的功能和select()相似,其函數原型為:

#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);

參數fds:列出需要poll()檢查的文件描述符,該參數為pollfd結構體數組,其定義為:

/* fd:要監視的文件描述符,fd如果無效則events監視事件也無效,並且revents返回0。
 * events:要監視的事件,可監視的事件類型為:
     POLLIN:有數據可以讀取
     POLLPRI:有緊急的數據需要讀取
     POLLOUT:可以寫數據
     POLLERR:指定的文件描述符發生錯誤
     POLLHUP:指定的文件描述符掛起
     POLLNVAL:無效的請求
     POLLRDNORM:等同於POLLIN
 * revents:返回參數,返回的事件。
*/

struct pollfd {
    int   fd;         /* file descriptor */
    short events;     /* requested events bit mask */
    short revents;    /* returned events bit mask */
};

參數nfds:指定了數組fds中元素的個數。

參數timeout :參數timeout 決定了poll()的阻塞行為,具體如下。

如果timeout等於−1,poll()會一直阻塞直到 fds 數組中列出的文件描述符有一個達到就緒態(定義在對應的 events字段中)或者捕獲到一個信號。

如果timeout等於 0,poll()不會阻,只是執行一次檢查看看哪個文件描述符處於就緒態。

如果timeout大於0,poll()至多阻塞timeout毫秒,直到 fds 列出的文件描述符中有一個達到就緒態,或者直到捕獲到一個信號為止。

poll()的返回值:作為函數的返回值,poll()會返回如下幾種情況中的一種。

返回−1:表示有錯誤發生。一種可能的錯誤是 EINTR,表示該調用被一個信號處理例程中斷。(如果被信號處理例程中斷,poll()絕不會自動恢復。)

返回0:表示該調用在任意一個文件描述符成為就緒態之前就超時了。

返回正整數:表示有1個或多個文件描述符處於就緒態了。返回值表示數組fds中擁有非零revents字段的pollfd結構體數量。

使用poll函數對設備進行非阻塞訪問的操作示例:

int main(void)
{
    int ret, fd;
    struct pollfd fds;
    
    fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞訪問 */
    
    /* 構造結構體 */
	fds.fd = fd;
    fds.events = POLLIN; /* 監視數據是否可以讀取 */
    
    ret = poll(&fds, 1, 500); /* 輪詢文件是否可操作,超時500ms */
    if (ret > 0) { /* 數據有效 */
        /* 讀取數據 */
    } else if (ret == 0) {  /* 超時 */
        ... ...
    } else if (ret < 0) {  /* 錯誤 */
        ... ...
    }
    
    return 0;
}

2.3 epoll()函數

當多路復用的文件數量龐大、I/O流量頻繁的時候,一般不太適合使用select()和poll(),此種情況下,select()和poll()的性能表現較差,宜使用epoll。與epoll相關的用戶編程接口:

epoll_create()用於創建一個epoll句柄,size指定要監聽多少個fd。

/* 返回值:成功返回epoll句柄,創建失敗返回-1。 */
#include <sys/epoll.h>
int epoll_create(int size);

當創建好epoll句柄后,它本身也會占用一個fd值,因此在使用完epoll后,需要調用close()關閉。

epoll_ctl()用於告訴內核監聽的事件類型:

/* 返回值:成功返回0;失敗返回-1,並且設置errno的值為相應的錯誤碼 */
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

參數epfd:為epoll_create()函數的返回值。

參數op:表示動作,包括:

EPOLL_CTL_ADD:注冊新的fd到epfd中。
EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件。
EPOLL_CTL_DEL:從epfd中刪除一個fd。

參數fd:需要監聽的文件描述符fd

參數event:是告訴內核需要監聽的事件類型。struct epoll_event結構如下:

struct epoll_event { 
    __uint32_t events; /* Epoll events */ 
    epoll_data_t data; /* User data variable */ 
}

events可以是以下幾個宏的“或”:

EPOLLIN:表示對應的文件描述符可以讀。

EPOLLOUT:表示對應的文件描述符可以寫。

EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示的是有socket帶外數據到來)。

EPOLLERR:表示對應的文件描述符發生錯誤。

EPOLLHUP:表示對應的文件描述符被掛斷。

EPOLLET:將epoll設為邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來說的。LT(Level Triggered)是缺省的工作方式,在LT情況下,內核告訴用戶一個fd是否就緒了,之后用戶可以對這個就緒的fd進行I/O操作。但是如果用戶不進行任何操作,該事件並不會丟失,而ET(Edge-Triggered)是高速工作方式,在這種模式下,當fd從未就緒變為就緒時,內核通過epoll告訴用戶,然后它會假設用戶知道fd已經就緒,並且不會再為那個fd發送更多的就緒通知。

EPOLLONESHOT:意味着一次性監聽,當監聽完這次事件之后,如果還需要繼續監聽這個fd的話,需要再次把這個fd加入到epoll隊列里。

epoll_wait()函數用於等待事件的產生:

#include <sys/epoll.h>

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

參數epfd:要等待的epoll。

參數events:輸出參數,用來從內核得到事件的集合。

參數maxevents:告訴內核本次最多收多少個事件,maxevents不能大於創建epoll_creat()時的size。

參數timeout:超時時間(以毫秒為單位,0:立即返回;-1:永久等待)

函數返回值:需要處理的事件數目,如果返回0,則表示已經超時。

3 設備驅動中的輪詢編程

3.1 poll()函數原型

功能說明: 使用select()/poll()/epoll()函數的應用程序允許一個進程來決定它是否可讀或者寫一個或多個文件而不阻塞。這些調用也可阻塞進程直到任何一個給定集合的文件描述符可用來讀或寫。因此, 它們常常用在必須使用多輸入輸出流的應用程序,而不必粘連在它們任何一個上。

支持任何一個這些調用都需要來自設備驅動的支持,這個支持(對所有 3 個調用)由驅動的 poll 方法調用。設備驅動中file_operations數據結構中的poll()函數原型:

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait);

參數filp:file結構體指針

參數wait:輪詢表指針,一般傳遞給poll_wait函數。

返回值:表示是否能對設備進行無阻塞讀、寫訪問的掩碼,描述哪個操作可馬上被實現。

通過以下幾個標志來指示可能的操作:

#define POLLIN		0x0001
#define POLLPRI		0x0002
#define POLLOUT		0x0004
#define POLLERR		0x0008
#define POLLHUP		0x0010
#define POLLRDNORM	0x0040
#define POLLRDBAND	0x0080
#define POLLWRNORM	0x0100
#define POLLWRBAND	0x0200

POLLIN:意味着設備可以無阻塞地讀,這個位必須設置。

POLLRDNORM:這個位必須設置,如果“正常”數據可用來讀,一個可讀的設備返回(POLLIN|POLLRDNORM)。

POLLRDBAND:這個位指示帶外數據可用來從設備中讀取,很少使用。

POLLPRI:高優先級數據(帶外)可不阻塞地讀取,這個位使 select 報告在文件上遇到一個異常情況,因為 selct 報告帶外數據作為一個異常情況。

POLLHUP:當讀這個設備的進程見到文件尾,驅動必須設置 POLLUP(hang-up)。 一個調用 select 的進程被告知設備是可讀的,如同selcet功能所規定的。

POLLERR:一個錯誤情況已在設備上發生,當調用 poll,設備被報告位可讀可寫,因為讀寫都返回一個錯誤碼而不阻塞。

POLLOUT:這個位在返回值中設置,如果設備可被寫入而不阻塞。

POLLWRNORM:這個位和 POLLOUT 有相同的含義,並且有時它確實是相同的數。一個可寫的設備返回( POLLOUT|POLLWRNORM)。

POLLWRBAND:如同 POLLRDBAND,這個位意思是帶有零優先級的數據可寫入設備。只有poll的數據報實現使用這個位,因為一個數據報看傳送帶外數據。

3.2 poll_wait()函數

poll_wait()函數用於向poll_table注冊等待隊列,該函數不會引起阻塞,只是將應用程序添加到poll_table中,函數的原型為:

void poll_wait(struct file * filp, wait_queue_head_t * queue, poll_table *wait);

poll_wait函數不會阻塞的等待某件事發生,僅把當前進程添加到wait參數指定的等待列表(poll_table)中,實際作用是讓喚醒參數queue所對應的等待隊列可以喚醒因select()而睡眠的進程。

3.3 poll()函數使用調用模板

poll()函數的典型模板:

static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{ 
    unsigned int mask = 0; 
    struct xxx_dev *dev = filp->private_data; /* 獲得設備結構體指針 */

	...
    poll_wait(filp, &dev->r_wait, wait); /* 加入讀等待隊列 */
    poll_wait(filp, &dev->w_wait, wait); /* 加入寫等待隊列*/

    if (...) /* 可讀 */ 
	    mask |= POLLIN | POLLRDNORM; /* 標示數據可獲得(對用戶可讀) */

    if (...) /* 可寫 */ 
    	mask |= POLLOUT | POLLWRNORM; /* 標示數據可寫入 */
    ... 
    return mask; 
}	

4 支持輪詢操作的globalfifo驅動

4.1 內核設備驅動globalfifo

在globalfifo的代碼中增加poll()函數。

完整代碼如下:

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>

/* 直接使用立即數當作命令不合理,暫定 */
#define MEM_CLEAR           0x1
#define GLOBALFIFO_MAJOR    230
#define GLOBALFIFO_SIZE     0x1000

static int globalfifo_major = GLOBALFIFO_MAJOR;
module_param(globalfifo_major, int, S_IRUGO);

/* 設備結構體 */
struct  globalfifo_dev {
    struct cdev cdev;
    unsigned int current_len;  /* 當前FIFO中有效數據的長度 */
    unsigned char mem[GLOBALFIFO_SIZE];
    struct mutex mutex;
    wait_queue_head_t r_wait;
    wait_queue_head_t w_wait;
};

struct globalfifo_dev *globalfifo_devp;

static int globalfifo_open(struct inode *inode, struct file *filp)
{
    /* 使用文件的私有數據作為獲取globalfifo_dev的實例指針 */
    filp->private_data = globalfifo_devp;
    return 0;
}

static int globalfifo_release(struct inode *inode, struct file *filp)
{
    return 0;
}

/**
 * 設備ioctl函數
 * @param[in] filp:文件結構體指針
 * @param[in] cmd: 命令,當前僅支持MEM_CLEAR
 * @param[in] arg: 命令參數
 * @return  若成功返回0,若出錯返回錯誤碼
 */
static long globalfifo_ioctl(struct file *filp, unsigned int cmd,
    unsigned long arg)
{
    struct globalfifo_dev *dev = filp->private_data;

    switch (cmd) {
    case MEM_CLEAR:
        mutex_lock(&dev->mutex);
        dev->current_len = 0;
        memset(dev->mem, 0, GLOBALFIFO_SIZE);
        mutex_unlock(&dev->mutex);
        printk(KERN_INFO "globalfifo is set to zero\n");
        break;
    
    default:
        return -EINVAL;
    }
    return 0;
}

/**
 * 查詢對一個或多個文件描述符的讀或寫是否會阻塞
 * @param[in] filp:文件結構體指針
 * @param[in] wait: 輪詢表指針
 * @return  返回位掩碼指示是否非阻塞的讀或寫是可能的
 */
static unsigned int globalfifo_poll(struct file *filp,
    struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    struct globalfifo_dev *dev = filp->private_data;

    mutex_lock(&dev->mutex);

    /* 調用select()而阻塞的進程可以被r_wait和w_wait喚醒 */
    poll_wait(filp, &dev->r_wait, wait);
    poll_wait(filp, &dev->w_wait, wait);

    if (dev->current_len != 0) {
        /* 設備可以無阻塞的讀,正常數據可用來讀 */
        mask |= POLLIN | POLLRDNORM;
    }

    if (dev->current_len != GLOBALFIFO_SIZE) {
        /* 設備可以無阻塞的寫 */
        mask |= POLLOUT | POLLWRNORM;
    }

    mutex_unlock(&dev->mutex);
    return mask;
}


/**
 * 讀設備
 * @param[in] filp:文件結構體指針
 * @param[out] buf: 用戶空間內存地址,不能在內核中直接讀寫
 * @param[in] size: 讀取的字節數
 * @param[in/out] ppos: 讀的位置相當於文件頭的偏移
 * @return  若成功返回實際讀的字節數,若出錯返回錯誤碼
 */
static ssize_t globalfifo_read(struct file *filp,
    char __user *buf, size_t size, loff_t *ppos)
{
    int ret = 0;
    unsigned long count = size;
    struct globalfifo_dev *dev = filp->private_data;

    DECLARE_WAITQUEUE(wait, current);

    mutex_lock(&dev->mutex);
    add_wait_queue(&dev->r_wait, &wait);

    while (dev->current_len == 0) {
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }

        __set_current_state(TASK_INTERRUPTIBLE);
        mutex_unlock(&dev->mutex);

        schedule();
        if (signal_pending(current)) {
            ret = -ERESTARTSYS;
            goto out2;
        }
        
        mutex_lock(&dev->mutex);
    }

    if (count > dev->current_len)
        count = dev->current_len;

    /* 內核空間到用戶空間緩存區的復制 */
    if (copy_to_user(buf, dev->mem, count)) {
        ret = -EFAULT;
        goto out;
    } else {
        memcpy(dev->mem, dev->mem + count, dev->current_len - count);
        dev->current_len -= count;
        printk(KERN_INFO "read %lu bytes(s) from %u\n", count, dev->current_len);
        wake_up_interruptible(&dev->w_wait);
        ret = count;
    }

out:
    mutex_unlock(&dev->mutex);

out2:
    remove_wait_queue(&dev->r_wait, &wait);
    set_current_state(TASK_RUNNING);
    return ret;
}

/**
 * 寫設備
 * @param[in] filp:文件結構體指針
 * @param[in] buf: 用戶空間內存地址,不能在內核中直接讀寫
 * @param[in] size: 寫入的字節數
 * @param[in/out] ppos: 寫的位置相當於文件頭的偏移
 * @return  若成功返回實際寫的字節數,若出錯返回錯誤碼
 */
static ssize_t globalfifo_write(struct file *filp,
    const char __user *buf, size_t size, loff_t *ppos)
{
    int ret = 0;
    unsigned long count = size;
    struct globalfifo_dev *dev = filp->private_data;

    DECLARE_WAITQUEUE(wait, current);

    mutex_lock(&dev->mutex);
    add_wait_queue(&dev->w_wait, &wait);

    while (dev->current_len == GLOBALFIFO_SIZE) {
        if (filp->f_flags & O_NONBLOCK) {
            ret = -EAGAIN;
            goto out;
        }
        __set_current_state(TASK_INTERRUPTIBLE);

        mutex_unlock(&dev->mutex);

        schedule();
        if (signal_pending(current)) {
            ret = -ERESTARTSYS;
            goto out2;
        }
        mutex_lock(&dev->mutex);
    }

    if (count > GLOBALFIFO_SIZE - dev->current_len)
        count = GLOBALFIFO_SIZE - dev->current_len;

    /* 用戶空間緩存區到內核空間緩存區的復制 */
    if (copy_from_user(dev->mem + dev->current_len, buf, count)) {
        ret = -EFAULT;
        goto out;
    } else {
        dev->current_len += count;
        printk(KERN_INFO "written %lu bytes(s) from %u\n", count, dev->current_len);
        wake_up_interruptible(&dev->r_wait);
        ret = count;
    }

out:
    mutex_unlock(&dev->mutex);

out2:
    remove_wait_queue(&dev->w_wait, &wait);
    set_current_state(TASK_RUNNING);
    return ret;
}

/**
 * 文件偏移設置
 * @param[in] filp:文件結構體指針
 * @param[in] offset: 偏移值大小
 * @param[in] orig: 起始偏移位置
 * @return  若成功返回文件當前位置,若出錯返回錯誤碼
 */
static loff_t globalfifo_llseek(struct file *filp, loff_t offset, int orig)
{
    loff_t ret = 0;
    switch (orig) {
    case 0:  /* 從文件頭位置設置偏移 */
        if (offset < 0) {
            ret = -EINVAL;
            break;
        }
        if ((unsigned int)offset > GLOBALFIFO_SIZE) {
            ret = -EINVAL;
            break;
        }
        filp->f_pos = (unsigned int)offset;
        ret = filp->f_pos;
        break;
    case 1:  /* 從當前位置設置偏移 */
        if ((filp->f_pos + offset) > GLOBALFIFO_SIZE) {
            ret = -EINVAL;
            break;
        }
        if ((filp->f_pos + offset) < 0) {
            ret = -EINVAL;
            break;
        }
        filp->f_pos += offset;
        ret = filp->f_pos;
        break;
    
    default:
        ret = -EINVAL;
        break;;
    }
    return ret;
}

static const struct file_operations globalfifo_fops = {
	.owner = THIS_MODULE,
	.llseek = globalfifo_llseek,
	.read = globalfifo_read,
	.write = globalfifo_write,
	.unlocked_ioctl = globalfifo_ioctl,
	.open = globalfifo_open,
	.release = globalfifo_release,
    .poll = globalfifo_poll,
};

static void globalfifo_setup_cdev(struct globalfifo_dev *dev, int index)
{
    int err, devno = MKDEV(globalfifo_major, index);

    /* 初始化cdev */
    cdev_init(&dev->cdev, &globalfifo_fops);
    dev->cdev.owner = THIS_MODULE;
    /* 注冊設備 */
    err = cdev_add(&dev->cdev, devno, 1);
    if (err)
        printk(KERN_NOTICE "Error %d adding globalfifo%d", err, index);
}

/* 驅動模塊加載函數 */
static int __init globalfifo_init(void)
{
    int ret;
    dev_t devno = MKDEV(globalfifo_major, 0);

    /* 獲取設備號 */
    if (globalfifo_major)
        ret = register_chrdev_region(devno, 1, "globalfifo");
    else {
        ret = alloc_chrdev_region(&devno, 0, 1, "globalfifo");
        globalfifo_major = MAJOR(devno);
    }
    
    if (ret < 0)
        return ret;
    
    /* 申請內存 */
    globalfifo_devp = kzalloc(sizeof(struct globalfifo_dev), GFP_KERNEL);
    if (!globalfifo_devp) {
        ret = -ENOMEM;
        goto fail_malloc;
    }
    globalfifo_setup_cdev(globalfifo_devp, 0);

    mutex_init(&globalfifo_devp->mutex);

    init_waitqueue_head(&globalfifo_devp->r_wait);
    init_waitqueue_head(&globalfifo_devp->w_wait);

    return 0;

fail_malloc:
    unregister_chrdev_region(devno, 1);
    return ret;
}
module_init(globalfifo_init);

/* 驅動模塊卸載函數 */
static void __exit globalfifo_exit(void)
{
    cdev_del(&globalfifo_devp->cdev);
    kfree(globalfifo_devp);
    /* 釋放設備號 */
    unregister_chrdev_region(MKDEV(globalfifo_major, 0), 1);
}
module_exit(globalfifo_exit);

MODULE_AUTHOR("MrLayfolk");
MODULE_LICENSE("GPL v2");

Makefile:

KVERS = $(shell uname -r)

# Kernel modules
obj-m += globalfifo_poll.o

# Specify flags for the module compilation.
#EXTRA_CFLAGS=-g -O0

build: kernel_modules

kernel_modules:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules

clean:
	make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean

4.2 用戶空間驗證globalfifo設備輪詢

在用戶空間編寫一個應用程序調用select()來監控globalfifo的可讀寫狀態。

完整代碼如下:

#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

#define FIFO_CLEAR  0x1
#define BUFFER_LEN  20

int main(void)
{
    int fd, num;
    char rd_ch[BUFFER_LEN];
    fd_set rfds, wfds;  /* 讀/寫文件描述符集 */

    /* 以非阻塞方式打開設備文件 */
    fd = open("/dev/globalfifo", O_RDONLY | O_NONBLOCK);
    if (fd != -1) {
        /* FIFO清0 */
        if (ioctl(fd, FIFO_CLEAR) < 0)
            printf("ioctl command failed!\n");

        while (1) {
            sleep(2);
            
            FD_ZERO(&rfds);  //將rfds所指向的集合初始化為空
            FD_ZERO(&wfds);  //將wfds所指向的集合初始化為空
            FD_SET(fd, &rfds);  //將文件描述符fd添加到由rfds所指向的集合
            FD_SET(fd, &wfds);  //將文件描述符fd添加到由wfds所指向的集合

            select(fd + 1, &rfds, &wfds, NULL, NULL);
            /* 數據可獲得 */
            if (FD_ISSET(fd, &rfds))
                printf("Poll monitor: can be read!\n");
            /* 數據可寫入 */
            if (FD_ISSET(fd, &wfds))
                printf("Poll monitor: can be written!\n");
        }
    } else {
        printf("Device open failure\n");
    }

    return 0;
}

4.3 編譯測試

編譯設備驅動程序並加載ko,然后創建一個字符設備節點:

$ make
$ insmod globalfifo_poll.ko 
$ mknod /dev/globalfifo c 230 0 

編譯用戶態應用程序,並且運行:

$ gcc app_poll.c 
$ ./a.out 
Poll monitor: can be written!
Poll monitor: can be written!
Poll monitor: can be written!

剛開始運行時,設備只能進行寫操作,然后讓設備空間寫一些字符,設備變為可讀可寫,然后讀取設備空間字符,設備變為只能進行寫操作。

$ ./a.out 
Poll monitor: can be written!
Poll monitor: can be written!
Poll monitor: can be written!
$ echo "hello" > /dev/globalfifo 
Poll monitor: can be written!
Poll monitor: can be read!
Poll monitor: can be written!
Poll monitor: can be read!
$ cat /dev/globalfifo 
hello
Poll monitor: can be written!
Poll monitor: can be written!

5 阻塞IO實驗

在按鍵實驗的基礎上進行改進。如果采用之前循環的調用read函數讀取按鍵值,CPU使用率很高,這是不合適的。

image-20220522213502541

正確的方式是:沒有有效按鍵事件發生的時候,應用程序應該處於休眠狀態,當有按鍵事件發生時,才喚醒運行。

5.1 驅動程序

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>

#define KEY_DEVICE_CNT     1    /* 設備個數 */
#define KEY_NAME    "key_block" /*設備名字*/

/* 定義鍵值 */
#define KEY0_VALUE      0x01    /* KEY0按鍵值 */
#define INVALID_KEY     0xFF    /* 無效鍵值 */

#define KEY_NUM         1       /* 按鍵數量 */

/* 中斷IO描述結構體 */
struct irq_key_desc {
    int gpio;               /* GPIO */
    int irq_num;            /* 中斷號 */
    unsigned char value;    /* 按鍵對應的鍵值 */
    char name[10];          /* 名字 */
    irqreturn_t(*handler)(int, void *); /* 中斷處理函數 */
};

/* KEY設備結構體 */
struct chrdev_key {
    dev_t devid;            /* 設備號 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 類 */
    struct device *device;  /* 設備 */
    int major;              /* 主設備號 */
    int minor;              /* 次設備號 */
    struct device_node *nd; /* 設備節點 */
    atomic_t key_value;     /* 有效的按鍵值 */
    atomic_t release_key;   /* 標記一次完成的按鍵是否完成 */
    struct timer_list timer;    /* 定時器 */
    struct irq_key_desc irq_key[KEY_NUM]; /* 按鍵描述 */
    unsigned char cur_key_num;  /* 當前的按鍵號 */
    wait_queue_head_t r_wait;   /* 讀等待隊列頭 */
};

struct chrdev_key chrdev_key;

/* 中斷服務函數:開啟定時器,延時10ms,定時器用於消抖
 * 參數irq:中斷號
 * 參數dev_id:設備結構
 * 返回值:中斷處理結果
 */
irqreturn_t key0_irq_handler(int irq, void *dev_id)
{
    struct chrdev_key *dev = (struct chrdev_key *)dev_id;

    dev->cur_key_num = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定時器服務函數:消除按鍵抖動,定時器到了后再次讀取按鍵值,如果按鍵處於按下則表示按鍵有效
 * arg:設備結構體變量
*/
void timer_func(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_key_desc *key_desc = NULL;
    struct chrdev_key *dev = (struct chrdev_key *)arg;

    num = dev->cur_key_num;
    key_desc = &dev->irq_key[num];
    value = gpio_get_value(key_desc->gpio);
    if (value == 0) { /* 按下按鍵 */
        atomic_set(&dev->key_value, key_desc->value);
    } else {    /* 按鍵松開 */
        atomic_set(&dev->key_value, 0x80 | key_desc->value);
        atomic_set(&dev->release_key, 1);   /* 標記松開按鍵 */
    }

    /* 喚醒進程 */
    if (atomic_read(&dev->release_key)) {  /* 完成一次按鍵過程 */
        wake_up_interruptible(&dev->r_wait);
    }
}

/* 按鍵IO初始化 */
static int key_gpio_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    chrdev_key.nd = of_find_node_by_path("/key");
    if (chrdev_key.nd == NULL) {
        printk("Cannot find device node!\r\n");
        return -EINVAL;
    }

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        chrdev_key.irq_key[i].gpio = of_get_named_gpio(chrdev_key.nd, "key-gpio", i);
        if (chrdev_key.irq_key[i].gpio < 0) {
            printk("Cannot get key%d!\r\n", i);
        }
    }

    /* 初始化key使用的IO,並且設置為中斷模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(chrdev_key.irq_key[i].name, 0x0, sizeof(chrdev_key.irq_key[i].name));
        sprintf(chrdev_key.irq_key[i].name, "KEY%d", i);
        gpio_request(chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].name);
        gpio_direction_input(chrdev_key.irq_key[i].gpio); /* 設置為輸入 */
        chrdev_key.irq_key[i].irq_num = irq_of_parse_and_map(chrdev_key.nd, i);
        printk("key%d: gpio:%d, irq_num:%d\r\n", i, chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].irq_num);
    }

    /* 申請中斷 */
    chrdev_key.irq_key[0].handler = key0_irq_handler;
    chrdev_key.irq_key[0].value = KEY0_VALUE;
    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(chrdev_key.irq_key[i].irq_num, chrdev_key.irq_key[i].handler,
            IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_SHARED,
            chrdev_key.irq_key[i].name, &chrdev_key);
        if (ret < 0) {
            printk("key%d irq %d request failed!\r\n", i, chrdev_key.irq_key[i].irq_num);
            return -EFAULT;
        }
        printk("key%d irq %d request successfully!\r\n", i, chrdev_key.irq_key[i].irq_num);
    }

    /* 創建定時器 */
    init_timer(&chrdev_key.timer);
    chrdev_key.timer.function = timer_func;

    /* 初始化等待隊列頭 */
    init_waitqueue_head(&chrdev_key.r_wait);

    return 0;
}

static int key_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &chrdev_key;

    printk("Key open successful!\r\n");

    return 0;
}

/* 從設備讀取數據
 * 參數filp:要打開的設備文件(文件描述符)
 * 參數buf:要返回給用戶空間的數據緩沖區
 * 參數cnt:要讀取的數據長度
 * 參數offt:相對於文件首地址的偏移
 * 返回值:讀取的字節數,如果為負值,表示讀取失敗。
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char key_value = 0;
    unsigned char release_key = 0;
    struct chrdev_key *dev = filp->private_data;

    DECLARE_WAITQUEUE(wait, current);   /* 定義一個等待隊列項 */
    if (atomic_read(&dev->release_key) == 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); /* 將等待隊列項移除 */
    }

    key_value = atomic_read(&dev->key_value);
    release_key = atomic_read(&dev->release_key);

    if (release_key) { /* key按下 */
        if (key_value & 0x80) {
            key_value &= ~0x80;
            ret = copy_to_user(buf, &key_value, sizeof(key_value));
        } else {
            key_value = INVALID_KEY;
            ret = copy_to_user(buf, &key_value, sizeof(key_value));
            goto data_error;
        }
        /* 按下標記清0 */
        atomic_set(&dev->release_key, 0x0);
    } 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;
}

/* 設備操作函數 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
};

static int __init chrdev_key_init(void)
{
    /* 注冊字符設備驅動 */
    /* 創建設備號 */
    if (chrdev_key.major) {  /* 定義了設備號 */
        chrdev_key.devid = MKDEV(chrdev_key.major, 0);
        register_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT, KEY_NAME);
    } else {  /* 沒有定義設備號 */
        alloc_chrdev_region(&chrdev_key.devid, 0, KEY_DEVICE_CNT, KEY_NAME);
        chrdev_key.major = MAJOR(chrdev_key.devid);
        chrdev_key.minor = MINOR(chrdev_key.devid);
    }

    /* 初始化cdev */
    chrdev_key.cdev.owner = THIS_MODULE;
    cdev_init(&chrdev_key.cdev, &key_fops);

    /* 添加一個cdev */
    cdev_add(&chrdev_key.cdev, chrdev_key.devid, KEY_DEVICE_CNT);

    /* 創建類 */
    chrdev_key.class = class_create(THIS_MODULE, KEY_NAME);
    if (IS_ERR(chrdev_key.class)) {
        return PTR_ERR(chrdev_key.class);
    }

    /* 創建設備 */
    chrdev_key.device = device_create(chrdev_key.class, NULL, chrdev_key.devid, NULL, KEY_NAME);
    if (IS_ERR(chrdev_key.device)) {
        return  PTR_ERR(chrdev_key.device);
    }

    /* 初始化按鍵 */
    atomic_set(&chrdev_key.key_value, INVALID_KEY);
    atomic_set(&chrdev_key.release_key, 0x0);
    if (key_gpio_init() != 0) {
        goto ERROR;
    }

    printk("Key device driver register successful!\r\n");
    return 0;

ERROR:
    /* 注銷字符設備驅動 */
    cdev_del(&chrdev_key.cdev);
    unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);

    device_destroy(chrdev_key.class, chrdev_key.devid);
    class_destroy(chrdev_key.class);

    return -EINVAL;
}

static void __exit chrdev_key_exit(void)
{
    unsigned char i = 0;

    /* 刪除定時器 */
    del_timer_sync(&chrdev_key.timer);

    /* 釋放中斷 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(chrdev_key.irq_key[i].irq_num, &chrdev_key);
    }

    /* 注銷字符設備驅動 */
    cdev_del(&chrdev_key.cdev);
    unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);

    device_destroy(chrdev_key.class, chrdev_key.devid);
    class_destroy(chrdev_key.class);

    printk("Key device driver unregister successful!\r\n");
}

module_init(chrdev_key_init);
module_exit(chrdev_key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MrLayfolk");

5.2 編譯、測試

應用程序不變,插入KO進行測試:

root@ATK-IMX6U:~# insmod key_drv.ko 
[ 1212.175597] key0: gpio:18, irq_num:47
[ 1212.179400] key0 irq 47 request successfully!
[ 1212.198277] Key device driver register successful!

root@ATK-IMX6U:~# ./key_app /dev/key_block &
[1] 828
root@ATK-IMX6U:~# [ 1244.596787] Key open successful!

root@ATK-IMX6U:~# Key press, value = 0x1, ret = 0.

root@ATK-IMX6U:~# top
  828 root      20   0    1688    304    268 S  0.3  0.1   0:00.01 key_app 

6 非阻塞IO實驗

6.1 內核驅動

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/gpio.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/poll.h>

#define KEY_DEVICE_CNT     1        /* 設備個數 */
#define KEY_NAME    "key_noblock"   /*設備名字*/

/* 定義鍵值 */
#define KEY0_VALUE      0x01    /* KEY0按鍵值 */
#define INVALID_KEY     0xFF    /* 無效鍵值 */

#define KEY_NUM         1       /* 按鍵數量 */

/* 中斷IO描述結構體 */
struct irq_key_desc {
    int gpio;               /* GPIO */
    int irq_num;            /* 中斷號 */
    unsigned char value;    /* 按鍵對應的鍵值 */
    char name[10];          /* 名字 */
    irqreturn_t(*handler)(int, void *); /* 中斷處理函數 */
};

/* KEY設備結構體 */
struct chrdev_key {
    dev_t devid;            /* 設備號 */
    struct cdev cdev;       /* cdev */
    struct class *class;    /* 類 */
    struct device *device;  /* 設備 */
    int major;              /* 主設備號 */
    int minor;              /* 次設備號 */
    struct device_node *nd; /* 設備節點 */
    atomic_t key_value;     /* 有效的按鍵值 */
    atomic_t release_key;   /* 標記一次完成的按鍵是否完成 */
    struct timer_list timer;    /* 定時器 */
    struct irq_key_desc irq_key[KEY_NUM]; /* 按鍵描述 */
    unsigned char cur_key_num;  /* 當前的按鍵號 */
    wait_queue_head_t r_wait;   /* 讀等待隊列頭 */
};

struct chrdev_key chrdev_key;

/* 中斷服務函數:開啟定時器,延時10ms,定時器用於消抖
 * 參數irq:中斷號
 * 參數dev_id:設備結構
 * 返回值:中斷處理結果
 */
irqreturn_t key0_irq_handler(int irq, void *dev_id)
{
    struct chrdev_key *dev = (struct chrdev_key *)dev_id;

    dev->cur_key_num = 0;
    dev->timer.data = (volatile long)dev_id;
    mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));

    return IRQ_RETVAL(IRQ_HANDLED);
}

/* 定時器服務函數:消除按鍵抖動,定時器到了后再次讀取按鍵值,如果按鍵處於按下則表示按鍵有效
 * arg:設備結構體變量
*/
void timer_func(unsigned long arg)
{
    unsigned char value;
    unsigned char num;
    struct irq_key_desc *key_desc = NULL;
    struct chrdev_key *dev = (struct chrdev_key *)arg;

    num = dev->cur_key_num;
    key_desc = &dev->irq_key[num];
    value = gpio_get_value(key_desc->gpio);
    if (value == 0) { /* 按下按鍵 */
        atomic_set(&dev->key_value, key_desc->value);
    } else {    /* 按鍵松開 */
        atomic_set(&dev->key_value, 0x80 | key_desc->value);
        atomic_set(&dev->release_key, 1);   /* 標記松開按鍵 */
    }

    /* 喚醒進程 */
    if (atomic_read(&dev->release_key)) {  /* 完成一次按鍵過程 */
        wake_up_interruptible(&dev->r_wait);
    }
}

/* 按鍵IO初始化 */
static int key_gpio_init(void)
{
    unsigned char i = 0;
    int ret = 0;

    chrdev_key.nd = of_find_node_by_path("/key");
    if (chrdev_key.nd == NULL) {
        printk("Cannot find device node!\r\n");
        return -EINVAL;
    }

    /* 提取GPIO */
    for (i = 0; i < KEY_NUM; i++) {
        chrdev_key.irq_key[i].gpio = of_get_named_gpio(chrdev_key.nd, "key-gpio", i);
        if (chrdev_key.irq_key[i].gpio < 0) {
            printk("Cannot get key%d!\r\n", i);
        }
    }

    /* 初始化key使用的IO,並且設置為中斷模式 */
    for (i = 0; i < KEY_NUM; i++) {
        memset(chrdev_key.irq_key[i].name, 0x0, sizeof(chrdev_key.irq_key[i].name));
        sprintf(chrdev_key.irq_key[i].name, "KEY%d", i);
        gpio_request(chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].name);
        gpio_direction_input(chrdev_key.irq_key[i].gpio); /* 設置為輸入 */
        chrdev_key.irq_key[i].irq_num = irq_of_parse_and_map(chrdev_key.nd, i);
        printk("key%d: gpio:%d, irq_num:%d\r\n", i, chrdev_key.irq_key[i].gpio, chrdev_key.irq_key[i].irq_num);
    }

    /* 申請中斷 */
    chrdev_key.irq_key[0].handler = key0_irq_handler;
    chrdev_key.irq_key[0].value = KEY0_VALUE;
    for (i = 0; i < KEY_NUM; i++) {
        ret = request_irq(chrdev_key.irq_key[i].irq_num, chrdev_key.irq_key[i].handler,
            IRQF_TRIGGER_FALLING | IRQF_TRIGGER_RISING | IRQF_SHARED,
            chrdev_key.irq_key[i].name, &chrdev_key);
        if (ret < 0) {
            printk("key%d irq %d request failed!\r\n", i, chrdev_key.irq_key[i].irq_num);
            return -EFAULT;
        }
        printk("key%d irq %d request successfully!\r\n", i, chrdev_key.irq_key[i].irq_num);
    }

    /* 創建定時器 */
    init_timer(&chrdev_key.timer);
    chrdev_key.timer.function = timer_func;

    /* 初始化等待隊列頭 */
    init_waitqueue_head(&chrdev_key.r_wait);

    return 0;
}

static int key_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &chrdev_key;

    printk("Key open successful!\r\n");

    return 0;
}

/* 從設備讀取數據
 * 參數filp:要打開的設備文件(文件描述符)
 * 參數buf:要返回給用戶空間的數據緩沖區
 * 參數cnt:要讀取的數據長度
 * 參數offt:相對於文件首地址的偏移
 * 返回值:讀取的字節數,如果為負值,表示讀取失敗。
*/
static ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    int ret = 0;
    unsigned char key_value = 0;
    unsigned char release_key = 0;
    struct chrdev_key *dev = filp->private_data;

    if (filp->f_flags & O_NONBLOCK) {   /* 非阻塞訪問 */
        if (atomic_read(&dev->release_key) == 0) { /* 沒有按鍵按下 */
            return -EAGAIN;
        }
    }
    /* 加入等待隊列,等待被喚醒,也就是有按鍵按下 */
    ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->release_key));
    if (ret) {
        goto wait_error;
    }

    key_value = atomic_read(&dev->key_value);
    release_key = atomic_read(&dev->release_key);

    if (release_key) { /* key按下 */
        if (key_value & 0x80) {
            key_value &= ~0x80;
            ret = copy_to_user(buf, &key_value, sizeof(key_value));
        } else {
            key_value = INVALID_KEY;
            ret = copy_to_user(buf, &key_value, sizeof(key_value));
            goto data_error;
        }
        /* 按下標記清0 */
        atomic_set(&dev->release_key, 0x0);
    } else {
        goto data_error;
    }
    return 0;

wait_error:
    return ret;

data_error:
    return -EINVAL;
}

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

    poll_wait(filp, &dev->r_wait, wait);

    if (atomic_read(&dev->release_key)) { /* 按鍵按下 */
        mask = POLLIN | POLLRDNORM;  /* 返回PLLIN */
    }

    return mask;
}

/* 設備操作函數 */
static struct file_operations key_fops = {
    .owner = THIS_MODULE,
    .open = key_open,
    .read = key_read,
    .poll = key_poll,
};

static int __init chrdev_key_init(void)
{
    /* 注冊字符設備驅動 */
    /* 創建設備號 */
    if (chrdev_key.major) {  /* 定義了設備號 */
        chrdev_key.devid = MKDEV(chrdev_key.major, 0);
        register_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT, KEY_NAME);
    } else {  /* 沒有定義設備號 */
        alloc_chrdev_region(&chrdev_key.devid, 0, KEY_DEVICE_CNT, KEY_NAME);
        chrdev_key.major = MAJOR(chrdev_key.devid);
        chrdev_key.minor = MINOR(chrdev_key.devid);
    }

    /* 初始化cdev */
    chrdev_key.cdev.owner = THIS_MODULE;
    cdev_init(&chrdev_key.cdev, &key_fops);

    /* 添加一個cdev */
    cdev_add(&chrdev_key.cdev, chrdev_key.devid, KEY_DEVICE_CNT);

    /* 創建類 */
    chrdev_key.class = class_create(THIS_MODULE, KEY_NAME);
    if (IS_ERR(chrdev_key.class)) {
        return PTR_ERR(chrdev_key.class);
    }

    /* 創建設備 */
    chrdev_key.device = device_create(chrdev_key.class, NULL, chrdev_key.devid, NULL, KEY_NAME);
    if (IS_ERR(chrdev_key.device)) {
        return  PTR_ERR(chrdev_key.device);
    }

    /* 初始化按鍵 */
    atomic_set(&chrdev_key.key_value, INVALID_KEY);
    atomic_set(&chrdev_key.release_key, 0x0);
    if (key_gpio_init() != 0) {
        goto ERROR;
    }

    printk("Key device driver register successful!\r\n");
    return 0;

ERROR:
    /* 注銷字符設備驅動 */
    cdev_del(&chrdev_key.cdev);
    unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);

    device_destroy(chrdev_key.class, chrdev_key.devid);
    class_destroy(chrdev_key.class);

    return -EINVAL;
}

static void __exit chrdev_key_exit(void)
{
    unsigned char i = 0;

    /* 刪除定時器 */
    del_timer_sync(&chrdev_key.timer);

    /* 釋放中斷 */
    for (i = 0; i < KEY_NUM; i++) {
        free_irq(chrdev_key.irq_key[i].irq_num, &chrdev_key);
    }

    /* 注銷字符設備驅動 */
    cdev_del(&chrdev_key.cdev);
    unregister_chrdev_region(chrdev_key.devid, KEY_DEVICE_CNT);

    device_destroy(chrdev_key.class, chrdev_key.devid);
    class_destroy(chrdev_key.class);

    printk("Key device driver unregister successful!\r\n");
}

module_init(chrdev_key_init);
module_exit(chrdev_key_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("MrLayfolk");

6.2 應用程序

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <linux/ioctl.h>
#include <poll.h>

int main(int argc, char *argv[])
{
    int fd = 0;
    int ret = 0;
    char *file_name= NULL;
    struct pollfd fds;
    fd_set read_fds;
    struct timeval timeout;
    unsigned char key_value = 0;

    if (argc != 2) {
        printf("Error Usage!\r\n");
        return -1;
    }

    file_name = argv[1];

    /* open file */
    fd = open(file_name, O_RDWR | O_NONBLOCK);  /* 非阻塞訪問 */
    if (fd < 0) {
        printf("file %s open failded!\r\n", file_name);
        return -1;
    }

    while (1) {
        FD_ZERO(&read_fds);
        FD_SET(fd, &read_fds);

        timeout.tv_sec = 0;
        timeout.tv_usec = 500000; /* 500ms */
        ret = select(fd + 1, &read_fds, NULL, NULL, &timeout);
        switch (ret) {
            case 0:  /* 超時 */
                printf("select timeout!\r\n");
                break;
            case -1: /* 錯誤 */
                printf("select error!\r\n");
                break;
            default:  /* 可以讀取數據 */
                if (FD_ISSET(fd, &read_fds)) {
                    ret = read(fd, &key_value, sizeof(key_value));
                    if (ret < 0) {
                        printf("read data error, ret = %d!\r\n", ret);
                    } else {
                        if (key_value) {
                            printf("read key value = %d\r\n", key_value);
                        }
                    }
                }
                break;
        }
    }

    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!\r\n", file_name);
        return -1;
    }

    return 0;
}

6.3 加載測試

root@ATK-IMX6U:~# insmod key_drv.ko 
[ 2845.522384] key0: gpio:18, irq_num:47
[ 2845.542375] key0 irq 47 request successfully!
[ 2845.553998] Key device driver register successful!

root@ATK-IMX6U:~# ./key_app /dev/key_noblock 
[ 2925.047194] Key open successful!
select timeout!
select timeout!
read key value = 1
select timeout!
select timeout!
read key value = 1
select timeout!
select timeout!

采用非阻塞方式讀處理以后,noblockioApp的CPU占用率也低至0.0%。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM