阻塞型I/O

        之前我们了解了非阻塞型I/O,就是遇到不可用资源的时候会立刻返回,那么对于阻塞型I/O的话就是遇到不可用的资源后,不会立刻返回,而是会在那里等待,等到资源可用的时候再去做操作。具体来说就是,如果进程发现资源不可用,主动会将自己的状态设置为TASK_UNINTERRUPTIBLE或者TASK_INTERRUPTIBLE,然后见自己加入一个驱动所维护的等待列队中,最后调用schedule主动放弃CPU,操作系统将从运行列队上移除,并调度其他的进程执行。对于这样一个比较谦逊的进程,内核是非常喜欢的,这也是将这种I/O方式设置成默认方式的原因。相比与非阻塞型I/O,其最大的有点就是资源不可用时,不占用CPU的时间,而阻塞型I/O必须要定期尝试,看看资源是否可以使用,这对于键盘、鼠标这类设备来说,其效率是非常低的。但是阻塞型I/O也有一个明显的缺点,那就是对于进程在休眠期间再也无法做其他事情了。

        既然有休眠,那应该有对应的唤醒操作,否则进程将会一直休眠下去。驱动程序应该在资源可用是负责执行唤醒操作。比如读进程休眠了,那么对于虚拟串口(FIFO)而言,写进程就应该负责唤醒操作,在真实的串口设备中,通常应该是中断处理负责唤醒。例如当串口收到了数据,产生一个中断,为了让程序更友好,休眠的进程应该能够被信号唤醒,这在资源不可获得,但却想要撤销休眠时,是比较有用的,通常也是一种比较推荐的方法。另外,我们也可以指定进程休眠的最长时间,超时后程序自动苏醒。

        从上面的描述中可以发现,要实现阻塞操作,最重要的数据结构是等待列队。等待列队头的数据类型是wait_queue_head_t,列队节点数据类型是wait_queue_t,围绕等待列队有很多宏和函数,如下

DECLARE_WAIT_QUEUE_HEAD(name)

init_waitqueue_head(q)

wait_event(wq, condition)

wait_event_timeout(wq, condition, timeout)

wake_up(x)

wait_event_interruptible(wq, condition)

wait_event_interruptible_timeout(wq, condition, timeout)

wake_up_interruptible(x)

wait_event_interruptible_locked(wq, condition)

wait_event_interruptible_locked_irq(wq, condition)

wait_event_interruptible_exclusive_locked(wq, condition)

wait_event_interruptible_exclusive_locked_irq(wq, condition)

wake_up_locked(x)

         DECLARE_WAIT_QUEUE_HEAD静态定义了一个等待队列头,init_waitqueue_head用于初始化一个等待列对头。wait_event是在条件condition不成立的情况下将当前进程放入到等待队列并休眠的基本操作。他拥有非常多的变体,timeout表示超时限制,interruptiable表示进程在休眠时可以通过信号来唤醒;exclusive表示该进程具有排他性,默认情况下,唤醒操作将唤醒等待列队中所有进程,但是如果一个进程以排他的方式休眠,那么唤醒操作在唤醒这个进程后不会继续唤醒其他进程;locked要求在调用前先获得等待列队内部的锁,irq要求在上锁的同时禁止中断。这些函数如果不带timeout,那么返回0表示被成功唤醒,返回-ERESTARTSYS表示被信号唤醒,如果带timeout,返回0表示超时,返回大于0的值表示被成功唤醒,这个值是离超时还剩余的时间。他们唤醒有对应关系,简单说,带locked的用wake_up_locked唤醒,不带locked而带interruptiable的用wake_up_interruptible来唤醒,否则用wake_up唤醒。相关函数可以查看“include/linux/wait.h”。

/*************************************************************************
	> File Name: blockio.c
	> Author: longway.bai
	> Mail: 953821672@qq.com
	> Created Time: 2021年12月19日 星期日 14时12分50秒
 ************************************************************************/

#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>

#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>

#define CHRDEV_MAJOR 256
#define CHRDEV_MINOR 0
#define CHRDEV_CNT   1
#define CHRDEV_NAME  "vser"

//static struct cdev vsdev; 
struct vser_dev {
    struct cdev cdev;
    wait_queue_head_t rwqh;
    wait_queue_head_t wwqh;
    struct kfifo *fifo;
};

static struct vser_dev vsdev;
DEFINE_KFIFO(vsfifo, char, 32);  

