10.Linux驱动-阻塞与非阻塞IO实验


阻塞和非阻塞 IO 是 Linux 驱动开发中常见的两种设备访问模式,在编写驱动的时候一定要考虑到阻塞和非阻塞。下面来学习一下阻塞和非阻塞 IO,及如何在驱动程序中处理阻塞与非阻塞,如何在驱动程序使用等待队列和 poll 机制。

1. 阻塞和非阻塞 IO

1.1 阻塞IO

应用程序对驱动进行操作,如果获取不到驱动中的设备资源,会将应用程序对应的线程挂起,直到驱动中设备资源可用,下面图示,应用程序调用 read 函数从设备中读取数据,当设备不可用或数据未准备好的时候就会进入到休眠态。等设备可用的时候就会从休眠态唤醒,然后从设备中读取数据返回给应用程序

在这里插入图片描述

应用程序使用阻塞IO方式访问驱动

fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */

1.2 非阻塞IO

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

在这里插入图片描述

应用程序通过非阻塞IO方式访问驱动

fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */

2.等待队列

2.1 等待队列头

Linux中提供等待队列(wait queue)来实现对阻塞进程进行唤醒的工作,在驱动中使用等待队列,必须先定义并初始化等待队列头,头文件位置为include/linux/wait.h

struct __wait_queue_head {
    
    
	spinlock_t lock;
	struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

初始化等待队列头

void init_waitqueue_head(wait_queue_head_t *q)

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

2.2 等待队列项

等待队列头就相当于一个等待队列的头部,每一个访问设备的进程都是一个等待队列项,当设备不可以用时将这些进程对应的队列项添加到等待队列头中,即添加到等待队列中,结构体 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,表示当前进程

2.3 从等待队列头添加/删除队列项

当设备不可以访问时,需要将等待队列项添加到创建的等待队列头中,只有添加到等待队列头之后进程才可以进行休眠状态,当设备可以访问时将进程对应的队列项从等待队列头中移除即可,

2.3.1 等待队列头添加队列项的API函数

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

  • wait:要加入的等待队列项

  • 返回值:无

2.3.2 移除等待队列项

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

  • wait:要删除的等待队列项。

  • 返回值:无。

3.等待唤醒

当设备可以使用的时候要唤醒进入休眠状态的进程,唤醒函数:

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 状态的进程。

4.等待事件

主动唤醒的同时,也可以设置等待队列等待某个事件,当被 wake_up_interruptible唤醒之后,自己再检查condition条件是否满足,满足则唤醒,否则继续休眠

函数 作用
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,可以被信号 打断。

文章推荐:https://blog.csdn.net/qingkongyeyue/article/details/76302536

5.轮询

应用程序使用轮询的方式对于驱动进行非阻塞访问,应用程序的poll,epoll和select函数用来处理轮询,通过这三个函数查询设备是否可以操作,如果可以操作则向设备中写入或者读取数据,当应用程序调用这三个函数会调用驱动中的poll函数,所以需要在驱动中实现poll函数

5.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 用于监视这些文件的异常。

  • fd_set变量的操作函数

void FD_ZERO(fd_set *set) /*将set的所有位的清0*/
void FD_SET(int fd, fd_set *set)/*将set变量某一个位置1*/
void FD_CLR(int fd, fd_set *set)/*将set变量某一个位置0*/
int FD_ISSET(int fd, fd_set *set)/*测试文件fd是否属于某个集合*/
  • timeout:超时时间,当我们调用 select 函数等待某些文件描述符可以设置超时时间,超时时间使用结构体 timeval 表示,结构体定义如下所示:

    struct timeval {
          
          
    	long tv_sec; /* 秒 */
    	long tv_usec; /* 微妙 */
    };
    

    当 timeout 为 NULL 的时候就表示无限期的等待。

  • 返回值: 0,表示的话就表示超时发生,没有任何文件描述符可以进行操作; -1,发生错误;其他值,可以进行操作的文件描述符个数。

5.1.1 伪代码

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;
 }
}

5.2 poll函数

在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!这个时候就可以使用 poll 函数,poll 函数本质上和 select 没有太大的差别,但是 poll 函数没有最大文件描述符限制, Linux 应用程序中 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 为错误类型

