Linux阻塞与非阻塞IO

简介

通过阻塞方式访问的话应用程序会处于休眠态,等待驱动设备可以使用,非阻塞方式的话会通过 poll 函数来不断的轮询,查看驱动设备文件是否可以使用。

1、阻塞和非阻塞 IO 是 Linux 驱动开发里面很常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。
2、这里的 IO 指的是 Input/Output,也就是输入/输出,是应用程序对驱动设备的输入/输出操作。
3、阻塞式IO(他应用程序里面还是循环read,我猜想应该是应用里read走到设备里读,然后设备里读被睡眠了,这一步就被卡住了,也就是进入了休眠状态,等数据到了,read被唤醒了,之后应用程序的read步骤就能进行下去了,就待着读到的数,重新回到了应用层)

  • 当应用程序对设备驱动进行操作的时候,如果不能获取到设备资源,那么阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。
  • 阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。
  • Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作
  • 对于设备驱动文件的默认读取方式就是阻塞式的,我们前面所有例程APP也都是阻塞式的。其创建如下所示:
1 int fd; 
2 int data = 0; 
3
4 fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */
5 ret = read(fd, &data, sizeof(data)); /* 读取数据 */

4、非阻塞IO

  • 对于非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。
  • 如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。
  • 应用程序要实现非阻塞方式访问设备,只需要添加参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了。
1 int fd; 
2 int data = 0; 
3
4 fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */
5 ret = read(fd, &data, sizeof(data)); /* 读取数据 */

等待队列

阻塞访问最大的好处就是当设备文件不可操作的时候进程可以进入休眠态,这样可以将CPU 资源让出来。但是,当设备文件可以操作的时候就必须唤醒进程,一般在中断函数里面完成唤醒工作。Linux 内核提供了等待队列(wait queue)来实现阻塞进程的唤醒工作。

API1

1、创建等待队列头:wait_queue_head_t

  • 如果我们要在驱动中使用等待队列,必须创建并初始化一个等待队列头
  • 等待队列头使用结构体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;

2、初始化等待队列头_init_waitqueue_head
定义好等待队列头以后需要初始化,使用 init_waitqueue_head 函数初始化等待队列头,函数原型如下:

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

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

3、定义并初始化一个等待队列项_DECLARE_WAITQUEUE

  • 等待队列头就是一个等待队列的头部。
  • 每个访问设备的进程都是一个队列项,当设备不可用的时候就要将这些进程对应的等待队列项添加到等待队列里面。
  • 结构体 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 就是给当前正在运行的进程创建并初始化了一个等待队列项。

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

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

等待添加:

void add_wait_queue(wait_queue_head_t *q, 
 					wait_queue_t *wait)
  • q:等待队列项要加入的等待队列头。
  • wait:要加入的等待队列项。
  • 返回值:无。

等待移除:

void remove_wait_queue(wait_queue_head_t *q, 
						wait_queue_t *wait)
  • q:要删除的等待队列项所处的等待队列头。
  • wait:要删除的等待队列项。
  • 返回值:无。

5、等待唤醒
当设备可以使用的时候就要唤醒进入休眠态的进程,唤醒可以使用如下两个函数:

  • 参数 q 就是要唤醒的等待队列头,这两个函数会将这个等待队列头中的所有进程都唤醒。
void wake_up(wait_queue_head_t *q)
  • wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程
void wake_up_interruptible(wait_queue_head_t *q)
  • wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。

使用1:创建队列项

首先在驱动中创建并定义等待队列头wait_queue_head_t。

	/* 初始化等待队列头 */
	init_waitqueue_head(&imx6uirq.r_wait);
static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	DECLARE_WAITQUEUE(wait, current);	/* 定义一个等待队列 */
	
	//name 就是等待队列项的名字	
