zl程序教程

您现在的位置是:首页 >  其他

当前栏目

Linux设备驱动--轮询操作

2023-03-15 22:49:25 时间

注:本文是《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
");
            break;
        case -1: /* 错误 */
            printf("error!
");
            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
");
        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
", 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
", 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!
");

        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!
");
            /* 数据可写入 */
            if (FD_ISSET(fd, &wfds))
                printf("Poll monitor: can be written!
");
        }
    } else {
        printf("Device open failure
");
    }

    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!
");
        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!
", 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
", 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!
", i, chrdev_key.irq_key[i].irq_num);
            return -EFAULT;
        }
        printk("key%d irq %d request successfully!
", 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!
");

    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!
");
    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!
");
}

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!
");
        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!
", 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
", 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!
", i, chrdev_key.irq_key[i].irq_num);
            return -EFAULT;
        }
        printk("key%d irq %d request successfully!
", 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!
");

    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!
");
    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!
");
}

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!
");
        return -1;
    }

    file_name = argv[1];

    /* open file */
    fd = open(file_name, O_RDWR | O_NONBLOCK);  /* 非阻塞访问 */
    if (fd < 0) {
        printf("file %s open failded!
", 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!
");
                break;
            case -1: /* 错误 */
                printf("select error!
");
                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!
", ret);
                    } else {
                        if (key_value) {
                            printf("read key value = %d
", key_value);
                        }
                    }
                }
                break;
        }
    }

    ret = close(fd);
    if (ret < 0) {
        printf("file %s close failed!
", 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%。