5.2.1 伪代码

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) {
    
     /* 错误 */
		......
	}
}

5.3 epoll函数

selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。为此, epoll应运而生, epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。应用程序需要先使用 epoll_create 函数创建一个 epoll 句柄, epoll_create 函数原型如下:

int epoll_create(int size)
  • size: 从 Linux2.6.8 开始此参数已经没有意义了,随便填写一个大于 0 的值就可以。

  • 返回值: epoll 句柄,如果为-1 的话表示创建失败。

epoll 句柄创建成功以后使用 epoll_ctl 函数向其中添加要监视的文件描述符以及监视的事件, epoll_ctl 函数原型如下所示:

int epoll_ctl(int epfd,
	int op,
	int fd,
	struct epoll_event *event)
  • epfd:要操作的epoll句柄,由epoll_create创建

  • op:表示要对于epfd进行的操作

    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 的值为相应的错误码

5.3.1 epoll_wait

一切都设置好以后应用程序就可以通过 epoll_wait 函数来等待事件的发生,类似 select 函数。 epoll_wait 函数原型如下所示:

int epoll_wait(int epfd,
	struct epoll_event *events,
	int maxevents,
	int timeout)
  1. epfd:前面创建的epfd

  2. events:指向 epoll_event 结构体的数组,当有事件发生的时候 Linux 内核会填写 events,调用者可以根据 events 判断发生了哪些事件

  3. maxevents: events 数组大小,必须大于 0。

  4. timeout: 超时时间,单位为 ms。

  5. 返回值: 0,超时; -1,错误;其他值,准备就绪的文件描述符数量。

6.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 参数。

7.阻塞IO实验

在probe函数中初始化等待队列头,在中断处理程序中使用wake_up唤醒等待队列,应用程序调用read函数时,如果驱动还在休眠状态,则阻塞,等中断程序唤醒,且驱动中read函数wait_event_interruptible的第二个值为真时,则解除休眠,处于运行状态

7.1 驱动程序

1.修改设备树,在根结点下添加

btn-gpio {
    
    
		#address-cells = <1>;
		#size-cells = <1>;
		compatible = "imx-btn";
		pinctrl-names = "default";
		pinctrl-0 = <&pinctrl_key>;
		key-gpio = <&gpio1 18 GPIO_ACTIVE_LOW>; /* KEY0 */
		interrupt-parent = <&gpio1>;
		interrupts = <18 IRQ_TYPE_EDGE_BOTH>; /* FALLING RISING */
		status = "okay";
	};

2.在pinctrl结点下添加

pinctrl_key: keygrp {
    
    
			fsl,pins = <
				MX6UL_PAD_UART1_CTS_B__GPIO1_IO18		0xF080	/* KEY0 */
			>;
		};		

执行make dtbs之后拷贝到tftboot
驱动代码

blockio.h

#ifndef _BLOCK_IO_H
#define _BLOCK_IO_H

#include <linux/types.h>			/*设备号所在头文件*/
#include <linux/module.h>           /*内核模块声明的相关函数*/
#include <linux/init.h>			    /*module_init和module_exit*/
#include <linux/kernel.h>		    /*内核的各种函数*/
#include <asm/io.h>                 /*readl函数*/
#include <linux/cdev.h>             /*cdev*/
#include <linux/device.h>           /*class & device*/
#include <linux/fs.h>
#include <asm/uaccess.h>           /*copy_from_user*/
#include <linux/gpio.h>            /*gpio fileoperation*/
#include <linux/of.h>             
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>
#include <linux/signal.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/wait.h>   
#include <asm/io.h>
#include <linux/compat.h>
#include <linux/sched.h>


#define DEVICE_NAME "btn_driver"
#define DEVICE_CNT   1
#define LED_ON		 1
#define LED_OFF      0


struct btn_data
{
    
    
	dev_t 	devid;		        /*设备号*/
	struct 	cdev cdev;           /*cdev*/
	struct 	class  *class;	    /*类*/
	struct 	device *device;	    /*设备*/
	int    	major;			    /*主设备号*/
	int    	minor;			    /*次设备号*/
    int    	irq;
    int    	gpio_num;
    struct 	device_node* dev_node;
    struct 	timer_list   timer;    /*定时器*/
	wait_queue_head_t 	 wait_queue_head;
};

