Linux 非阻塞IO实验-基于正点原子IMX6ULL开发板

1 非阻塞 IO简介

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

在这里插入图片描述
应用程序要采用非阻塞的方式来访问驱动设备文件,可以使用如下所示代码:

 int fd; 
 int data = 0;

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

使用 open 函数打开“/dev/xxx_dev”设备文件的时候添加了参数“O_NONBLOCK”,表示以非阻塞方式打开设备,这样从设备中读取数据的时候就是非阻塞方式的了。

2 应用程序select

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

3 应用程序poll函数

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

4 Linux 驱动下的 poll 操作函数

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

**当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。**所以驱动程序的编写者需要提供对应的 poll 函数,poll 函数原型如下所示:

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

我们需要在驱动程序的 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 参数。

5 驱动程序

imx6uirq.c 文件内容如下

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_irq.h>
#include <linux/gpio.h>
#include <linux/of_gpio.h>
#include <linux/string.h>
#include <linux/irq.h>
#include <asm/mach/map.h>
#include <asm/uaccess.h>
#include <asm/io.h>
#include <linux/interrupt.h>
#include <linux/wait.h>
#include <linux/ide.h>
#include <linux/poll.h>

#define IMX6UIRQ_CNT 1
#define IMX6UIRQ_NAME "noblockio"
#define KEY_NUM 1
#define KEY0VALUE 0x01
#define INVAKEY 0xFF

/*key 结构体*/
struct irq_keydesc
{
    int gpio;                            /*io 编号*/
    int irqnum;                          /*中断号*/
    unsigned char value;                 /*键值*/
    char name[10];                       /*名字*/
    irqreturn_t (*handler)(int, void *); /*中断处理函数*/
};

/*imx6uirq 设备结构体*/
struct imx6uirq_dev
{
    dev_t devid;
    int major;
    int minor;
    struct cdev cdev;
    struct class *class;
    struct device *device;
    struct device_node *nd;
    struct irq_keydesc irqkey[KEY_NUM];
    struct timer_list timer;

    atomic_t keyvalue;
    atomic_t releasekey;

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

struct imx6uirq_dev imx6uirq; /*irq设备 */

static int imx6uirq_open(struct inode *inode, struct file *filp)
{
    filp->private_data = &imx6uirq;
    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;
    unsigned char releasekey;
    struct imx6uirq_dev *dev = filp->private_data;

    if (filp->f_flags & O_NONBLOCK) /*新增重点:非阻塞访问*/
    {
        if (atomic_read(&dev->releasekey) == 0)/*没有按键按下*/
        {
            return -EAGAIN;
        }
    }
    else /*阻塞*/
    {
        /*等待事件*/
        wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); /*等待按键有效*/
    }

#if 0
    /*等待事件*/
    wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); /*等待按键有效*/
#endif

#if 0
    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)) /*signal_pending 函数用于判断当前进程是否有信号处理,
                                    返回值不为0的话表示有信号需要处理*/
        {
            ret = -ERESTARTSYS;
            goto data_error;
        }
        __set_current_state(TASK_RUNNING);      /*将当前任务设置为运行状态*/
        remove_wait_queue(&dev->r_wait, &wait); /*将对应的队列项从等待队列头删除*/
    }
#endif

#if 0
    DECLARE_WAITQUEUE(wait, current);        /*定义一个等待队列项*/
    add_wait_queue(&dev->r_wait, &wait);     /*将队列项添加到等待队列头*/
    __set_current_state(TASK_INTERRUPTIBLE); /*当前进程设置为可被打断的状态*/
    schedule();                              /*切换*/

    /*唤醒以后从这里运行*/
    if (signal_pending(current)) /*signal_pending 函数用于判断当前进程是否有信号处理,
                                    返回值不为0的话表示有信号需要处理*/
    {
        ret = -ERESTARTSYS;
        goto data_error;
    }
#endif

    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); /*按下标志清0*/
    }
    else
    {
        goto data_error;
    }

data_error:
#if 0
    __set_current_state(TASK_RUNNING);      /*将当前任务设置为运行状态*/
    remove_wait_queue(&dev->r_wait, &wait); /*将对应的队列项从等待队列头删除*/
#endif
    return ret;
}

 /*
 * @description : poll 函数,用于处理非阻塞访问
* @param - filp : 要打开的设备文件(文件描述符)
* @param - wait : 等待列表(poll_table)
* @return : 设备或者资源状态,
 */

