T12 阻塞與非阻塞IO

1 阻塞與非阻塞

阻塞I/O與非阻塞I/O是設備訪問的兩種不一樣模式,驅動程序能夠靈活的支持用戶空間對設備的這兩種訪問方式。node

  • 阻塞操做:指的是執行設備操做的時候若是不能得到資源,那麼就掛起進程直到知足操做條件以後再進行操做。被掛起的進程進而進入休眠狀態,被調度器移走,直到可以獲取資源後才繼續執行
  • 非阻塞操做:進程在不能進行設備操做的時候並不會掛起,它可能直接返回或者放棄,或者不斷地查詢,直到能夠進行操做。非阻塞應用程序一般使用select系統調用查詢是否能夠對設備進行無阻塞的訪問最終會引起設備驅動中poll函數執行
  • 注意:阻塞與非阻塞是設備訪問的兩種方式,在寫阻塞與非阻塞驅動的時候,常常用到等待隊列

2 等待隊列

  • 在linux設備驅動中阻塞進程可使用等待隊列來實現。
  • 在內核中,等待隊列是有不少用處的,尤爲是在中斷處理進程同步定時等場合,可使用等待隊列實現阻塞進程的喚醒。它以隊列爲基礎數據結構,與進程調度機制緊密結合,可以用於實現內核中的異步事件通知機制,同步對系統資源的訪問

2.1 等待隊列API

  • 在linux中等待隊列的結構以下:
struct __wait_queue_head {
    spinlock_t lock;			//自旋鎖,用來對task_list鏈表起保護做用,實現了等待隊列的互斥訪問
    struct list_head task_list;	//用來存放等待的進程,
};
typedef struct __wait_queue_head wait_queue_head_t;
  • API
/* 定義等待隊列 */
wait_queue_head_t wait;

/* 初始化等待隊列 */
init_waitqueue_head(&wait);

/* 定義並初始化等待隊列 */
#define DECLARE_WAIT_QUEUE_HEAD(name) wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITIALIZER(name)

/* 添加等待隊列,將等待隊列元素wait添加到等待隊列隊頭q所指向的等待隊列鏈表中 */
void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

/* 移除等待隊列 */
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

/* 等待事件,在等待隊列中睡眠直到condition爲真 */
/* 注意:
	@queue:做爲等待隊列頭的等待隊列被喚醒
	@condition:必須爲真,不然就阻塞
	@timeout:相較於condition,timeout有更高的優先級
*/
wait_event(wq, condition);
wait_evevt_timeout(wq, condition, timeout);
wait_event_interruptible(wq, condition);
wait_event_interruptible_timeout(wq, condition, timeout);

/* 睡眠,其中sleep_on做用是將目前進程的狀態設置爲TASK_UNINTERRUPTIBLE,直到資源可用,q引導的等待隊列被喚醒;
		interruptible_sleep_on是將進程狀態設置爲TASK_INTERRUPTIBLE
*/
sleep_on(wait_queue_head_t *q);
interruptible_sleep_on(wait_queue_head_t *q);

/* 喚醒等待隊列,可喚醒處於TASK_INTERRUPTIBLE和TASK_UNINTERRUPTIBLE狀態的進程 */
#define wake_up(x)	__wake_up(x, TASK_NORMAL, 1, NULL)

/* 只能喚醒處於TASK_INTERRUPTIBLE狀態的進程 */
#define wake_up_interruptible(x)	__wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)

2.2 睡眠、阻塞、掛起解釋

對線程的控制比如一個老闆招募了一個僱工幹活。linux

  • 掛起:比如老闆對僱工說:「你睡覺去吧,等用得着你的時候我會去主動叫你,而後你接着幹」
  • 睡眠:比如老闆對僱工說:「你睡覺去吧,某時某刻以後來報道,而後接着幹」
  • 阻塞:老闆忽然發現僱工在睡覺,緣由是由於僱工的笤帚被偷了(無資源),而後你也沒讓僱工幹其餘的事情,他就只好睡覺。等僱工找到笤帚以後,他將繼續幹活