struct btn_data *data;
#endif

blockio.c

#include "blockio.h"

static irqreturn_t btn_interrupt(int irq, void *handle)
{
    
    
	//硬件消抖
	mod_timer(&data->timer, jiffies +msecs_to_jiffies(10));
    wake_up(&(data->wait_queue_head));	
	return IRQ_HANDLED;
}

static void btn_timer_func(unsigned long handle)
{
    
    
	//printk("%s\n",__func__);
}
//cat
static ssize_t btn_status_show(struct device *dev,struct device_attribute *attr, char *buf)
{
    
    
	int ret;
    ret = gpio_get_value(data->gpio_num);
	printk("%s:ret=%d\n",__func__,ret);
	
    return sprintf(buf, "%d\n", ret);		
}

//声明btn_status文件节点
static DEVICE_ATTR(btn_status, S_IRUSR, btn_status_show,NULL);

static struct attribute *atk_imx6ul_btn_sysfs_attrs[] = {
    
    
	&dev_attr_btn_status.attr,
	NULL,
};


static const struct attribute_group dev_attr_grp = {
    
    
      .attrs = atk_imx6ul_btn_sysfs_attrs,
	  NULL,
};

static int imx6ul_btn_open(struct inode *inode, struct file *file)
{
    
    
	file->private_data = data;
	return 0;
}


static ssize_t imx6ul_btn_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
    
    
	int ret;
	int val;
	char *readbuf = kzalloc(sizeof(char), GFP_KERNEL);
	
	//加入等待队列,等待按键按下才读
	ret = wait_event_interruptible(data->wait_queue_head,!gpio_get_value(data->gpio_num));
	if(ret)
	{
    
    
		printk("%s:wait_event_interruptible failed\n",__func__);
	}
	
    val = gpio_get_value(data->gpio_num);
	
	//将val格式化为字符串
	sprintf(readbuf,"%d\n",val);
	
	ret = copy_to_user(buf,readbuf,1);
	if(ret == 0)
    {
    
    
       
    }else
    {
    
    
        
    }
	
	return 0;
}

static ssize_t imx6ul_btn_write(struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
    
    

	return 0;

}

static int imx6ul_btn_close(struct inode *inode, struct file *file)
{
    
    
	printk(KERN_INFO "imx6ul_btn_close\n");
	//删除异步通知
	return 0;
}

static struct file_operations btn_fops = {
    
    
	.owner   = THIS_MODULE,
	.read    = imx6ul_btn_read,
	.write   = imx6ul_btn_write,
	.open    = imx6ul_btn_open,
	.release = imx6ul_btn_close,
};

static int btn_parse_dt(void)
{
    
    
	int ret;

    /*1.获取设备树中compatible属性的字符串值imx-btn*/
	data->dev_node = of_find_compatible_node(NULL,NULL,"imx-btn");
	if(data->dev_node == NULL)
	{
    
    
		printk("led device node find failed\n");
		return -1;
	}

	/*2.获取gpio编号,将节点中的“key-gpio”属性值转换为对应的gpio编号。*/
	data->gpio_num = of_get_named_gpio(data->dev_node, "key-gpio", 0);
	if(data->gpio_num < 0)
	{
    
    
		printk("failed to get gpio\n");
		return -1;
	}
	
	/*3.申请gpio管脚*/
	ret = gpio_request(data->gpio_num, "btn-gpio");
	if(ret != 0)
	{
    
    
		printk("gpio request failed\n");
		return -1;
	}

	/*4.设置gpio为输入*/
	ret = gpio_direction_input(data->gpio_num);
	if(ret != 0)
	{
    
    
		printk("gpio direction input failed\n");
		return -1;
	}

    /*5.设置gpio为中断gpio,获取gpio的中断号*/
    data->irq = gpio_to_irq(data->gpio_num);
    ret = devm_request_threaded_irq(data->device, data->irq, NULL,
					  btn_interrupt,IRQ_TYPE_EDGE_BOTH| IRQF_ONESHOT,"btn-irq", data);
	if (ret) {
    
    
		printk("Failed to register interrupt\n");
		return -1;
	}

    disable_irq(data->irq);

	return 0;
}


