Skip to the content.

我觉得锁是理解xv6内核代码的一个难点,锁实现本身更简单一些,但是锁的使用反而更难理解,这跟常规的思路可能不太一致。

本部分记录下xv6中的自旋锁,自旋锁顾名思义就是不停的旋转等待,直到获得锁。 它在等待CPU过程中相当于一个while(1),会消耗CPU时间。直接看xv6中spinlock的结构体,注释说Mutual exclusion lock,其实这个自旋锁也是互斥锁,仅有一个线程可以获得锁。

// Mutual exclusion lock. 
struct spinlock {
  uint locked;       // Is the lock held?

  // For debugging:
  char *name;        // Name of lock.
  struct cpu *cpu;   // The cpu holding the lock.
};

整个结构体中其实只有一个locked需要关注,简单来说 locked = 1, 说明获得锁,否则未获得锁。

0x01. spinlock 相关函数

spinlock 相关函数相对来说还是比较容易理解,最主要就是 acquire 和 release 两个函数。

1. acquire 获得 spinlock

void
acquire(struct spinlock *lk)
{
  push_off(); // 这个函数很重要
  if(holding(lk)) panic("acquire");
  // 使用编译器提供的函数来实现原子操作,等待,直到locked=0,然后给他设置成locked=1
  while(__sync_lock_test_and_set(&lk->locked, 1) != 0);
  // 内存屏障,不让编译器优化改变这里的代码执行顺序,以免出错
  __sync_synchronize();
  // 设置锁的CPU
  lk->cpu = mycpu();
}

2. release 释放 spinlock

void
release(struct spinlock *lk)
{
  if(!holding(lk)) panic("release");
  lk->cpu = 0;
  __sync_synchronize();
  __sync_lock_release(&lk->locked);  // 与__sync_lock_test_and_set 对应
  pop_off();  // 与 push_off 对应
}

3. push_off 和 pop_off

4. __sync_lock_test_and_set 和 __sync_lock_release

在 riscv中__sync_lock_test_and_set 函数就是如下面的代码实现:

  a5 = 1
  s1 = &lk->locked
  amoswap.w.aq a5, a5, (s1)

__sync_lock_test_and_set 是 GNU 提供的函数, 针对不同的架构提供原子 lock 原子操作,不需要自己写汇编代码。

__sync_lock_release 函数也是如此。

5. holding 函数

验证 lock 是否已经被获得,是返回1,否则返回0。

int
holding(struct spinlock *lk)
{
  int r;
  r = (lk->locked && lk->cpu == mycpu());
  return r;
}

0x02. spinlock 的使用

被spinlock保护的代码区域叫做临界区,也就是说acquire和release之间的代码就是临界区代码。 同时有多个CPU运行到这段代码的时候,他们不会同时运行,而是变成顺序执行。可能如下图所示:

spinlock 保护的是什么? 最简单的就是保护一个全局变量。

1. 保护 ticks

时钟中断发生的时候,xv6需要维护一个时钟中断次数的全局变量ticks,每次需要将ticks++,为了避免 竞争,所以需要用spinlock保护。非常简单,如下操作即可:

// kernel/trap.c  省略了其他代码

// 定义
struct spinlock tickslock;
uint ticks;
// ...
// 初始化锁
initlock(&tickslock, "time");
// ...
// 使用锁
void
clockintr()
{
  acquire(&tickslock);  // 加锁
  ticks++;
  wakeup(&ticks); // 暂时忽略
  release(&tickslock);   // 解锁
}

当然,spinlock 保护的内容可以更多一些,比如 p->lock 就复杂一些。

2. 进程锁

// kernel/proc.h
// Per-process state  
struct proc {
  struct spinlock lock;

  // p->lock must be held when using these:
  enum procstate state;        // Process state
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  int xstate;                  // Exit status to be returned to parent's wait
  int pid;                     // Process ID

  // wait_lock must be held when using this:
  struct proc *parent;         // Parent process

  // ...
};

进程锁 p->lock 需要保护的内容较多: