嵌入式Linux(13):Liunx阻塞和非阻塞IO(访问设备驱动)

Linux 驱动开发常见的设备访问模式:阻塞和非阻塞IO

1、阻塞和非阻塞 IO

1.1 设备访问

IO不是STM32的GPIO引脚,指的是Input/Output(输入/输出),是应用程序对驱动设备的输入/输出操作。当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么

  • 阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。
  • 非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃

在这里插入图片描述

  • 应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序。

在这里插入图片描述

  • 应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或数据未准备好的时候会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功。

应用程序实现阻塞访问:
(对于设备驱动文件的默认读取方式就是阻塞式)

int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

采用非阻塞的方式来访问驱动设备文件

int fd;
int data = 0;
fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
ret = read(fd, &data, sizeof(data)); /* 读取数据 */

1.2 等待队列(阻塞IO访问)

等待队列头

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。

Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作,如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头,等待队列头使用结构体wait_queue_head_t 表示, wait_queue_head_t 结构体定义在文件 include/linux/wait.h 中(内核定义好了)

39 struct __wait_queue_head {
40 		spinlock_t lock;
41 		struct list_head task_list;
42 };
43 typedef struct __wait_queue_head wait_queue_head_t;

(自己代码做的)使用 init_waitqueue_head 函数初始化等待队列头:

void init_waitqueue_head(wait_queue_head_t *q)
	参数 q 就是要初始化的等待队列头。

也可以使用宏 DECLARE_WAIT_QUEUE_HEAD 来一次性完成等待队列头的定义和初始化。

等待队列项

等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。结构体 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)
	name 就是等待队列项的名字, 
	tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current 
  • 在 Linux 内 核 中 current 相 当 于 一 个 全 局 变 量 , 表 示 当 前 进 程 。 因 此 宏DECLARE_WAITQUEUE 就是给当前正在运行的进程创建并初始化了一个等待队列项。

将队列项添加/移除等待队列头

当设备不可访问的时候就需要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态。当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可。

等待队列项添加 API 函数如下:

void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
	函数参数和返回值含义如下:
		q: 等待队列项要加入的等待队列头。
		wait:要加入的等待队列项。
		返回值:无。

等待队列项移除 API 函数如下:

void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)
	函数参数和返回值含义如下:
		q: 要删除的等待队列项所处的等待队列头。
		wait:要删除的等待队列项。
		返回值:无

等待唤醒

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

void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)
	参数 q 就是要唤醒的等待队列头

这两个函数会将这个等待队列头中的所有进程都唤醒
wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程,而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。

等待事件

也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程

函数描述
wait_event(wq, condition)等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞 。 此 函 数 会 将 进 程 设 置 为TASK_UNINTERRUPTIBLE 状态
wait_event_timeout(wq, condition, timeout)功能和 wait_event 类似,但是此函数可以添加超时时间,以 jiffies 为单位。此函数有返回值,如果返回 0 的话表示超时时间到,而且 condition为假。为 1 的话表示 condition 为真,也就是条件满足。
wait_event_interruptible(wq, condition)与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。
wait_event_interruptible_timeout(wq,condition, timeout)与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断

1.3轮询(非阻塞IO访问)

用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询poll、 epoll 和 select 可以用于处理轮询,应用程序通过 select、 epoll 或 poll 函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者向设备写入数据。

应用程序调用 select、 epoll 或 poll 函数的时候设备驱动程序中的 poll 函数就会执行,因此需要在设备驱动程序中编写 poll 函数。

应用程序中

应用程序中使用的 select、 poll 和 epoll 这三个函数

(1)select 函数
int select(	int nfds,
			fd_set *readfds,
			fd_set *writefds,
			fd_set *exceptfds,
			struct timeval *timeout)