static int btn_driver_probe(struct platform_device *pdev)
{
    
    
	u32 ret;
	
	data = devm_kzalloc(&pdev->dev,sizeof(struct btn_data), GFP_KERNEL);
    if(data == NULL)
    {
    
    
        printk(KERN_ERR"kzalloc data failed\n");
        return -ENOMEM;
    }
	
	/*2.字符设备驱动框架那一套*/
	/*2.1 之前定义了主设备号*/
	if(data->major)
	{
    
    
		/*选择次设备号*/
		data->devid = MKDEV(data->major,0);
		/*注册设备号*/
		ret = register_chrdev_region(data->devid, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0)
		{
    
    
			printk("register_chrdev_region faibtn\n");
			return ret;
		}
	}else
	{
    
    
		/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
		alloc_chrdev_region(&data->devid, 0, DEVICE_CNT,  DEVICE_NAME); /* 申请设备号 */
		data->major = MAJOR(data->devid); /* 获取分配号的主设备号 */
		data->minor = MINOR(data->devid); /* 获取分配号的次设备号 */
	}

	data->cdev.owner = THIS_MODULE;
	cdev_init(&data->cdev,&btn_fops);
	
	/*自动创建设备结点,在/dev目录下体现*/
	ret = cdev_add(&data->cdev,data->devid,DEVICE_CNT);
	if(ret < 0)
	{
    
    
		printk("cdev_add device faibtn\n");
		goto fail_cdev_add;
	}

	data->class = class_create(THIS_MODULE,DEVICE_NAME);
	if(IS_ERR(data->class))
	{
    
    
		printk("class creat faibtn\n");
		goto fail_class_create;
	}

	/*生成dev/DEVICE_NAME文件*/
	data->device = device_create(data->class,NULL,data->devid,NULL,DEVICE_NAME);
	if(IS_ERR(data->device))
	{
    
    
		printk("device class faibtn\n");
		goto fail_device_create;
	}

    /*创建led_enable结点,直接通过系统调用来操作驱动*/
	ret = sysfs_create_group(&data->device->kobj,&dev_attr_grp);
	if(ret)
	{
    
    
		printk("failed to create sys files\n");
		goto fail_sys_create;
	}
	
		
	/*1.设备树解析*/
	ret = btn_parse_dt();
	if (ret < 0) {
    
    
		printk("btn parse error");
		return ret;
	}
	
    //struct data *device = dev_get_drvdata(dev);
    /*初始化定时器,工作队列*/
    //INIT_WORK(&data.btn_work,btn_exchange_work);
    setup_timer(&data->timer,btn_timer_func,(unsigned long)data);//最后一个值可以传指针
	
	init_waitqueue_head(&(data->wait_queue_head));

    enable_irq(data->irq);
	
    /*激活定时器,add_timer不激活定时器*/
    mod_timer(&data->timer, jiffies +msecs_to_jiffies(0));

	
	printk("%s:probe success\n",__func__);
    
	return 0;
fail_cdev_add:
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	return -1;
fail_class_create:
	cdev_del(&data->cdev);
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	return -1;
fail_device_create:
	cdev_del(&data->cdev);
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	class_destroy(data->class);
    return -1;
fail_sys_create:    
    sysfs_remove_group(&data->device->kobj,&dev_attr_grp);
    cdev_del(&data->cdev);
    unregister_chrdev_region(data->devid,DEVICE_CNT);
    class_destroy(data->class);    
    return -1;
}

static int btn_driver_remove(struct platform_device *pdev)
{
    
    

    //依赖device,先删除
	sysfs_remove_group(&data->device->kobj, &dev_attr_grp);
    cdev_del(&data->cdev);
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	/*依赖于class所以先删除*/
	device_destroy(data->class, data->devid);
	class_destroy(data->class);
	
    //删除定时器
    del_timer_sync(&data->timer);
	
	gpio_free(data->gpio_num);
	return 0;
}

/* 匹配列表,btn_of_match中的compatible与设备树中的
 * compatible匹配,匹配成功则跑probe函数
 */
 static const struct of_device_id btn_of_match[] = {
    
    
     {
    
     .compatible = "imx-btn" },
     {
    
     /* Sentinel */ }
 };

 /*
 * platform 平台驱动结构体
 */
 static struct platform_driver btn_driver = {
    
    
	.driver = {
    
    
	    .name = "imx-btn",
	    .of_match_table = btn_of_match,
     },
	.probe  = btn_driver_probe,
	.remove = btn_driver_remove,
 };

