1.自旋锁
1.1.论述
自旋锁实现的前提是汇编提供了Lock指令前缀可用于修饰需要访问目标操作数在内存的汇编指令。
Lock修饰的汇编指令在执行过程前会锁住内存。这样所有逻辑处理器均无法执行内存访问。在汇编指令结束后,解除锁定。这样其他需要访问内存的逻辑处理器的访问操作(可能是读取或写入)才能正常进行。
自旋锁的死锁问题:
当一个逻辑处理器持有一个自旋锁A时,且该自旋锁A只能被自己后续释放时,被中断,中断处理时,该逻辑处理器再次通过spin_lock获取自旋锁A时,将产生死锁。
注意:
当一个逻辑处理器1持有一个自旋锁A时,
另一个逻辑处理器2通过spin_lock获取自旋锁A时,不会产生死锁。逻辑处理器2将通过循环等待,直到自旋锁A被释放。然后,获得锁,继续执行。
自旋锁评价:
自旋锁是是实现互斥访问的核心技术。因为等待锁时自选等待,会持续让逻辑处理器忙碌,但不会涉及上下文切换,执行现场保存与恢复。比较适合,短期持有锁的场景下的互斥保护。
1.2.实践
#ifndef __SPINLOCK_H__
#define __SPINLOCK_H__
#include "preempt.h"
typedef struct
{
// __volatile__修饰的作用是每次访问变量时候必须访问内存
__volatile__ unsigned long lock; //1:unlock,0:lock
}spinlock_T;
inline void spin_init(spinlock_T * lock)
{
lock->lock = 1;
}
// 自旋锁的背景是:
// 多核并行场景。
// 多个核访问同一个共享数据区时,需要互斥。
// 比如每个核同时进行打印。
// 每个核各自打印一行,
// 多个核如果同时打印,打印会混乱。互斥保护下,可保证各自打印的完整。
// 比如每个核,同时对一个变量进行自增1(可能只需一个汇编指令,但是由于流水线技术,这个汇编指令会被拆分为多个独立执行单元)。
// 自增可能拆分为3个汇编指令:取内存到寄存器,寄存器数值+1,写回内存。
// 没有互斥保护下,两个核并行操作,结果可能是+1,也可能是+2
// 等等。
// 自旋锁依赖的是:
// 目的操作数指向内存时,汇编指令允许加前缀Lock修饰。
// 修饰后,这个汇编指令执行期间,内存总线会被锁定。
// 这意味着,这个汇编指令执行期间,其他处理器无法访问内存。
inline void spin_lock(spinlock_T * lock)
{
preempt_disable();
__asm__ __volatile__ ( "1: \n\t"
"lock decq %0 \n\t"
"jns 3f \n\t"
"2: \n\t"
"pause \n\t"
"cmpq $0, %0 \n\t"
"jle 2b \n\t"
"jmp 1b \n\t"
"3: \n\t"
:"=m"(lock->lock)
:
:"memory"
);
}
inline void spin_unlock(spinlock_T * lock)
{
__asm__ __volatile__ ( "movq $1, %0 \n\t"
:"=m"(lock->lock)
:
:"memory"
);
preempt_enable();
}
inline long spin_trylock(spinlock_T * lock)
{
unsigned long tmp_value = 0;
preempt_disable();
// xchgq自身是原子的。相当于加了Lock修饰。
__asm__ __volatile__ ( "xchgq %0, %1 \n\t"
:"=q"(tmp_value),"=m"(lock->lock)
:"0"(0)
:"memory"
);
if(!tmp_value)
preempt_enable();
return tmp_value;
}
#endif
2.原子变量
2.1.论述
有了自旋锁的基础,原子变量很好理解。
就是对变量的操作是原子的。
我们正常将一个内存变量执行加1。处理器执行时,可能被分解为:
从内存读取数值到寄存器A
对寄存器A执行加1
将寄存器A内容写回内存
如果两个逻辑处理器单元,同时执行内存变量加1。则最后写回内存的可能是+1也可能是+2。
原子变量,执行上述操作时,就是:
锁定内存
从内存读取数值到寄存器A
对寄存器A执行加1
将寄存器A内容写回内存
解除内存锁定
从而杜绝上述问题。
2.2.实践
#ifndef __ATOMIC_H__
#define __ATOMIC_H__
typedef struct
{
__volatile__ long value;
} atomic_T;
// 读取也只一个执行单元。
// 不加lock修饰也可以。
#define atomic_read(atomic) ((atomic)->value)
// 因为此变量的其他操作都是锁定的。
// 所以,这里设置值本身就一个执行单元。不加lock修饰也可以。
#define atomic_set(atomic,val) (((atomic)->value) = (val))
// 原子变量--增加值
inline void atomic_add(atomic_T * atomic,long value)
{
__asm__ __volatile__ ( "lock addq %1, %0 \n\t"
:"=m"(atomic->value)
:"r"(value)
:"memory"
);
}
// 原子变量--减少值
inline void atomic_sub(atomic_T *atomic,long value)
{
__asm__ __volatile__ ( "lock subq %1, %0 \n\t"
:"=m"(atomic->value)
:"r"(value)
:"memory"
);
}
// 原子自增
inline void atomic_inc(atomic_T *atomic)
{
__asm__ __volatile__ ( "lock incq %0 \n\t"
:"=m"(atomic->value)
:"m"(atomic->value)
:"memory"
);
}
// 原子自减
inline void atomic_dec(atomic_T *atomic)
{
__asm__ __volatile__ ( "lock decq %0 \n\t"
:"=m"(atomic->value)
:"m"(atomic->value)
:"memory"
);
}
// 原子or--比特位的或
inline void atomic_set_mask(atomic_T *atomic,long mask)
{
__asm__ __volatile__ ( "lock orq %1, %0 \n\t"
:"=m"(atomic->value)
:"r"(mask)
:"memory"
);
}
// 原子and--比特位的与
inline void atomic_clear_mask(atomic_T *atomic,long mask)
{
__asm__ __volatile__ ( "lock andq %1, %0 \n\t"
:"=m"(atomic->value)
:"r"(~(mask))
:"memory"
);
}
#endif
3.信号量
3.1.论述
信号量用于描述一个资源有若干份。
生产者执行资源增加。消费者执行资源消费。
消费者消费时,如果有资源可供消费,更新资源后继续执行。如果没有,则需要阻塞自己,放弃逻辑处理器使用权,等待后续有资源时,被生产者唤醒(重新加入可调度队列,等待调度)。
生产者生产后,如果没有人在阻塞等待,只需更新资源可供数量,继续执行。如果有人在阻塞等待,则需要找到一个等待着,唤醒它(重新加入可调度队列)。
3.2.实践
#ifndef __SEMAPHORE_H__
#define __SEMAPHORE_H__
#include "atomic.h"
#include "lib.h"
#include "task.h"
#include "schedule.h"
typedef struct
{
// 双向链表
struct List wait_list;
// 任务控制对象
struct task_struct *tsk;
} wait_queue_T;
// wait_queue_T初始化,关联到一个任务控制对象
void wait_queue_init(wait_queue_T * wait_queue,struct task_struct *tsk)
{
list_init(&wait_queue->wait_list);
wait_queue->tsk = tsk;
}
// wait_queue_T&atomic_T构成信号量
typedef struct
{
atomic_T counter;
wait_queue_T wait;//哨兵节点
} semaphore_T;
// 信号量
// 适合场景:
// 房间10个位置。
// 有人要进入房间,执行semaphore_down
// 有人离开房间,执行semaphore_up
// semaphore_down,semaphore_up需要是原子的。
void semaphore_init(semaphore_T * semaphore,unsigned long count)
{
// 原子设置值
atomic_set(&semaphore->counter,count);
// 初始化
wait_queue_init(&semaphore->wait,NULL);
}
void __up(semaphore_T * semaphore)
{
// 从链式结构找到首个wait_queue_T
wait_queue_T * wait = container_of(list_next(&semaphore->wait.wait_list),wait_queue_T,wait_list);
// 将其从链式结构移除
list_del(&wait->wait_list);
// 设置为运行
wait->tsk->state = TASK_RUNNING;
// 加入调度队列。等待调度。
insert_task_queue(wait->tsk);
}
// 对外接口--资源增加
void semaphore_up(semaphore_T * semaphore)
{
if(list_is_empty(&semaphore->wait.wait_list))// 目前没人再等待
atomic_inc(&semaphore->counter);// 这个计数,可以认为是可用资源数
else
__up(semaphore);// 因为当前有资源可用。所以需要找到一个等待者。唤醒它。
}
void __down(semaphore_T * semaphore)
{
wait_queue_T wait;
wait_queue_init(&wait,current);
current->state = TASK_UNINTERRUPTIBLE;
list_add_to_before(&semaphore->wait.wait_list,&wait.wait_list);// 执行尾部放入
schedule();// 因为当前进程陷入等待。所以,需要触发调度。重新选择进程执行。
}
// 对外接口--资源减少
void semaphore_down(semaphore_T * semaphore)
{
if(atomic_read(&semaphore->counter) > 0)// 因为有资源可用
atomic_dec(&semaphore->counter);// 更新剩余资源数
else // 没有资源可用
__down(semaphore);// 需要放入等待队列
}
#endif