阻塞IO與非阻塞IO

lethe1203發表於2024-03-25
參考資料:
《正點原子Linux驅動教程》
等待佇列可參考:https://www.cnblogs.com/lethe1203/p/18092974

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

相關文章