module_platform_driver(btn_driver);

MODULE_AUTHOR("fib");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("btn driver of atk imx6ull");

Makefile:

KERNELDIR := /home/klz/linux/linux-4.1.15

CURRENT_PATH := $(shell pwd)
obj-m := noblockio.o

build: kernel_modules

kernel_modules:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
copy:
	sudo cp *.ko /home/klz/linux/nfs/rootfs/lib/modules/4.1.15
	
clean:
	$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean	

执行make && make copy拷贝到根文件系统下

应用程序:

blockIoApp.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"
#include "signal.h"


static int fd  = 0; /* 文件描述符 */ 


 /*
 * @description : main 主程序
 * @param - argc : argv 数组元素个数
 * @param - argv : 具体参数
 * @return : 0 成功;其他 失败
 */
 int main(int argc, char *argv[])
 {
    
    
	int flags = 0;
	char *filename;
	int ret = 0;
	unsigned char buf[1];

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

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if (fd < 0) {
    
    
		printf("Can't open file %s\r\n", filename);
		return -1;
	}

	 while(1) {
    
    
		ret = read(fd,buf,sizeof(buf));
		if(ret < 0)
		{
    
    
	   		 printf("read buf is unvailed\n");
		}
		else
		{
    
    
			buf[0] = atoi((const char*)&buf[0]);
			printf("key_value=%d,key_status=%s\n",buf[0],buf[0]?"Release":"Touch");
		}
	}

 close(fd);
 return 0;
}

8.非阻塞IO实验

非阻塞IO需要在驱动中实现poll函数,在应用程序中调用poll,select函数实际上都会调用到底层驱动的poll函数,当没有数据可读时,返回错误码

8.1驱动代码

设备树同上

驱动代码:

noblockio.h

#ifndef _BLOCK_IO_H
#define _BLOCK_IO_H

#include <linux/types.h>			/*设备号所在头文件*/
#include <linux/module.h>           /*内核模块声明的相关函数*/
#include <linux/init.h>			    /*module_init和module_exit*/
#include <linux/kernel.h>		    /*内核的各种函数*/
#include <asm/io.h>                 /*readl函数*/
#include <linux/cdev.h>             /*cdev*/
#include <linux/device.h>           /*class & device*/
#include <linux/fs.h>
#include <asm/uaccess.h>           /*copy_from_user*/
#include <linux/gpio.h>            /*gpio fileoperation*/
#include <linux/of.h>             
#include <linux/of_gpio.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/fcntl.h>
#include <linux/signal.h>
#include <linux/irq.h>
#include <linux/interrupt.h>
#include <linux/slab.h>
#include <linux/wait.h>   
#include <asm/io.h>
#include <linux/compat.h>
#include <linux/sched.h>
#include <linux/poll.h>


#define DEVICE_NAME "no_blockio"
#define DEVICE_CNT   1

struct btn_data
{
    
    
	dev_t 	devid;		        /*设备号*/
	struct 	cdev cdev;           /*cdev*/
	struct 	class  *class;	    /*类*/
	struct 	device *device;	    /*设备*/
	int    	major;			    /*主设备号*/
	int    	minor;			    /*次设备号*/
    int    	irq;
    int    	gpio_num;
    struct 	device_node* dev_node;
    struct 	timer_list   timer;    /*定时器*/
	wait_queue_head_t 	 wait_queue_head;
};

struct btn_data *data;
#endif

noblockio.c

#include "noblockio.h"

static irqreturn_t btn_interrupt(int irq, void *handle)
{
    
    
	//硬件消抖
	mod_timer(&data->timer, jiffies +msecs_to_jiffies(10));
	if(!gpio_get_value(data->gpio_num))
	{
    
    
        wake_up(&(data->wait_queue_head));	
	}
	
	return IRQ_HANDLED;
}

static void btn_timer_func(unsigned long handle)
{
    
    
	//printk("%s\n",__func__);
}
//cat
static ssize_t btn_status_show(struct device *dev,struct device_attribute *attr, char *buf)
{
    
    
	int ret;
       ret = gpio_get_value(data->gpio_num);
	printk("%s:ret=%d\n",__func__,ret);
	
    return sprintf(buf, "%d\n", ret);		
}

