Linux 内核延时分为两种方式,一种 忙延时 一种 休眠延时。
忙延时,CPU在原地空转,等待条件满足,适用于延时时间较短的场景。
实现忙延时的API:
ndelay(x) x 表示纳秒
udelay(x) x 表示微秒
mdelay(x) x 表示毫秒数
如果延迟大于CPU能够响应的最短中断的时间,比如 ARM 架构 HZ 为100,那么响应中断的最短间隔为 10毫秒,如果延迟超过了该值(10毫秒),那么不再建议使用 忙延时,而采用休眠延时。
忙延时,是不释放CPU资源的,忙延时依旧消耗CPU资源。
休眠仅仅存在于进程上下文中,因为中断上下文不允许休眠。
休眠函数:
msleep(x) 休眠 x 毫秒
ssleep(x) 休眠 x 秒
schedule() 永久休眠,直到 被唤醒 (可采用 wake_up 去唤醒,涉及到Linux 内核的进程调度。)
schedule_timeout(5*HZ); 休眠5秒,如果是 5 不带 HZ,那么表示5个时钟中断时间,跟 jiffies 单位一致。
对于单CPU而言,无法实现并行,而可以通过任务的切换调度,实现交替运行,实现并发。
多个CPU同时平行执行多个任务,即并行。
竞态:多个执行单元同时对共享资源进行访问,便会产生竞态。
Linux 内核 产生竞态的几种情形
1. 单CPU 进程间抢占
2. 单CPU 中断 和 进程之前抢占
3. 单CPU 中断与中断间抢占
4. 多核对共享资源的抢占
共享资源有哪些呢?
共享资源:软件上的全局变量,或者硬件资源,硬件寄存器等。
互斥访问:当一个执行单元在访问共享资源的时候,其他执行单元被禁止访问。
临界区:访问共享资源的代码区域
执行路径具有原子性:代码执行过程不允许发生CPU资源的切换,一般通过屏蔽中断实现。
由于竞态的存在,对于共享资源的访问,就会带来安全问题。
解决方案: 中断屏蔽 ,自旋锁,信号量,原子操作
1. 能够解决进程间抢占引起的竞态问题
2. 能够解决中断与进程间引起的竞态问题
3. 能够解决中断与中断间引起的竞态问题
多 CPU 之前引起的竞态问题无法解决
中断屏蔽所保护的临界区执行速度要快,不能进行休眠
屏蔽中断的API:
unsigned long flags;
// 屏蔽中断
local_irq_save(flags);
// critical operation codes
// 恢复中断
local_irq_restore(flags);
local 表示,屏蔽的是当前的这个 CPU,其他CPU与才屏蔽或者恢复中断操作无关,所以,多核引发的竞态问题,中断屏蔽无法解决。
注意,save 与 restore的操作,一定要对称。
能够解决多核引起的竞态问题
能够解决进程间抢占资源引起的竞态问题
但是,无法解决由于中断引起的竞态问题,如需解决中断引起的竞态问题,需要使用衍生自旋锁,所以衍生自旋锁可以解决“所有的竞态问题”。
自旋锁的特点:
1. 自旋锁所保护的临界区,尽量执行要快,不能有休眠操作。
2. 自旋锁获取不到时,会进入忙等待,所以自旋锁是可以用在中断上下文中。
spinlock_t lock;
spin_lock_init(&lock);
spin_lock(&lock);
spin_trylock(&lock);
spin_unlock(&lock);
spin_lock_init: 初始化一个spin lock,并且 ,没有返回值,内核中是一个宏,代码在 do while 循环里,也没法儿用去接收所谓的返回值。
spin_lock: 无返回值,如果加锁成功,则返回,然后继续往下走,如果失败,不会返回,CPU进入忙等待。
spin_trylock:返回值其实是 int,但是内核中使用到的地方,都是在条件上下文中 ,使用方式都是:成功返回true,失败返回false。
如果成功,记住需要采用 spin_unlock进行释放,返回false的时候,说明获取锁失败,那么后续无需unlock。
解决中断引起的竞态问题自旋锁
unsigned long flags;
spinlock_t lock;
spin_lock_init(&lock);
// 加锁
spin_lock_irqsave(&lock, flags);
// 解锁
spin_unlock_irqrestore(&lock, flags);
spin_lock用于防止在不同CPU上的执行单元对共享资源的同时访问以及不同进程上下文互相抢占导致的对共享资源的非同步访问,而中断失效和软中断失效却是为了阻止在同一CPU上软中断或中断对共享资源的非同步访问。
不是IPC机制下应用进程间的信号量,而是内核信号量。
信号量又称睡眠锁,本质就是为了解决自旋锁保护临界区不能休眠的问题。
没有获取信号量的任务就会进入休眠等待状态。所以信号量不能使用于中断上下文中。
down 接口如果成功获取到了信号量,进程将进入不可中断的休眠状态。如果,这不是预期行为,我们可以调用 down_interruptible()去获取信号量。
down_trylock: 特别说明,成功,将返回true,失败,返回false,进程不会休眠。为 true 时,进程依旧会进入不可中断的休眠状态。如果失败,立刻返回false,不会休眠,那么该接口是可以在中断上下文中使用。
进程状态:
S: 可中断的休眠状态
D: 不可中断的休眠状态
待续。为了试验,先搞点儿别的。。。。