//tsk 表示这个等待队列项属于哪个任务(进程),一般设置为current
	DECLARE_WAITQUEUE(wait, current);	/* 定义并初始化一个等待队列项  */
	
	//直接进入休眠
	add_wait_queue(&dev->r_wait, &wait);	/* 将等待队列添加到等待队列头 进入休眠状态下 */
	__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 即当前进程是可被打断的状态*/
	schedule();							/* 进行一次任务切换 然后此进程就休眠了,除非被唤醒,然后接着执行下去*/
	//因为上面设置了可被打断,因此有两种可能性导致被唤醒
	if(signal_pending(current))	{			/* 判断是否为信号引起的唤醒 */
		ret = -ERESTARTSYS;					//signal_pending函数用于判断当前进程是否有信号处理,返回值
		goto wait_error;					//不为0的话表示有信号需要处理
	}
	//不是因为信号,就是因为唤醒,就接着执行下去了
	__set_current_state(TASK_RUNNING);      /* 将当前任务设置为运行状态 */
	remove_wait_queue(&dev->r_wait, &wait);    /* 将对应的队列项从等待队列头删除 */
	//一系列的处理函数

wait_error:
	set_current_state(TASK_RUNNING);		/* 将当前任务或线程设置为运行态 */
	remove_wait_queue(&dev->r_wait, &wait);	/* 将等待队列移除 */
	return ret;

data_error:
	return -EINVAL;
}
void timer_function(unsigned long arg)
{
	/* 唤醒进程 */
	if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 */
		/* wake_up(&dev->r_wait); */
		wake_up_interruptible(&dev->r_wait);
	}
}

API2

除了主动唤醒以外,也可以设置等待队列等待某个事件,当这个事件满足以后就自动唤醒等待队列中的进程,和等待事件有关的 API 函数如下所示:

函数描述
wait_event(wq, condition)等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞 。 此 函 数 会 将 进 程 设 置 为TASK_UNINTERRUPTIBLE 状态 ,也就是不可被打断的状态,就是无法使用kill -9将这个进程关掉了,如果想可以被关掉,就要使用wait_event_interruptible
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,可以被信号打断。

使用2:等待事件

这样就只需要三步就行了,检测就使用top查看cpu占用率,ps看哪个进程,最后使用kill -9删除

	/* 初始化等待队列头 */
	init_waitqueue_head(&imx6uirq.r_wait);

在read中阻塞进程

static ssize_t imx6uirq_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
	/* 加入等待队列,等待被唤醒,也就是有按键按下 */
	//r_wait 就是上面创建的等待队列头
 ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
	if (ret) {
		goto wait_error;
	} 
}

读取到按键值后,就将进行唤醒

void timer_function(unsigned long arg)
{
	/* 唤醒进程 */
	if(atomic_read(&dev->releasekey)) {	/* 完成一次按键过程 */
		/* wake_up(&dev->r_wait); */
		wake_up_interruptible(&dev->r_wait);
	}
}

阻塞代码

功能实现

上次读取按键值的实验,APP使用while函数不断read,因此CPU占用率是极高的。使用如下命令后台加载应用程序,然后输入top查看CPU使用情况,发现,CPU占用率高达99.6%

./imx6uirqApp /dev/imx6uirq &

在这里插入图片描述
最好的方法就是在没有有效的按键事件发生的时候,imx6uirqApp 这个应用程序应该处于休眠状态,当有按键事件发生以后 imx6uirqApp 这个应用程序才运行,打印出按键值,这样就会降低 CPU 使用率,本小节我们就使用阻塞 IO 来实现此功能。

使用这种编程方式后,CPU使用率几乎为0
输入top查看
关闭的话就按ps 然后kill -9

头文件

ctrl+shift+p
在这里插入图片描述

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux kernel/include",
                "/home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux kernel/arch/arm/include",
                "/home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux kernel/arch/arm/include/generated"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/gcc",
            "cStandard": "gnu17",
            "cppStandard": "gnu++14",
            "intelliSenseMode": "linux-gcc-x64"
        }
    ],
    "version": 4
}

makefile

KERNELDIR := /home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux_kernel
CURRENT_PATH := $(shell pwd)

#obj-m 表示将 led.c 这个文件编译为 led.ko 模块。
obj-m := imx6uirq.o

build: kernel_modules