函数参数和返回值含义如下:

  • nfds: 所要监视的这三类文件描述集合中, 最大文件描述符加 1。
  • readfds、 writefds 和 exceptfds:这三个指针指向描述符集合,这三个参数指明了关心哪些描述符、需要满足哪些条件等等,这三个参数都是 fd_set 类型的, fd_set 类型变量的每一个位都代表了一个文件描述符。
    • readfds 用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时。可以将 readfs设置为 NULL,表示不关心任何文件的读变化。
    • writefds 和 readfs 类似,只是 writefs 用于监视这些文件是否可以进行写操作。
    • exceptfds 用于监视这些文件的异常。
    • timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,当 timeout 为 NULL 的时候就表示无限期的等待。
      超时时间使用结构体 timeval 表示,结构体定义如下所示
struct timeval {
long tv_sec; /* 秒 */
long tv_usec; /* 微妙 */
};
    • 返回值: 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。

在应用程序,定义一个 fd_set 变量,使用宏进行操作

void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)
  • FD_ZERO 用于将 fd_set 变量的所有位都清零,
  • FD_SET 用于将 fd_set 变量的某个位置 1,也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符。
  • FD_CLR 用户将 fd_set变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符。
  • FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。

使用 select 函数对某个设备驱动文件进行读非阻塞访问的操作:

void main(void)
{
		int ret, fd; /* 要监视的文件描述符 */
 		fd_set readfds; /* 读操作文件描述符集 */
		struct timeval timeout; /* 超时结构体 */

		fd = open("dev_xxx", O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */
        FD_ZERO(&readfds); /* 清除 readfds */
        FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */
	 /* 构造超时时间 */
		timeout.tv_sec = 0;
		timeout.tv_usec = 500000; /* 500ms */
		
		ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
		switch (ret) {
			case 0: /* 超时 */
				printf("timeout!\r\n");
				break;
			case -1: /* 错误 */
 				printf("error!\r\n");
				break;
			default: /* 可以读取数据 */
				 if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
					/* 使用 read 函数读取数据 */
				 }
					break;
		 }
}
(2)poll 函数

在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024。 poll 函数没有最大文件描述符限制

int poll(struct pollfd *fds,
         nfds_t nfds,
         int timeout)

函数参数和返回值含义如下:

  • fds: 要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体 pollfd类型的, pollfd 结构体如下所示:
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
};
    • fd 是要监视的文件描述符,如果 fd 无效的话那么 events 监视事件也就无效,并且 revents返回 0。
    • events 是要监视的事件,可监视的事件类型如下所示:

POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于POLLIN

    • revents 是返回参数,也就是返回的事件, 由 Linux 内核设置具体的返回事件。
  • nfds: poll 函数要监视的文件描述符数量。
  • timeout: 超时时间,单位为 ms。
  • 返回值:返回 revents 域中不为 0 的 pollfd 结构体个数,也就是发生事件或错误的文件描述符数量; 0,超时; -1,发生错误,并且设置 errno 为错误类型。

使用 poll 函数对某个设备驱动文件进行读非阻塞访问的操作

void main(void)
{
	int ret;
	int fd; /* 要监视的文件描述符 */
	struct pollfd fds;

	fd = open(filename, O_RDWR | O_NONBLOCK); /* 非阻塞式访问 */

	/* 构造结构体 */
	fds.fd = fd;
 	fds.events = POLLIN; /* 监视数据是否可以读取 */
	ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
 	if (ret) { /* 数据有效 */
		......
 		/* 读取数据 */
 		......
 	 } else if (ret == 0) { /* 超时 */
 		......
 	 } else if (ret < 0) { /* 错误 */
 		......
 	}
}
(3)epoll 函数

selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。

epoll 为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。应用程序需要先使用 epoll_create 函数创建一个 epoll 句柄

int epoll_create(int size)
	函数参数和返回值含义如下:
		size: 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。
		返回值: epoll 句柄,如果为-1 的话表示创建失败。

epoll 句柄创建成功以后使用 epoll_ctl 函数向其中添加要监视的文件描述符以及监视的事件

int epoll_ctl(int epfd,
			  int op,
              int fd,
              struct epoll_event *event)