/*重点:当应用程序调用 select 或者 poll 函数的时候 imx6uirq_poll 函数就会执行
*
*/

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

    poll_wait(filp, &dev->r_wait, wait);//将等待队列头添加到 poll_table 中

    /*判断是否可读*/
    if (atomic_read(&dev->releasekey)) /*按键按下,可读*/
    {
        mask |= POLLIN | POLLRDNORM; /*返回POLLIN*/
    }
    return mask;
}

/*操作集*/
static const struct file_operations imx6uirq_fops = {
    .owner = THIS_MODULE,
    .open = imx6uirq_open,
    .read = imx6uirq_read,
    .poll = imx6uirq_poll
};

/*按键中断处理函数*/
static irqreturn_t key0_handler(int irq, void *dev_id)
{
    struct imx6uirq_dev *dev = dev_id;

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

/*定时器处理函数*/
static void timer_func(unsigned long arg)
{
    int value = 0;
    struct imx6uirq_dev *dev = (struct imx6uirq_dev *)arg;

    value = gpio_get_value(dev->irqkey[0].gpio);
    if (value == 0) /*按下*/
    {
        atomic_set(&dev->keyvalue, dev->irqkey[0].value);
    }
    else if (value == 1) /*释放*/
    {
        atomic_set(&dev->keyvalue, 0x80 | (dev->irqkey[0].value));
        atomic_set(&dev->releasekey, 1); /*完整的按键过程*/
    }

    /*唤醒进程*/
    if (atomic_read(&dev->releasekey))
    {
        wake_up(&dev->r_wait);
    }
}

/*按键初始化*/
static int keyio_init(struct imx6uirq_dev *dev)
{
    int ret = 0;
    int i = 0;
    /*1,按键初始化*/
    dev->nd = of_find_node_by_path("/key");
    if (dev->nd == NULL)
    {
        ret = -EINVAL;
        goto fail_nd;
    }

    for (i = 0; i < KEY_NUM; i++)
    {
        dev->irqkey[i].gpio = of_get_named_gpio(dev->nd, "key-gpios", i);
    }

    for (i = 0; i < KEY_NUM; i++)
    {
        memset(dev->irqkey[i].name, 0, sizeof(dev->irqkey[i].name));
        sprintf(dev->irqkey[i].name, "KEY%d", i);
        gpio_request(dev->irqkey[i].gpio, dev->irqkey[i].name);
        gpio_direction_input(dev->irqkey[i].gpio);

        dev->irqkey[i].irqnum = gpio_to_irq(dev->irqkey[i].gpio); /*获取中断号*/
#if 0   
        dev->irqkey[i].irqnum = irq_of_parse_and_map(dev->nd,i);
#endif
    }

    dev->irqkey[0].handler = key0_handler;
    dev->irqkey[0].value = KEY0VALUE;
    /*2,按键中断初始化*/
    for (i = 0; i < KEY_NUM; i++)
    {
        ret = request_irq(dev->irqkey[i].irqnum, dev->irqkey[0].handler,
                          IRQF_TRIGGER_RISING | IRQF_TRIGGER_FALLING,
                          dev->irqkey[i].name, &imx6uirq);
        if (ret)
        {
            printk("irq %d failed!\r\n", dev->irqkey[i].irqnum);
            goto fail_irq;
        }
    }

    /*初始化定时器*/
    init_timer(&imx6uirq.timer);
    imx6uirq.timer.function = timer_func;
    return 0;

fail_irq:
    for (i = 0; i < KEY_NUM; i++)
    {
        gpio_free(dev->irqkey[i].gpio);
    }
fail_nd:
    return ret;
}

/*驱动入口函数*/
static int __init imx6uirq_init(void)
{
    int ret = 0;

    /*1,注册字符设备驱动*/
    imx6uirq.major = 0;
    if (imx6uirq.major)
    { /*给定主设备号*/
        imx6uirq.devid = MKDEV(imx6uirq.major, 0);
        ret = register_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
    }
    else
    {
        ret = alloc_chrdev_region(&imx6uirq.devid, 0, IMX6UIRQ_CNT, IMX6UIRQ_NAME);
        imx6uirq.major = MAJOR(imx6uirq.devid);
        imx6uirq.minor = MINOR(imx6uirq.devid);
    }

    if (ret < 0)
    {
        goto fail_devid;
    }

    printk("imx6uirq major =%d, minor =%d \r\n", imx6uirq.major, imx6uirq.minor);

    /*2,初始化cdev*/
    imx6uirq.cdev.owner = THIS_MODULE;
    cdev_init(&imx6uirq.cdev, &imx6uirq_fops);

    /*3,添加cdev*/
    ret = cdev_add(&imx6uirq.cdev, imx6uirq.devid, IMX6UIRQ_CNT);
    if (ret)
    {
        goto fail_cdevadd;
    }
    /*4,创建类*/
    imx6uirq.class = class_create(THIS_MODULE, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.class))
    {
        ret = PTR_ERR(imx6uirq.class);
        goto fail_class;
    }
    /*5,创建设备*/
    imx6uirq.device = device_create(imx6uirq.class, NULL, imx6uirq.devid, NULL, IMX6UIRQ_NAME);
    if (IS_ERR(imx6uirq.device))
    {
        ret = PTR_ERR(imx6uirq.device);
        goto fail_device;
    }

    /*初始化IO*/
    ret = keyio_init(&imx6uirq);
    if (ret < 0)
    {
        goto fail_keyinit;
    }

    /*初始化原子变量*/
    atomic_set(&imx6uirq.keyvalue, INVAKEY);
    atomic_set(&imx6uirq.releasekey, 0);

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

fail_keyinit:

fail_device:
    class_destroy(imx6uirq.class);
fail_class:
    cdev_del(&imx6uirq.cdev);
fail_cdevadd:
    unregister_chrdev_region(imx6uirq.devid, IMX6UIRQ_CNT);
fail_devid:

    return ret;
}

