等待队列头实现阻塞 IO(BIO)

2023-12-22 10:32:19

等待队列头实现阻塞 IO(BIO)

等待队列是内核实现阻塞和唤醒的内核机制。
等待队列以循环链表为基础结构,链表头和链表项分别为等待队列头等待队列元素
整个等待队列由等待队列头进行管理。

模型

在这里插入图片描述

等待队列头

等待队列头使用结构体 wait_queue_head_t,定义在 linux/wait.h,结构体原型如下:

struct wait_queue_head {
	spinlock_t		lock;
	struct list_head	head;
};
typedef struct wait_queue_head wait_queue_head_t;

等待队列头的初始化有两种方法

init_waitqueue_head

  • 定义一个等待队列头 wait_queue_head_t read_wait
  • 等待 队列头初始化 init_waitqueue_head(&read_wait)

DECLARE_WAIT_QUEUE_HEAD

DECLARE_WAIT_QUEUE_HEAD 宏一次性完成等待队列头的定义和初始化

等待队列项

等待队列项使用结构体 struct wait_queue_entry,定义在 linux/wait.h,结构体原型如下:

struct wait_queue_entry {
	unsigned int		flags;
	void			*private;
	wait_queue_func_t	func;
	struct list_head	entry;
};

使用方法

  • 初始化等待队列头,并将条件设置为假的(condition=0)
  • 在需要阻塞的地方调用 wait_event 或者 wait_event_interruptible,使进程进入休眠
  • 当条件满足时,需要解除休眠,先将条件设置为真(condition=1),然后调用 wake_up 或者 wake_up_interruptible函数唤醒等待队列中的休眠进程。

驱动程序

#include "linux/device/class.h"
#include "linux/export.h"
#include "linux/uaccess.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/wait.h>

#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */
#define CHRDEVBASE_NUM 1             /* 设备数目 */

static char *string_test = "kernel data this tyustli test";

typedef struct {
    dev_t dev_id;          /* 设备号 */
    struct cdev c_dev;     /* cdev */
    struct class *class;   /* 类 */
    struct device *device; /* 设备 */
    int major;             /* 主设备号 */
    int minor;             /* 次设备号 */
    int flag;              /* read flag */
    char write_buf[100];
    char read_buf[100];
    wait_queue_head_t read_wait; /* 读等待队列头 */
} new_chrdev_t;

static new_chrdev_t new_chrdev1;

static int chrdevbase_open(struct inode *inode, struct file *file)
{
    file->private_data =
            container_of(inode->i_cdev, new_chrdev_t, c_dev); /* 设置私有数据 */

    printk("k: chrdevbase open\r\n");

    return 0;
}

static ssize_t chrdevbase_read(struct file *file, char __user *buf,
                               size_t count, loff_t *ppos)
{
    unsigned long ret = 0;
    new_chrdev_t *dev = (new_chrdev_t *)file->private_data;

    wait_event_interruptible(dev->read_wait, dev->flag);

    memcpy(dev->read_buf, string_test, strlen(string_test));

    ret = copy_to_user(buf, dev->read_buf, count);
    if (ret == 0) {
        printk("k: read data success\r\n");
    } else {
        printk("k: read data failed ret = %ld\r\n", ret);
    }

    return ret;
}

static ssize_t chrdevbase_write(struct file *file, const char __user *buf,
                                size_t count, loff_t *ppos)
{
    unsigned long ret = 0;

    new_chrdev_t *dev = (new_chrdev_t *)file->private_data;

    ret = copy_from_user(dev->write_buf, buf, count);
    if (ret == 0) {
        printk("k: write data success write data is: %s\r\n", dev->write_buf);
    } else {
        printk("k: write data failed ret = %ld\r\n", ret);
    }

    dev->flag = 1;
    wake_up_interruptible(&dev->read_wait);

    return count;
}

static int chrdevbase_release(struct inode *inode, struct file *file)
{
    printk("k: chrdevbase release\r\n");

    return 0;
}

static struct file_operations chrdevbase_fops = {
    .owner = THIS_MODULE,
    .open = chrdevbase_open,
    .read = chrdevbase_read,
    .write = chrdevbase_write,
    .release = chrdevbase_release,
};

