Linux系统驱动(七)IO模型---阻塞IO和非阻塞IO

一、IO模型

(一)种类

非阻塞IO
阻塞IO
IO多路复用
异步通知(信号驱动IO)

(二)非阻塞IO模型

当应用程序使用非阻塞IO模型读取数据时,不管数据是否就绪,函数都要立即返回。如果数据就绪,返回的就是读取到的数据,如果没有数据就返回错误。

非阻塞IO模型会增加系统调用的次数,让程序的执行效率变低

1. 实现机制

US:
 fd = open("/dev/mycdev0",O_RDWR|O_NONBLOCK); //非阻塞方式打开
    /*或者使用如下方式实现非阻塞打开
     fd = open("/dev/mycdev0",O_RDWR);
     unsigned int flags = fcntl(fd, F_GETFL);
     fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    */
 read(fd,buf,sizeof(buf));
----------------------------------------------------
KS:
 mycdev_read(struct file* file,char __user* ubuf, size_t size, loff_t* offs)
    {
        if(file->f_flags & O_NONBLOCK){
            //非阻塞方式打开的
            //1.如果数据就绪返回数据
            //2.如果数据没有就绪返回错误码
        }
        return size;
    }

2. 驱动实现非阻塞方式读取数据

ssize_t myled_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    //对参数进行检查
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    //使用了非阻塞方式打开
    if(file->f_flags & O_NONBLOCK){
        //如果数据准备好就返回数据,否则返回错误
        if(strlen(kbuf)){
            ret = copy_to_user(ubuf,kbuf,size);
            if(ret){
                pr_err("copy_to_user error\n");
                return -EIO;
            }
            return size;
        }
        return -ENODATA;//返回错误码
    }
    return size;
}

(三)阻塞IO模型

当应用程序通过阻塞方式打开设备文件的时候,调用read函数读取数据。如果数据没有就绪,read阻塞,进程进入休眠状态;如果数据就绪了,进程需要从休眠态变为运行状态,并将就绪的数据返回给用户空间的read即可。

1. 实现机制

US:
 fd = open("/dev/mycdev0",O_RDWR); //阻塞方式打开
 read(fd,buf,sizeof(buf)); //读取数据
-------------------------------------------------------------
KS:
mycdev_read(struct file* file,char __user* ubuf, size_t size, loff_t* offs)
{
        if(file->f_flags & O_NONBLOCK){
            //非阻塞
            return -EINVAL; //无效参数
        }else{
            //阻塞,数据没有就绪进程休眠,数据就绪了进程不休眠
        }
        //将数据拷贝到用户空间
        
        //返回拷贝的字节数
}

mycdev_write()
{
        //唤醒休眠
}
  • 注:进程处于休眠状态,数据就绪时,进程如何知道?
    方法1:当底层硬件数据就绪时会产生硬件中断,在中断处理函数中唤醒休眠的进程。
    方法2:当A进程数据没有就绪而休眠时,可以再启动一个B进程去唤醒A进程。

此示例采用方法2

2. 阻塞IO模型的API

1. 定义等待队列头
	wait_queue_head_t wq_head;

2. 初始化等待队列头
	init_waitqueue_head(&wq_head);

3. 阻塞等待
	wait_event(wq_head, condition);

	wait_event_interruptible(wq_head, condition);
4. 唤醒
	condition = 1;
	wake_up(&wq_head);
	wake_up_interruption(&wq_head);
  • 注:实现机制:
  • 将进程加入到等待队列中,当调用wake_up函数时,就会唤醒队列头,然后检查condition是否满足条件,如果满足就退出休眠,否则继续休眠

3. 驱动实现阻塞IO模型

#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include "mycdev.h"

int major=0;//主设备号
int minor=0;//次设备号
dev_t mydev;//设备号
struct cdev* cdev;
char kbuf[128];
struct class *mycls;
struct device* mydevice;
dev_t currt_dev;
/***1. 定义队列头,初始化等待条件***/
wait_queue_head_t my_wait_head;//定义队列头
int condition=0;

int mycdev_open(struct inode* inode, struct file* file)
{
    pr_info("%s:%d\n",__func__,__LINE__);

    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int currt_index = (int)file->private_data;
    int ret;
    //对参数进行检查
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    //使用了非阻塞方式打开
    if(file->f_flags & O_NONBLOCK){
        //如果数据准备好就返回数据,否则返回错误
        if(strlen(kbuf)){
            ret = copy_to_user(ubuf,kbuf,size);
            if(ret){
                pr_err("copy_to_user error\n");
                return -EIO;
            }
            return size;
        }
        return -ENODATA;//返回错误码
    }else{
        /***3. 阻塞等待条件满足,进程进入休眠***/
        ret = wait_event_interruptible(my_wait_head,condition);
        printk("wake up %d\n",currt_index);
        if(ret){//如果不为0说明是信号中断唤醒的
            pr_err("wake_up by signal...\n");
            return ret;
        }
        //如果是数据就绪唤醒,就将数据拷贝给用户
        ret = copy_to_user(ubuf,kbuf,size);
        if(ret){
            pr_err("copy_to_user error\n");
            return -EIO;
        }
        //拷贝数据完成后,要将condition置0,为下次阻塞做准备
        condition= 0;
        return size;
    }
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    int currt_index = (int)file->private_data;
    memset(kbuf, 0, sizeof(kbuf));
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        pr_err("copy_from_user error\n");
        return -EIO;
    }
    /***4. 获取到了用户空间的数据,将满足条件置1***/
    condition = 1; //将条件置1
    wake_up_interruptible(&my_wait_head);
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    return 0;
}

