Linux的IO模型

1.IO模型

IO模型的种类

    非阻塞

    阻塞

    IO多路复用

    异步通知(信号驱动IO)

2.非阻塞IO模型

非阻塞:在使用open打开文件的时候需要或上O_NONBLOCK的宏,如果是非阻塞打开方式

在调用read函数读取数据的时候,不管底层的数据是否准备好都要立即返回到用户空间。

open("/dev/myled0",O_RDWR|O_NOBLOCK);  //非阻塞方式打开
read(fd,buf,sizeof(buf));
---------------------------------------------------------------
mycdev_read()
{
    if(file->f_flags &O_NONBLOCK){
        //非阻塞
       	//从硬件中读取数据
        //将数据拷贝到用户空间即可
    }
}

3阻塞IO模型

3.1阻塞IO模型的原理

阻塞:用户使用open打开设备文件的时候,默认就是阻塞方式打开的。如果用户

调用read函数想要读取数据的时候,如果数据没有准备好,进程休眠。如果硬件的

数据准备好了就会产生硬件中断,在中断处理函数中唤醒这个休眠的进程即可。从

硬件中将数据读取到内核空间,然后调用copy_to_user将数据拷贝到用户空间即可。

fd = open("/dev/myled0",O_RDWR);  //阻塞方式打开 
//将阻塞打开方式改为非阻塞
//fcntl(fd,F_SETFL,fcntl(fd,F_GETFL)|O_NONBLOCK);
read(fd,buf,sizeof(buf));
---------------------------------------------------------------
mycdev_read()
{
    if(file->f_flags &O_NONBLOCK){
        //非阻塞
    }else{
        //阻塞(进程休眠)
        
    }
    //从硬件中读取数据
    
    //将数据拷贝到用户空间
}

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

在这里插入图片描述

3.2阻塞IO模型的API

1.定义等待队列头
    wait_queue_head_t wq;
2.初始化等待队列头
    init_waitqueue_head(&wq);	
3.阻塞(定义等待队列项,将等待队列项放到等待队列结尾,进程休眠)
	wait_event(wq, condition)  //不可中断的等待态
       功能:让进程进入不可中断的等待态
    参数:
    	@wq:等待队列头
        @condition:数据是否准备好的条件  
            //0数据没有准备好进程需要休眠 ,真代表数据准备好了,进程不需要休眠。
    返回值:无
    wait_event_interruptible(wq, condition) //可中断的等待态
    功能:让进程进入可中断的等待态
    参数:
    	@wq:等待队列头
        @condition:数据是否准备好的条件  
            //0数据没有准备好进程需要休眠 ,真代表数据准备好了,进程不需要休眠。
    返回值:如果是数据准备好了返回0,如果是信号唤醒的休眠返回错误码-ERESTARTSYS
4.唤醒
    condition=1wake_up(&wq)
    wake_up_interruptible(&wq)

3.3阻塞IO模型实例

B唤醒A进程的方式
在这里插入图片描述

myled.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 "myled"
#define COUNT 3
struct cdev* cdev;
int major = 238;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;

//定义等待队列头
wait_queue_head_t wq;
//数据是否准备好的条件,0没有准备好,1准备好了
int condition=0;

