设备 IO

IO有两个特性:阻塞/非阻塞 与 同步/异步,组合一下就是四种情况。

  • 同步
    设备驱动中,read/write方法属于同步IO
    在资源不可用时,read/write阻塞,比如等待队列等实现,就属于阻塞/同步的IO方式。
    如果是立即返回(EAGAIN,EBUSY等),就属于非阻塞。
    具体实现可以根据应用程序打开设备文件时是否带O_NONBLOCK标志来决定。
static ssize_t xxx_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)
{
    if(!ready_to_read) {
        if(filp->f_flag & O_NONBLOCK) {
            //非阻塞方式打开,则直接返回
            return -EAGAIN;
        }
        //阻塞方式,等待资源可用
        wait_event_interruptible(waitqueue, read_to_read);
    }
   .........
}
  • 异步
    所谓异步,就是在应用程序发送读写请求后,立即返回但并不立即获取数据,只是相当于向系统发送了一个读写请求,待驱动程序准备好后,再通知应用程序,再进行真正的读写。

异步的非阻塞方式有fasync方法:
应用程序设置了设备文件的 FASYNC属性后,驱动程序就可以在恰当的时候通过消息通知应用程序。
应用程序操作步骤:

//设置信号处理函数,驱动程序应当通过SIGIO来通知
signal(SIGIO, sig_handler); 
//设置设备文件的属主,设置为进程本身
fcntl(fd, F_SETOWN, getpid());
//设置设备文件FASYNC属性
f_flags  = fcntl(fd, F_GETFL);
fcntl(fd, F_SETFL, f_flags | FASYNC); //此时会调用驱动 fasync方法

驱动程序实现:

//定义fasync_struct 
struct fasync_struct *async_queue;
//实现fasync方法
int xxx_fasync(int fd, struct file *filp, int mode)
{
   ...............
    //注意async_queue是二级指针,fasync_help作用就是初始化async_queue
    ret = fasnc_helper(fd, filp, mode, &async_queue);
}
//在合适的时机通知应用程序
kill_fasync(&fasync_queue, SIGIO, POLL_IN); //POLL_IN为可读,POLL_OUT为可写

异步阻塞方式:IO多路复用(poll-select)
使用poll-select方法可以监视一组文件(fd_set),其中只要有一个文件可读/写,应用程序中select就会返回。
驱动程序中实现设备的poll方法:

static unsigned int xxx_poll(struct file *filp, poll_table *wait)
{
    unsigned mask = 0;
    poll_wait(filp, &rd_wait_queue, wait);
    poll_wait(filp, &wr_wait_queue, wait);
    if(is_readable) {
        mask |= POLL_IN | POLLRDNORM;
    }
    if(is_writeable) {
        mask |= POLL_OUT | POLL_WRNORM;
    }
    return mask;
}

驱动程序中 poll方法要做的事很少:
1、首先调用 poll_wait 将一个等待队列添加到poll_table中,该等待队列一般在设备文件变得可读/写时被唤醒。这里只是加入,并不开始在等待队列上休眠。
2、判断文件当前是否可读/写,这个一般有一个全局变量来表示。
3、如果判断时可读/写了,就置位mask并返回,否则返回0.
4、在我们的poll方法之上,有一个调用它的上级函数,在这个函数里会阻塞等待,当之前阻塞的等待队列被唤醒时,就会再次调用poll方法,直到返回非0.
参考:

理解poll_waite
问答:
如果当前不可读,那么在sys_poll->do_poll中当前进程就会睡眠在等待队列上,这个等待队列是由驱动程序提供的(就是poll_wait中传入的那个)。当可读的时候,驱动程序可能有一部分代码运行了(比如驱动的中断服务程序),那么在这部分代码中,就会唤醒等待队列上的进程,也就是之前睡眠的那个,当那个进程被唤醒后do_poll会再一次的调用驱动程序的poll函数,这个时候应用程序就知道是可读的了。

你可能感兴趣的:(设备 IO)