IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 系统运维 -> Linux系统中的IO模型 -> 正文阅读

[系统运维]Linux系统中的IO模型

Linux系统中的IO模型

1.1 linux系统中的IO模型的种类

(1)非阻塞IO模型

(2)阻塞IO模型

(3)IO多路复用

(4)异步通知

1.2 非阻塞IO模型

非阻塞IO模型:当应用程序通过非阻塞的方式打开文件的时候,通过read读取

数据的时候,不管数据是否准备好,都要立即返回。

fd = open("/dev/mycdev0",O_RDWR|O_NONBLOCK); //使用非阻塞的方式打开文件
read(fd,buf,sizeof(buf));
--------------------------------------------------------
driver_read(struct file *file,,,)
{
    if(file->f_flags &O_NONBLOCK){
        //非阻塞,不管数据是否准备好都立即将数据拷贝到用户空间
    }
}

1.3 阻塞IO模型

1.3.1 阻塞IO模型的实现流程

阻塞IO模型:应用层以阻塞的方式打开设备文件,然后调用read函数读取硬件的数据。

如果硬件的数据没有准备好,需要让进程进入到休眠的状态。当硬件的数据准备好的

时候产生中断,在中断的处理函数中唤醒休眠的进程即可。

1.3.2阻塞IO模型函数调用流程

fd = open("/dev/mycdev0",O_RDWR); //使用阻塞的方式打开文件
read(fd,buf,sizeof(buf));
--------------------------------------------------------
driver_read(struct file *file,,,)
{
    if(file->f_flags &O_NONBLOCK){
        //非阻塞
    }else{
        //阻塞,让进程进入休眠状态
    }
}

中断的处理函数:
    唤醒休眠的进程

1.3.3阻塞IO模型的API

1.定义等待队列头
    wait_queue_head_t wq;
2.初始化等待队列头
   init_waitqueue_head(&wq);
3.调用对应的函数完成阻塞(定义等待队列项,将等待队列项放到队列的队尾,进程休眠)
    wait_event(wq, condition)  
    //让进程进入到不可中断的等待态
	wait_event_interruptible(wq, condition)   
    //让进程进入到可中断的等待态
    //返回值:如果是数据准备好了它返回0,如果是信号唤醒的进程的休眠返回-ERESTARTSYS
    
    注:condition代表的是数据是否准备好的标志,如果condition为真代表数据准备好了
    进程不需要休眠,如果condition为假代表数据没有准备好进程需要进入休眠的状态。
4.唤醒休眠的进程
    condition=1wake_up(&wq);
    wake_up_interruptible(&wq);
	
	注:上述的两个函数唤醒的是等待队列,队列中的进程是否真的被唤醒取决于
        condition的真假,如果condition为真,进程就准备被唤醒了,如果condition
        为假进程没有被唤醒,进程继续休眠。

在这里插入图片描述

1.3.4阻塞IO模型的实例

mycdev.c

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#define CNAME "mycdev"
struct cdev* cdev;
unsigned int major = 511;
unsigned int minor = 0;
const int count = 3;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };

wait_queue_head_t wq; //定义等待队列头
int condition = 0; //数据是否准备好的状态,默认没有准备好
int mycdev_open(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    //判断用户是否阻塞的方式打开的设备文件
    if (file->f_flags & O_NONBLOCK) {
        //非阻塞
        return -EINVAL;
    } else {
        //阻塞
        //如果是数据准备好了它返回0,如果是信号唤醒的进程的休眠返回-ERESTARTSYS
        
        ret = wait_event_interruptible(wq, condition);
        if (ret) {
            //使用Ctrl+c结束进程时 会打印下面这句话
            printk("receive signal.....\n");
            return ret;
        }
    }
    //将数据拷贝到用户空间
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy data to user error\n");
        return -EIO;
    }
	// 传递完数据 将condition清零 等待数据准备
    condition=0;
    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy data form user error\n");
        return -EIO;
    }
     //当得到用户空间的数据 将condition置为1 代表数据准备好了 同时唤醒正在休眠的进程
    //驱动的write可以唤醒驱动的read
    condition = 1;  
    wake_up_interruptible(&wq);//唤醒的工作

    return size;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev alloc memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.初始化对象
    cdev_init(cdev, &fops);

    // 3.申请设备号
    if (major > 0) {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    } else {
        //动态申请
        ret = alloc_chrdev_region(&devno, 0, count, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), count);
    if (ret) {
        printk("register char device driver error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < count; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    init_waitqueue_head(&wq); //初始化等待队列头

    return 0; /*!!!!!!!!!!!!!!!不要忘记写!!!!!!!!!!!!!!!*/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), count);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < count; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

    cdev_del(cdev);

    unregister_chrdev_region(MKDEV(major, minor), count);

    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