//声明btn_status文件节点
static DEVICE_ATTR(btn_status, S_IRUSR, btn_status_show,NULL);

static struct attribute *atk_imx6ul_btn_sysfs_attrs[] = {
    
    
	&dev_attr_btn_status.attr,
	NULL,
};


static const struct attribute_group dev_attr_grp = {
    
    
      .attrs = atk_imx6ul_btn_sysfs_attrs,
       NULL,
};

static int imx6ul_btn_open(struct inode *inode, struct file *file)
{
    
    
	file->private_data = data;
	return 0;
}


static ssize_t imx6ul_btn_read(struct file *file, char __user *buf, size_t cnt, loff_t * loff)
{
    
    
	int ret;
	int val;
	char *readbuf = kzalloc(sizeof(char), GFP_KERNEL);
	
	if(file->f_flags & O_NONBLOCK)//如果为非阻塞访问
	{
    
    
		if(!gpio_get_value(data->gpio_num))
		{
    
    
			//按键没有按下则返回其他值
			return -EAGAIN;
		}
	}
	else//阻塞访问
	{
    
    
		//加入等待队列,等待按键按下才读
		ret = wait_event_interruptible(data->wait_queue_head,!gpio_get_value(data->gpio_num));
		if(ret)
		{
    
    
			printk("%s:wait_event_interruptible failed\n",__func__);
		}
	}
	
    val = gpio_get_value(data->gpio_num);
	
	//将val格式化为字符串
	sprintf(readbuf,"%d\n",val);
	
	ret = copy_to_user(buf,readbuf,1);
	if(ret == 0)
        {
    
    
       
        }else
        {
    
    
        
        }
	
	return 0;
}

static ssize_t imx6ul_btn_write(struct file *file, const char __user *buf, size_t cnt, loff_t *loff)
{
    
    

	return 0;

}

static int imx6ul_btn_close(struct inode *inode, struct file *file)
{
    
    
	printk(KERN_INFO "imx6ul_btn_close\n");
	return 0;
}

/*
* @description : poll 函数,用于处理非阻塞访问
* @param - filp : 要打开的设备文件(文件描述符)
* @param - wait : 等待列表(poll_table)
* @return : 设备或者资源状态,
*/
static unsigned int imx6ul_btn_poll(struct file *filp,struct poll_table_struct *wait)
{
    
    
	unsigned int mask = 0;//返回的资源状态
    //将等待队列头添加到 poll_table 中
	poll_wait(filp,&data->wait_queue_head,wait);
	
	//按键按下表示有数据可读
	if(!gpio_get_value(data->gpio_num))
	{
    
    
		mask = POLLIN | POLLRDNORM;//返回资源状态
	}
	
	return mask;
}


static struct file_operations btn_fops = {
    
    
	.owner   = THIS_MODULE,
	.read    = imx6ul_btn_read,
	.write   = imx6ul_btn_write,
	.open    = imx6ul_btn_open,
	.poll 	 = imx6ul_btn_poll,
	.release = imx6ul_btn_close,
};

static int btn_parse_dt(void)
{
    
    
	int ret;

        /*1.获取设备树中compatible属性的字符串值imx-btn*/
	data->dev_node = of_find_compatible_node(NULL,NULL,"imx-btn");
	if(data->dev_node == NULL)
	{
    
    
		printk("led device node find failed\n");
		return -1;
	}

	/*2.获取gpio编号,将节点中的“key-gpio”属性值转换为对应的gpio编号。*/
	data->gpio_num = of_get_named_gpio(data->dev_node, "key-gpio", 0);
	if(data->gpio_num < 0)
	{
    
    
		printk("failed to get gpio\n");
		return -1;
	}
	
	/*3.申请gpio管脚*/
	ret = gpio_request(data->gpio_num, "btn-gpio");
	if(ret != 0)
	{
    
    
		printk("gpio request failed\n");
		return -1;
	}

	/*4.设置gpio为输入*/
	ret = gpio_direction_input(data->gpio_num);
	if(ret != 0)
	{
    
    
		printk("gpio direction input failed\n");
		return -1;
	}

       /*5.设置gpio为中断gpio,获取gpio的中断号*/
      data->irq = gpio_to_irq(data->gpio_num);
      ret = devm_request_threaded_irq(data->device, data->irq, NULL,
					  btn_interrupt,IRQ_TYPE_EDGE_BOTH| IRQF_ONESHOT,"btn-irq", data);
	if (ret) {
    
    
		printk("Failed to register interrupt\n");
		return -1;
	}

        disable_irq(data->irq);

	return 0;
}


