linux系统下的非阻塞访问和阻塞访问

一、阻塞访问和非阻塞访问的定义及区别

        在实际开发过程中,可能遇到对同一个设备驱动进行多次访问的情况,根据访问方式的不同,可以分为阻塞和非阻塞访问,这两种访问的区别体现在设备繁忙时,对于当前进程的处理方式不同。

        阻塞访问:

        当执行设备操作时,无法获得资源,则将该进程挂起,等到获得可以执行设备操作资源的时候再执行,等待期间处于休眠状态。一般通过等待队列来实现处理的先后顺序。

        非阻塞访问:

        当执行设备操作时,无法获得资源,但不会将进程挂起,而是不断轮询直到可以操作为止。其中read,write,open等文件操作会收到非阻塞访问的影响。

二、基本概念和函数的介绍

1、等待队列

        等待队列是阻塞访问实现唤醒进程的手段。

   a、 定义

        等待队列由等待队列头和等待队列项构成,他们之间由双向循环列表连接,用一个list_head类型的域作为连接件。

struct __wait_queue_head {
      spinlock_t lock;                    /* 保护等待队列的原子锁 (自旋锁),在对task_list与操作的过程中,使用该锁实现对等待队列的互斥访问*/
      struct list_head task_list;          /* 等待队列,双向循环链表,存放等待的进程 */
};

/*__wait_queue,该结构是对一个等待任务的抽象。每个等待任务都会抽象成一个wait_queue,并且挂载到wait_queue_head上。该结构定义如下:*/

struct __wait_queue {
 unsigned int flags;#define WQ_FLAG_EXCLUSIVE   0x01
 void *private;                       /* 通常指向当前任务控制块 */
 wait_queue_func_t func;             
 struct list_head task_list;              /* 挂入wait_queue_head的挂载点 */
};typedef struct __wait_queue wait_queue_t;
/* 任务唤醒操作方法,该方法在内核中提供,通常为auto remove_wake_function */

    b、操作

        使用等待队列时,通常在设备结构体里面定义一个等待队列头。

定义等待队列头:

wait_queue_head_t my_queue;  
init_waitqueue_head(&my_queue);

  也可以使用宏定义将两步合成一步:

DECLARE_WAIT_QUEUE_HEAD (my_queue);

定义等待队列项:

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

DECLARE_WAITQUEUE(name,tsk);    //该等待队列名为name,private为tsk

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

从等待队列头里添加/删除等待队列

        当进程不可访问时,将其添加进等待队列

void add_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)

        当设备可以访问时,将等待队列中进程对于的等待队列项移除

void remove_wait_queue(wait_queue_head_t *q,wait_queue_t *wait)

                q :等待队列项要加入的等待队列头。
                wait:要加入的等待队列项。

等待唤醒

        当设备可以使用时,可以主动唤醒处于休眠期的进程

void wake_up(wait_queue_head_t *q)
void wake_up_interruptible(wait_queue_head_t *q)

        wake_up 函数可以唤醒处于 TASK_INTERRUPTIBLE 和 TASK_UNINTERRUPTIBLE 状态的进程,而 wake_up_interruptible 函数只能唤醒处于 TASK_INTERRUPTIBLE 状态的进程。

等待事件

        可以设置当满足某个事件的时候,自主唤醒进程

函数描述
wait_event(wq, condition)等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),否则一直阻塞 。 此 函 数 会 将 进 程 设 置 为
TASK_UNINTERRUPTIBLE 状态
wait_event_timeout(wq, condition, timeout)功能和 wait_event 类似,但是此函数可以添加超时时间,以 jiffies 为单位。此函数有返回值,如果返回 0 的话表示超时时间到,而且 condition为假。为 1 的话表示 condition 为真,也就是条件满足了。
wait_event_interruptible(wq, condition)与 wait_event 函数类似,但是此函数将进程设置为 TASK_INTERRUPTIBLE,就是可以被信号打断。
wait_event_interruptible_timeout(wq,
condition, timeout)
与 wait_event_timeout 函数类似,此函数也将进程设置为 TASK_INTERRUPTIBLE,可以被信号打断。

2、轮询

        a、定义

        轮询用于非阻塞访问。分为select、poll、epoll。

select

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

        fd_set 类型变量的每一个位都代表了一个文件描述符

        readfds 用于监视指定描述符集的读变化,也就是监视这些文件是否可以读取,只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0 的值表示文件可以读取。如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时。可以将 readfs设置为 NULL,表示不关心任何文件的读变化。

        writefds 和 readfs 类似,只是 writefs 用于监视这些文件是否可以进行写操作。

        exceptfds 用于监视这些文件的异常。

        可以使用下面的宏对fd_set类型的变量进行操作

        

void FD_ZERO(fd_set *set)
void FD_SET(int fd, fd_set *set)
void FD_CLR(int fd, fd_set *set)
int FD_ISSET(int fd, fd_set *set)

        FD_ZERO 用于将 fd_set 变量的所有位都清零,FD_SET 用于将 fd_set 变量的某个位置 1,