函数参数和返回值含义如下:

  • epfd: 要操作的 epoll 句柄,也就是使用 epoll_create 函数创建的 epoll 句柄。
  • op: 表示要对 epfd(epoll 句柄)进行的操作,可以设置为:

EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。
EPOLL_CTL_MOD 修改参数 fd 的 event事件。
EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符。

  • fd:要监视的文件描述符。
  • event: 要监视的事件类型,为 epoll_event 结构体类型指针, epoll_event 结构体类型如下所示:
struct epoll_event {
	uint32_t events; /* epoll 事件 */
	epoll_data_t data; /* 用户数据 */
};
    • 结构体 epoll_event 的 events 成员变量表示要监视的事件,可选的事件如下所示:

EPOLLIN 有数据可以读取。
EPOLLOUT 可以写数据。
EPOLLPRI 有紧急的数据需要读取。
EPOLLERR指定的文件描述符发生错误。
EPOLLHUP 指定的文件描述符挂起。
EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。
EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd,那么就需要将 fd 重新添加到 epoll 里面。

上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件。

  • 返回值: 0,成功; -1,失败,并且设置 errno 的值为相应的错误码。

应用程序通过 epoll_wait 函数来等待事件的发生,类似 select 函数。 epoll_wait 函数原型如下所示:

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

函数参数和返回值含义如下:

  • epfd: 要等待的 epoll。
  • events: 指向 epoll_event 结构体的数组,当有事件发生的时候 Linux 内核会填写 events,调用者可以根据 events 判断发生了哪些事件。
  • maxevents: events 数组大小,必须大于 0。
  • timeout: 超时时间,单位为 ms。
  • 返回值: 0,超时; -1,错误;其他值,准备就绪的文件描述符数量。

epoll 更多的是用在大规模的并发服务器上

Linux 驱动下的 poll 操作函数

当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。

驱动程序的编写者需要提供对应的 poll 函数, poll 函数原型如下所示:

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

函数参数和返回值含义如下:

  • filp: 要打开的设备文件(文件描述符)。
  • wait: 结构体 poll_table_struct 类型指针, 由应用程序传递进来的。一般将此参数传递给poll_wait 函数。
  • 返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:

POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于POLLIN,普通数据可读

我们需要在驱动程序的 poll 函数中调用 poll_wait 函数, poll_wait 函数不会引起阻塞,只是将应用程序添加到 poll_table 中, poll_wait 函数原型如下:

void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
		参数 wait_address 是要添加到 poll_table 中的等待队列头,
		参数 p 就是 poll_table,就是file_operations 中 poll 函数的 wait 参数。

2、实验

2.1 阻塞IO实验

Linux 中断实验中,我们直接在应用程序中通过 read 函数不断的读取按键状态,当按键有效的时候就打印出按键值。这种方法有个缺点,那就是 imx6uirqApp 这个测试应用程序拥有很高的 CPU 占用率。

  • 后台运行模式打开 imx6uirqApp 这个测试软件,命令如下:./imx6uirqApp /dev/imx6uirq &
  • 测试驱动是否正常工作,如果驱动工作正常的话输入“top”命令查看 imx6uirqApp 这个应用程序的 CPU 使用率
    在这里插入图片描述
    降低 CPU 使用率

驱动程序编写

本实验我们在Linux 中断实验的基础上完成,主要是对其添加阻塞访问相关的代码。新建名为“14_blockio”的文件夹,然后在 14_blockio 文件夹里面创建 vscode 工程,工作区命名为“blockio”。将“13_irq”实验中的 imx6uirq.c 复制到 14_blockio 文件夹中,并重命名为 blockio.c。接下来修改 blockio.c 这个文件,在其中添加阻塞相关的代码
(其他代码看中断实验)

  • 修改设备名字
#define IMX6UIRQ_NAME       "blockio"       /*设备名字*/
  • imx6uirq 设备结构体添加等待队列头