int myled_open(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t myled_read(struct file* filp,
    char __user* ubuf, size_t size, loff_t* offs)
{
    int ret;
    //判断打开的方式
    if(filp->f_flags & O_NONBLOCK){
        //非阻塞
        return -EINVAL;
    }else{
        //阻塞
        ret = wait_event_interruptible(wq,condition);
        if(ret == -ERESTARTSYS){
            printk("receive signal....\n");
            return 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 -EINVAL;
    }

    //将condition设置为0,为后面调用考虑
    condition=0;

    return size;
}

ssize_t myled_write(struct file* filp,
    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 from user error\n");
        return -EINVAL;
    }

    //如果condition不设置为1,唤醒之后又会休眠
    condition=1;
    wake_up_interruptible(&wq);

    return size;
}

int myled_close(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

const struct file_operations fops = {
    .open = myled_open,
    .read = myled_read,
    .write = myled_write,
    .release = myled_close,
};

static int __init myled_init(void)
{
    int ret, i;
    dev_t devno;
    // 1.分配对象
    cdev = cdev_alloc();
    if (cdev == NULL) {
        printk("alloc cdev memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }

    // 2.对象初始化
    cdev_init(cdev, &fops);

    // 3.设备号的申请
    if (major == 0) {
        //动态申请
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    }

    // 4.字符设备驱动的注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("register chardev 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 = minor; i < minor + 3; i++) {
        dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%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 >= minor; 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 myled_exit(void)
{
    int i;
    //注销设备节点
    for (i = minor; i < minor + 3; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    //字符设备驱动的注销
    cdev_del(cdev);
    //设备号释放
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    //释放cdev的内存空间
    kfree(cdev);
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");

test.c

#include <head.h>

int main(int argc, const char* argv[])
{
    int fd;
    pid_t pid;
    char buf[128] = "i am test block code...............\n";
    if ((fd = open("/dev/myled0", O_RDWR)) == -1)
        PRINT_ERR("open error");

    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("child buf = %s", buf);
        }
    } else {
        while (1) {
            sleep(3);
            write(fd, buf, sizeof(buf));
        }
        wait(NULL);
    }

    close(fd);
    return 0;
}

3.4阻塞IO模型中阻塞的实现流程

在这里插入图片描述

//wait_event_interruptible如果数据没有准备好让进程进入休眠状态
//wq_head:等待队列头

//condition:数据是否准备好的条件
#define wait_event_interruptible(wq_head, condition)				
({										
	int __ret = 0;								

	//如果用户传递的condition为0,对0取反就是真,就会执行if语句里面的内容
	//如果用户传递的condition为真,此时if语句就不成立
	if (!(condition))							
		__ret = __wait_event_interruptible(wq_head, condition);		
	
	__ret;	//如果数据准备好了这个宏返回的是0								
})


#define __wait_event_interruptible(wq_head, condition)				
	___wait_event(wq_head, condition, TASK_INTERRUPTIBLE, 0, 0,schedule())
	//wq_head:等待队列头
	//condition:数据是否准备好的条件
	//TASK_INTERRUPTIBLE:进程的状态,可中断的等待态
	//0,0
	//schedule();主动放弃cpu
	

#define ___wait_event(wq_head, condition, state, exclusive, ret, cmd)		
({			
	struct wait_queue_entry __wq_entry; 
	//等待队列项,__wq_entry等待队列项的变量
	long __ret = ret;	/* explicit shadow */				
										
	init_wait_entry(&__wq_entry, exclusive ? WQ_FLAG_EXCLUSIVE : 0);	
	//对等待队列项进程初始化
	
	for (;;) {								
		long __int = prepare_to_wait_event(&wq_head, &__wq_entry, state);
		//将等待队列项放到等待队列头之后
		//将进程的状态修改为可中断的等待态 
		//set_current_state(TASK_INTERRUPTIBLE);
										
		if (condition)	 //如果数据准备好condition为真从这里退出						
			break;							
										
		if ( signal_pending_state(state, current)) 
			//如果收到了信号,这里的if为真,从这里退出
		{ 		
			list_del_init(&wq_entry->entry);
			__ret = -ERESTARTSYS;						
			goto __out; 					
		}								
										
		schedule();
		//如果上述的两个条件都不为真,执行schedule()函数,主动放弃cpu
		//直到收到信号或者调用唤醒的函数才回到这里继续执行						
	}		
	
	finish_wait(&wq_head, &__wq_entry); 	
	//set_current_state(TASK_RUNNING); 将进程的状态重新设置为运行态
	//将等待队列项从等待队列头上删除
__out:	__ret;									
})

4.IO多路复用

4.1IO多路复用原理简介

在这里插入图片描述
如果在同一个app应用程序想同时监听多个硬件的数据,如果使用阻塞的IO模型会出现如下效果:

当read(fd1,buf1,sizeof(buf1))调用的使用由于si7006数据没有准备好进程会休眠,而此时

鼠标的数据准备准备好了,要去读取鼠标的数据,但是此时read(fd2,buf2,sizeof(buf2))调用

不到。所以就会出现数据准备好没有取读的现象。可通过IO多路复用的机制来解决。

IO多路复用:将打开的文件描述符fd1和fd2可以放到select监听的表中,让select监听

这两个文件描述符,如果文件描述符的数据没有准备好,进程休眠。如果有其中一个或

多个硬件的数据准备好,就会产生中断,在中断处理函数中唤醒这个休眠的进程。select

就返回了。借助 FD_ISSET(int fd, fd_set *set)判断当前的文件描述符数据是否准备好

了如果数据准备好了调用read将数据读走即可。
在这里插入图片描述

4.2IO多路复用接口调用过程

us:
    fd1 = open("/dev/mycdev0",O_RDWR);
    fd2 = open("/dev/input/mouse0",O_RDWR);

    fd_set rfds;
    FD_ZERO(&rfds);
    FD_SET(fd1,&rfds);
    FD_SET(fd2,&rfds);
	
	select(fd2+1,&rfds,NULL,NULL,NULL); //select/poll/epoll

	if(FD_ISSET(fd1,&rfds)){
        read(fd1,buf1,sizeof(buf1));
    }
	if(FD_ISSET(fd2,&rfds)){
        read(fd2,buf2,sizeof(buf2));
    }
	
______________________________________________________________________________
ks:fops://应用成的select、poll、epoll调用的时候驱动的poll函数都会执行
	#include <linux/poll.h>
	__poll_t (*poll) (struct file *file, struct poll_table_struct *wait)
    {
        //1.定义返回值的变量
        __poll_t mask=0;
        //2.猜测是和阻塞相关的函数
        poll_wait(file,等待队列头,wait)//3.如果数据准备好
        if(condition){
            mask |= EPOLLIN ;   //EPOLLIN 可读  EPOLLOUT 可写
        }
        //4.返回结果
        return mask;
    }

	grep ".poll = " * -nR

4.3IO多路复用的实例

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"
#define COUNT 3
struct cdev* cdev;
int major = 238;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;

//定义等待队列头
wait_queue_head_t wq;
//数据是否准备好的条件,0没有准备好,1准备好了
int condition = 0;

int mycdev_open(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t mycdev_read(struct file* filp,
    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 -EINVAL;
    }

    //将condition设置为0,为后面调用考虑
    condition = 0;

    return size;
}

ssize_t mycdev_write(struct file* filp,
    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 from user error\n");
        return -EINVAL;
    }

    //如果condition不设置为1,唤醒之后又会休眠
    condition = 1;
    wake_up_interruptible(&wq);

    return size;
}
__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.返回结果
    return mask;
}
int mycdev_close(struct inode* inode, struct file* filp)
{
    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("alloc cdev memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }

    // 2.对象初始化
    cdev_init(cdev, &fops);

    // 3.设备号的申请
    if (major == 0) {
        //动态申请
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    }

    // 4.字符设备驱动的注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("register chardev 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 = minor; i < minor + 3; 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 >= minor; 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 = minor; i < minor + 3; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    //字符设备驱动的注销
    cdev_del(cdev);
    //设备号释放
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    //释放cdev的内存空间
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

test.c

#include <head.h>

char buf[128] = { 0 };
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 mycdev0 error");

    if ((fd2 = open("/dev/input/mouse0", O_RDWR)) ==-1) 
        PRINT_ERR("open mouse0 error");

    while(1){
        FD_ZERO(&rfds);
        FD_SET(fd1,&rfds);
        FD_SET(fd2,&rfds);

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

        if(FD_ISSET(fd1,&rfds)){
            memset(buf,0,sizeof(buf));
            read(fd1,buf,sizeof(buf));
            printf("mycdev0:buf = %s\n",buf);
        }
        if(FD_ISSET(fd2,&rfds)){
            memset(buf,0,sizeof(buf));
            read(fd2,buf,sizeof(buf));
            printf("mouse0:buf = %s\n",buf);
        }
    }
    close(fd1);
    close(fd2);
    return 0;
}

4.4IO多路复用的内部原理

4.4.1VFS层的引出

在这里插入图片描述

__poll_t mycdev_poll(struct file* file,
    struct poll_table_struct* wait)
{
    // 1.定义返回值的变量
    __poll_t mask = 0;

    // 2.程序不能在这里阻塞,如果在这里阻塞,上图的流程就说不通了
    //这个函数调用的时候会构造等待队列。阻塞过程发生在VFS(虚拟文件系统层)
    poll_wait(file, &wq, wait);
    // 3.如果数据准备好
    if (condition) {
        mask |= EPOLLIN; // EPOLLIN 可读  EPOLLOUT 可写
    }
    // 4.返回结果
    return mask;
}

4.4.2IO多路复用sys_select分析

//vi -t sys_select

SYSCALL_DEFINE5(select, int, n, fd_set __user *, inp, fd_set __user *, outp,                    
        fd_set __user *, exp, struct __kernel_old_timeval __user *, tvp) 
	|
//SYSCALL_DEFINE5它是一个可以替换为sys_select函数
	|
long sys_select(int n, fd_set __user * inp, fd_set __user * outp,					
			fd_set __user * exp, struct __kernel_old_timeval __user * tvp) 

{
	//n:最大的文件描述符的值加1
	//inp:用户读表的首地址

	//outp:写表的首地址
NULL
	//exp:其他表的首地址
NULL
	//tvp:超时时间   NULL
    return kern_select(n, inp, outp, exp, tvp);
}


static int kern_select(int n, fd_set __user *inp, fd_set __user *outp,
		       fd_set __user *exp, struct __kernel_old_timeval __user *tvp)
{

	int ret;
	ret = core_sys_select(n, inp, outp, exp, to);
	return poll_select_finish(&end_time, tvp, PT_TIMEVAL, ret);
}


int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,
			   fd_set __user *exp, struct timespec64 *end_time)
{
	fd_set_bits fds;
	void *bits;
	int ret, max_fds;
	size_t size, alloc_size;
	struct fdtable *fdt;
	long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];

	//1.对最大文件描述符的值校验及更正
	if (n < 0)  //判断用户传递的n是否合法,如果不合法就直接报错返回
		goto out_nofds;
	//从进程中获取最大文件描述符和用户传递的最大文件描述符比对
	//如果用户传递的不对强制更正。
	fdt = files_fdtable(current->files);
	max_fds = fdt->max_fds;
	if (n > max_fds)
		n = max_fds;
	

	//2.在内核空间分配6张表的内存,将用户空间的三张表
	//拷贝到内核空间,并将准备好文件描述符的三张表先清空
	//in out ex :保存用户传递过来的表
	//res_in res_out res_ex:保存准备好文件描述符的表
	alloc_size = 6 * size;
	bits = kvmalloc(alloc_size, GFP_KERNEL);

	fds.in      = bits;
	fds.out     = bits +   size;
	fds.ex      = bits + 2*size;
	fds.res_in  = bits + 3*size;
	fds.res_out = bits + 4*size;
	fds.res_ex  = bits + 5*size;

	//将用户的读,写,其他表拷贝到刚分配的内核空间的表中
	//fds.in   fds.out    fds.ex
	if ((ret = get_fd_set(n, inp, fds.in)) ||
	    (ret = get_fd_set(n, outp, fds.out)) ||
	    (ret = get_fd_set(n, exp, fds.ex)))
		goto out;
	//清空准备好的文件描述符的表
	zero_fd_set(n, fds.res_in);
	zero_fd_set(n, fds.res_out);
	zero_fd_set(n, fds.res_ex);


	//3.遍历文件描述符
	ret = do_select(n, &fds, end_time);


	//4.将准备好的文件描述符拷贝到用户空间
	if (set_fd_set(n, inp, fds.res_in) ||
	    set_fd_set(n, outp, fds.res_out) ||
	    set_fd_set(n, exp, fds.res_ex))
		ret = -EFAULT;

}



static int do_select(int n, fd_set_bits *fds, struct timespec64 *end_time)
{
	ktime_t expire, *to = NULL;
	struct poll_wqueues table;
	poll_table *wait;
	int retval, i, timed_out = 0;
	u64 slack = 0;
	__poll_t busy_flag = net_busy_loop_on() ? POLL_BUSY_LOOP : 0;
	unsigned long busy_start = 0;

	
	//1.校验最大的文件描述符的值
	retval = max_select_fd(n, fds);
	if (retval < 0)
		return retval;
	n = retval;
	

	//2.填充一个初始化等待队列的函数,但是这个函数并没有在这里被调用
	poll_initwait(&table);
	wait = &table.pt;  //将table中初始化好的函数指针赋值给wait结构体
	//wait后面会传递给驱动的poll函数


	retval = 0;
	for (;;) {
		unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
		//定义六个指针,分别进行赋值
		inp = fds->in; outp = fds->out; exp = fds->ex;
		rinp = fds->res_in; routp = fds->res_out; rexp = fds->res_ex;
		
		
		for (i = 0; i < n; ++rinp, ++routp, ++rexp) {
			unsigned long in, out, ex, all_bits, bit = 1, j;
			unsigned long res_in = 0, res_out = 0, res_ex = 0;
			__poll_t mask;

			//取出来文件描述符表中第一个unsigned long的成员
			//然后让inp指向下一个unsigned long
			in = *inp++; out = *outp++; ex = *exp++;
			all_bits = in | out | ex;
			//如果取到的值为0,表示这里没有要监听的文件描述符
			//执行下一循环取出下一个unsigned long的值,在判断
			if (all_bits == 0) {
				i += BITS_PER_LONG;
				continue;
			}
			
			//循环判断那个in中的那个bit位是1
			for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
				struct fd f;

				if (i >= n) //如果i>=n说明已经到了最大文件描述符了直接退出
					break;

				//判断all_bits对应的bit为是否为1,如果不为1
				//执行下一次循环,判断下一个bit是否为1.
				if (!(bit & all_bits))
					continue;

				//这里的i是文件描述符,根据文件描述符可以取到file结构体
				file = fd->fd_array[fd]->file
				//f = fdget(i);
				if (file) {
					//调用驱动的poll函数执行,驱动的poll的
					//返回值EPOLLIN或0,如果是EPOLLIN表示数据准备好了
					//如果返回的是0,表示数据没有准备好
					mask = file->f_op->poll(file, wait);

					if ((mask & POLLIN_SET) && (in & bit)) {
						res_in |= bit;
						retval++;
						wait->_qproc = NULL;
					}
					if ((mask & POLLOUT_SET) && (out & bit)) {
						res_out |= bit;
						retval++;
						wait->_qproc = NULL;
					}
					if ((mask & POLLEX_SET) && (ex & bit)) {
						res_ex |= bit;
						retval++;
						wait->_qproc = NULL;
					}
				}
			}
		}
		
		//select什么时候返回?
		//数据准备好返回
		//超时时间到了返回
		//信号到来的时候返回
		if (retval || timed_out || signal_pending(current))
			break;
	
		//如果遍历所有的文件描述符之后,如果所有的驱动返回的值都是0
		//表示select监听的所有文件描述符的数据都没有准备好,此时进程
		//调用poll_schedule_timeout休眠
		if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,
					   to, slack))
			timed_out = 1;
	}

	poll_freewait(&table);

	return retval;
}