test.c

#include <head.h>

char buf[128] = "i am test block IO mode....";
int main(int argc, const char* argv[])
{
    
    int fd;
    pid_t pid;
    if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
        PRINT_ERR("open error");
	//使用父子进程验证 write唤醒read
    pid = fork();
    if (pid == -1) {
        PRINT_ERR("fork error");
    } else if (pid == 0) {
        while (1) {
            memset(buf, 0, sizeof(buf));
            read(fd, buf, sizeof(buf));
            printf("buf = %s\n", buf);
        }
    } else {
        while(1){
            //保证子进程中的read先执行
            sleep(3);
            write(fd,buf,sizeof(buf));
        }
        wait(NULL);
    }
    close(fd);
    return 0;
}

1.3.5 阻塞实现的流程

//让进程进入到可中断的等待态 (信号)
//wq_head:等待队列头
//condition:如果condition为真代表数据准备好,如果condition为假代表数据没有准备好
//返回0代表数据准备好,如果返回-ERESTARTSYS代表信号唤醒的休眠
#define wait_event_interruptible(wq_head, condition)				
({										
	int __ret = 0;								
	//如果condition为假if语句成立,如果condition为真if不成立
	if (!(condition))							
		__ret = __wait_event_interruptible(wq_head, condition);	
	__ret;	 //这个就是宏的返回值								
})


#define __wait_event_interruptible(wq_head, condition)		
	___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,	schedule())


#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		
({		
	//1.定义等待队列项的结构体
	struct wait_queue_entry __wq_entry; 	
	
	long __ret = ret;//返回值的变量 			

	//初始化等待队列项
	/*
		void init_wait_entry(struct wait_queue_entry *wq_entry, int flags)
		{
			wq_entry->flags = flags; //0
			wq_entry->private = current; //进程的结构体 task_struct
			wq_entry->func = autoremove_wake_function; //唤醒的函数
			INIT_LIST_HEAD(&wq_entry->entry); //构成队列的链表
		}
	*/
	init_wait_entry(&__wq_entry,0);	
	
	for (;;) {		
		//1.将等待队列项放到等待队列头之后
		//2.将进程的状态标记为可中断的等待态(此时进程没有真正的休眠)
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, TASK_INTERRUPTIBLE);
										
		if (condition)	 
			//数据是否准备好,如果准备好就退出循环						
			break;							
										
		if (signal_pending_state(state, current)) { 
			//判断是否收到了信号,如果收到信号退出循环		
			list_del_init(&wq_entry->entry);
			__ret = -ERESTARTSYS;;						
			goto __out; 					
		}								
										
		schedule();//让进程进入休眠状态的函数		
		//主动放弃cpu,进程真正进入到休眠状态
	}									
	finish_wait(&wq_head, &__wq_entry); 	
	//将等待队列项从等待队列上删除
	//将进程的状态标记为运行态
	
__out:	__ret;									
})

1.4 IO多路复用的IO模型

1.4.1 IO多路复用的实现的流程

IO多路复用:在同一个进程中想同时监听多个硬件的数据,此时就需要使用IO多路复用