#后面的 modules 表示编译模块,
#-C 表示将当前的工作目录切换到指定目录中,也就是 KERNERLDIR 目录。
# M 表示模块源码目录,“make modules”命令中加入 M=dir 以后程序会自动到指定的 dir 目录中读取模块的源码并将其编译为.ko 文件。
kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean


#make编译

make

轮询

如果用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。
应用程序可以选择调用 select、epoll 或 poll 函数,此时驱动驱动程序中的 poll 函数就会执行。

应用层

select函数

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

比如我们现在要从一个设备文件中读取数据,那么就可以定义一个 fd_set 变量,这个变量要传递给参数 readfds。当我们定义好一个 fd_set 变量以后可以使用如下所示几个宏进行操作:

void FD_ZERO(fd_set *set)

  • 将 fd_set 变量的所有位都清零

void FD_SET(int fd, fd_set *set)
将 fd_set 变量的某个位置 1, 也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符

void FD_CLR(int fd, fd_set *set)

  • 用于将 fd_set变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符。

int FD_ISSET(int fd, fd_set *set)

  • FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。

poll和epoll

epoll 更多的是用在大规模的并发服务器上,因为在这种场合下 select 和 poll 并不适合。当
设计到的文件描述符(fd)比较少的时候就适合用 selcet 和 poll

epoll更好用,poll不用了
epoll以后用到了在学吧

驱动层

当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序
file_operations 操作集中的 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 参数。

非阻塞代码

也实现了使CPU使用率极低,因为select函数他也有阻塞时间

在这里插入图片描述

noblockio.c

#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/gpio.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_gpio.h>
#include <linux/semaphore.h>
#include <linux/timer.h>
#include <linux/of_irq.h>
#include <linux/irq.h>
#include <linux/wait.h>
#include <linux/poll.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>

#define IMX6UIRQ_CNT		1			/* 设备号个数 	*/
#define IMX6UIRQ_NAME		"noblockio"	/* 名字 		*/
#define KEY0VALUE			0X01		/* KEY0按键值 	*/
#define INVAKEY				0XFF		/* 无效的按键值 */
#define KEY_NUM				1			/* 按键数量 	*/

/* 中断IO描述结构体 */
struct irq_keydesc {
	int gpio;								/* gpio */
	int irqnum;								/* 中断号     */
	unsigned char value;					/* 按键对应的键值 */
	char name[10];							/* 名字 */
	irqreturn_t (*handler)(int, void *);	/* 中断服务函数 */
};

/* imx6uirq设备结构体 */
struct imx6uirq_dev{
	dev_t devid;			/* 设备号 	 */	
	struct cdev cdev;		/* cdev 	*/                 
	struct class *class;	/* 类 		*/
	struct device *device;	/* 设备 	 */
	int major;				/* 主设备号	  */
	int minor;				/* 次设备号   */
	struct device_node	*nd; /* 设备节点 */	
	atomic_t keyvalue;		/* 有效的按键键值 */
	atomic_t releasekey;	/* 标记是否完成一次完成的按键,包括按下和释放 */
	struct timer_list timer;/* 定义一个定时器*/
	struct irq_keydesc irqkeydesc[KEY_NUM];	/* 按键init述数组 */
	unsigned char curkeynum;				/* 当前init按键号 */

	wait_queue_head_t r_wait;	/* 读等待队列头 */
};

struct imx6uirq_dev imx6uirq;	/* irq设备 */

/* @description		: 中断服务函数,开启定时器		
 *				  	  定时器用于按键消抖。
 * @param - irq 	: 中断号 
 * @param - dev_id	: 设备结构。
 * @return 			: 中断执行结果
 */
static irqreturn_t key0_handler(int irq, void *dev_id)
{
	struct imx6uirq_dev *dev = (struct imx6uirq_dev*)dev_id;

	dev->curkeynum = 0;
	dev->timer.data = (volatile long)dev_id;
	mod_timer(&dev->timer, jiffies + msecs_to_jiffies(10));	/* 10ms定时 */
	return IRQ_RETVAL(IRQ_HANDLED);
}