4.4.3IO多路复用总结

应用层:
	select(maxfd+1,&rfds,NULL,NULL,NULL)
_____________________(系统调用swi 142)________________________________________
内核层:   |vfs: NATIVE(142, sys_select) 
		   |sys_select
    	   |1.校验最大的文件描述符的值
    	   |2.在内核空间分配6张表的内核,将用户空间的表拷贝到内核空间
    	   |3.遍历文件描述符
    	   |   mask = fd_set->fd->fd_array[fd]->file->f_op->poll (file,wait);
    	   |4.如果所有的文件描述符返回的mask都是0,此时进程休眠
    	   |5.如果休眠的进程被唤醒了,再次遍历文件描述符表,从中找到mask不为0|文件描述符,将这些文件描述符放到准备好的文件描述符表中
           |6.将准备好的文件描述符拷贝到用户空间
           |__________________________________________________________________
           |drivers
		   |1.定义返回值的变量 __poll_t mask=0;
           |2.调用poll_wait函数
    	   |3.如果数据准备好设置mask|=EPOLLIN
		   |4.return mask
______________________________________________________________________________

4.5select/poll/epoll的区别?

select: (表)

​ 1.select最多能够监听的文件描述符的个数是1024

​ 2.select需要反复构造表,需要反复从用户空间向内核空间拷贝要监听的表,效率低