也就是向 fd_set 添加一个文件描述符,参数 fd 就是要加入的文件描述符。FD_CLR 用户将 fd_set

        变量的某个位清零,也就是将一个文件描述符从 fd_set 中删除,参数 fd 就是要删除的文件描述符。FD_ISSET 用于测试一个文件是否属于某个集合,参数 fd 就是要判断的文件描述符。

poll

        相比于select,poll函数可以监视的文件描述符数量没有限制。

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

struct pollfd {
int fd; /* 文件描述符 */
short events; /* 请求的事件 */
short revents; /* 返回的事件 */
};

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

POLLIN 有数据可以读取。
POLLPRI 有紧急的数据需要读取。
POLLOUT 可以写数据。
POLLERR 指定的文件描述符发生错误。
POLLHUP 指定的文件描述符挂起。
POLLNVAL 无效的请求。
POLLRDNORM 等同于 POLLIN

epoll

        epoll一般用于网络编程,这里就不介绍了。

三、使用方法

1、使用等待队列实现阻塞访问

fd = open(filename, O_RDWR);    //应用程序使用阻塞的方式打开文件

        ①在设备结构体定义等待队列头

struct imx6uirq_dev{
.............

wait_queue_head_t r_wait;
}

        ②初始化等待队列头

init_waitqueue_head(&imx6uirq.r_wait);

                前两步可以参考前面的宏定义方法,一步完成

         ③定义一个等待队列并实现满足条件时移除

DECLARE_WAITQUEUE(wait, current); /* 定义一个等待队列 */
add_wait_queue(&dev->r_wait, &wait); /* 添加到等待队列头 */
__set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态 */
schedule(); /* 进行一次任务切换 */
if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
.........................
__set_current_state(TASK_RUNNING); /* 设置为运行状态 */
remove_wait_queue(&dev->r_wait, &wait); /* 将等待队列移除 */

                也可以使用等待事件和主动唤醒

ret = wait_event_interruptible(dev->r_wait, atomic_read(&dev->releasekey)); 

wake_up_interruptible(&dev->r_wait);

2、非阻塞访问打开文件

fd = open("dev_xxx", O_RDWR | O_NONBLOCK);

         在驱动中判断为非阻塞访问的方法:

if (filp->f_flags & O_NONBLOCK)

3、select的使用

fd = open("dev_xxx", O_RDWR | O_NONBLOCK);
FD_ZERO(&readfds); /* 清除 readfds */
FD_SET(fd, &readfds); /* 将 fd 添加到 readfds 里面 */

/* 构造超时时间 */
timeout.tv_sec = 0;
timeout.tv_usec = 500000; /* 500ms */

ret = select(fd + 1, &readfds, NULL, NULL, &timeout);

switch (ret) {
  case 0: /* 超时 */
    printf("timeout!\r\n");
    break;
  case -1: /* 错误 */
    printf("error!\r\n");
    break;
  default: /* 可以读取数据 */
    if(FD_ISSET(fd, &readfds)) { /* 判断是否为 fd 文件描述符 */
    /* 使用 read 函数读取数据 */
    }
 break;

4、poll的使用

fd = open(filename, O_RDWR | O_NONBLOCK);

fds.fd = fd;
fds.events = POLLIN;

ret = poll(&fds, 1, 500); /* 轮询文件是否可操作,超时 500ms */
if (ret) { /* 数据有效 */

 /* 读取数据 */

} else if (ret == 0) { /* 超时 */
 ......
} else if (ret < 0) { /* 错误 */
 ......
 }
 }

5、设备驱动中的poll函数

        在应用程序中,无论调用select还是poll,都会出发设备驱动中的poll函数。设备中的poll函数定义在file operations操作函数集合里面,我们需要对其进行定义。

static struct file_operations imx6uirq_fops = {
.poll = imx6uirq_poll,
}

//函数原型
unsigned int (*poll) (struct file *, struct poll_table_struct *);  

        其在file_operations结构体中。参数file是file结构体指针,第二个参数是轮询表指针 。     

        在poll函数中,主要调用poll_wait函数,将对应的等待队列头添加到poll_table。最后返回表示是否能对设备进行无阻塞读、写访问的掩码。

例子:

unsigned int imx6uirq_poll(struct file *filp, struct poll_table_struct *wait)
{
	unsigned int mask = 0;
	struct imx6uirq_dev *dev = (struct imx6uirq_dev *)filp->private_data;

	poll_wait(filp, &dev->r_wait, wait);	/* 将等待队列头添加到poll_table中 */
	
	if(atomic_read(&dev->releasekey)) {		/* 按键按下 */
		mask = POLLIN | POLLRDNORM;			/* 返回PLLIN */
	}
	return mask;
}

参考链接:linux中的阻塞机制及等待队列 - touchcode - 博客园

                  Linux设备驱动开发-linux驱动中的非阻塞访问方式_浩瀚之水的专栏-CSDN博客

                正点原子I.MX6U 嵌入式 Linux 驱动开发指南

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值