/* @description	: 定时器服务函数,用于按键消抖,定时器到了以后
 *				  再次读取按键值,如果按键还是处于按下状态就表示按键有效。
 * @param - arg	: 设备结构变量
 * @return 		: 无
 */
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->irqkeydesc[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(&dev->r_wait); */
		wake_up_interruptible(&dev->r_wait);
	}
}

/*
 * @description	: 按键IO初始化
 * @param 		: 无
 * @return 		: 无
 */
static int keyio_init(void)
{
	unsigned char i = 0;
	char name[10];
	int ret = 0;
	
	imx6uirq.nd = of_find_node_by_path("/key");
	if (imx6uirq.nd== NULL){
		printk("key node not find!\r\n");
		return -EINVAL;
	} 

	/* 提取GPIO */
	for (i = 0; i < KEY_NUM; i++) {
		imx6uirq.irqkeydesc[i].gpio = of_get_named_gpio(imx6uirq.nd ,"key-gpio", i);
		if (imx6uirq.irqkeydesc[i].gpio < 0) {
			printk("can't get key%d\r\n", i);
		}
	}
	
	/* 初始化key所使用的IO,并且设置成中断模式 */
	for (i = 0; i < KEY_NUM; i++) {
		memset(imx6uirq.irqkeydesc[i].name, 0, sizeof(name));	/* 缓冲区清零 */
		sprintf(imx6uirq.irqkeydesc[i].name, "KEY%d", i);		/* 组合名字 */
		gpio_request(imx6uirq.irqkeydesc[i].gpio, name);
		gpio_direction_input(imx6uirq.irqkeydesc[i].gpio);	
		imx6uirq.irqkeydesc[i].irqnum = irq_of_parse_and_map(imx6uirq.nd, i);
#if 0
		imx6uirq.irqkeydesc[i].irqnum = gpio_to_irq(imx6uirq.irqkeydesc[i].gpio);
#endif
	}

	/* 申请中断 */
	imx6uirq.irqkeydesc[0].handler = key0_handler;
	imx6uirq.irqkeydesc[0].value = KEY0VALUE;
	
	for (i = 0; i < KEY_NUM; i++) {
		ret = request_irq(imx6uirq.irqkeydesc[i].irqnum, imx6uirq.irqkeydesc[i].handler, 
		                 IRQF_TRIGGER_FALLING|IRQF_TRIGGER_RISING, imx6uirq.irqkeydesc[i].name, &imx6uirq);
		if(ret < 0){
			printk("irq %d request failed!\r\n", imx6uirq.irqkeydesc[i].irqnum);
			return -EFAULT;
		}
	}

	/* 创建定时器 */
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_function;

	/* 初始化等待队列头 */
	init_waitqueue_head(&imx6uirq.r_wait);
	return 0;
}

/*
 * @description		: 打开设备
 * @param - inode 	: 传递给驱动的inode
 * @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量
 * 					  一般在open的时候将private_data指向设备结构体。
 * @return 			: 0 成功;其他 失败
 */
static int imx6uirq_open(struct inode *inode, struct file *filp)
{
	filp->private_data = &imx6uirq;	/* 设置私有数据 */
	return 0;
}

 /*
  * @description     : 从设备读取数据 
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - buf     : 返回给用户空间的数据缓冲区
  * @param - cnt     : 要读取的数据长度
  * @param - offt    : 相对于文件首地址的偏移
  * @return          : 读取的字节数,如果为负值,表示读取失败
  */
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)	/* 没有按键按下,返回-EAGAIN */
			return -EAGAIN;
	} 
	else /* 阻塞访问 */
	{							
		/* 加入等待队列,等待被唤醒,也就是有按键按下 */
 		ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 
		if (ret) {
			goto wait_error;
		}
	}

	keyvalue = atomic_read(&dev->keyvalue);
	releasekey = atomic_read(&dev->releasekey);

	if (releasekey) { /* 有按键按下 */	
		if (keyvalue & 0x80) {
			keyvalue &= ~0x80;
			ret = copy_to_user(buf, &keyvalue, sizeof(keyvalue));
		} else {
			goto data_error;
		}
		atomic_set(&dev->releasekey, 0);/* 按下标志清零 */
	} else {
		goto data_error;
	}
	return 0;