static int vser_open(struct inode *inode, struct file *filp)
{
    filp->private_data = container_of(inode->i_cdev, struct vser_dev, cdev);
	return 0;
}

static int vser_release(struct inode *inode, struct file *filp)
{
	return 0;
}

static ssize_t vser_read(struct file *filp, char __user *buf, size_t count, loff_t *pos)
{
    int ret;
	unsigned int copied;
    struct vser_dev *dev = filp->private_data;

    if(kfifo_is_empty(dev->fifo))
    {
        /*用户层已非阻塞的方式读,且kfifo已经empty的话则返回error*/
        if(filp->f_flags & O_NONBLOCK)
            return -EAGAIN;

        /*fifo为空的情况下进程休眠,知道fifo不为空或者接收到信号才被唤醒,如果是被信号唤醒,则返回-ERESTARTSYS*/
        if(wait_event_interruptible(dev->rwqh, !kfifo_is_empty(dev->fifo)))
            return -ERESTARTSYS;
    }

    ret = kfifo_to_user(dev->fifo, buf, count, &copied); 

    /*当fifo不满的时候唤醒所有等待的写进程*/
    if (!kfifo_is_full(dev->fifo))
        wake_up_interruptible(&dev->wwqh);

	return ret == 0 ? copied : ret;
}

static ssize_t vser_write(struct file *filp, const char __user *buf, size_t count, loff_t *pos)
{
    int ret;
	unsigned int copied;
    struct vser_dev *dev = filp->private_data;

    if(kfifo_is_full(dev->fifo))
    {
         /*用户层已非阻塞的方式写,且kfifo已经full的话则返回error*/
        if (filp->f_flags & O_NONBLOCK)
            return -EAGAIN;

        /*fifo为满的情况下进程休眠,知道fifo不满或者接收到信号才被唤醒,如果是被信号唤醒,则返回-ERESTARTSYS*/
        if(wait_event_interruptible(dev->wwqh, !kfifo_is_full(dev->fifo)))
            return -ERESTARTSYS;
    }

    ret = kfifo_from_user(dev->fifo, buf, count, &copied); 

    /*当fifo不空的时候唤醒所有等待的读进程*/
    if (!kfifo_is_empty(dev->fifo))
        wake_up_interruptible(&dev->rwqh);

	return ret == 0 ? copied : ret;
}

static struct file_operations vser_ops = {
	.owner = THIS_MODULE,
	.open = vser_open,
	.release = vser_release,
	.read = vser_read,
	.write = vser_write,
};

static int __init vser_init(void)
{
	int ret;
	dev_t dev; 

	dev = MKDEV(CHRDEV_MAJOR, CHRDEV_MINOR); 

	ret = register_chrdev_region(dev, CHRDEV_CNT, CHRDEV_NAME);
	if (ret)
		goto reg_err;

	cdev_init(&vsdev.cdev, &vser_ops);

	vsdev.cdev.owner = THIS_MODULE;

	ret = cdev_add(&vsdev.cdev, dev, CHRDEV_CNT);
	if (ret)
		goto add_err;

    vsdev.fifo = (struct kfifo*)&vsfifo;

    /*初始化一个等待列头*/
    init_waitqueue_head(&vsdev.rwqh);
    init_waitqueue_head(&vsdev.wwqh);

	return 0;

add_err:
	unregister_chrdev_region(dev, CHRDEV_CNT);
reg_err:
	return ret;
}

static void __exit vser_exit(void)
{
	dev_t dev;

	dev = MKDEV(CHRDEV_MAJOR, CHRDEV_MINOR);

	cdev_del(&vsdev.cdev);
	unregister_chrdev_region(dev, CHRDEV_CNT);
}

module_init(vser_init);
module_exit(vser_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("longway<longway.bai@outlook.com>");
MODULE_DESCRIPTION("A simple module");
MODULE_ALIAS("virtual-serial");

测试结果

# cat /dev/vser

^C

# cat /dev/vser &

[2] 4853

# echo "sssssssssssssssssssss" > /dev/vser

# sssssssssssssssssssss

# echo "hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhssss" > /dev/vser

hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh

# hhhhhhssss

# fg

cat /dev/vser

^C

# echo "hhhhhhhhhhhhhhhhhhhhhhhhhhsssssssssssssssss" > /dev/vser

^C

# cat /dev/vser

hhhhhhhhhhhhhhhhhhhhhhhhhhssssss

        以上我们发现,当fifo为空的时候,读的时候进程会卡住, 当我们写入数据后,会立刻打印出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值