的机制。将需要监听的文件描述符放在需要监听文件描述符的表中,通过select/poll/epoll

完成监听,如果所有的硬件的数据都没有准备好此时进程休眠。当有硬件的数据准备好的时候

会产生硬件的中断,在中断的处理函数中唤醒休眠的进程,当进程被唤醒之后从准备好的文件

描述符表中找出准备好的文件描述符,从里面读取数据即可。

1.4.2 IO多路复用接口调用流程

user:
	fd1 = open("/dev/mycdev0",O_RDWR);
	fd2 = open("/dev/input/mouse0",O_RDWR);
	
	fd_set rfds; //定义读表
	FD_ZERO(&rfds); //清空表
	FD_SET(fd1,&rfds);//将fd1放入表中
	FD_SET(fd2,&rfds)//将fd2放入表中
	
    select(maxfd+1,&rfds,NULL,NULL,NULL)
	
    if(FD_ISSET(fd1,&rfds)){
        //fd1数据准备好了
        read(fd1,buf1,sizeof(buf1));
    }
 	if(FD_ISSET(fd2,&rfds)){
        //fd2数据准备好了
        read(fd2,buf2,sizeof(buf2));
    }
----------------------------------------------
kernel:fops: //应用层的select/poll/epoll对应的都是驱动中的poll函数
		grep ".poll =" * -nR
	__poll_t (*poll) (struct file *file, struct poll_table_struct *wait);
	{
        1.定义返回值的变量__poll_t mask = 0;
        2.调用poll_wait(是和阻塞相关的函数)
            poll_wait(file, 等待队列头, wait);
        3.判断数据是否准备好了
            if(condition){
                mask |= EPOLLIN; //EPOLLIN 数据可读  EPOLLOUT数据可写
            }
		4.返回mask
            return mask;
    }

1.4.3 IO多路复用实例

test.c

#include <head.h>
#include <sys/select.h>
char buf[128] = "i am test block IO mode....";
int main(int argc, const char* argv[])
{
    int fd1, fd2, ret;
    fd_set rfds; //定义读表

    if ((fd1 = open("/dev/mycdev0", O_RDWR)) == -1)
        PRINT_ERR("open error");
    if ((fd2 = open("/dev/input/mouse0", O_RDWR)) == -1)
        PRINT_ERR("open error");
    while (1) {
        FD_ZERO(&rfds); //清空表
        FD_SET(fd1, &rfds); //将fd1放入表中
        FD_SET(fd2, &rfds); //将fd2放入表中

        ret = select(fd2 + 1, &rfds, NULL, NULL, NULL);
        if (ret == -1)
            PRINT_ERR("select error");

        if (FD_ISSET(fd1, &rfds)) {
            // fd1数据准备好了
            memset(buf, 0, sizeof(buf));
            read(fd1, buf, sizeof(buf));
            printf("mycdev0:%s\n", buf);
        }
        if (FD_ISSET(fd2, &rfds)) {
            // fd2数据准备好了
            memset(buf, 0, sizeof(buf));
            read(fd2, buf, sizeof(buf));
            printf("mouse0:%s\n", buf);
        }
    }
    close(fd1);
    return 0;
}

mycdev.c

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#define CNAME "mycdev"
struct cdev* cdev;
unsigned int major = 511;
unsigned int minor = 0;
const int count = 3;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };

wait_queue_head_t wq; //定义等待队列头
int condition = 0; //数据是否准备好的状态,默认没有准备好
int mycdev_open(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);

    //将数据拷贝到用户空间
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy data to user error\n");
        return -EIO;
    }
    //将condition清零
    condition=0;
    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy data form user error\n");
        return -EIO;
    }
    condition = 1;  
    wake_up_interruptible(&wq);//唤醒的工作

    return size;
}
__poll_t mycdev_poll(struct file *file, struct poll_table_struct *wait)
{
   //定义返回值的变量
    __poll_t mask = 0;
    // 调用poll_wait
    poll_wait(file, &wq_head, wait);
    // 判断数据是否准备好了
    if (condition) {
        mask |= EPOLLIN; // EPOLLIN 表示数据可读 EPOLLOUT代表数据可写
    }
    //返回mask
    return mask;
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .poll = mycdev_poll,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev alloc memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.初始化对象
    cdev_init(cdev, &fops);

    // 3.申请设备号
    if (major > 0) {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    } else {
        //动态申请
        ret = alloc_chrdev_region(&devno, 0, count, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), count);
    if (ret) {
        printk("register char device driver error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < count; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    init_waitqueue_head(&wq); //初始化等待队列头

    return 0; /*!!!!!!!!!!!!!!!不要忘记写!!!!!!!!!!!!!!!*/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), count);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < count; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

    cdev_del(cdev);

    unregister_chrdev_region(MKDEV(major, minor), count);

    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

1.5 sys_select执行流程总结

US:
	select(maxfd+1,&rfds,NULL,NULL,NULL);
---------------------------------------------------------------
VFS(虚拟文件系统): sys_select
    1.校验最大文件描述符
    2.分配6张表的内存(前三张表用来存储用户的文件描述符,后三张表存储准备好的文件描述符)
    3.将用户空间表拷贝到内存空间
    4.遍历文件描述符
    	mask = fd_set->fd->fd_array[fd]->file->f_op->poll(file,wait);
		如果所有的文件描述符对应驱动返回的mask都是0,说明所有的硬件的数据
        都没有准备好
	5.进程休眠 (schedule相关的函数)
    6.如果进程被唤醒(数据准备好,超时时间到,收到了信号),再次遍历
      文件描述符,找出数据准备好的文件描述符(如果mask不为0,说明数据就准备好了)
         mask = fd_set->fd->fd_array[fd]->file->f_op->poll(file,wait);
	   将准备好的文件描述符放到准备好的文件描述符的表中
	7.将准备好的文件描述符拷贝到用户空间	
---------------------------------------------------------------
驱动层:
__poll_t mycdev_poll(struct file* file, struct poll_table_struct* wait)
{
    // 1.定义返回值变量
    __poll_t mask = 0;
    // 2.提交等待队列头,并构造等待队列(不会休眠)
    poll_wait(file, &wq, wait);
    // 3.判断数据是否准备好
    if (condition)
        mask |= EPOLLIN; // EPOLLIN 可读  EPOLLOUT 可写
    // 4.返回mask
    return mask;
}

1.6 select/poll/epoll有什么区别?

select:表

? 1.最多只能监听1024个文件描述

? 2.用户空间的表会被清空,需要反复构造文件描述符的表,需要反复从用户空间向内核空间拷贝表效率低

? 3.当select进程休眠被唤醒之后,需要再次编译文件描述符的表,找出准备好的文件描述符,效率比较低。

poll:结构体数组

? 1.poll监听的文件描述符没有个数限制

? 2.poll的表不会被清空,不需要反复拷贝文件描述符,效率比较高。

? 3.当poll进程休眠被唤醒之后,需要再次编译文件描述符的表,找出准备好的文件描述符,效率比较低。

epoll:红黑树

? 1.epoll监听的文件描述符没有个数限制

? 2.epoll的表不会被清空,不需要反复拷贝文件描述符,效率比较高。

? 3.当epoll进程休眠被唤醒之后,能直接拿到准备好的文件描述符,不需要遍历,效率高。

1.7 epoll的使用

 #include <sys/epoll.h>
int epoll_create(int size);
功能:创建epoll
参数:
    @size:参数已经被忽略了,只需要填写大于0的值即可
返回值:成功返回epfd,失败返回-1置位错误码

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
功能:关于epoll的控制操作
参数:
    @epfd:epoll的文件描述符
	@op:控制方式
        EPOLL_CTL_ADD:添加
        EPOLL_CTL_MOD:修改
        EPOLL_CTL_DEL:删除
	@fd:被操作的文件描述符
    @event:(事件)结构体指针
           typedef union epoll_data {
               void        *ptr;
               int          fd;  <====一般填写这个成员
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      //EPOLLIN 读  EPOLLOUT 写
               epoll_data_t data;        //存放用户的数据
           };
返回值:成功返回0,失败返回-1置位错误码

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
功能:阻塞等待文件描述符就绪
参数:
    @epfd:epoll的文件描述符
    @events:准备好的事件的结构体地址
	@maxevents:返回的最大的文件描述符的个数
    @timeout:超时
        >0 :毫秒级别的超时时间
        =0 :立即返回
        =-1:不关心超时时间
返回值:
     成功返回准备好的文件描述符的个数
     返回0代表超时时间到了
     失败返回-1置位错误码

代码实现

#include <head.h>
#include <sys/epoll.h>

int main(int argc, const char* argv[])
{
    int epfd, fd, ret;
    struct epoll_event event;
    struct epoll_event revents[10];
    char buf[128] = {0};
    if ((epfd = epoll_create(10)) == -1)
        PRINT_ERR("epoll create error");
for (int i = 1; i < argc; i++) {
    if ((fd = open(argv[i], O_RDWR)) == -1)
        PRINT_ERR("open error");

    event.events = EPOLLIN;
    event.data.fd = fd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event))
        PRINT_ERR("epoll ctl error");
}