wait_error:
	return ret;
data_error:
	return -EINVAL;
}

 /*
  * @description     : poll函数,用于处理非阻塞访问
  * @param - filp    : 要打开的设备文件(文件描述符)
  * @param - wait    : 等待列表(poll_table)
  * @return          : 设备或者资源状态,
  */
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 函数不会引起阻塞,只是将应用程序添加到 poll_table 中
	//参数 wait_address 是要添加到 poll_table 中的等待队列头
	//参数 p 就是 poll_table,就是file_operations 中 poll 函数的 wait 参数 我们就这样作就行,我也不知道为什么,下次有机会在看把
	poll_wait(filp, &dev->r_wait, wait);	/* 将等待队列头添加到poll_table中 */
	
	//是否可读
	if(atomic_read(&dev->releasekey)) {		/* 按键按下 就是可读*/
		mask = POLLIN | POLLRDNORM;			/* POLLIN:有数据可读 */
	}
	return mask;
}

/* 设备操作函数 */
static struct file_operations imx6uirq_fops = {
	.owner = THIS_MODULE,
	.open = imx6uirq_open,
	.read = imx6uirq_read,
	.poll = imx6uirq_poll,
};

/*
 * @description	: 驱动入口函数
 * @param 		: 无
 * @return 		: 无
 */
static int __init imx6uirq_init(void)
{
	/* 1、构建设备号 */
	if (imx6uirq.major) {
		imx6uirq.devid = MKDEV(imx6uirq.major, 0);
		register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
	} else {
		alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
		imx6uirq.major = MAJOR(imx6uirq.devid);
		imx6uirq.minor = MINOR(imx6uirq.devid);
	}

	/* 2、注册字符设备 */
	cdev_init(&imx6uirq.cdev, &imx6uirq_fops);
	cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);

	/* 3、创建类 */
	imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.class)) {	
		return PTR_ERR(imx6uirq.class);
	}

	/* 4、创建设备 */
	imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
	if (IS_ERR(imx6uirq.device)) {
		return PTR_ERR(imx6uirq.device);
	}
		
	/* 5、始化按键 */
	atomic_set(&imx6uirq.keyvalue, INVAKEY);
	atomic_set(&imx6uirq.releasekey, 0);
	keyio_init();
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit imx6uirq_exit(void)
{
	unsigned i = 0;
	/* 删除定时器 */
	del_timer_sync(&imx6uirq.timer);	/* 删除定时器 */
		
	/* 释放中断 */	
	for (i = 0; i < KEY_NUM; i++) {
		free_irq(imx6uirq.irqkeydesc[i].irqnum, &imx6uirq);
	}
	cdev_del(&imx6uirq.cdev);
	unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
	device_destroy(imx6uirq.class, imx6uirq.devid);
	class_destroy(imx6uirq.class);
}	
	
module_init(imx6uirq_init);
module_exit(imx6uirq_exit);
MODULE_LICENSE("GPL");
	
	

noblockioApp.c

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"
#include "poll.h"
#include "sys/select.h"
#include "sys/time.h"
#include "linux/ioctl.h"


/*
 * @description		: main主程序
 * @param - argc 	: argv数组元素个数
 * @param - argv 	: 具体参数
 * @return 			: 0 成功;其他 失败
 */