​ 3.如果select监听的所有的文件描述符没有准备好数据,进程休眠。如果有文件描述符

​ 的数据准备好了,需要重新遍历一遍文件描述符的表,效率比较低。

poll: (结构体数组)

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

​ 2.poll不需要返回拷贝监听的文件描述符

​ 3.如果poll监听的所有的文件描述符没有准备好数据,进程休眠。如果有文件描述符

​ 的数据准备好了,需要重新遍历一遍文件描述符的表,效率比较低。

epoll: (红黑树)

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

​ 2.epoll不需要返回拷贝监听的文件描述符

​ 3.epoll当文件描述符数据准备好的时候,不需要遍历,可以直接拿到准备好的文件描述符。

4.6epoll的使用

4.6.1epoll的API

#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 *revents,
                      int maxevents, int timeout);
功能:阻塞等待准备好的文件描述符
参数:
    @epfd:epoll的文件描述符
	@revents:返回准备好的事件结构体
    @maxevents:同时返回的最大的时间结构体的个数
	@timeout:超时
        	>0 :毫秒超时
            =0 :立即返回
            =-1:不关心超时
返回值:成功大于0,表示准备好的文件描述符的个数
        返回0,表示超时时间到了
        失败返回-1置位错误码

4.6.2epoll的实例

