驱动中IO模型

        驱动中的IO模型也分为三种:阻塞IO、非阻塞IO、IO多路复用

1.阻塞IO

        当我们在应用程序中读取硬件数据时,不管硬件数据有没有准备好,read()函数不会阻塞而是继续向下执行。

        在应用程序中使用open打开文件,并用调用read函数:
 

//应用程序
fd=open("/dev/mycdev",O_RDWR|O_NONBLOCK);
read(fd,buf,sizeof(buf));

        其中调用open函数时,需要与上一个O_NONBLOCK来保证是非阻塞的打开文件。

        调用read时会调用驱动中的mycdev_read()函数,在这个函数中判断该文件是否是非阻塞方式:

//驱动
mycdev_read(struct file*file,ubuf,size,lof)
{
   
    if(file->f_flags&O_NONBLOCK)
    {
        //当前是非阻塞
        //直接读取硬件寄存器的数据
        //  copy_to_user();   
    }
}

        file->f_flags代表open函数的第二个参数,若该参数与O_NONBLOCK为真则为非阻塞,执行非阻塞的代码。

2.阻塞IO

        比如当应用程序中读取硬件数据时,如果硬件数据没有准备好,此时进程阻塞在read()函数位置,直到硬件数据就绪,通过read()函数读取硬件数据,程序向下执行,进程阻塞等待数据时处于休眠态。

        与非阻塞IO不同的是阻塞IO在调用open时不会或上一个O_NONBLOCK,默认O_RDWR就行,这样当file->f_flags与上O_NONBLOCK时就会被判定为假,执行阻塞IO的代码:
 

//****************应用程序*****************
fd=open("/dev/mycdev",O_RDWR);
read(fd,buf,sizeof(buf));
//**************驱动********************
mycdev_read(struct file*file,ubuf,size,lof)
{
   
    if(file->f_flags&O_NONBLOCK)
    {
        //当前是非阻塞
        //直接读取硬件寄存器的数据
        //  copy_to_user();   
    }
    else
    {
            //1.判断硬件数据是否准备好
            //2.如果准备好,把准备好的硬件数据拷贝到用户
            //3.如果硬件数据没准备好,将进程切换到休眠状态,直到数据准备好再将进程唤醒
    }
}

        为了实现阻塞IO需要设定一个condition,在驱动中判断condition的真假,如果condition为真,则表示硬件数据准备好,直接拷贝数据到用户空间,如果condition为假,则将进程的task_struct添加到等待队列中,将进程切换为休眠状态。

        当一个硬件数据准备完成后,产生中断,在中断处理函数中将condition设置为1,并将等待队列中的进程唤醒。

相关API:

1.定义等待队列头
wait_queue_head_t wq_head;
2.初始化等待队列头
init_waitqueue_head(&wq_head);
3.wait_event(wq_head, condition)  
功能:检查condition的真假,如果为真,则函数执行结束,如果为假,将进程切换到不可中断休眠状态
参数:wq_head:等待队列头
    condition:标志变量
4.wait_event_interruptible(wq_head, condition) 
   功能:检查condition的真假,如果为真,则函数执行结束,如果为假,将进程切换到可中断休眠状态
参数:wq_head:等待队列头
    condition:标志变量
返回值:如果condition为真则返回0,如果被一个信号中断,则返回错误码-ERESTARTSYS 