2.3 等待隊列示例

  • 驅動代碼
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <asm/uaccess.h>
#include <linux/slab.h>
#include <linux/miscdevice.h>
#include <linux/wait.h>

#include "cdev_test_v4.h"

#define BUF_SIZR           1024
#define MAJOR_NUM          168
#define MINOR_NUM          0

struct demo_cdev {
    char *buffer;                           //my private memory
    int value;
    struct miscdevice *mdev;
    wait_queue_head_t queue_head;
    bool is_empty;
};

/* step1: malloc memory for char device */
struct demo_cdev *demo_dev;

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    printk(KERN_INFO "Enter: %s\n", __func__);
    /* determine whether user has finished reading data */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }

    if (demo->is_empty) {
        /* make this process sleep */
        wait_event_interruptible(demo->queue_head, !(demo->is_empty));
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}


static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;

    printk(KERN_INFO "Enter: %s\n", __func__);
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }

    demo->is_empty = false;
    /* wake up this process */
    wake_up(&demo->queue_head);

    *pos += write_bytes;
    return write_bytes;
}
static int demo_open(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    file->private_data = (void *)demo_dev;
    return 0;
}

static int demo_release(struct inode *node, struct file *file)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    return 0;
}

static long demo_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
   int ret = 0;
   struct demo_cdev *demo = file->private_data;

   switch(cmd) {
       case CDEV_TEST_V4_CLEAN:
           memset(demo->buffer, 0x00, BUF_SIZR);
           printk(KERN_INFO "cmd: clean\n");
           break;
       case CDEV_TEST_V4_GETVAL:
           put_user(demo->value, (int *)arg);
           printk(KERN_INFO "cmd: getval\n");
           break;
       case CDEV_TEST_V4_SETVAL:
           demo->value = (int)arg;
           printk(KERN_INFO "cmd: setval\n");
           break;
       default:
           break;
   } 
   return (long)ret;
}

static struct file_operations demo_operation= {
    .open = demo_open,
    .release = demo_release,
    .read = demo_read,
    .write = demo_write,
    .unlocked_ioctl = demo_ioctl,
};

static struct miscdevice misc_struct = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "misc_dev",
    .fops = &demo_operation,
};

static int __init demo_init(void)
{
    int ret = -1;
    printk(KERN_INFO "Enter: %s\n", __func__);
    demo_dev = (struct demo_cdev *)kmalloc(sizeof(struct demo_cdev), GFP_KERNEL);
    if (!demo_dev) {
        printk(KERN_ERR "failed to malloc demo_dev\n");
        ret = -ENOMEM;
        goto ERROR_MALLOC_DEMODEV;
    }
    demo_dev->value = 1;
    demo_dev->buffer = (char *)kmalloc(BUF_SIZR, GFP_KERNEL);
    if (!demo_dev->buffer) {
        printk(KERN_ERR "malloc %d bytes failed\n", BUF_SIZR);
        ret = -ENOMEM;
        goto ERROR_MALLOC_BUFFER;
    }
    memset(demo_dev->buffer, 0x00, BUF_SIZR);

    demo_dev->is_empty = true;
    init_waitqueue_head(&demo_dev->queue_head);
    

    demo_dev->mdev = &misc_struct; 
    ret = misc_register(demo_dev->mdev);
    if (ret < 0) {
        printk(KERN_ERR "failed to register misc\n");
        goto ERROR_MISC;
    }

    printk(KERN_INFO "demo char device init done\n");
    return 0;

ERROR_MISC:
    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
ERROR_MALLOC_BUFFER:
    kfree(demo_dev);
    demo_dev = NULL;
ERROR_MALLOC_DEMODEV:
    return ret;
}

static void __exit demo_exit(void)
{
    printk(KERN_INFO "Enter: %s\n", __func__);
    misc_deregister(demo_dev->mdev);

    kfree(demo_dev->buffer);
    demo_dev->buffer = NULL;
    kfree(demo_dev);
    demo_dev = NULL;
    printk(KERN_INFO "demo char device exit done\n");
}