/*驱动出口函数*/
static void __exit imx6uirq_exit(void)
{
    int i = 0;
    /*1、释放中断*/
    for (i = 0; i < KEY_NUM; i++)
    {
        free_irq(imx6uirq.irqkey[i].irqnum, &imx6uirq);
    }

    /*2、释放IO*/
    for (i = 0; i < KEY_NUM; i++)
    {
        gpio_free(imx6uirq.irqkey[i].gpio);
    }

    /*3、删除定时器*/
    del_timer(&imx6uirq.timer);

    /*注销字符设备驱动*/
    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");
MODULE_AUTHOR("supersmart");

6 测试APP

.imx6uirqAPP 测试应用程序内容如下

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

/*
 *argc:应用程序参数个数
 * argv[]:具体的参数内容,字符串形式
 * ./imx6uirqAPP <filename> <0:1> 0 关灯,1 开灯
 * ./imx6uirqAPP /dev/imx6uirq
 */

#define CLOSE_CMD _IO(0xEF, 1)           //关闭命令
#define OPEN_CMD _IO(0xEF, 2)            //打开命令
#define SETPERIOD_CMD _IOW(0xEF, 3, int) //设置周期

int main(int argc, char *argv[])
{
    //fd_set readfds;
    struct pollfd fds;

    int fd, ret;
    char *filename;
    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("file %s open failed!\r\n", filename);
        return -1;
    }

#if 0
    /*循环读取*/
    while (1)
    {
        FD_ZERO(&readfds);
        FD_SET(fd, &readfds);

        timeout.tv_sec = 1;
        timeout.tv_usec = 0; /*1s*/

        ret = select(fd + 1, &readfds, NULL, NULL, &timeout);
        switch (ret)
        {
        case 0: /*超时*/
            printf("select timeout! \r\n");
            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 = %#x \r\n", data);
                    }
                }
            }
            break;
        }
    }
#endif

    /*循环读取*/
    while (1)
    {
        fds.fd = fd;
        fds.events = POLLIN;

        ret = poll(&fds, 1, 500); /*超时500ms*/
        if (ret == 0) /*超时*/
        {
            printf("timeout \r\n");
        }
        else if (ret < 0) /*错误*/
        {
        }
        else /*可读取*/
        {
            if (fds.revents | POLLIN) /*可读取*/
            {
                ret = read(fd, &data, sizeof(data));
                if (ret < 0)
                {
                }
                else
                {
                    if (data)
                    {
                        printf("key value = %#x \r\n", data);
                    }
                }
            }
        }
    }
    close(fd);
    return 0;
}

7 测试验证(略)

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值