linux驱动-阻塞_非阻塞_异步通知

linux设备驱动中的阻塞,非阻塞I/O,异步通知


阻塞操作

在执行设备操作时, 若不能获得资源则进程睡眠,让出CPU,当满足可操纵条件后,内核唤醒进程继续执行;

由于阻塞的进程会进入睡眠状态,因此需要适时的唤醒它,否则进程就”一睡不醒了”.

在linux驱动中, 采用等待队列(wait queue)的方式进行唤醒睡眠的进程.

等待队列的驱动编程:(在已有的设备驱动基本框架上继续)

这里,假设一个设备要实现读/写.

首先就要考虑: 读的前提是要有数据可读,如果没有就需要阻塞等待,直到忘里面写入数据,然后才能读.(这里暂时不考虑设备空/满等造成的死锁);

其次, 当设备满了, 想要继续写,就要阻塞等待读操作把数据取走,然后才能继续写.

好, 接下来看看具体的驱动编程实现:

  1. 在模块加载函数xxx_init();中添加初始化等待队列头.(有几个添加几个)

    init_waitqueue_head (q_head);

    其中, q_head是在设备外新建的一个等待队列头,当然也可以将它封装到一个设备结构体中.
    wait_queue_head_t q_head;

  2. 读操作中进入睡眠状态, 等待唤醒

    wait_event_interruptible(q_head, condition);

    当前进程进入睡眠等待的可打断状态, 直到条件condition满足时才被唤醒. (这里条件就是文件可读,这个条件可根据实际情况来设置,如网卡有数据/串口有数据等等)

  3. 写操作完成后, 发出唤醒的信号

    wake_up_interruptible(wait_queue_head_t *q_head);

    当写的操作完成后, 设备可读了, 然后就发出wake_up_interruptible信号,唤醒等待队列q_head中的进程. 这样读操作自动被唤醒执行.


非阻塞操作:

进程在不能进行设备操作是并不睡眠,而是直接返回结果;(使用较多.)

实现过程:

这样要实现设备的状态的监控,如果使用while(1)循环的轮询,那么会占用大量的资源.不合适.

因此应用层通过select() / poll() /epoll() 则可妥善的处理这样的问题.把轮询交给kernel(sys_poll)进行监控处理(其实linux操作系统的调度算法才是核心, 这里linux提供了简单的接口,减轻了咱们的难度).然后轮询时间有kernel来控制, 它最终时刻调用驱动程序中的.poll, 通过它的返回值便可查询硬件的状态,从而返回给应用层.

驱动编程实现:

  1. 应用层:
    调用select/poll/epoll;(略)

  2. 驱动层(kernel):
    file_operation结构体中实现一下.poll即可.

    unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait_table);

    其中: wait_table是轮询表指针. 该函数进行2项工作:

    1. 对可能引起设备文件状态变化的等待队列q_head 调用 poll_wait()函数, 将对应的等待队列头添加到poll_table;

      bool poll_wait (struct file * filp,wait_queue_head_t * wait_address,poll_table * wait_table);

      Note: 这里的wait与wait_event意义是不同的, 这里不会引起阻塞.它的目的就是将当前进程添加到参数wait_table等待列表中而已.

    2. 返回表示能否进行无阻塞访问(读/写)的掩码.(bool是内核定义的0/1二值整形类型)

编程典型模板;

static unsigned int xxx_poll(struct file* filp, poll_table *wait)
{
    unsigned int mask = 0 ;
    struct xxx_dev *dev = filp->private_data;   //获得设备结构体指针

    ...
    poll_wait(filp, &dev->r_wait, wait);  //加等待队列头(读)
    poll_wait(filp, &dev->w_wait, wait);    //(写)

    if(..condition..)   //readable
    {
        mask |= POLLIN | POLLRDNORM;    //标示数据可读取
        ...
    }

    if(...condition..)  //writable
    {
        mask |= POLLOUT | POLLWRNORM;   //标示数据可写入
    }

    return mask;
}

异步通知:

一旦设备就绪主动通知应用程序, 这样应用程序不需要查询设备状态,类似”中断”,即”信号驱动的异步I/O”.

异步通知的发出是 驱动主动 发出的.

编程实现:

  1. 应用层:
    启动信号驱动机制

    signal(SIGIO, input_handler);   //将信号SIGIO与处理函数链接起来
    fcntl(STDIN_FILENO, F_SETOWN, getpid());    //把当前进程作为标准输入文件STDIN_FILENO的拥有者, 这样内核就知道把信号发送给哪个进程了
    int oflags = fcntl(STDIN_FILENO, F_GETFL);
    fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);  //这两步启用异步通知机制,并对设备设置FASYNC标志.
    

    综上, 为了在user应用空间中处理一个设备释放的信号,需要完成:

    1. 通过F_SETOWN这个IO控制命令设置设备文件的owner为本进程,这样设备驱动放出的信号才能被本进程接收到;
    2. 通过F_SETFL命令设置文件支持FASYNC, 异步通知模式;
    3. signal()函数连接信号和信号处理函数;
  2. 驱动层:(驱动主动释放信号)
    为了使设备支持异步通知机制,驱动程序中需要完成:

    1. 支持F_SETOWN, 能够在这个控制命令中处理中设置flip->f_owner为对应进程ID.(内核已完成,驱动无需处理);
    2. 支持F_SETFL命令(设置文件标志),每当文件的FASYNC改变时, 驱动程序中的fasync()函数将能够得到执行.因此驱动需要实现fasync()函数;
    3. 在设备资源可获得是,调用kill_fasync()函数激发相应的信号;

      —-驱动中的这3项工作与应用程序中的3项工作是一一对应的.

    4. 定义异步通知的结构体

      struct fasync_struct *async_queue;

    5. 在file_operation结构体声明具体函数

      .fsync = xxx_fasync,

    6. 确定在什么地方发出信号,一边kernel接受驱动信号并给应用发信号.

      kill_fasync()

使用场景:

使用不很很频繁的时候.

end

猜你喜欢

转载自blog.csdn.net/u014132720/article/details/52276748
今日推荐