程序锅

  • 首页
  • 分类
  • 标签
  • 归档
  • 关于

  • 搜索
基础知识 Etcd LeetCode 计算机体系结构 Kubernetes Containerd Docker 容器 云原生 Serverless 项目开发维护 ELF 深入理解程序 Tmux Vim Linux Kernel Linux numpy matplotlib 机器学习 MQTT 网络基础 Thrift RPC OS 操作系统 Clang 研途 数据结构和算法 Java 编程语言 Golang Python 个人网站搭建 Nginx 计算机通用技术 Git

MIT 6.828 课程 | HW7-xv6 locking

发表于 2019-11-25 | 分类于 MIT6.828 | 0 | 阅读次数 3155

在这个HW中,我们将探索interrupt和lock之间的相互作用。首先我们来看一下spinlock.c和spinlock.h中的一些函数和定义

struct spinlock;
initlock();
acquire();
release()

这些主要是lock的具体实现,比如struct spinlock是lock的结构体,initlock()是初始化一个锁,acquire()是获得一个锁,release()是释放一个lock。

Don't do this

struct spinlock lk;
initlock(&lk, "test lock");
acquire(&lk);
acquire(&lk);

上面这段操作,连续两个acquire(),将会造成panic。

Interrupts in ide.c

acquire()函数通过cli指令(使用 pushchi())来保证当前的处理器的中断是关闭了的,中断关闭状态持续到处理器的最后一个lock被release掉(通过popsti()来使用sti指令)。

// 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))
    panic("acquire");

   ......
}

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

  lk->pcs[0] = 0;
  lk->cpu = 0;
  ......
  // Release the lock, equivalent to lk->locked = 0.
  asm volatile("movl $0, %0" : "+m" (lk->locked) : );

  popcli();
}

假如我们在持有ide lock的时候,打开了interrupt会发生什么勒?我们在ide.c中iderw()函数中,在acquire后面调用 sti(),并且在release之前调用cli()(sti()函数会打开interrupt,而cli()函数会关闭中断)。

void
iderw(struct buf *b)
{
  ......

  acquire(&idelock);  //DOC:acquire-lock
  sti();
  ......
  cli();
  release(&idelock);
}

重新编译kernel和启动QEMU,有些时候kernel在启动之后会panic,多尝试几次之后直到kernel正常启动。

有几次kernel的启动会panic,只要是因为我们在acquire关闭中断之后,又打开了中断,这样子会陷入死锁。比如在启动过程中,发生了中断,而这个中断又会去申请一个锁,然而锁又被其他所持有,但是其他又在等待中断处理结束,那么就陷入死锁了。

Interrupts in file.c

将你添加在ide.c中添加的sti()和cli()移掉。下面我们来看一下当hold file_table_lock的时候,打开中断会发生什么。这lock是用来保护file descriptor table的,当一个应用程序open或close一个文件的时候kernel会修改这个表。那么下面我们在file.c的filealloc()函数中,在acquire()后面调用call(),同时在每一个re

lease()之前调用cli(),并且我们也需要把x86.h这个头文件include进去

......
#include "x86.h"
......
// Allocate a file structure.
struct file*
filealloc(void)
{
  struct file *f;

  acquire(&ftable.lock);
  sti();
  for(f = ftable.file; f < ftable.file + NFILE; f++){
    if(f->ref == 0){
      f->ref = 1;
      cli();
      release(&ftable.lock);
      return f;
    }
  }
  cli();
  release(&ftable.lock);
  return 0;
}

之后重新编译kernel并运行,你会发现启动多少次后都不会panic。这个主要是因为filealloc()在alloc的时候,时间很短,所以很难发生死锁。

xv6 lock implementation

为什么release()函数要先清理掉lk->pcs[0]和lk->cpu,之后再清理掉lk->locked。

因为假如先清理掉lk->locked,那么lock就没了,新的进程可能就会在lk->pcs[0]和lk->cpu被清零之前获得lock,但是此时的lk->cpu和lk->pcs[0]还是之前的那个,那么就会导致不一致。

卷死我
dawnguo 微信支付

微信支付

dawnguo 支付宝

支付宝

  • 本文作者: dawnguo
  • 本文链接: /archives/59
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 操作系统 # OS
RPC | RPC 概念及 Thrift 简介
深入理解程序 | 静态链接的过程
  • 文章目录
  • 站点概览
dawnguo

dawnguo

215 日志
24 分类
37 标签
RSS
Creative Commons
© 2018 — 2025 程序锅
0%