static int __init chrdevbase_init(void)
{
    int err = 0;

    err = alloc_chrdev_region(&new_chrdev1.dev_id, 0, CHRDEVBASE_NUM,
                              CHRDEVBASE_NAME);
    if (err < 0) {
        printk("k: alloc chrdev region failed err = %d\r\n", err);
        goto err_chrdev;
    }

    /* get major 1 and minor 1 */
    new_chrdev1.major = MAJOR(new_chrdev1.dev_id);
    new_chrdev1.minor = MINOR(new_chrdev1.dev_id);
    printk("k: newcheled major=%d,minor=%d\r\n", new_chrdev1.major,
           new_chrdev1.minor);

    new_chrdev1.c_dev.owner = THIS_MODULE;
    cdev_init(&new_chrdev1.c_dev, &chrdevbase_fops);
    err = cdev_add(&new_chrdev1.c_dev, new_chrdev1.dev_id, 1);
    if (err < 0) {
        printk("k: cdev add failed err = %d\r\n", err);
        goto err_cdev_add;
    }
    new_chrdev1.class = class_create("chr_test1");
    if (IS_ERR(new_chrdev1.class)) {
        err = PTR_ERR(new_chrdev1.class);
        goto err_class_create;
    }

    new_chrdev1.device = device_create(new_chrdev1.class, NULL,
                                       new_chrdev1.dev_id, NULL, "chr_test1");
    if (IS_ERR(new_chrdev1.device)) {
        err = PTR_ERR(new_chrdev1.device);
        goto err_device_create;
    }

    /* 初始化等待队列头 */
    init_waitqueue_head(&new_chrdev1.read_wait);

    new_chrdev1.flag = 0;

    printk("k: base module init\r\n");

    return 0;

err_device_create:
    class_destroy(new_chrdev1.class);
err_class_create:
    cdev_del(&new_chrdev1.c_dev);
err_cdev_add:
    unregister_chrdev_region(new_chrdev1.dev_id, CHRDEVBASE_NUM);
err_chrdev:
    return err;
}

static void __exit chrdevbase_exit(void)
{
    device_destroy(new_chrdev1.class, new_chrdev1.dev_id);
    class_destroy(new_chrdev1.class);
    cdev_del(&new_chrdev1.c_dev);

    unregister_chrdev_region(new_chrdev1.dev_id, CHRDEVBASE_NUM);

    printk("k: base module exit!\r\n");
}

module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("tyustli");
MODULE_INFO(intree, "Y"); /* loading out-of-tree module taints kernel */

应用程序

应用程序使用 O_RDWR 默认以阻塞的方式打开

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"

static char usrdata[] = { "user data!" };

int main(int argc, char *argv[])
{
    int fd, retvalue;
    char *filename;
    char readbuf[100], writebuf[100];

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

    filename = argv[1];

    /* 打开驱动文件 */
    fd = open(filename, O_RDWR);
    if (fd < 0) {
        printf("u: can't open file %s\r\n", filename);
        return -1;
    }

    /* 从驱动文件读取数据 */
    if (atoi(argv[2]) == 1) {
        retvalue = read(fd, readbuf, 50);
        if (retvalue < 0) {
            printf("u: read file %s failed!\r\n", filename);
        } else {
            /*  读取成功,打印出读取成功的数据 */
            printf("u: read data:%s\r\n", readbuf);
        }
    }

    /* 向设备驱动写数据 */
    if (atoi(argv[2]) == 2) {
        memcpy(writebuf, usrdata, sizeof(usrdata));
        retvalue = write(fd, writebuf, 50);
        if (retvalue < 0) {
            printf("u: write file %s failed!\r\n", filename);
        }
    }

    /* 关闭设备 */
    retvalue = close(fd);
    if (retvalue < 0) {
        printf("u: can't close file %s\r\n", filename);
        return -1;
    }

    return 0;
}

模块使用

模块安装

modprobe my_module

查看设备节点

ls /dev
~ # ls /dev/
chr_test1        ptypc            tty32            tty7
console          ptypd            tty33            tty8
cpu_dma_latency  ptype            tty34            tty9
full             ptypf            tty35            ttyAMA0
gpiochip0        random           tty36            ttyAMA1
gpiochip1        root             tty37            ttyAMA2
gpiochip2        rtc0             tty38            ttyAMA3
gpiochip3        snd              tty39            ttyp0
hwrng            tty              tty4             ttyp1
input            tty0             tty40            ttyp2
kmsg             tty1             tty41            ttyp3

模块使用

~ # /lib/modules/6.5.7+/my_app /dev/chr_test1 1 &
~ # k: chrdevbase open

~ # /lib/modules/6.5.7+/my_app /dev/chr_test1 2
k: chrdevbase open
k: write data success write data is: user data!
k: read data success
k: chrdevbase release
~ # u: read data:kernel data this tyustli test
k: chrdevbase release

[1]+  Done                       /lib/modules/6.5.7+/my_app /dev/chr_test1 1

参考

  • https://juejin.cn/post/7129070726249709599#heading-0

文章来源:https://blog.csdn.net/tyustli/article/details/135140523
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。