const struct file_operations myfops={
    .open=mycdev_open,
    .release=mycdev_close,
    .write=mycdev_write,
    .read=mycdev_read,
};

static int __init mycdev_init(void){
    int i;
    //分配对象
    cdev = cdev_alloc();
    if(NULL == cdev){ //成功返回结构体指针,失败返回NULL
        pr_err("cdv_err error");
        return -ENOMEM;
    }
    //初始化对象:部分成员初始化
    cdev_init(cdev,&myfops);
    //申请设备号:如果major为0,则动态申请,否则就静态指定
    if(major > 0){
        register_chrdev_region(MKDEV(major,minor),1,"mycdev");
    }else if(major == 0){
        alloc_chrdev_region(&mydev,0,1,"mycdev"); //此处的name是cat /proc/devices
        major=MAJOR(mydev);
        minor=MINOR(mydev);
    }
    printk("major = %d",major);
    //注册
    cdev_add(cdev,MKDEV(major,minor),1); 
    //自动创建设备节点
    mycls=class_create(THIS_MODULE,"mycdev");
    mydevice = device_create(mycls,NULL,MKDEV(major,minor+i),NULL,"mycdev");
    /***2. 初始化等待队列头***/
    init_waitqueue_head(&my_wait_head);

    return 0;
}
static void __exit mycdev_exit(void){
    device_destroy(mycls, MKDEV(major, minor));
    class_destroy(mycls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), 1);
    kfree(cdev);
}

module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

4. 进阶版:实现多个阻塞IO的驱动进程同时运行时,指定某个进程唤醒

#include <linux/module.h>
#include <linux/io.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include "mycdev.h"

int major=0;//主设备号
int minor=0;//次设备号
dev_t mydev;//设备号
struct cdev* cdev;
char kbuf[128];
struct class *mycls;
struct device* mydevice;
dev_t currt_dev;

wait_queue_head_t my_wait_head;//定义队列头
int condition[128] = {0}; //每进入一个进程就为其分配一个唯一的标识号
int my_index=0;

int mycdev_open(struct inode* inode, struct file* file)
{
    file->private_data = (void *)my_index++;
    if(my_index == 128)
        my_index=0;
    pr_info("%s:%d\n",__func__,__LINE__);

    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int currt_index = (int)file->private_data;
    int ret;
    //对参数进行检查
    if(size>sizeof(kbuf)){
        size=sizeof(kbuf);
    }
    //使用了非阻塞方式打开
    if(file->f_flags & O_NONBLOCK){
        //如果数据准备好就返回数据,否则返回错误
        if(strlen(kbuf)){
            ret = copy_to_user(ubuf,kbuf,size);
            if(ret){
                pr_err("copy_to_user error\n");
                return -EIO;
            }
            return size;
        }
        return -ENODATA;//返回错误码
    }
    //使用阻塞方式打开
    ret = wait_event_interruptible(my_wait_head,condition[currt_index]);
    printk("wake up %d\n",currt_index);
    if(ret){//如果不为0说明是信号中断唤醒的
        pr_err("wake_up by signal...\n");
        return ret;
    }
    //如果是数据就绪唤醒,就将数据拷贝给用户
    kbuf[0]='0'+currt_index;
    ret = copy_to_user(ubuf,kbuf,size);
    if(ret){
        pr_err("copy_to_user error\n");
        return -EIO;
    }
    //拷贝数据完成后,要将condition置0,为下次阻塞做准备
    condition[currt_index] = 0;
    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    memset(kbuf, 0, sizeof(kbuf));
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    //从用户空间读取到要唤醒的进程的下标
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        pr_err("copy_from_user error\n");
        return -EIO;
    }
    condition[kbuf[0]-'0'] = 1; //将条件置1
    printk("cond%c is ok\n",kbuf[0]);
    wake_up_interruptible(&my_wait_head);
    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    return 0;
}

const struct file_operations myfops={
    .open=mycdev_open,
    .release=mycdev_close,
    .write=mycdev_write,
    .read=mycdev_read,
};