static int btn_driver_probe(struct platform_device *pdev)
{
    
    
	u32 ret;
	
	data = devm_kzalloc(&pdev->dev,sizeof(struct btn_data), GFP_KERNEL);
        if(data == NULL)
        {
    
    
            printk(KERN_ERR"kzalloc data failed\n");
            return -ENOMEM;
        }
	
	/*2.字符设备驱动框架那一套*/
	/*2.1 之前定义了主设备号*/
	if(data->major)
	{
    
    
		/*选择次设备号*/
		data->devid = MKDEV(data->major,0);
		/*注册设备号*/
		ret = register_chrdev_region(data->devid, DEVICE_CNT, DEVICE_NAME);
		if(ret < 0)
		{
    
    
			printk("register_chrdev_region faibtn\n");
			return ret;
		}
	}else
	{
    
    
		/*向内核申请主次设备号,DEVICE_NAME体现在/proc/devices*/
		alloc_chrdev_region(&data->devid, 0, DEVICE_CNT,  DEVICE_NAME); /* 申请设备号 */
		data->major = MAJOR(data->devid); /* 获取分配号的主设备号 */
		data->minor = MINOR(data->devid); /* 获取分配号的次设备号 */
	}

	data->cdev.owner = THIS_MODULE;
	cdev_init(&data->cdev,&btn_fops);
	
	/*自动创建设备结点,在/dev目录下体现*/
	ret = cdev_add(&data->cdev,data->devid,DEVICE_CNT);
	if(ret < 0)
	{
    
    
		printk("cdev_add device faibtn\n");
		goto fail_cdev_add;
	}

	data->class = class_create(THIS_MODULE,DEVICE_NAME);
	if(IS_ERR(data->class))
	{
    
    
		printk("class creat faibtn\n");
		goto fail_class_create;
	}

	/*生成dev/DEVICE_NAME文件*/
	data->device = device_create(data->class,NULL,data->devid,NULL,DEVICE_NAME);
	if(IS_ERR(data->device))
	{
    
    
		printk("device class faibtn\n");
		goto fail_device_create;
	}

    /*创建led_enable结点,直接通过系统调用来操作驱动*/
	ret = sysfs_create_group(&data->device->kobj,&dev_attr_grp);
	if(ret)
	{
    
    
		printk("failed to create sys files\n");
		goto fail_sys_create;
	}
	
		
	/*1.设备树解析*/
	ret = btn_parse_dt();
	if (ret < 0) {
    
    
		printk("btn parse error");
		return ret;
	}
	
    //struct data *device = dev_get_drvdata(dev);
    /*初始化定时器,工作队列*/
    //INIT_WORK(&data.btn_work,btn_exchange_work);
    setup_timer(&data->timer,btn_timer_func,(unsigned long)data);//最后一个值可以传指针
	
	init_waitqueue_head(&(data->wait_queue_head));

    enable_irq(data->irq);
	
    /*激活定时器,add_timer不激活定时器*/
    mod_timer(&data->timer, jiffies +msecs_to_jiffies(0));

	
	printk("%s:probe success\n",__func__);
    
	return 0;
fail_cdev_add:
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	return -1;
fail_class_create:
	cdev_del(&data->cdev);
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	return -1;
fail_device_create:
	cdev_del(&data->cdev);
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	class_destroy(data->class);
    return -1;
fail_sys_create:    
    sysfs_remove_group(&data->device->kobj,&dev_attr_grp);
    cdev_del(&data->cdev);
    unregister_chrdev_region(data->devid,DEVICE_CNT);
    class_destroy(data->class);    
    return -1;
}

