嵌入式linux 阻塞和非阻塞IO

        当应用程序对设备驱动进行操作时,如果不能获取设备资源,那么阻塞式IO就会将对应程序的线程挂起,直到设备资源可以获取为止。对于非阻塞式IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可用,要么直接放弃。

阻塞式访问

非阻塞式访问

        阻塞式和非阻塞式访问也就在应用程序中打开文件的方式有区别。

等待队列

        阻塞访问最大的好处就是当设备文件不可操作的时候线程可以进入休眠,这样可以将CPU资源让出来,当设备文件可以操作的时候必须要唤醒,一般在中断函数里面完成唤醒工作,linux内核提供了等待队列(wait queue)来实现阻塞式进程的唤醒工作。如果我们要在驱动中使用等待队列就必须创建一个等待队列头,等待队列头使用结构体wait_queue_head_t表示。

/* 等待队列头 */
wait_queue_head_t r_wait;

/* 初始化等待队列头 */
init_waitqueue_head(&imx6uirq.r_wait);

等待队列项

        等待队列头就是一个等待队列的头部,每个访问设备的进程都是一个队列项,当设备不可用的时候就需要将这些进程对应的等待队列项添加到等待队列里面,结构体wait_queue_t表示等待队列项。

/* 定义一个等待队列 */
//wait是这个等待队列项的名字,current指的是当前访问的进程,一般都会current
DECLARE_WAITQUEUE(wait, current);        	

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

        当设备不可访问的时候(被其他进程占用,资源被锁),就需要将进程的对应的等待队列项添加到前面创建的等待队列头中,只有添加到等待队列头中以后进程才可以进入休眠状态,当设备可以访问以后可以将进程对应的等待队列项从等待队列头中移除即可。

/* 将等待队列添加到等待队列头 */
add_wait_queue(&dev->r_wait, &wait);


/* 将对应的队列项从等待队列头删除 */
remove_wait_queue(&dev->r_wait, &wait);

等待唤醒

        当设备可以使用的时候就要唤醒进入休眠的进程了,唤醒可以使用如下两个函数唤醒函数就是为了前面的休眠函数提供服务的,在read函数里面进入休眠,就不会占用大量的CPU资源,在中断(定时器)中唤醒,只有每次唤醒的时候才会使用CPU资源。这样也就解决了阻塞式IO会占用大量CPU资源的问题了。

//r_wait就是等待队列头
wake_up_interruptible(&dev->r_wait);

wake_up(&dev->r_wait);

等待事件

        除了上面的主动唤醒以外,也可以设置等待队列等待某一个事件,当这个事件满足以后就可以自动唤醒等待队列中的进程了。阻塞和唤醒属于一体的。

 轮询

        如果用户程序以非阻塞式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。poll,epoll和select可以用于处理轮询,应用程序通过select,epoll和poll函数来查询设备是否可以操作,如果可以操作的话就从设备读取或者写入数据,当应用程序调用select,epoll,和poll函数的时候设备驱动程序中的poll函数就会执行。

1,select函数

         nfds:所要监视的三类文件描述集合中,最大文件描述符加1

        readfds,writefds,和exceptfds:这三个指针指向描述符集合,这三个参数指明了关心那些描述符,需要满足那些条件等等,这三个参数都是fd_set类型的,fd_set类型变量的每一个位都代表了一个文件描述符readfds 用于监视指定描述符集的读变化,也就是监视这些文件是否 可以读取,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取。可以将readfds设置为NULL,表示不关心任何文件的读变化。writefds readfs 类似,只是 writefs 用于监视这些文件是否可以进行写操作exceptfds 用于监视这些文件的异常。

        timeout:如果没有文件可以读取,那么就会根据timeout参数来判断是否超时

//定义超时时间的结构体
struct timeval timeout;

timeout.tv_sec = 0;        /* 0s */
timeout.tv_usec = 500000; /* 500ms */

        比如我们现在要从一个设备文件中读取数据,那么就可以定义一个fd_set变量,这个变量要传递给readfds,当我们定义好一个fd_set变量以后就可以使用如下所示几个宏进行操作:

          FD_ZERO用于将fd_set变量的所有位都清0FD_SET用于将fd_set变量的某个位置1,也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符。FD_CLR 用于将 fd_set 变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符。FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。    

 应用程序中

//监视指定描述符集的读变化
fd_set readfds;

//将读变化描述符集清0
FD_ZERO(&readfds);

//将指定文件fd加入到读变化描述符集,所以其中的某一位会置1
FD_SET(fd, &readfds);

//select 函数对某个设备驱动文件进行读非阻塞访问
//ret为0表示超时,为-1表示错误,为正表示有多少个文件可以进行读取
ret = select(fd + 1, &readfds, NULL, NULL, &timeout);

2,poll函数

        在单个线程中,select函数能够监视的文件描述符数量有最大限制,一般为1024,可以修改内核将监视的文件描述符数量该大,但是这样会降低效率,这个时候就可以使用poll函数,poll函数本质上和select函数没有太大的差距,但是poll函数没有最大文件描述舒服符的限制。

         fds:要监视的文件描述符集合以及要监视的事件,为一个数组,数组元素都是结构体,pollfd类型的,结构体类型如下:

         fd:fd是要监视的文件描述符,如果fd无效的话,那么events监视的事件也就无效了,并且revents返回0。events是要监视的事件,可以监视的类型如下:

        revents:是返回的事件,监视到请求的事件的时候,就会返回事件

        nfds:poll函数要监视文件描述符的数量

        返回值:大于0,发生事件的数量,0代表超时,-1表示错误

//要监视的文件描述符集合以及要监视的事件
struct pollfd fds;
//构造结构体
fds.fd = fd;
fds.events = POLLIN;         /* 监视数据是否可以读取 */
ret = poll(&fds, 1, 500);    /* 轮询文件是否可操作,超时 500ms */
linux驱动程序下的poll操作函数
        当应用程序调用select或poll函数对驱动程序进行非阻塞访问的时候,驱动程序file_operations操作集中的poll函数救回执行。
        

        wait:结构体poll_table_struct类型指针,由应用程序传进来,一般将此参数传递给poll_wait函数。

        返回值:向应用程序返回设备或者资源状态,也就是返回事件(前面的revents),可以返回的事件其实和上面监视的事件一样的。

         我们再驱动程序中的pll函数(操作集)中调用poll_wait函数,poll_wait函数不会引起阻塞,只是将应用程序添加到poll_table中,poll_wait函数原型如下:

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值