while (1) {
    if ((ret = epoll_wait(epfd, revents, 10, -1)) == -1)
        PRINT_ERR("epoll wait error");

    for (int i = 0; i < ret; i++) {
        if(revents[i].events & EPOLLIN){
            memset(buf,0,sizeof(buf));
            read(revents[i].data.fd,buf,sizeof(buf));
            printf("fd = %d,data = %s\n",revents[i].data.fd,buf);
        }
    }
}

return 0;
}

1.8 异步通知IO模型

1.8.1 异步通知IO模型实现思想

异步通知IO模型:应用程序在使用异步通知IO模型的时候需要使用signal(SIGIO,handler)

为SIGIO信号绑定一个信号处理函数。应用程序就可以执行任意自己想要执行的代码。当

硬件的数据准备号之后会产生中断,在中断的处理函数中给这个进程发送信号。当进程

收到信号后执行信号处理函数即可。(信号驱动IO)

异步修饰的是通知而不是IO模型

1.8.2 异步通知IO模型实现流程

us:
	void handle(int signo)
    {
        //信号处理函数,在信号处理函数中将数据读走即可
    }
	//1.注册信号处理函数
	signal(SIGIO,handle);
	//2.调用驱动的fasync函数,完成异步通知队列的初始化
	unsigned int flags=fcntl(fd,F_GETFL);
	fcntl(fd,F_SETFL,flags | FASYNC);
	//3.告诉驱动将信号发给当前的进程
	fcntl(fd,F_SETOWN,getpid());
---------------------------------------------------------
VFS: sys_fcntl
SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
sys_fcntl(unsigned int fd,unsigned int cmd,unsigned long arg)
    err = do_fcntl(fd, cmd, arg, f.file);
    	switch (cmd) {
        case F_GETFL:
       		err = filp->f_flags;                                                         
        	break;
    	case F_SETFL:
        	err = setfl(fd, filp, arg);
                	arg = filp->f_flags | FASYNC;
                  if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
                    error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0);
                   }
       		break;
        }
---------------------------------------------------------
ks:fops:
    struct fasync_struct *fapp;
	int (*fasync) (int fd, struct file *filp, int on)
    {  //初始化异步通知队列
        return fasync_helper(fd,filep,on,&fapp);
    }

	//发送信号
	void kill_fasync(&fapp, SIGIO, POLL_IN);  

1.8.3 异步通知IO模型的实例

mycdev.c