#include <head.h>

int main(int argc, const char* argv[])
{
    int epfd, fd,ret;
    char buf[128] = {0};
    struct epoll_event event;
    struct epoll_event revents[10];
    // 1.创建epoll
    if ((epfd = epoll_create(10)) == -1)
        PRINT_ERR("epoll create error");

    // 2.将监听的文件描述符放入epoll
    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");
    }
    // 3.等待准备好的文件描述符
    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;
}

5.IO模模型之异步通知

5.1异步通知的IO模型原理

在进程中注册一个信号处理函数,如果硬件的数据准备好的时候,会产生中断,

在中断处理函数中给这个进程发送信号即可。如果内核没有发出信号应用程序,

不需要阻塞,运行自己特有的代码即可。

应用层:
		1.注册信号处理函数
    		signal(SIGIO,信号处理函数);
		2.调用底层的fasync,在它内部完成发信号前的初始化
            unsigned int flags = fcntl(fd,F_GETFL); //file->f_flags
			fcntl(fd,F_SETFL,flags|FASYNC);
		3.告诉内核进程的pid
            fcntl(fd,F_SETOWN,getpid());
---------------------------------------------------------------------
vi -t 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);
----------------------------------|-----------------------------------
内核层:fops:
	int (*fasync) (int fd, struct file *file, int on)
    {
        //在它内部完成发信号前的初始化
        //int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp)
        return fasync_helper(fd,file,on,&fapp);
    }

	发信号:
       kill_fasync(fapp,SIGIO, POLL_IN/EPOLLIN);

