Linux阻塞和非阻塞

一. 定义

        阻塞式 IO 就会将应用程序对应的线程挂起,直到设备资源可以获取为止。

        非阻塞 IO,应用程序对应的线程不会挂起,它要么一直轮询等待,直到设备资源可以使用,要么就直接放弃。

二. 应用层区别

        fd = open("/dev/xxx_dev", O_RDWR); /* 阻塞方式打开 */

        fd = open("/dev/xxx_dev", O_RDWR | O_NONBLOCK); /* 非阻塞方式打开 */ 


三. 操作

1. 等待队列头

         等待队列(wait queue)来实现阻塞进程的唤醒工作

a. 定义wait_queue_head_t (include/linux/wait.h)

struct wait_queue_head {
	spinlock_t		lock;
	struct list_head	head;
};
typedef struct wait_queue_head wait_queue_head_t;

b. 初始化

init_waitqueue_head(wait_queue_head_t *q)

宏 DECLARE_WAIT_QUEUE_HEAD 一次性完成等待队列头的定义和初始化

c. 等待唤醒

         当设备可以使用的时候就要唤醒进入休眠态的进程。

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

参数q: 要唤醒的等待队列头

d. 等待事件

wait_event(wq, condition)
wait_event_timeout(wq,condition, timeout)
wait_event_interruptible(wq, condition)
wait_event_interruptible_timeout(wq, condition, timeout)

    等待以 wq 为等待队列头的等待队列被唤醒,前提是 condition 条件必须满足(为真),
否则一直阻塞。此函数会将进程设置为 TASK_UNINTERRUPTIBLE 状态.

 2. 轮询

         用户应用程序以非阻塞的方式访问设备,设备驱动程序就要提供非阻塞的处理方式,也就是轮询。poll、 epoll 和 select 可以用于处理轮询。

2.1 select 

a. select 定义
int select(int nfds, fd_set *readfds, fd_set *writefds,
         fd_set *exceptfds, struct timeval *timeout)

nfds: 要操作的文件描述符个数。
readfds、 writefds 和 exceptfds:这三个指针指向描述符集合,
    这三个参数指明了关心哪些描述符、需要满足哪些条件等等
    这三个参数都是 fd_set 类型的, fd_set 类型变量的每一个位都代表了一个文件描述符。

    只要这些集合里面有一个文件可以读取那么 seclect 就会返回一个大于 0表示文件可以读取。
    如果没有文件可以读取,那么就会根据 timeout 参数来判断是否超时。

    可以将 readfs 设置为 NULL,表示不关心任何文件的读变化。 writefds 和 readfs 类似

timeout:超时时间,当 timeout 为 NULL 的时候就表示无限期的等待。
    struct timeval {
        long tv_sec; /* 秒 */
        long tv_usec; /* 微妙 */
     };

返回值: 0,表示的话就表示超时发生,但是没有任何文件描述符可以进行操作;
        -1,发生错误;
        其他值,可以进行操作的文件描述符个数。

当我们定义好一个 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_ISSET 用于测试 fd_set 的某个位是否置 1,也就是判断某个文件是否可以
    进行操作,参数 fd 就是要判断的文件描述符。
b. select举例

void main(void)
{
    int ret,fd;
    fd_set readfds;

    struct timeval timeout;

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

    FD_ZERO(&readfds);
    FD_SET(fd,&readfds);

    timeout.tv_sec = 0;
    timeout.tv_usec = 500000;

    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))
            {
                //todo
            }
            break;
    }

}

2.2 poll

        在单个线程中, select 函数能够监视的文件描述符数量有最大的限制,一般为 1024,
可以修改内核将监视的文件描述符数量改大,但是这样会降低效率!

a.poll定义
int poll(struct pollfd *fds, nfds_t nfds, int timeout)

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

timeout: 超时时间,单位为 ms

fds: 要监视的文件描述符集合以及要监视的事件,为一个数组,
    struct pollfd {
        int fd; /* 文件描述符 */
        short events; /* 请求的事件 */
        short revents; /* 返回的事件 */
    };
    
    fd 是要监视的文件描述符,如果 fd 无效的话那么 events 监视事件也就无效,并且 revents返回 0。             
    
    events 是要监视的事件,可监视的事件类型如下所示:
        POLLIN 有数据可以读取。
        POLLPRI 有紧急的数据需要读取。
        POLLOUT 可以写数据。
        POLLERR 指定的文件描述符发生错误。
        POLLHUP 指定的文件描述符挂起。
        POLLNVAL 无效的请求。
        POLLRDNORM 等同于 POLLIN

    revents 是返回参数,也就是返回的事件,有 Linux 内核设置具体的返回事件。
 b. poll举例