#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/poll.h>
#define CNAME "mycdev"
struct cdev* cdev;
unsigned int major = 511;
unsigned int minor = 0;
const int count = 3;
struct class* cls;
struct device* dev;
char kbuf[128] = { 0 };
struct fasync_struct *fapp;
int mycdev_open(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
ssize_t mycdev_read(struct file* file,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);

    //将数据拷贝到用户空间
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_to_user(ubuf, kbuf, size);
    if (ret) {
        printk("copy data to user error\n");
        return -EIO;
    }
 
    return size;
}
ssize_t mycdev_write(struct file* file,
    const char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    if (size > sizeof(kbuf))
        size = sizeof(kbuf);
    ret = copy_from_user(kbuf, ubuf, size);
    if (ret) {
        printk("copy data form user error\n");
        return -EIO;
    }
    
    //发送SIGIO信号
    kill_fasync(&fapp,SIGIO,POLL_IN);
    return size;
}

int mycdev_fasync(int fd, struct file *filp, int on)
{ 
    //异步通知队列的初始化
    return fasync_helper(fd,filp,on,&fapp);
}
int mycdev_close(struct inode* inode, struct file* file)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}
const struct file_operations fops = {
    .open = mycdev_open,
    .read = mycdev_read,
    .write = mycdev_write,
    .fasync = mycdev_fasync,
    .release = mycdev_close,
};
static int __init mycdev_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("cdev alloc memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }
    // 2.初始化对象
    cdev_init(cdev, &fops);

    // 3.申请设备号
    if (major > 0) {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), count, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    } else {
        //动态申请
        ret = alloc_chrdev_region(&devno, 0, count, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    }
    // 4.注册
    ret = cdev_add(cdev, MKDEV(major, minor), count);
    if (ret) {
        printk("register char device driver error\n");
        goto ERR3;
    }
    // 5.自动创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class create error\n");
        ret = PTR_ERR(cls);
        goto ERR4;
    }
    for (i = 0; i < count; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "mycdev%d", i);
        if (IS_ERR(dev)) {
            printk("device create error\n");
            ret = PTR_ERR(dev);
            goto ERR5;
        }
    }

    return 0; /*!!!!!!!!!!!!!!!不要忘记写!!!!!!!!!!!!!!!*/
ERR5:
    for (--i; i >= 0; i--) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
ERR4:
    cdev_del(cdev);
ERR3:
    unregister_chrdev_region(MKDEV(major, minor), count);
ERR2:
    kfree(cdev);
ERR1:
    return ret;
}
static void __exit mycdev_exit(void)
{
    int i;
    for (i = 0; i < count; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);

    cdev_del(cdev);

    unregister_chrdev_region(MKDEV(major, minor), count);

    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

test.c

#include <head.h>
#include <signal.h>
int fd;
char buf[128] = { 0 };
void handle(int signo)
{
    if (signo == SIGIO) {
        memset(buf, 0, sizeof(buf));
        read(fd, buf, sizeof(buf));
        printf("buf = %s\n", buf);
    }
}
int main(int argc, const char* argv[])
{

    if ((fd = open("/dev/mycdev0", O_RDWR)) == -1)
        PRINT_ERR("open error");

    if ((SIG_ERR == signal(SIGIO, handle)))
        PRINT_ERR("signal error");
    fcntl(fd, F_SETFL, fcntl(fd, F_GETFL) | FASYNC);
    fcntl(fd, F_SETOWN, getpid());
    
    while (1);
    close(fd);
    return 0;
}
  系统运维 最新文章
配置小型公司网络WLAN基本业务(AC通过三层
如何在交付运维过程中建立风险底线意识,提
快速传输大文件,怎么通过网络传大文件给对
从游戏服务端角度分析移动同步(状态同步)
MySQL使用MyCat实现分库分表
如何用DWDM射频光纤技术实现200公里外的站点
国内顺畅下载k8s.gcr.io的镜像
自动化测试appium
ctfshow ssrf
Linux操作系统学习之实用指令(Centos7/8均
上一篇文章      下一篇文章      查看所有文章
加:2022-10-31 12:39:36  更:2022-10-31 12:43:39 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/25 18:38:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码