Linux设备驱动一 (1)阻塞型IO及非阻塞型IO

来源:互联网 发布:多级提成的软件 编辑:程序博客网 时间:2024/05/06 04:21

        阻塞是指没有获得资源则挂起进程,直到获得资源为止。被挂起的进程进入休眠状态,被调度器的运行队列移走,直到等待条件被满足。

        非阻塞是不能进行设备操作时不挂起,或放弃,或反复查询,直到可以进行操作为止。

        驱动程序常需要这种能力:当应用程序进行read(),write()等系统调用时,若设备的资源不能获取,而用户又希望以阻塞的方式访问设备,驱动程序应该在设备驱动程序的xxx_read(), xxx_write()等操作中将进程阻直到资源可以获取,以后,应用程序read(),write()等调用返回,整个过程仍然进行了正确的设备访问,用户并没有感知到;若用户以非阻塞的方式访问设备文件,则当设备资源不可获取时,设备驱动的xxx_read(),xxx_write()等操作应立即返回,read(),write()等系统调用也随即被访问。

        阻塞不是低效率,如果设备驱动不阻塞, 用户想获取设备资源只能不断查询,消耗CPU资源,阻塞访问时,不能获取资源的进程将进入休眠,将CPU资源让给其他资源。阻塞的进程会进入休眠状态,因此,必须确保有一个地方能唤醒休眠的进程。唤醒进程的地方最大可能发生在中断里面,因为硬件资源获得的同时往往伴随着一个中断。


阻塞型IO

        首先说明一下等待队列相关的两个函数

        wait_event_interruptible(),这个函数先将当前进程的状态设置成 TASK_INTERRUPTIBLE,然后调用schedule(), 而schedule()会将位于TASK_INTERRUPTIBLE状态的当前进程从runqueue 队列中删除。从runqueue队列中删除的结果是,当前这个进程将不再参与调度,除非通过其他函数将这个进程重新放入这个runqueue队列中, 这就是wake_up()的作用了。 
        由于这一段代码位于一个由condition控制的for(;;)循环中,所以当由shedule()返回时(当然是被wake_up之后,通过其他进程的schedule()而再次调度本进程),如果条件condition不满足,本进程将自动再次被设置为TASK_INTERRUPTIBLE状态,接下来执行schedule()的结果是再次被从runqueue队列中删除。这时候就需要再次通过wake_up重新添加到runqueue队列中。 


        如此反复,直到condition为真的时候被wake_up. 可见,成功地唤醒一个被wait_event_interruptible()的进程,需要满足: 
        1)condition为真的前提下,2) 调用wake_up()。 
        所以,如果你仅仅修改condition,那么只是满足其中一个条件,这个时候,被wait_event_interruptible()起来的进程尚未位于runqueue队列中,因此不会被 schedule。这个时候只要wake_up一下就立刻会重新进入运行调度。 

        关于wait_event_interruptible的返回值,根据 wait_event_interruptible 的宏定义知: 

       1) 条件condition为真时调用这个函数将直接返回0,而当前进程不会被 wait_event_interruptible 和从 runqueue队列中删除。 
       2) 如果要被wait_event_interruptible的当前进程有nonblocked pending signals, 那么会直接返回-ERESTARTSYS(i.e. -512),当前进程不会被wait_event_interruptible 和从runqueue队列中删除。 
       3) 其他情况下,当前进程会被正常的wait_event_interruptible,并从runqueue队列中删除,进入TASK_INTERRUPTIBLE状态退出运行调度,直到再次被唤醒加入runqueue队列中后而参与调度,将正常返回0。 


        接下来就一按键中断为例(基于HI3520D)

        1、创建等待队列

        等待队列是一个存放着等待某个特定事件进程链表。

            定义并初始化队列头有两种方法:

            1) 静态定义并初始化;定义并初始化一个叫name的等待队列

            DECLARE_WAIT_QUEUE_HEAD(name);

            2) 分开两步执行。
            定义
            wait_queue_head_t test_queue;
            初始化
            init_waitqueue_head(&test_queue);


        2、在read函数中就实现了进程休眠,使用了函数 ”wait_evenr_interruptible”

            wait_event_interruptible(wq, condition)

            使用:

            如果condition为真,当前进程不会被 wait_event_interruptible 和从 runqueue队列中删除

            返回值:

            为真时调用这个函数将直接返回0


        3、在中断函数中唤醒休眠,使用了函数 wake_up_interruptible(wait_queue_head_t *queue)”

            wait_up_interruptible(&wq);


        以下贴出示例代码


        创建等待队列和condition

        static DECLARE_WAIT_QUEUE_HEAD(button_waitq);
        static volatile int event_press = 0;


        read函数

        static int button_dev_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
        {
        unsigned long err = 0;

        /* event_press=0, 则表示无按键按下, 此时无数据可读, 进程进入休眠 */
        if(!event_press) 
        {
            wait_event_interruptible(button_waitq, event_press);
        }
        event_press = 0;
        P_DEBUG("button_dev_read run!\r\n");
        /* 把按键值传会用户空间 */
        err = copy_to_user(buff, &key, min(sizeof(key),count));
        return err ? -EFAULT : 0;

        }


        在中断中唤醒

        static irqreturn_t button_dev_irq_interrupt(int irqno, void *param)
        {
            event_press = 1;
            wake_up_interruptible(&button_waitq);
        }

        应用程序代码

        fd = open("/dev/gkpollbutton", O_RDWR);  
        if (fd < 0)
        {
         printf("error: can't open /dev/gkbutton!\n");
         return -1;
        }

        read(fd, readbuff, 4);
        printf("key[0]=%d\r\n",readbuff[0]);
        printf("key[1]=%d\r\n",readbuff[1]);


非阻塞型IO

        实现非阻塞操作也很简单,判断filp->f_flags中的是否存在O_NONBLOCK标志(标志在定义,并被自动包含),如果有就返回-EAGAIN。

        其中O_NONBLOCK是在应用程序打开设备函数中定义,如下;

fd = open("/dev/gkpollbutton", O_RDWR|O_NONBLOCK);  

        在驱动中增加如下代码,红色部分

        static int button_dev_read(struct file *filp, char __user *buff, size_t count, loff_t *offp)
        {
        unsigned long err = 0;
        if(filp->f_flags & O_NONBLOCK)
        {
        return -EAGAIN;
        }

        /* event_press=0, 则表示无按键按下, 此时无数据可读, 进程进入休眠 */
        if(!event_press) 
        {
            wait_event_interruptible(button_waitq, event_press);
        }
        event_press = 0;
        P_DEBUG("button_dev_read run!\r\n");
        /* 把按键值传会用户空间 */
        err = copy_to_user(buff, &key, min(sizeof(key),count));
        return err ? -EFAULT : 0;

        }

        应用程序代码

        fd = open("/dev/gkpollbutton", O_RDWR|O_NONBLOCK);  
        if (fd < 0)
        {
        printf("error: can't open /dev/gkbutton!\n");
        return -1;
        }


        err = read(fd, readbuff, 4);
        if(err == -1)
        {
        perror("open");
        printf("errno = %d\n", errno);
        }
        else
        {
        printf("key[0]=%d\r\n",readbuff[0]);
        printf("key[1]=%d\r\n",readbuff[1]);
        }

        errno是-EAGAIN错误号返回给用户态的


0 0