阻塞睡眠实现机制

  在看阻塞睡眠实现机制前,我们来看一下内核中广泛用到的等待队列。

  Linux内核的等待队列为双循环链表结构,与进程调度机制紧密结合,能够用于实现核心的异步事件通知机制。它有两种数据结构:等待队列头(wait_queue_head_t)和等待队列项(wait_queue_t)。等待队列头和等待队列项中都包含一个list_head(双链表)。通过这样一个双链表把等待进程链接起来。

  

  下面来看两者数据结构:

struct __wait_queue_head {
    spinlock_t lock; //自旋锁,实现对等待队列的互斥访问
    struct list_head task_list; //双向循环链表,存放等待的进程。
};
typedef struct __wait_queue_head wait_queue_head_t;

struct __wait_queue {
    unsigned int flags;
    void *private;
    wait_queue_func_t func;
    struct list_head task_list;
};
typedef struct __wait_queue wait_queue_t;

   

  我们知道缺省状态下IO是阻塞的(除非设定O_NONBLOC),如果一个进程调用 read 但是没有数据可用(尚未), 这个进程必须阻塞. 这个进程在有数据达到时被立刻唤醒, 并且那个数据被返回给调用者, 即便小于在给方法的 count 参数中请求的数量,反之,如果一个进程调用 write 并且在缓冲中没有空间, 这个进程必须阻塞,并且它必须在一个与用作 read 的不同的等待队列中. 当一些数据被写入硬件设备, 并且在输出缓冲中的空间变空闲, 这个进程被唤醒并且写调用成功, 尽管数据可能只被部分写入如果在缓冲只没有空间给被请求的 count 字节。

  我们来看一个读操作的例子:

static ssize_t scull_p_read (struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
        struct scull_pipe *dev = filp->private_data;
        if (down_interruptible(&dev->sem)) //加锁
                return -ERESTARTSYS;

        while (dev->rp == dev->wp) //无东西可读
        {
                up(&dev->sem); //解锁
                if (filp->f_flags & O_NONBLOCK) //非阻塞方式,立即返回
                        return -EAGAIN;

                //阻塞访问,睡眠等待,等到读条件满足时继续执行。
                if (wait_event_interruptible(dev->inq, (dev->rp != dev->wp)))
                        return -ERESTARTSYS;
                if (down_interruptible(&dev->sem)) //重新加锁
                        return -ERESTARTSYS;
        }

        //读取数据。
        ……
        up (&dev->sem); 

        wake_up_interruptible(&dev->outq);
        return count;
}

  从上面的例子我们可以看到,是通过调用wait_event_interruptible()实现阻塞等待的,来看一下wait_event_interruptible()实现

   #define wait_event_interruptible(wq, condition)
        ({
               int __ret = 0;
                if ( (!condition) )
                        __wait_event_interruptible(wq, condition, __ret);
                __ret;
        })
        #define __wait_event_interruptible(wq, condition, __ret)
        do {
                DEFINE_WAIT(__wait); // 定义等待队列__wait
                for(;;) {
                        prepare_to_wait(&wq, &__wait, TASK_INTERRUPTIBLE);  //将等待队列__wait加入以wq为首的等待队列链表中,并且将进程状态设置为TASK_INTERRUPTIBLE
                        if (condition) //如果condition满足则跳出
                                break;
                       if (!signal_pending(current)) { //没被信号唤醒
                               schedule(); // 放弃CPU,调度其它进程执行
                                continue;
                        }
                          ret = - ERESTARTSYS;
                          break;
                }
                finish_wait(&wq, &__wait); //将等待队列__wait从等待队列头wq指向的等待队列链表中移除,将进程状态设置为TASK_RUNNING
        }while(0)

总结起来, 阻塞睡眠步骤一般为:

  1)分配和初始化一个 wait_queue_t 结构, 随后将其添加到正确的等待队列. 当所有东西都就位了, 负责唤醒工作的人就可以找到正确的进程

  2)设置进程的状态来标志它为睡眠(TASK_UNINTERRUPTIBLE,TASK_INTERRUPTIBLE)

  3)调用schedule(),让出CPU。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-10-27 18:12:56

阻塞睡眠实现机制的相关文章

java并发编程(8)原子变量和非阻塞的同步机制

原子变量和非阻塞的同步机制 一.锁的劣势 1.在多线程下:锁的挂起和恢复等过程存在着很大的开销(及时现代的jvm会判断何时使用挂起,何时自旋等待) 2.volatile:轻量级别的同步机制,但是不能用于构建原子复合操作 因此:需要有一种方式,在管理线程之间的竞争时有一种粒度更细的方式,类似与volatile的机制,同时还要支持原子更新操作 二.CAS 独占锁是一种悲观的技术--它假设最坏的情况,所以每个线程是独占的 而CAS比较并交换:compareAndSwap/Set(A,B):我们认为内存

linux设备驱动程序中的阻塞、IO多路复用与异步通知机制