5.2异步通知的实例

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"
#define COUNT 3
struct cdev* cdev;
int major = 238;
int minor = 0;
char kbuf[128] = { 0 };
struct class* cls;
struct device* dev;
struct fasync_struct *fapp;

int mycdev_open(struct inode* inode, struct file* filp)
{
    printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);
    return 0;
}

ssize_t mycdev_read(struct file* filp,
    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 -EINVAL;
    }

    return size;
}

ssize_t mycdev_write(struct file* filp,
    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 from user error\n");
        return -EINVAL;
    }
    //发信号
    kill_fasync(&fapp,SIGIO,EPOLLIN);
    
    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* filp)
{
    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("alloc cdev memory error\n");
        ret = -ENOMEM;
        goto ERR1;
    }

    // 2.对象初始化
    cdev_init(cdev, &fops);

    // 3.设备号的申请
    if (major == 0) {
        //动态申请
        ret = alloc_chrdev_region(&devno, minor, COUNT, CNAME);
        if (ret) {
            printk("dynamic:request device number error\n");
            goto ERR2;
        }
        major = MAJOR(devno);
        minor = MINOR(devno);
    } else {
        //静态指定
        ret = register_chrdev_region(MKDEV(major, minor), COUNT, CNAME);
        if (ret) {
            printk("static:request device number error\n");
            goto ERR2;
        }
    }

    // 4.字符设备驱动的注册
    ret = cdev_add(cdev, MKDEV(major, minor), COUNT);
    if (ret) {
        printk("register chardev 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 = minor; i < minor + 3; 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 >= minor; 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 = minor; i < minor + 3; i++) {
        device_destroy(cls, MKDEV(major, i));
    }
    class_destroy(cls);
    //字符设备驱动的注销
    cdev_del(cdev);
    //设备号释放
    unregister_chrdev_region(MKDEV(major, minor), COUNT);
    //释放cdev的内存空间
    kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");

test.c

#include <head.h>
int fd;
char buf[128] = { 0 };
void signal_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 mycdev0 error");
    // 1.注册信号处理函数
    if ((signal(SIGIO, signal_handle)) == SIG_ERR)
        PRINT_ERR("signal error");

    // 2.调用底层的fasync
    unsigned int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);
    // 3.告诉内核当前进程的pid
    fcntl(fd, F_SETOWN, getpid());

    while (1) {
        sleep(1);
    }

    close(fd);
    return 0;
}

DEV(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 = minor; i < minor + 3; i++) {
device_destroy(cls, MKDEV(major, i));
}
class_destroy(cls);
//字符设备驱动的注销
cdev_del(cdev);
//设备号释放
unregister_chrdev_region(MKDEV(major, minor), COUNT);
//释放cdev的内存空间
kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE(“GPL”);


test.c

```c
#include <head.h>
int fd;
char buf[128] = { 0 };
void signal_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 mycdev0 error");
    // 1.注册信号处理函数
    if ((signal(SIGIO, signal_handle)) == SIG_ERR)
        PRINT_ERR("signal error");

    // 2.调用底层的fasync
    unsigned int flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);
    // 3.告诉内核当前进程的pid
    fcntl(fd, F_SETOWN, getpid());

    while (1) {
        sleep(1);
    }

    close(fd);
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值