阻塞IO与非阻塞IO

参考资料:
《正点原子Linux驱动教程》
 

阻塞IO:

阻塞IO简单理解:用户进程通过read函数读取内核相应设备的file_operations.read函数,举个例子:
 1 #include <linux/module.h>
 2 #include <linux/kernel.h>
 3 #include <linux/fs.h>
 4 #include <linux/uaccess.h>
 5 
 6 #define DEVICE_NAME "blockio_demo"
 7 #define BUF_SIZE 1024
 8 
 9 static char buffer[BUF_SIZE] = "Hello, this is a block IO demo!\n";
10 static int major_num;
11 
12 static ssize_t blockio_read(struct file *file, char __user *user_buf, size_t count, loff_t *ppos)
13 {
14     ssize_t bytes_read = 0;
15 
16     // Simulate blocking IO by adding a delay
17     msleep(2000); // Block for 2000 milliseconds (2 seconds)
18 
19     if (*ppos >= BUF_SIZE) {
20         return 0; // EOF
21     }
22 
23     bytes_read = min(count, (size_t)(BUF_SIZE - *ppos));
24 
25     if (copy_to_user(user_buf, buffer + *ppos, bytes_read) != 0) {
26         return -EFAULT; // Error copying data to user space
27     }
28 
29     *ppos += bytes_read;
30 
31     return bytes_read;
32 }
33 
34 static struct file_operations fops = {
35     .read = blockio_read,
36 };
37 
38 static int __init blockio_init(void)
39 {
40     major_num = register_chrdev(0, DEVICE_NAME, &fops);
41 
42     if (major_num < 0) {
43         printk(KERN_ALERT "Failed to register a major number\n");
44         return major_num;
45     }
46 
47     printk("Block IO demo module loaded with major number: %d\n", major_num);
48 
49     return 0;
50 }
51 
52 static void __exit blockio_exit(void)
53 {
54     unregister_chrdev(major_num, DEVICE_NAME);
55     printk("Block IO demo module unloaded\n");
56 }
57 
58 module_init(blockio_init);
59 module_exit(blockio_exit);
60 
61 MODULE_LICENSE("GPL");
62 MODULE_AUTHOR("lethe1203");
63 MODULE_DESCRIPTION("block IO");
在第17行中,使用了msleep函数进行休眠,应用程序如果read该设备节点,单CPU的情况下去看CPU占用情况,发现CPU利用率其实很低的,这就是因为msleep进入了休眠。2s休眠结束进程继续工作
 

非阻塞IO:

非阻塞IO工作原理如下:
应用程序使用非阻塞访问方式从设备读取数据,当设备不可用或者数据未准备好时会立即向内核返回一个错误码,表示数据读取失败。应用程序会再次重新读取数据,这样一直往复循环,直到数据读取成功
在file_operations中有一个poll函数,该函数就是用来实现非阻塞访问方式的
同时,应用程序非阻塞方式访问可调用select、poll、epoll等函数
 
正点原子imx6ull测试demo:
#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);
    }

    /* 申请中断 */
    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(filp, &dev->r_wait, wait);    /* 将等待队列头添加到poll_table中 */
    
    if(atomic_read(&dev->releasekey)) {        /* 按键按下 */
        mask = POLLIN | POLLRDNORM;            /* 返回PLLIN */
    }
    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);
        gpio_free(imx6uirq.irqkeydesc[i].gpio);
    }
    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");

 

应用测试demo:
#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;
    }


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

 

 
posted @ 2024-03-25 22:45  lethe1203  阅读(23)  评论(0编辑  收藏  举报