static int btn_driver_remove(struct platform_device *pdev)
{
    
    

    //依赖device,先删除
	sysfs_remove_group(&data->device->kobj, &dev_attr_grp);
    cdev_del(&data->cdev);
	unregister_chrdev_region(data->devid,DEVICE_CNT);
	/*依赖于class所以先删除*/
	device_destroy(data->class, data->devid);
	class_destroy(data->class);
	
    //删除定时器
    del_timer_sync(&data->timer);
	
	gpio_free(data->gpio_num);
	return 0;
}

/* 匹配列表,btn_of_match中的compatible与设备树中的
 * compatible匹配,匹配成功则跑probe函数
 */
 static const struct of_device_id btn_of_match[] = {
    
    
     {
    
     .compatible = "imx-btn" },
     {
    
     /* Sentinel */ }
 };

 /*
 * platform 平台驱动结构体
 */
 static struct platform_driver btn_driver = {
    
    
	.driver = {
    
    
	    .name = "imx-btn",
	    .of_match_table = btn_of_match,
     },
	.probe  = btn_driver_probe,
	.remove = btn_driver_remove,
 };

module_platform_driver(btn_driver);

MODULE_AUTHOR("fib");
MODULE_LICENSE("GPL v2");
MODULE_DESCRIPTION("btn driver of atk imx6ull");

应用程序为:no_blockApp.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"
#include "signal.h"


static int fd  = 0; /* 文件描述符 */ 


 /*
 * @description : main 主程序
 * @param - argc : argv 数组元素个数
 * @param - argv : 具体参数
 * @return : 0 成功;其他 失败
 */
 int main(int argc, char *argv[])
 {
    
    
	int flags = 0;
	char *filename;
	int ret = 0;
	fd_set readfds;	/*读操作文件描述符*/
	struct pollfd fds;
	struct timeval timeout; /* 超时结构体 */
	unsigned char buf[1];

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

	filename = argv[1];
	fd = open(filename, O_RDWR);
	if (fd < 0) {
    
    
		printf("Can't open file %s\r\n", filename);
		return -1;
	}
    
	
	//传入第三个参数为0,调用select
	if(atoi(argv[2]) == 0)
	{
    
    
	
		 while(1) 
		 {
    
    
			FD_ZERO(&readfds);//清空读文件操作符使用位为0
			FD_SET(fd,&readfds);//将fd添加到读文件操作符中
		
			/*构建超时时间*/
			timeout.tv_sec = 0;
			timeout.tv_usec = 500000; /* 500ms */
			
			ret = select(fd+1,&readfds,NULL,NULL,&timeout);
			switch(ret)
			{
    
    
				case 0://超时
				{
    
    
					printf("select timeout\n");
					break;
				}
				case -1://错误
				{
    
    
					printf("select error\n");
					break;
				}
				default:
				{
    
    
					if(FD_ISSET(fd,&readfds))//判断fd是否需要读集合
					{
    
    
						ret = read(fd,buf,sizeof(buf));
						if(ret < 0)
						{
    
    
							printf("read buf is unvailed\n");
						}
						else
						{
    
    
							buf[0] = atoi((const char*)&buf[0]);
							printf("key_value=%d,key_status=%s\n",buf[0],buf[0]?"Release":"Touch");
						}
					}
					break;
				}
			}
			
		}
	
	}
	else if(atoi(argv[2]) == 1)
	{
    
    
		fds.fd 	   = fd;//要检测的文件描述符
		fds.events = POLLIN;
		
		while(1)
		{
    
    
			ret = poll(&fds,1,500);//监视的文件描述符数量为1,超时时间为500ms
			switch(ret)
			{
    
    
				case 0://超时
				{
    
    
					printf("poll timeout\n");
					break;
				}
				case -1://错误
				{
    
    
					printf("poll error\n");
					break;
				}
				default:
				{
    
    
					ret = read(fd,buf,sizeof(buf));
					if(ret < 0)
					{
    
    
						printf("read buf is unvailed\n");
					}
					else
					{
    
    
						buf[0] = atoi((const char*)&buf[0]);
						printf("key_value=%d,key_status=%s\n",buf[0],buf[0]?"Release":"Touch");
					}
					break;
				}
			}
		}
	}

 close(fd);
 return 0;
}

9.测试

./no_blockApp /dev/no_blockio 0 //调用select
./no_blockApp /dev/no_blockio 1 //调用poll

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43824344/article/details/120253266