module_init(demo_init);
module_exit(demo_exit);

MODULE_AUTHOR("Qi Han");
MODULE_LICENSE("GPL");
  • 調試過程
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo insmod wait_queue.ko 
[sudo] hq 的密碼: 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ sudo chmod 666 /dev/misc_dev # 此處會阻塞直到有數據 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ cat /dev/misc_dev 
Peng fei tan -


# 寫入數據喚醒阻塞的進程
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$ echo "Peng fei tan" -> /dev/misc_dev 
hq@hq-virtual-machine:~/桌面/linux-5.8/drivers/study_driver_hq/cdev_test$

3 環形隊列FIFO

3.1 老式設備驅動不足與改進

  • 不足:原來的驅動設備在驅動的類裏面維護了一段緩存空間,大小爲1024個字節,其中讀取代碼的數據以下,若是單次讀寫不超過BUF_SIZE大小個字節的數據,那麼能夠正常使用下述讀寫方法。假如單次讀寫數據量超過了BUF_SIZE大小個數據量的話將會致使數據沒法正常地寫入與讀出,主要是由於偏移量的問題,即*pos += read_bytes;,當讀取超過1024個字節數據的時候,偏移量就會大於1024,那麼會致使數組越界,所以咱們可使用環形隊列FIFO來儲存數據,當偏移量pos大於1024的時候會從新從0開始偏移
#define BUF_SIZR           1024

struct demo_cdev {
    char *buffer;                           //my private memory
	...
};

static ssize_t demo_read(struct file *file, char __user *buf, size_t size, loff_t *pos)
{
    int ret = -1;
    int read_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
    
    /* determine whether user has finished reading data */
    /* 判斷當前讀取的數據量是否大於buffer所容納的數據量 */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    /* 判斷當前讀取的數據量是否大於buffer中剩餘的數據量 */
    if (size > (BUF_SIZR - *pos)) {
        read_bytes = BUF_SIZR - *pos;
    } else {
        read_bytes = size;
    }
    ret = copy_to_user(buf, kbuf, read_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    *pos += read_bytes;
    return read_bytes;
}

static ssize_t demo_write(struct file *file, const char __user *buf, size_t size, loff_t *pos)
{
    int ret;
    int write_bytes;
    struct demo_cdev *demo = file->private_data;
    char *kbuf = demo->buffer + *pos;
	
    /* 判斷當前寫入的數據量是否大於buffer所容納的數據量 */
    if (*pos >= BUF_SIZR) {
        return 0;
    }
    /* 判斷當前寫入的數據量是否大於buffer所剩餘的數據量 */
    if (size > (BUF_SIZR - *pos)) {
        write_bytes = BUF_SIZR - *pos;
    } else {
        write_bytes = size;
    }
    ret = copy_from_user(kbuf, buf, write_bytes);
    if (ret != 0) {
        return -EFAULT;
    }
    
    *pos += write_bytes;
    return write_bytes;
}
  • 改進:咱們能夠添加2個指針和等待隊列,一個讀指針和一個寫指針,剛開始讀寫指針均指向環形隊列的隊頭,當讀進程讀取數據的時候若是發現隊列中沒有數據,那麼讀進程將阻塞;同理寫進程向隊列中寫入數據的時候若是發現隊列滿了,那麼寫進程需阻塞
  • 假設咱們先寫了4字節數據,那麼寫指針將會向前偏移4個單位,進而當讀進程讀取數據的時候能夠用寫指針減去讀指針(此時寫指針較讀指針向前偏移了4個單位,即表示隊列中有4字節數據可讀),當讀進程讀取完4字節數據以後,讀指針也須要偏移4個單位,那麼此時讀寫指針將會重合,即表述隊列中無數據可讀

3.2 環形隊列實現

  • 驅動代碼
相關文章
相關標籤/搜索