void main(void)
{
    int ret,fd,nfds;
    
    struct pollfd fds;

    struct timeval timeout;

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

    timeout.tv_sec = 0;
    timeout.tv_usec = 500000;

    fds.fd = fd;
    fds.events = POLL_IN;

    ret = poll(&fds,1,500);
    if(ret)
    {
        //todo
    }else if (ret == 0){
        //超时
    }else if(ret < -)
    {
        //error
    }
   

}

2.3 epoll 

         传统的 selcet 和 poll 函数都会随着所监听的 fd 数量的增加,出现效率低下的问题,而且 poll 函数每次必须遍历所有的描述符来检查就绪的描述符,这个过程很浪费时间。

        epoll 就是为处理大并发而准备的,一般常常在网络编程中使用 epoll 函数。

        epoll 更多的是用在大规模的并发服务器上,因为在这种场合下 select 和 poll 并不适
合。当涉及到的文件描述符(fd)比较少的时候就适合用 selcet 和 poll.

 a. epoll—API
流程:
1. 创建一个 epoll 句柄
2. epoll 句柄创建成功以后使用 epoll_ctl 函数向其中添加要监视的文件描述符以及监视的事件.
3.一切都设置好以后应用程序就可以通过 epoll_wait 函数来等待事件的发生,类似 select函数。 


int epoll_create(int size) //size:随便填写一个大于 0 的值就可以。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
    epfd: 要操作的 epoll 句柄,也就是使用 epoll_create 函数创建的 epoll 句柄。
    op: 表示要对 epfd(epoll 句柄)进行的操作,可以设置为:
        EPOLL_CTL_ADD 向 epfd 添加文件参数 fd 表示的描述符。
        EPOLL_CTL_MOD 修改参数 fd 的 event 事件。
        EPOLL_CTL_DEL 从 epfd 中删除 fd 描述符
    
    fd:要监视的文件描述符。
    event: 要监视的事件类型,为 epoll_event 结构体类型指针
        struct epoll_event {
            uint32_t events; /* epoll 事件 */
            epoll_data_t data; /* 用户数据 */
         };
        
        结构体 epoll_event 的 events 成员变量表示要监视的事件,可选的事件如下所示:
            EPOLLIN 有数据可以读取。
            EPOLLOUT 可以写数据。
            EPOLLPRI 有紧急的数据需要读取。
            EPOLLERR 指定的文件描述符发生错误。
            EPOLLHUP 指定的文件描述符挂起。
            EPOLLET 设置 epoll 为边沿触发,默认触发模式为水平触发。
            EPOLLONESHOT 一次性的监视,当监视完成以后还需要再次监视某个 fd, 
                那么就需要将fd重新添加到 epoll 里面。
        上面这些事件可以进行“或”操作,也就是说可以设置监视多个事件。


int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout)
    maxevents: events 数组大小,必须大于 0。
    返回值: 0,超时;
            -1,错误;
            其他值,准备就绪的文件描述符数量。

2.4 驱动层poll操作

        当应用程序调用 select 或 poll 函数来对驱动程序进行非阻塞访问的时候,驱动程序file_operations 操作集中的 poll 函数就会执行。

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)
    filp: 要打开的设备文件(文件描述符)。
    wait: 结构体 poll_table_struct 类型指针,由应用程序传递进来的。
        一般将此参数传递给 poll_wait 函数。
    
    返回值:向应用程序返回设备或者资源状态,可以返回的资源状态如下:
        POLLIN 有数据可以读取。
        POLLPRI 有紧急的数据需要读取。
        POLLOUT 可以写数据。
        POLLERR 指定的文件描述符发生错误。
        POLLHUP 指定的文件描述符挂起。
        POLLNVAL 无效的请求。
        POLLRDNORM 等同于 POLLIN,普通数据可读
 在驱动程序的 poll 函数中调用 poll_wait 函数, 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 参数。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值