/*imx6uirq设备结构体*/
struct imx6uirq_dev{
    dev_t devid;            /*设备号*/
    struct cdev cdev;       /*cdev*/
    struct class *class;    /*lei*/
    struct device *device;  /*设备*/
    int major;              /*主设备号*/
    int minor;              /*次设备号*/
    struct device_node * nd; /*设备节点*/
    atomic_t keyvalue;       /*有效按键值*/
    atomic_t releasekey;     /*标记是否完成一次完整的按键*/
    struct timer_list timer; /*定义一个定时器*/
    struct irq_keydesc irqkey[KEY_NUM]; /*按键描述数组*/
    unsigned char curkeynum;            /*当前按键号*/
    wait_queue_head_t r_wait;          /*读等待队列头*/
};
  • 定时器中断处理函数执行,表示有按键按下,先判断一下是否是一次有效的按键,如果是的话就通过 wake_up_interruptible 函数来唤醒等待队列r_wait
void timer_function(unsigned long arg)
 {
     unsigned char value;
     unsigned char num;
     struct irq_keydesc *keydesc;
     struct imx6uirq_dev *dev=(struct imx6uirq_dev*)arg;

     num=dev->curkeynum;
    keydesc=&dev->irqkey[num];
    value=gpio_get_value(keydesc->gpio);/* 读取 IO 值 */
    if(value == 0){                   /* 按下按键 */
        atomic_set(&dev->keyvalue,keydesc->value);
    }else{
        atomic_set(&dev->keyvalue, 0x80|keydesc->value);/* 按键松开 */
        atomic_set(&dev->releasekey, 1); /* 标记松开按键 */
    }

    /*唤醒进程*/
    if(atomic_read(&dev->releasekey)){            /* 完成一次按键过程 */
        wake_up_interruptible(&dev->r_wait);
    }
 }
  • 在按键初始化IO函数初始化等待队列头
static int keyio_init(struct imx6uirq_dev *dev)
{
    unsigned char i = 0;
    int ret=0;
   ..........
   
    /*5. 创建定时器*/
    init_timer(&dev->timer);
    dev->timer.function=timer_function;

    /*6.初始化等待队列头*/
    init_waitqueue_head(&dev->r_wait);
    return 0;
}
  • 在设备读取数据函数进行队列的操作
/*从设备读取数据*/
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){             /*有按键按下*/
        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;
}

注意:在设备读取函数,将任务或者进程加入到等待队列头;
在中断处理函数唤醒等待队列。

运行测试

  • 测试App程序跟中断实验的一样
  • 编译驱动程序和测试 APP
    编写 Makefile 文件,将 obj-m 变量的值改为 blockio.o
    输入命令make -j32编译出驱动模块文件:blockio.ko
    输入如命令arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp编译出 blockioApp 测试程序
  • blockio.ko 和 blockioApp 这两个文件拷贝到rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入insmod blockio.ko加载blockio.ko 驱动模块在这里插入图片描述
  • 后台模式运行:./blockioApp /dev/blockio &,按下开发板上的 KEY0 键,终端就会输出按键值,按键值获取成功,在这里插入图片描述
  • 输入“top”命令,查看 blockioAPP 这个应用 APP 的 CPU 使用率在这里插入图片描述
    • CPU 使用率从 99.6%降低到了 0.0%,这里的 0.0%并不是说 blockioApp 这个应用程序不使用 CPU 了,只是因为使用率太小了, CPU 使用率可能为0.00001%。
  • 首先输出“Ctrl+C”关闭 top 命令界面,进入到命令行模式。然后使用“ps”命令查看一下 blockioApp 这个应用程序的 PID在这里插入图片描述
  • blockioApp 这个应用程序的 PID 为 86,使用“kill -9 PID”即可“杀死”指定 PID 的进程,“kill”命令关闭后台运行的应用程序在这里插入图片描述

2.2 非阻塞IO实验

驱动程序编写

新建名为“15_noblockio”的文件夹,然后在 15_noblockio 文件夹里面创建 vscode 工程,工作区命名为“noblockio”。将“14_blockio”实验中的 blockio.c 复制到 15_noblockio 文件夹中,并重命名为 noblockio.c。在上一小节实验的 blockio.c 文件的基础上修改

  • 添加头文件
