【Linux-IMX6ULL-阻塞与非阻塞】

1 阻塞与非阻塞

  对于Linux而言:

  1. 阻塞:当资源不可用时应用程序就会挂起,当资源可用时,唤醒任务,应用程序使用open打开驱动文件,默认是阻塞的方式打开,占用CPU高。
  2. 非阻塞:当资源不可用时,应用程序轮询查看、或放弃,会有超时处理,使用open打开驱动文件时,使用O_NONBLOCK会以非阻塞的方式打开文件。

2 实现阻塞式访问的方法

2.1 等待队列

2.1.1 等待队列头

  等待队列头,类似链表一样的东西把任务给串起来,阻塞式访问的最大好处就是当设备文件不可用时进程可以进入到休眠状态,这样可以将CPU的资源让出来,同时休眠就要被唤醒,因此一般在中段函数里面实现唤醒的工作。

2.1.2 等待队列项

  使用宏DECLARE_WAITQUEUE(name,task)定义并初始化一个等待队列,name是等待队列项的名字,tsk是表示这个等待队列项属于哪个任务(进程),一般设置为current,Linux内核中current相当于一个全局变量,表示当前进程。因此宏DECLARE_WAITQUEUE相当于给当前正在运行的进程创建并初始化了一个等待队列项。

2.1.3 将队列项添加/移除等待队列头

  当设备不可访问时就要将进程对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠状态,当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可,等待队列项添加API函数如下:
  void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)

2.1.4 等待唤醒

  当设备可用时就要进行唤醒操作,即唤醒休眠的进程,唤醒可以使用下列两个函数:

  1. void wake_up(wait_queue_head_t *q)
  2. void wake_up_interruptible(wait_queue_head_t *q)

2.2 轮询

  用于处理非阻塞的问题,其中poll,epoll和select可以用于处理轮询、应用程序可以通过select、poll和epoll函数来查询设备是否可以操作,如果可以的操作的话就从设备读取或者向设备写入数据。

2.2.1 select()函数

  select函数的原型如下:

-int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

注意nfds参数是最大文件描述符+1,应用程序使用select函数

2.2.2 poll()函数

  poll()这个函数本质上与select()函数没有太大的区别,但是对于poll()函数而言,其没有最大文件描述符的限制

2.2.3 epoll()函数

  支持水平触发和边缘触发(Edge Triggered)。边缘触发只有在状态发生变化时才通知,能减少不必要的系统调用。

2.2.4 总结

  注意select,poll,epoll这三个函数只是针对于应用程序,当在驱动程序中时其只对应一个poll函数,在处理大量文件描述符的高并发场景下,epoll() 的性能通常远远优于 select() 和poll() ,例如,在一个 Web 服务器中,需要同时处理大量的客户端连接,如果使用 select() 或 poll() ,当连接数量增加到一定程度时,系统的性能可能会急剧下降,因为内核需要频繁地遍历大量的文件描述符集合。而使用 epoll() ,由于其高效的数据结构和触发模式,能够更好地应对这种高并发情况,总的来说,epoll() 在处理大规模 I/O 事件时具有明显的优势,但在简单场景或对兼容性要求较高的情况下,select() 和 poll() 可能仍然适用。

3 驱动里面的poll()函数

  当程序调用select或者poll函数进行非阻塞方式访问时,驱动程序file_operations操作集中的poll函数就会执行,所以驱动程序的编写需要提供对应的poll函数,poll函数原型如下:

  • unsigned int (*poll) (struct file *, struct poll_table_struct *);

  在poll函数里面调用poll_waot()函数,poll_wait()函数不会引起阻塞,只是将应用程序添加到poll_table中,其原型如下:

  • void poll_wait(struct file *filp,wait_queue_head_t *wait_address,poll_table *p)

其中参数wait_address就是要添加到poll_table中的等待队列,参数p就是poll_table,就是file_operations中poll函数的wait参数。

4 等待队列实验

4.1 未进行阻塞式访问

  如果并未在驱动程序中进行设置阻塞式访问,那么当我们在用户态调用驱动时,会出现CPU占用率奇高的情况,例如一个用户按键检测驱动,如果不采用阻塞式访问,那么当在用户态调用按键驱动时会出现下面的情况:这里要思考一下,如果只是加载驱动的话,CPU使用了率不会那么高,应为此时是在内核态,但是在用户界面调用这个驱动时就转换到了用户态,由于没有在内核态给按键驱动设置阻塞式访问,而且用户态中一直在循环中持续不断的读取按键状态,因此相当于把CPU的使用率给拉满了。为此要进行设置,当我不按下按键时,把这个进程休眠掉,等检测到按键时再调用这个按键,实现这个就可以用上述的方法。