5.wake_up(&wq_head)
功能:将不可中断休眠态的进程唤醒,当执行这个函数时如果condition为假,则进程被唤醒后会再次休眠
参数:等待队列头地址
6.wake_up_interruptible(&wq_head)
功能:将  可中断休眠态的进程唤醒当执行这个函数时如果condition为假,则进程被唤醒后会再次休眠
参数:等待队列头地址

 3.IO多路复用

        想要在一个进程中同时监听多个硬件的数据,就需要使用IO多路复用,IO多路复用的实现机制有三种:select/poll/epoll。IO多路复用的基本思想是在用户空间中将监听的事件文件描述符添加到事件集合中,调用函数进行判断集合中文件描述符对应的硬件数据是否准备就绪,如果没有一个事件发生,将进程切换到休眠状态(可中断休眠状态)。当有一个或者多个硬件数据准备好了,将休眠的进程唤醒,对准备好的硬件数据进行读写。

        select的基本思想是用一个集合存储文件描述符,然后用select函数进行监听,当文件描述符触发事件时就会停止阻塞,并执行相应文件描述符的代码。

        为了实现select和poll,在应用层设计了鼠标设备和自定义设备,使用open以阻塞方式打开两个设备文件,分别以fd1,fd2表示文件描述符。再定义一个可读集合,将文件描述符添加到该可读集合中进行循环监听,当文件描述符被触发时就停止阻塞,执行代码。

        在VFS层,需要在内核空间中申请一片内存,将用户空间的文件描述符表拷贝到这片空间中 然后针对描述符表中的每个文件描述符都调用各自文件描述符对应的驱动中的poll操作方法。判断每一个文件描述符对应的poll方法的返回值,如果返回值都是0,表示没有事件发生,此时将进程 切换为可中断休眠态。 当一个或者多个硬件数据准备好了,在中断处理函数中将进程唤醒,这里需要重新遍历文件描述符表,找出发生事件的文件描述符,将文件描述符拷贝到用户空间。用户空间将发生事件的硬件数据读走即可。

        在设备驱动层,只有一个操作方法——poll方法,它的功能是向上提交等待队列头。

        

//************用户空间*******************
int fd1,fd2;
fd1=open("/dev/mycdev0",O_RDWR);
if(fd1<0)
{
    printf("设备文件打开失败\n");
}
fd2=open("/dev/input/mouse0",O_RDWR);
if(fd2<0)
{
    printf("设备文件打开失败\n");
}
//定义可读集合
 fd_set readfds;
 while(1)
 {
 //清空集合
     FD_ZERO(&readfds);
 //将监听的事件文件描述符添加到可读集合
     FD_SET(fd1,&readfds);
     FD_SET(fd2,&readfds);
     select(fd2+1,&readfds)
     //当select停止阻塞,判断发生的事件并且去读取数据
     if(FD_ISSET(fd1,&readfds))
     {
         read(fd1,buf,sizeof(buf));     
     }
       if(FD_ISSET(fd2,&readfds))
     {
         read(fd2,buf,sizeof(buf));     
     }
 }
 close(fd1);
 close(fd2);
 //************************VFS*****************************
 sys_select()
 {
         1.在内核空间中申请一片内存,将用户空间的文件描述符表拷贝到这片空间中
         2.针对描述符表中的每个文件描述符都调用各自文件描述符对应的驱动中的poll操作方法.
         3.判断每一个文件描述符对应的poll方法的返回值,如果返回值都是0,表示没有事件发生,此时将进程
         切换为可中断休眠态
         4.当一个或者多个硬件数据准备好了,在中断处理函数中将进程唤醒,这里需要重新遍历文件描述符表,找出发生事件的文件描述符,将文件描述符拷贝到用户空间
         5.用空间将发生事件的硬件数据读走即可
 }
 //************************设备驱动层******************************
 //select/poll/epoll在设备驱动中的操作方法只有一个,就是下面的这个poll方法
     __poll_t (*poll) (struct file *file, struct poll_table_struct *wait)
     {
         //将等待队列头向上提交
         void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
         {
             功能:向上提交等待队列头
             参数:
                 filp: 文件结构体指针
                 wait_address:要提交的等待队列头首地址
                 p: 将队列头向上提交的通道 
         }
            if(condition)
                return POLLIN;//POLLIN表示发生的事件为读事件POLLOUT表示写事件
            else
                return 0;                  
     }

3.epoll

        相比于select,epoll与select有很大的区别,首先select是在用户层将文件描述符集合,写入内存,然后内存根据触发的文件描述符,将触发的文件描述符集合发给用户层,这样,用户层需要遍历才知道哪些文件描述符被触发。

        而epoll则是在用户层打开文件的时候,就将文件描述符存入内核中的epoll树中,然后根据触发的文件描述符,将其组成链表,将该链表发到用户层,用户层不用判断就能直接使用链表中的文件描述符。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值