int main(int argc, char *argv[])
{
	int fd;
	int ret = 0;
	char *filename;
	struct pollfd fds;
	fd_set readfds;
	struct timeval timeout;
	unsigned char data;

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

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

#if 0
	/* 构造结构体 */
	fds.fd = fd;
	fds.events = POLLIN;
		
	while (1) {
		ret = poll(&fds, 1, 500);
		if (ret) {	/* 数据有效 */
			ret = read(fd, &data, sizeof(data));
			if(ret < 0) {
				/* 读取错误 */
			} else {
				if(data)
					printf("key value = %d \r\n", data);
			} 	
		} else if (ret == 0) { 	/* 超时 */
			/* 用户自定义超时处理 */
		} else if (ret < 0) {	/* 错误 */
			/* 用户自定义错误处理 */
		}
	}
#endif

	while (1) {	
		FD_ZERO(&readfds);
		FD_SET(fd, &readfds);
		/* 构造超时时间 */
		timeout.tv_sec = 0;
		timeout.tv_usec = 500000; /* 500ms */
		ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
		switch (ret) {
			case 0: 	/* 超时 */
				/* 用户自定义超时处理 */
				break;
			case -1:	/* 错误 */
				/* 用户自定义错误处理 */
				break;
			default:  /* 可以读取数据 */
				if(FD_ISSET(fd, &readfds)) {
					ret = read(fd, &data, sizeof(data));
					if (ret < 0) {
						/* 读取错误 */
					} else {
						if (data)
							printf("key value=%d\r\n", data);
					}
				}
				break;
		}	
	}

	close(fd);
	return ret;
}

makefile

KERNELDIR := /home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux_kernel
CURRENT_PATH := $(shell pwd)

obj-m := noblockio.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules

clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean

头文件

{
    "configurations": [
        {
            "name": "Linux",
            "includePath": [
                "${workspaceFolder}/**",
                "/home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux kernel/include",
                "/home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux kernel/arch/arm/include",
                "/home/ni/linux/IMX6ULL/uboot/modi_nxp/2modi_old_linux kernel/arch/arm/include/generated"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang",
            "cStandard": "c11",
            "cppStandard": "c++17",
            "intelliSenseMode": "clang-x64"
        }
    ],
    "version": 4
}

测试

设备树

重新编译设备树,会编译所有发生更改了的dts文件

make dtbs

拷贝

cp arch/arm/boot/zImage /home/ni/linux/tftpboot/ni -f
cp arch/arm/boot/dts/imx6ull-ni-emmc.dtb /home/ni/linux/tftpboot/ni -f

设备

make -j12
arm-linux-gnueabihf-gcc blockioApp.c -o blockioApp
arm-linux-gnueabihf-gcc noblockioApp.c -o noblockioApp

复制到nfs/rootfs/lib/modules/4.1.15/

sudo cp blockio.ko /home/ni/linux/nfs/rootfs/lib/modules/4.1.15/ -f
sudo cp imx6uirqApp /home/ni/linux/nfs/rootfs/lib/modules/4.1.15/ -f

转到

cd lib/modules/4.1.15/

加载驱动模块

insmod blockio.ko

modprobe blockio.ko  //depmod

卸载驱动模块

rmmod blockio.ko

查看当前系统中存在的模块,可以看到当前系统只有imx6uirq这么一个模块。

lsmod

在这里插入图片描述
查看当前系统中有没有 imx6uirq这个设备

cat /proc/devices //查看当前系统中,所有设备

在这里插入图片描述
查看设备节点文件

ls /dev/imx6uirq -l
  • 如果imx6uirqApp想要读写 imx6uirq设备,直接对/dev/imx6uirq进行读写操作即可。相当于/dev/imx6uirq这个文件是 imx6uirq设备在用户空间中的实现。前面一直说 Linux 下一切皆文件,包括设备也是文件
    在这里插入图片描述

设备操作测试

  • 下述命令一共有两个参数“./blockioApp”、“/dev/blockio”,这两个参数分别对应 argv[0]、argv[1]。
  • 第一个参数表示运行blockioApp 这个软件
  • 第二个参数表示测试APP要打开/dev/blockio这个设备。
  • 以后台模式运行
./blockioApp /dev/blockio &

卸载模块

rmmod blockio.ko

在这里插入图片描述

检查cpu使用情况

输入“top”命令,查看 blockioAPP 这个应用 APP 的 CPU 使用率。降到非常低的一个水平,以至于直接显示为0了
在这里插入图片描述
关闭

  • ps命令查看一下 blockioApp 这个应用程序的 PID
    在这里插入图片描述

      kill -9 84
    

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

成草

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值