• linux驱动开发:IO模型


    目录

    1.非阻塞型I/O

    2.阻塞型IO(等待队列)

    3.IO多路复用poll

    4.异步IO

    5.异步通知(信号驱动IO)


    1.非阻塞型I/O

    设备不一定随时都能给用户提供服务,资源也就有了可用和不可用两种状态。举个例子,当用户想要读取虚拟串口的数据时,如果FIFO中没有数据,那么对读进程来说就是资源不可用,但对于写进程,此时资源是可用的。当FIFO为满时,对写进程来说资源是不可用的。当资源不可用时,应用程序和驱动一起的各种配合就组成了多种IO模型。

    如果应用程序以非阻塞方式来打开设备文件,当资源不可用时,驱动就应该立即返回,并用一个错误码来通知应用程序此时资源不可用,对于这样的方式驱动程序的读写接口代码应该做一些修改。

    1. static ssize_t my_read (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
    2. {
    3. int ret;
    4. unsigned int copied=0;
    5. if(kfifo_is_empty(&myfifo))//判断fifo中是否有数据
    6. if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
    7. return -EAGAIN;//没有数据可读立即返回错误码
    8. ret=kfifo_to_user(&myfifo,ubuf,size,&copied);//从管道中读数据到ubuf中
    9. return ret==0?copied:ret;
    10. }
    11. static ssize_t my_write (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
    12. {
    13. int ret;
    14. unsigned int copied=0;
    15. if(kfifo_is_full(&myfifo))//判断fifo中是否满了
    16. if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
    17. return -EAGAIN;//没有空间可写立即返回错误码
    18. ret=kfifo_from_user(&myfifo,ubuf,size,&copied);//给管道写数据
    19. return ret==0?copied:ret;
    20. }

    kfifo_is_empty:判断管道是否为空的宏,参数是管道的地址

    kfifo_is_full:判断管道是否为满的宏,参数是管道的地址

    O_NONBLOCK:表示以非阻塞方式打开设备文件

    2.阻塞型IO(等待队列)

    当进程以阻塞的方式打开设备文件时(默认方式),如果资源不可用,那么进程阻塞,也就是进程休眠,具体讲就是将在自己的状态主动设置为TASK_INTERRUPTIBLE,然后将自己加入一个驱动所维护的等待队列中,相较于非阻塞IO,最大的优点就是资源不可用时,不占用CPU时间,而非阻塞型IO必须定期尝试,看资源是否可以获得,但缺点就是进程在休眠期间不能做其他的事。

    当然我们也会有相应的唤醒操作,驱动程序在资源可用时负责执行唤醒操作,所以在我们实现阻塞操作,最重要的数据结构就是等待队列:根本性的工作就是构造并初始化等待头队列,构造等待队列节点,设置进程状体,将节点加入到等待队列,放弃CPU,调度其他进程执行,在资源可用时唤醒队列上的进程

    等待队列头的数据类型:wait_queue_head_t

    队列节点的数据类型:wait_queue_t

    下面介绍一些围绕等待队列的宏和函数

    头文件

    linux/wait.h
    linux/sched.h

    DECLARE_WAIT_QUEUE_HEAD(name)//静态定义一个等待队列头

    wait_queue_head_t my_wq; //定义等待队列

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

    wait_event(queue, condition); //等待事件不可中断

    wait_event_timeout(queue, condition,timeout);

    wait_event_interruptible(queue, condition); //等待事件可被中断

    wait_event_interruptible_timeout(queue, condition,timeout);

    //等待,直到条件为真

    //queue:等待队列对象

    //condition:等待条件

    //timeout:有超时检测

    //interruptible:为可中断,否则为不可中断

    //返回值:被信号打断,返回-ERESTARTSYS

    //带超时检测:返回0表示超时,返回大于0表示成功唤醒

    void wake_up(wait_queue_head_t *queue);

    void wake_up_interruptible(wait_queue_head_t *queue);

    //唤醒队列

    //queue:等待队列对象

    //分别唤醒对应的函数

    wait_event是在条件condition不成立的情况下将当前进程放入到等待队列并休眠的基本操作

    下面来看一段修改为阻塞IO的代码:

    1. #include
    2. #include
    3. wait_queue_head_t rwqh; //定义读等待队列头
    4. wait_queue_head_t wwqh; //定义写等待队列头
    5. static ssize_t my_read (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
    6. {
    7. int ret;
    8. unsigned int copied=0;
    9. if(kfifo_is_empty(&myfifo))//判断fifo中是否有数据
    10. {
    11. if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
    12. return -EAGAIN;//没有数据可读立即返回错误码
    13. //在管道为空的情况下进行休眠
    14. if(wait_event_interruptible(rwqh,!kfifo_is_empty(&myfifo)))
    15. return -ERESTARTSYS;
    16. }
    17. ret=kfifo_to_user(&myfifo,ubuf,size,&copied);//从管道中读数据到ubuf中
    18. if(!kfifo_is_full(&myfifo))
    19. wake_up_interruptible(&wwqh);
    20. return ret==0?copied:ret;
    21. }
    22. static ssize_t my_write (struct file *pf, char __user *ubuf, size_t size, loff_t *loff)
    23. {
    24. int ret;
    25. unsigned int copied=0;
    26. if(kfifo_is_full(&myfifo))//判断fifo中是否满了
    27. {
    28. if(file->f_flags & O_NONBLOCK)//O_NONBLOCK表示以非阻塞打开设备文件
    29. return -EAGAIN;//不可写立即返回错误码
    30. //在管道内容为满的情况下,进入等待休眠
    31. if(wait_event_interruptible(wwqh,!kfifo_is_full(&myfifo)))
    32. return -ERESTARTSYS;
    33. }
    34. ret=kfifo_from_user(&myfifo,ubuf,size,&copied);//向管道写数据
    35. if(!kfifo_is_empty(&myfifo))//如果管道不为空,唤醒读等待头
    36. wake_up_interruptible(&rwqh);
    37. return ret==0?copied:ret;
    38. }
    39. static int __init myuser_init(void)
    40. {
    41. init_waitqueue_head(&rwqh); //初始化读等待队列
    42. init_waitqueue_head(&wwqh); //初始化写等待队列
    43. }

    3.IO多路复用poll

    阻塞型IO相较于非阻塞型IO来说,最大的优点就是在设备的资源不可用时,进程主动放弃CPU,让其他的的进程运行,而不用不停轮询,但进程阻塞后无法做其他的操作,这在一个进程中要同时对多个设备进行操作时显得非常不方便,有几种解决办法,比如多进程,多线程和IO多路复用,这里我们主要讨论IO多路复用,应用层调用select或poll都会触发驱动层的poll执行。这里以poll为例来说明:基本思路,在驱动中实现poll操作,并通过poll_wait可以向驱动向poll_table结构添加一个等待队列

    poll系统调用的原型及相关数据类型回顾:

    int poll(struct pollfd *fds,nfds_t nfds,int timeout)

    //参数1.文件描述符集合

    //参数2.监听的文件描述符个数

    //参数3.超时检测:-1表示一直2监听

    struct pollfd{

            int fd;//文件描述符

            short events;//需要监听的事件

            short revents;//返回的事件

    };

    POLLIN:读

    POLLOUT:写

    来看看驱动层的示例代码:

    static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p); //用于实现poll

    //filp:文件指针

    //wait_address:等待队列

    //poll_table:poll_table对象

    1. static struct file_operations myfops={
    2. .open = my_open,
    3. .release = my_close,
    4. .read = my_read,
    5. .write = my_write,
    6. .poll = my_poll,
    7. };
    8. unsigned int my_poll (struct file *pf, struct poll_table_struct *ptab)
    9. {
    10. unsigned int mask = 0;
    11. printk("my_poll\n");
    12. poll_wait(pf,&rwqh,ptab);
    13. poll_wait(pf,&wwqh,ptab);
    14. if(!kfifo_is_full(&myfifo)){ //可以写入数据
    15. mask |= POLLOUT;
    16. }
    17. if(!kfifo_is_empty(&myfifo))
    18. { //可以读数据
    19. mask |= POLLIN;
    20. }
    21. return mask;
    22. }

    4.异步IO

    调用者只是发起IO操作的请求,然后立即返回,程序可以去做别的事情。具体的IO操作在驱动中完成,驱动中可能会被阻塞,也可能不会。当驱动的IO操作完成后,调用者将会得到通知,通常是内核向调用者发送信号,或者自动调用调用者注册的回调函数,通知操作是由内核完成的,而不是驱动本身。

    5.异步通知(信号驱动IO)

    异步通知类似前面的异步IO,只是当设备资源可用时它是向应用层发信号,而不能直接调用应用层注册的回调函数,并且发信号的操作也是驱动程序自身来完成的。相对与阻塞,非阻塞以及轮询,信号驱动io提供了一种类似与中断的解决机制。注册好后,一旦设备就绪,便主动通知到应用程序。

    驱动代码需要完成下面几个操作:

    1.构造struct fasync_struct队列的头 
    struct fasync_struct *fasyncqueue=NULL; 

    2.初始化fasync_struct
    int fasync_helper(int fd, struct file * filp, int on, struct fasync_struct **fapp); 

    3.信号的释放(发信号)
    void kill_fasync(struct fasync_struct **fp, int sig, int band);
    //当设备可以访问的时候,驱动程序需要向应用程序发出信号,相当于产生”中断“。kill_fastync函数负责发送指定的信号。
    //fp :fasync_struct对象
    //sig:信号,例如SIGIO
    //可读时设置为POLL_IN,可写时设置为POLL_OUT

    4.注意模块释放时,删除异步通知
    xxx_fasync(-1,filp,0);

    应用层编程流程如下:

    signal(SIGIO, signal_handler);    
    fcntl(fd, F_SETOWN, getpid());
    flags = fcntl(fd, F_GETFL);
    fcntl(fd, F_SETFL, flags | FASYNC);

    //1.通过signal()函数连接信号和信号处理函数,相当于注册中断处理函数
    //2.通过F_SETOWN IO控制命令设置设备文件的拥有者为本进程,这样从设备驱动发出的信号才能被本进程接收到
    //3.通过fcntl设置文件以支持FASYNC,即异步通知模式

    1. /***************驱动层部分代码示例*****************/
    2. wait_queue_head_t my_wq; //定义等待队列
    3. struct fasync_struct *fasyncqueue=NULL; //定义异步通知队列
    4. int my_open (struct inode *pi, struct file *pf);
    5. int my_close(struct inode *pi, struct file *pf);
    6. ssize_t my_read (struct file *pf, char __user *ubuf, size_t size, loff_t *loff);
    7. ssize_t my_write (struct file *pf, const char __user *ubuf, size_t size, loff_t *loff);
    8. unsigned int my_poll (struct file *pf, struct poll_table_struct *ptab);
    9. int my_fasync (int fd, struct file * pf, int mode);
    10. static struct file_operations myfops={
    11. .open = my_open,
    12. .release = my_close,
    13. .read = my_read,
    14. .write = my_write,
    15. .poll = my_poll,
    16. .fasync = my_fasync,
    17. };
    18. int my_fasync (int fd, struct file * pf, int mode)
    19. {
    20. //1.通过fasync_helper,开启异步通知功能
    21. return fasync_helper(fd,pf,mode,&fasyncqueue);
    22. }
    23. unsigned int my_poll (struct file *pf, struct poll_table_struct *ptab)
    24. {
    25. unsigned int mask = 0;
    26. printk("my_poll\n");
    27. poll_wait(pf,&my_wq,ptab);
    28. if(len==0){ //可以写入数据
    29. mask |= POLLOUT;
    30. }
    31. else{ //可以读数据
    32. mask |= POLLIN;
    33. }
    34. return mask;
    35. }
    36. ssize_t my_write (struct file *pf, const char __user *ubuf, size_t size, loff_t *loff)
    37. {
    38. int ret;
    39. ret = copy_from_user(kbuf,ubuf,size);
    40. if(ret!=0){
    41. return ret;
    42. }
    43. len = size;
    44. kill_fasync(&fasyncqueue,SIGIO,POLL_IN); //2.发出SIGIO信号,通知到应用层
    45. return size;
    46. }
    47. int my_close(struct inode *pi, struct file *pf)
    48. {
    49. //3.关闭文件的时候, 删除异步通知
    50. my_fasync(-1,pf,0);
    51. return 0;
    52. }

  • 相关阅读:
    windows下redis的安装及其他注意事项
    《3D 数学基础》几何检测-最近点
    【CSS】CSS实现元素逐渐消失(实现元素透明逐渐消失/模糊)
    Unreal Engine 学习笔记 (1)—— 日夜交替
    类的继承顺序题目解析
    【UBOOT】1-使用与烧写
    Servlet 需要提供对应的doGet() 与 doPost()方法
    IDEA中字符串怎么自动转义,双引号自动转义的小技巧
    备忘录模式
    Linux环境下Nginx安装及Ubuntu Server 15.0.4尝试安装Nginx
  • 原文地址:https://blog.csdn.net/m0_70983574/article/details/126588831