static int __init mycdev_init(void){
    int i;
    //分配对象
    cdev = cdev_alloc();
    if(NULL == cdev){ //成功返回结构体指针,失败返回NULL
        pr_err("cdv_err error");
        return -ENOMEM;
    }
    //初始化对象:部分成员初始化
    cdev_init(cdev,&myfops);
    //申请设备号:如果major为0,则动态申请,否则就静态指定
    if(major > 0){
        register_chrdev_region(MKDEV(major,minor),1,"mycdev");
    }else if(major == 0){
        alloc_chrdev_region(&mydev,0,1,"mycdev"); //此处的name是cat /proc/devices
        major=MAJOR(mydev);
        minor=MINOR(mydev);
    }
    printk("major = %d",major);
    //注册
    cdev_add(cdev,MKDEV(major,minor),1); 
    //自动创建设备节点
    mycls=class_create(THIS_MODULE,"mycdev");
    mydevice = device_create(mycls,NULL,MKDEV(major,minor+i),NULL,"mycdev");
    /***阻塞IO:初始化等待队列头***/
    init_waitqueue_head(&my_wait_head);

    return 0;
}
static void __exit mycdev_exit(void){
    device_destroy(mycls, MKDEV(major, minor));
    class_destroy(mycls);
    cdev_del(cdev);
    unregister_chrdev_region(MKDEV(major, minor), 1);
    kfree(cdev);
}

module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Linux IO 模型是指 Linux 操作系统中的 IO 处理机制。它的目的是解决多个程序同时使用 IO 设备时的资源竞争问题,以及提供一种高效的 IO 处理方式。 Linux IO 模型主要分为三种:阻塞 IO非阻塞 IOIO 多路复用。 阻塞 IO 指的是当程序进行 IO 操作时,会被挂起直到 IO 操作完成,这种方式简单易用,但是对于高并发环境不太适用。 非阻塞 IO 指的是程序进行 IO 操作时,如果无法立即完成,会立即返回一个错误码,程序可以通过循环不断地进行 IO 操作来实现轮询的效果。非阻塞 IO 可以提高程序的响应速度,但是会增加程序的复杂度。 IO 多路复用指的是程序可以同时监听多个 IO 设备,一旦有 IO 事件发生,就会立即执行相应的操作。IO 多路复用可以提高程序的效率,但是需要程序员手动编写代码来实现。 Linux IO 模型还有其他的实现方式,比如信号驱动 IO 和异步 IO 等。但是这些方式的使用比较复杂,一般不常用。 ### 回答2: Linux中的IO模型是指操作系统在处理输入输出的过程中所遵循的一种方式。它主要包括阻塞IO非阻塞IO、多路复用IO和异步IO四种模型阻塞IO是最简单的IO模型,当一个IO操作发生时,应用程序会被阻塞,直到IO操作完成才能继续执行。这种模型的特点是简单直接,但是当有多个IO操作时会造成线程的阻塞,影响系统的性能。 非阻塞IO是在阻塞IO的基础上发展而来的,应用程序在发起一个IO操作后可以继续执行其他任务,不必等待IO操作的完成。但是需要通过轮询来不断地检查IO操作是否完成,效率相对较低。 多路复用IO使用select、poll、epoll等系统调用来监听多个IO事件,当某个IO事件就绪时,应用程序才会进行读写操作,避免了前两种模型的效率问题。多路复用IO模型适用于连接数较多时的场景,如服务器的网络通信。 异步IO是最高效的IO模型,应用程序发起一个IO操作后,立即可以执行其他任务,不需要等待IO操作的完成。当IO操作完成后,操作系统会通知应用程序进行后续处理。异步IO模型常用于高吞吐量、低延迟的应用,如高性能服务器和数据库等。 总之,Linux IO模型提供了多种不同的方式来处理输入输出,每种模型都有其适用的场景和特点。选择合适的IO模型可以提高系统的性能和效率。 ### 回答3: Linux IO模型是指操作系统中用于处理输入输出操作的一种方法或机制。在Linux中,常见的IO模型阻塞IO非阻塞IOIO多路复用和异步IO阻塞IO是最基本的IO模型,当应用程序发起一个IO请求时,它将一直阻塞等待直到IO操作完成,期间无法做其他任务。虽然简单易用,但是对资源的利用不高。 非阻塞IO在发起一个IO请求后,不会阻塞等待IO操作完成,而是立即返回并继续做其他任务。应用程序需要不断地轮询IO操作状态,直到操作完成。由于需要不断轮询,对CPU的占用较高,但可以提高资源的利用率。 IO多路复用是通过一个线程同时监听多个IO事件,从而实现并发处理多个IO操作。在IO多路复用模型中,应用程序不需要进行轮询,而是通过调用select、poll或epoll等系统调用监听多个文件描述符的IO事件。这样可以在单个线程中处理多个IO操作,提高并发性能。 异步IO模型在发起一个IO请求后,应用程序不需要等待IO操作完成,而是继续做其他任务。当IO操作完成后,操作系统会通知应用程序。异步IO模型需要操作系统的支持,效率较高,但实现较为复杂。 通过选择合适的IO模型,可以根据不同的应用场景来提高IO操作的效率和性能。例如,对于需要同时处理大量连接的服务器应用,IO多路复用是一种常见的选择;而对于需要处理大量IO操作的高性能服务器,则可以考虑使用异步IO模型
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值