#include <linux/wait.h>
#include <linux/poll.h>
  • 修改名字
#define IMX6UIRQ_NAME       "noblockio"       /*设备名字*/
  • 从设备读数据函数
/*从设备读取数据*/
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) {              /* 非阻塞访问 */
        if(atomic_read(&dev->releasekey)==0)     /* 没有按键按下 */
        return -EAGAIN;
    }else{                                       /* 阻塞访问 */
        /* 加入等待队列,等待被唤醒,也就是有按键按下 */
        ret=wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
        if(ret){
            return ret;
        }
    }

    keyvalue=atomic_read(&dev->keyvalue);
    releasekey=atomic_read(&dev->releasekey);
    if(releasekey){             /*有按键按下*/
        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;

data_error:
    return -EINVAL;
}
  • 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_wait(filp,&dev->r_wait,wait);
    if(atomic_read(&dev->releasekey)){       /* 按键按下 */
        mask=POLLIN | POLLRDNORM;
    }
    return mask;
}

测试App

新建名为 noblockioApp.c 测试 APP 文件

#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"
#include "sys/time.h"
#include "sys/select.h"

/* 使用方法 :./blockioApp /dev/blockio 打开测试 App*/

int main(int argc, char* argv[])
{
    int fd;
    int ret = 0;
    char * filename;
    unsigned char data;
    struct pollfd fds;
    fd_set readfds;
    struct timeval timeout;

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

    filename = argv[1];

    /*打开驱动*/
    fd=open(filename,O_RDWR | O_NONBLOCK);    /*非阻塞访问*/
    if(fd<0){
        printf("file %s open failed!\r\n",argv[1]);
        return -1;
    }

    /*poll函数实现非阻塞访问*/
    fds.fd=fd;
    fds.events=POLLIN;
    
    while(1){
        ret=poll(&fds,1,500);
        if(ret){              /* 数据有效 */       
            ret=read(fd,&data,sizeof(data));
            if(data)
                printf("key value = %#X\r\n",data);     
        }else if(ret==0){                /* 超时 */
           // printf("timeout!\r\n");
        }else if(ret <0){
          //printf("error!\r\n");         /* 错误 */
        }   
    }

#if 0
    /*select函数实现*/
    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 0printf("timeout!\r\n"); 
                break;
            case -1:
                printf("error!\r\n");         /* 错误 */
                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;
        }
    }
#endif

    close(fd);/*关闭文件*/
    return ret;
}

运行测试

  • 编译驱动程序和测试 APP
    编写 Makefile 文件,将 obj-m 变量的值改为 noblockio.o
    输入命令make -j32编译出驱动模块文件:noblockio.ko
    输入如命令arm-linux-gnueabihf-gcc noblockioApp.c -o noblockioApp编译出 blockioApp 测试程序

  • noblockio.ko 和 noblockioApp 这两个文件拷贝到rootfs/lib/modules/4.1.15 目录中,重启开发板,进入到目录 lib/modules/4.1.15 中,输入insmod noblockio.ko加载noblockio.ko 驱动模块在这里插入图片描述

  • 后台模式运行:./noblockioApp /dev/noblockio &,按下开发板上的 KEY0 键,终端就会输出按键值,按键值获取成功在这里插入图片描述

  • 输入“top”命令,查看 blockioAPP 这个应用 APP 的 CPU 使用率在这里插入图片描述

    • CPU 使用率从 99.6%降低到了 0.0%,这里的 0.0%并不是说 noblockioApp 这个应用程序不使用 CPU 了,只是因为使用率太小了, CPU 使用率可能为0.00001%。
  • 首先输出“Ctrl+C”关闭 top 命令界面,进入到命令行模式。然后使用“ps”命令查看一下 blockioApp 这个应用程序的 PID在这里插入图片描述

  • blockioApp 这个应用程序的 PID 为 92,使用“kill -9 PID”即可“杀死”指定 PID 的进程,“kill”命令关闭后台运行的应用程序在这里插入图片描述

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值