4.2 等待队列阻塞式访问-方式1

  当进程可以进行访问时,不用把进程对应的等待队列项添加到前面创建的等待队列头中,使用流程:由于是按键检测,只需读就行了,因此下面的针对只读,当然其他的模式举一反三即可。

  1. 在按键设备中添加等待队列头:wait_queue_head_t r_wait; /*读等待队列头*/
  1. 在初始化中,进行等待队列初始化:init_waitqueue_head(&imx6uirq.r_wait);/*初始化等待队列头*/
  2. 进入读取程序时要插入等待时间,如果没有时间发生就进入休眠: wait_event(dev->r_wait,atomic_read(&dev->releasekey)); /*按键有效值*/
  3. 触发事件时要唤醒进程:
   /*唤醒进程*/
    if(atomic_read(&dev->releasekey)){
        wake_up(&dev->r_wait);
    }
  • 结果如下所示:
  

可以看到,其结果是和预期的一样,只有触发事件时才会调动CPU资源取处理问题。

4.3 等待队列阻塞式访问-方式2

  当设备不可用访问时就要将进程对应的等待队列项放进前面前面创建的等待队列头中,只有添加到等待队列头中以后进程才能进入休眠态,当设备可以访问以后再将进程对应的等待队列项从等待队列头中移除即可:实验方法流程如下:

4.4 非阻塞式访问

  接下来进行非阻塞式的访问:这里不在累述,就是按照模板进行操作,需要相比与等待队列阻塞式访问方式1而言要改动两处地方,第一处就是改动应用程序,使用selec函数进行监听打开文件的状态,第二处就是驱动层面要使用poll函数与应用层的selec对应,改动代码如下,参照正点原子的按键驱动程序:

  1. 驱动层面;
//1修改read函数
static ssize_t imx6uirq_read(struct file *file, char __user *buffer, size_t count, loff_t *ppos)
{
    int ret = 0;
    unsigned char keyvalue;
    unsigned char releasekey;
    struct imx6uirq_dev *dev = file->private_data;

    if(file->f_flags & O_NONBLOCK){
        /*以非阻塞的方式访问*/
        if(atomic_read(&dev->releasekey)==0){
            return -EAGAIN;
        }

    }else{
        wait_event_interruptible(dev->r_wait,atomic_read(&dev->releasekey));
    }


    keyvalue = atomic_read(&dev->keyvalue);
    releasekey = atomic_read(&dev->releasekey);
    if(releasekey){
        if(keyvalue  & 0x80){
            keyvalue &= ~0x80;
            ret = copy_to_user(buffer,&keyvalue,sizeof(keyvalue));
        }else{
            goto data_error;
        }
        atomic_set(&dev->releasekey,0);
    }else{
        goto data_error;
    }

data_error:
    return ret;
}
//2添加poll函数
static unsigned int imx6uirq_poll(struct file *file,struct poll_table_struct *wait)
{
    int mask = 0;
    struct imx6uirq_dev *dev=file->private_data;

    poll_wait(file,&dev->r_wait,wait);
    /*是否可读*/
    if(atomic_read(&dev->releasekey)){
    /*按下按键、可读*/
        mask=POLLIN|POLLRDNORM;
    }
    return  mask;
}
  1. 应用层面;
#define KEY0VALUE 0xF0
#define INVSKEY   0x00


int main(int argc,char *argv[])
{
    fd_set readfds;
    struct timeval timeout;
    char *filename;
    int fd, ret;
    unsigned char data;

    filename = argv[1];
    fd  = open(filename,O_RDWR|O_NONBLOCK);
    if(fd<0)
    {
        printf("file %s open failed!\r\n",filename);
        return -1;
    }
    while(1){
        FD_ZERO(&readfds);
        FD_SET(fd,&readfds);
        timeout.tv_sec=0;
        timeout.tv_usec=5000000;
        ret =select(fd+1,&readfds,NULL,NULL,&timeout);
        switch(ret)
        {
            case 0:/*超时*/;break;
            case -1:/**/;break;
            default:
            /*可以读取数据*/
            if(FD_ISSET(fd,&readfds)){
                 ret = read(fd,&data,sizeof(data));
                if(ret<0){
                    
                }else{
                    if(data){
                        printf("(KEY)VALUE_PRESS=%d\r\n",data);
                        }
                }
            }
            break;
        } 
    }
    close(fd);
    return 0;
}
  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值