XV6源代碼閱讀-同步機制

Exercise1 源代碼閱讀

鎖部分:spinlock.h/spinlock.c以及相關其餘文件代碼html

// Mutual exclusion lock.
struct spinlock {
  uint locked; // 0未被佔用, 1已被佔用
  
  // For debugging:
  char *name;        // Name of lock.
  struct cpu *cpu;   // The cpu holding the lock.
  uint pcs[10];      // The call stack (an array of program counters)
                     // that locked the lock.
};

// 初始化自旋鎖
void initlock(struct spinlock *lk, char *name)
{
  lk->name = name;
  lk->locked = 0;
  lk->cpu = 0;
}

// Acquire the lock.
// Loops (spins) until the lock is acquired.
// Holding a lock for a long time may cause
// other CPUs to waste time spinning to acquire it.
void acquire(struct spinlock *lk)
{
  // 關中斷
  pushcli(); // disable interrupts to avoid deadlock.
  if(holding(lk)) // 判斷鎖的持有是否爲當前cpu
    panic("acquire");

  // The xchg is atomic.
  // It also serializes, so that reads after acquire are not
  // reordered before it. 
  while(xchg(&lk->locked, 1) != 0); // 拿不到鎖開始自旋

  // Record info about lock acquisition for debugging.
  lk->cpu = cpu;
  getcallerpcs(&lk, lk->pcs);
}

// Release the lock.
void release(struct spinlock *lk)
{
  if(!holding(lk))
    panic("release");

  lk->pcs[0] = 0;
  lk->cpu = 0;

  // The xchg serializes, so that reads before release are 
  // not reordered after it.  The 1996 PentiumPro manual (Volume 3,
  // 7.2) says reads can be carried out speculatively and in
  // any order, which implies we need to serialize here.
  // But the 2007 Intel 64 Architecture Memory Ordering White
  // Paper says that Intel 64 and IA-32 will not move a load
  // after a store. So lock->locked = 0 would work here.
  // The xchg being asm volatile ensures gcc emits it after
  // the above assignments (and after the critical section).
  xchg(&lk->locked, 0);

  popcli();
}

Exercise2 帶着問題閱讀

1.什麼是臨界區? 什麼是同步和互斥? 什麼是競爭狀態? 臨界區操做時中斷是否應該開啓? 中斷會有什麼影響? XV6的鎖是如何實現的,有什麼操做? xchg 是什麼指令,該指令有何特性?node

  • 臨界區(Critical Section):訪問臨界區的那段代碼,多個進程/線程必須互斥進入臨界區;
  • 同步(Synchronization):指多個進程/線程可以按照程序員指望的方式來協調執行順序,爲了實現這個目的,必需要藉助於同步機制(如信號量,條件變量,管程等);
  • 互斥(Mutual Exclusion):互斥的目的是保護臨界區;
  • 競爭狀態:競爭是基於併發環境下的,單個進程/線程不存在競爭,在併發環境下,多個進程/線程都須要請求某資源的時候,只有競爭到該資源的進程/線程纔可以執行,釋放資源後,剩餘進程/線程按照預約的算法策略從新競爭;
  • 操做臨界區必須關中斷,對臨界區的操做是原子性的;
  • 中斷影響:中斷下降了併發性能,同時中斷也會致使頻繁的上下文切換,上下文切換會致使tlb快表失效,所以要儘量的縮減中斷處理的時間;
  • 自旋鎖(Spinlock):xv6中利用該數據結構實現多個進程/線程同步和互斥訪問臨界區。當進程/線程請求鎖失敗時進入循環,直至鎖可用併成功拿到後返回,對於單cpu系統自旋鎖浪費CPU資源,不利於併發,自旋鎖的優點體如今多CPU系統下,XV6支持多CPU。主要接口有void initlock(struct spinlock * lk, char * name)、void initlock(struct spinlock * lk, char * name)、void release(struct spinlock * lk);
  • xchg:xchg()函數使用GCC的內聯彙編語句,該函數中經過xchg原子性交換spinlock.locked和newval,並返回spinlock.locked原來的值。當返回值爲1時,說明其餘線程佔用了該鎖,繼續循環等待;當返回值爲0時,說明其餘地方沒有佔用該鎖,同時locked本設置成1了,因此該鎖被此處佔用。
// x86.h 調用方式如xchg(&lk->locked, 1)
static inline uint xchg(volatile uint *addr, uint newval)
{
  uint result;
  
  // The + in "+m" denotes a read-modify-write operand.
  asm volatile("lock; xchgl %0, %1" :
               "+m" (*addr), "=a" (result) :
               "1" (newval) :
               "cc");
  return result;
}

2.基於XV6的spinlock, 請給出實現信號量、讀寫鎖、信號機制的設計方案(三選二,請寫出相應的僞代碼)?程序員

  • 信號量實現
struct semaphore {
  int value;
  struct spinlock lock;
  struct proc *queue[NPROC]; // 進程等待隊列,這是一個循環隊列
  int end;   // 隊尾
  int start; // 隊頭
};

// 初始化信號量
void sem_init(struct semaphore *s, int value) {
  s->value = value;
  initlock(&s->lock, "semaphore_lock");
  end = start = 0;
}

void sem_wait(struct semaphore *s) {
  acquire(&s->lock); // 競爭鎖,若是競爭不到進入自旋
  s->value--; 
  if (s->value < 0) {
    s->queue[s->end] = myproc(); // myproc()獲取當前進程, 放入隊尾
    s->end = (s->end + 1) % NPROC; // 循環隊列計算新的隊尾
    // 1. 釋放鎖(下一個sem_wait的進程才能進入acquire),
    // 2. 而後進入睡眠等待, 被喚醒時從新競爭鎖
    sleep(myproc(), &s->lock); 
  }
  release(&s->lock);
}

void sem_signal(struct semaphore *s) {
  acquire(&s->lock); // 競爭鎖
  s->value++;
  if (s->value <= 0) {
    wakeup(s->queue[s->start]); // 喚醒循環隊列頭的進程
    s->queue[s->start] = 0; 
    s->start = (s->start + 1) % NPROC; // 從新計算隊頭
  }
  release(&s->lock);
}

// proc.h
// Per-process state
struct proc {
  uint sz;                     // Size of process memory (bytes)
  pde_t* pgdir;                // Page table
  char *kstack;                // Bottom of kernel stack for this process
  enum procstate state;        // Process state
  volatile int pid;            // Process ID
  struct proc *parent;         // Parent process
  struct trapframe *tf;        // Trap frame for current syscall
  struct context *context;     // swtch() here to run process
  void *chan;                  // If non-zero, sleeping on chan
  int killed;                  // If non-zero, have been killed
  struct file *ofile[NOFILE];  // Open files
  struct inode *cwd;           // Current directory
  char name[16];               // Process name (debugging)
};

參考文獻

[1] xv6鎖-博客園
[2] xv6鎖-xchg
[3] xv6鎖-CSDN
[4] xv6總體報告-百度文庫算法

相關文章
相關標籤/搜索