XV6——锁与并发

在操作系统中,什么情况下会导致并发?

  • 多线程
  • 多处理器
  • 中断

Xv6不支持多线程,但其他两种情况确实会在Xv6中发生。为了解决并发问题,需要一种机制:

Xv6中实现了两种锁:自旋锁睡眠锁

自选锁

// 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 == 0,说明无锁,可以访问
  • 当locked == 1,说明已经上锁,无法访问

加锁

// Acquire the lock.
// Loops (spins) until the lock is acquired.
void acquire(struct spinlock *lk)
{
	push_off(); // disable interrupts to avoid deadlock.
	if (holding(lk))
		panic("acquire");

	// On RISC-V, sync_lock_test_and_set turns into an atomic swap:
	//   a5 = 1
	//   s1 = &lk->locked
	//   amoswap.w.aq a5, a5, (s1)
	while (__sync_lock_test_and_set(&lk->locked, 1) != 0)
		;

	// Tell the C compiler and the processor to not move loads or stores
	// past this point, to ensure that the critical section's memory
	// references happen strictly after the lock is acquired.
	// On RISC-V, this emits a fence instruction.
	__sync_synchronize();

	// Record info about lock acquisition for holding() and debugging.
	lk->cpu = mycpu();
}

加锁过程可以拆分为4步:

  1. 关中断
  2. 获取锁
  3. 设置内存屏障
  4. 更新数据

解锁

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

	lk->cpu = 0;

	// Tell the C compiler and the CPU to not move loads or stores
	// past this point, to ensure that all the stores in the critical
	// section are visible to other CPUs before the lock is released,
	// and that loads in the critical section occur strictly before
	// the lock is released.
	// On RISC-V, this emits a fence instruction.
	__sync_synchronize();

	// Release the lock, equivalent to lk->locked = 0.
	// This code doesn't use a C assignment, since the C standard
	// implies that an assignment might be implemented with
	// multiple store instructions.
	// On RISC-V, sync_lock_release turns into an atomic swap:
	//   s1 = &lk->locked
	//   amoswap.w zero, zero, (s1)
	__sync_lock_release(&lk->locked);

	pop_off();
}

解锁过程与加锁过程相反:

  1. 更新数据
  2. 设置内存屏障
  3. 释放锁
  4. 开中断

关中断

void push_off(void)
{
	int old = intr_get();

	intr_off();
	if (mycpu()->noff == 0)
		mycpu()->intena = old;
	mycpu()->noff += 1;
}
  •  intr_get():查看S模式下的中断是否使能
  •  intr_off():关闭S模式下的中断

  •  intr_on():开启S模式下的中断

通过三个包装函数,实现对 sstatus 寄存器中 SIE 字段的操作,从而达到开关中断的目的

这里用 noff 变量来记录一个核心上到底用到了几把锁。必须保证只要有锁存在,这个核心的中断就保持关闭。

开中断

void pop_off(void)
{
	struct cpu *c = mycpu();
	if (intr_get())
		panic("pop_off - interruptible");
	if (c->noff < 1)
		panic("pop_off");
	c->noff -= 1;
	if (c->noff == 0 && c->intena)
		intr_on();
}

与关中断操作相反

睡眠锁

// Long-term locks for processes
struct sleeplock
{
	uint locked;		// Is the lock held?
	struct spinlock lk; // spinlock protecting this sleep lock

	// For debugging:
	char *name; // Name of lock.
	int pid;	// Process holding lock
};

加锁

void acquiresleep(struct sleeplock *lk)
{
	acquire(&lk->lk);
	while (lk->locked)
	{
		sleep(lk, &lk->lk);
	}
	lk->locked = 1;
	lk->pid = myproc()->pid;
	release(&lk->lk);
}

调用 sleep 之前先调用 acquire 获取锁的原因是为了确保当前进程能够在不被其他进程干扰的情况下检查锁的状态,并且能够安全地进入睡眠状态

睡眠

// Atomically release lock and sleep on chan.
// Reacquires lock when awakened.
void sleep(void *chan, struct spinlock *lk)
{
	struct proc *p = myproc();

	// Must acquire p->lock in order to
	// change p->state and then call sched.
	// Once we hold p->lock, we can be
	// guaranteed that we won't miss any wakeup
	// (wakeup locks p->lock),
	// so it's okay to release lk.

	acquire(&p->lock); // DOC: sleeplock1
	release(lk);

	// Go to sleep.
	p->chan = chan;
	p->state = SLEEPING;

	sched();

	// Tidy up.
	p->chan = 0;

	// Reacquire original lock.
	release(&p->lock);
	acquire(lk);
}

通过调用 sched 函数主动放弃 CPU 

因为系统中同时可能存在多个睡眠锁,所以 chan 字段是该进程用来记录是哪一个睡眠锁导致的睡眠,这样以后持有同一个睡眠锁的进程释放锁后,该进程才能精准的被唤醒。

解锁

void releasesleep(struct sleeplock *lk)
{
	acquire(&lk->lk);
	lk->locked = 0;
	lk->pid = 0;
	wakeup(lk);
	release(&lk->lk);
}

主动调用 wakeup 来唤醒所有持有该锁的其他进程

唤醒

// Wake up all processes sleeping on chan.
// Must be called without any p->lock.
void wakeup(void *chan)
{
	struct proc *p;

	for (p = proc; p < &proc[NPROC]; p++)
	{
		if (p != myproc())
		{
			acquire(&p->lock);
			if (p->state == SLEEPING && p->chan == chan)
			{
				p->state = RUNNABLE;
			}
			release(&p->lock);
		}
	}
}

遍历所有进程(除了释放该锁的进程),只要发现该进程处于睡眠状态且是因为该锁而睡眠,就唤醒它

参考文献

4. 互斥锁 | XV6 源代码阅读指南 (gitbook.io)

从自旋锁、睡眠锁、读写锁到 Linux RCU 机制讲解-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值