一.阻塞与非阻塞 阻塞与非阻塞是设备访问的两种方式.在写阻塞与非阻塞的驱动程序时,经常用到等待队列. 阻塞调用是指调用结果返回之前,当前线程会被挂起,函数只有在得到结果之后才会返回. 非阻塞指不能立刻得到结果之前,该函数不会阻塞当前进程,而会立刻返回. 函数是否处于阻塞模式和驱动对应函数中的实现机制是直接相关的,但并不是一一对应的,例如我们在应用层设置为阻塞模式,如果驱动中没有实现阻塞,函数仍然没有阻塞功能. 二.等待队列 在linux设备驱动程序中,阻塞进程可以使用等待队列来实现. 在内核中,

Java并发编程实战 第15章 原子变量和非阻塞同步机制

非阻塞的同步机制 简单的说,那就是又要实现同步,又不使用锁. 与基于锁的方案相比,非阻塞算法的实现要麻烦的多,但是它的可伸缩性和活跃性上拥有巨大的优势. 实现非阻塞算法的常见方法就是使用volatile语义和原子变量. 硬件对并发的支持 原子变量的产生主要是处理器的支持,最重要的是大多数处理器架构都支持的CAS(比较并交换)指令. 模拟实现AtomicInteger的++操作 首先我们模拟处理器的CAS语法,之所以说模拟,是因为CAS在处理器中是原子操作直接支持的.不需要加锁. public s

非阻塞同步机制和CAS

目录 什么是非阻塞同步 悲观锁和乐观锁 CAS 我们知道在java 5之前同步是通过Synchronized关键字来实现的,在java 5之后,java.util.concurrent包里面添加了很多性能更加强大的同步类.这些强大的类中很多都实现了非阻塞的同步机制从而帮助其提升性能. 什么是非阻塞同步 非阻塞同步的意思是多个线程在竞争相同的数据时候不会发生阻塞,从而能够在更加细粒度的维度上进行协调,从而极大的减少线程调度的开销,从而提升效率.非阻塞算法不存在锁的机制也就不存在死锁的问题. 在基于

Linux下C的线程同步机制

C里提供了保证线程安全性的三种方法: (添加头文件#include<pthread.h>,pthread 库不是 Linux 系统默认的库,连接时需要使用静态库 libpthread.a, 在编译中要加 -lpthread参数) 互斥锁 通过锁的机制实现线程间的互斥,同一时刻只有一个线程可以锁定它,当一个锁被某个线程锁定的时候,如果有另外一个线程尝试锁定这个临界区(互斥体),则第二个线程会被阻塞,或者说被置于等待状态.只有当第一个线程释放了对临界区的锁定,第二个线程才能从阻塞状态恢复运行. i

用Java实现非阻塞通信

用ServerSocket和Socket来编写服务器程序和客户程序,是Java网络编程的最基本的方式.这些服务器程序或客户程序在运行过程中常常会阻塞.例如当一个线程执行ServerSocket的accept()方法时,假如没有客户连接,该线程就会一直等到有了客户连接才从accept()方法返回.再例如当线程执行Socket的read()方法时,如果输入流中没有数据,该线程就会一直等到读入了足够的数据才从read()方法返回. 假如服务器程序需要同时与多个客户通信,就必须分配多个工作线程,让它们分

Android源码学习-----Handler机制

Handler 1.为什么要使用Handler 在Android4.0之后,google公司为从系统使用及用户体验方面考虑,如果做一些比较耗时的操作,就不允许直接在主线程中进行,而是要通过handler发送Message对象的方法来修改主线程的UI界面 2.Handler原理简介 在所有的UI操作界面中,都在执行一个死循环(Looper)在不断接收和监听用户发出的指令,一但接受到指令,就立即执行.   当子线程需要修改UI界面时,调用Handler的sendMessage()方法,向主线程发送消

Nginx之进程间的通信机制(信号、信号量、文件锁)

1. 信号 Nginx 在管理 master 进程和 worker 进程时大量使用了信号.Linux 定义的前 31 个信号是最常用的,Nginx 则通过重定义其中一些信号的处理方法来使用吸纳后,如接收到 SIGUSR1 信号就意味着需要重新打开文件. 使用信号时 Nginx 定义了一个 ngx_signal_t 结构体用于描述接收到的信号时的行为: typedef struct { // 需要处理的信号 int signo; // 信号对应的字符串名称 char *signame; // 这个

TCP的阻塞和重传

TCP的阻塞和重传 TCP的阻塞和重传机制 网络拥堵 现在网络上大部分的网络请求都是以TCP的方式进行传输的了.网络链路是固定的,各种链路情况也是不一样的.网络拥堵一直是TCP协议设计和使用的时候尽力要避免的.比如,从TCP协议的网络包协议设计来看,TCP使用一发一答的ACK的网络包确认方式,而不是使用NAK这种会增加确认包的方式来做确认机制.这个就是在尽力降低网络上的包传递数量,避免网络拥堵. 还有哪些控制网络拥堵的方式呢? 慢启动 当一个连接连接上网络的时候,并不应该一次向网络中就发送大量的