不落辰

知不可乎骤得,托遗响于悲风

0%

操作系统-xv6-文件系统2

创建写入文件例子
有待复习

例子

  • 分为3个阶段

      1. 创建文件
      1. 将”hi”写入文件
      1. 将”\n”写入文件
  • echo “hi” > x

  • createfile

    • write 33 : 将filex inode标记为将要被使用
      • sys_open -> create -> ialloc分配inode -> dip->type = type -> log_write保存修改
    • write 33 : 修改filex inode的link等属性
      • sys_open -> create -> ip->nlink = 1; ->iupdate -> log_write保存修改
    • write 70 : 写父目录的data block,也即新增的一个entry:包含了filex name以及inode编号。
      • sys_open -> create -> dirlink(parent_inode,name,node->inum) 写根目录的data block -> logwrite保存修改
    • write 32 : 写父目录的inode。因为大小改变
      • sys_open -> create -> dirlink 写根目录的inode -> logwrite保存
    • write 33 : 再次更新了文件x的inode
      • 新文件的inode所引用的block大小置为0
  • write “hi” to file

    • write 45 : 寻找free的datablock 并更新bitmap
      • sys_write -> filewrite -> writei -> bmap -> balloc分配block,修改bitmap的相应标志位 -> log_write保存
    • write 595 : 上一步在bitmap中找到的datablock的free data block是595
      • sys_write -> filewrite -> writei -> copyin -> log_write保存修改
    • write 595 : 同上一步
    • write 33 : 更新inode.size;direct block number 1st = 595
      • 这两个信息都会通过write 33一次更新到磁盘上的inode中
  • write “\n” to file

    • write 595 : 上一步在bitmap中找到的datablock的free data block是595
      • sys_write -> filewrite -> writei -> copyin -> log_write保存修改
    • write 33 : 更新inode.size;

并发举例:bget并发

  • 关于多个process同时create,其并发的正确性在bread->bget得到保证。
    • 情况举例:有多个进程同时调用bget的话,其中一个可以获取bcache.lock并扫描buffer cache list。此时,其他进程是没有办法修改buffer cache list的。之后,进程会查找block numberX是否被缓存在buffer cache list中。如果在的话将block cache的ref_cnt加1,表明当前进程对block cache有引用,之后再释放bcache的锁。如果有第二个进程也想扫描buffer cache list,那么这时它就可以获取bcache.lock。假设第二个进程也要获取block numberX的cache,那么它也会对相应的block cache的引用计数加1。最后这两个进程都会尝试对block 33的block cache调用acquiresleep函数。

    • struct bcache和buf 详情见后文。
    • bcache.lock : 保护buf list
      • 如果要修改buf list的结构 及其上的 buf meta信息,必须先acquire(&bcache.lock)
    • buf.lock : 保护block cache自身
      • 也即在保护struct buf.data[BSIZE]。要访问data[BSIZE],必须先acquiresleep(&b->lock);
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        // Look through buffer cache for block on device dev.
        // If not found, allocate a buffer.
        // In either case, return locked buffer.
        static struct buf*
        bget(uint dev, uint blockno)
        {
        struct buf *b;

        acquire(&bcache.lock);

        // Is the block already cached?
        for(b = bcache.head.next; b != &bcache.head; b = b->next){
        if(b->dev == dev && b->blockno == blockno){
        b->refcnt++;
        release(&bcache.lock);
        acquiresleep(&b->lock);
        return b;
        }
        }

        // Not cached.
        // Recycle the least recently used (LRU) unused buffer.
        for(b = bcache.head.prev; b != &bcache.head; b = b->prev){
        if(b->refcnt == 0) {
        b->dev = dev;
        b->blockno = blockno;
        b->valid = 0;
        b->refcnt = 1;
        release(&bcache.lock);
        acquiresleep(&b->lock);
        return b;
        }
        }
        panic("bget: no buffers");
        }

sleeplock

  • acquiresleep

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
      
    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);
    }
  • 由上可知,sleeplock就是比spinlock多了个切换线程,那么为什么block cache 也即 struct buf 使用 sleeplock ?

    • 在持有spinlock的过程中会关闭中断,而磁盘读取完成会发起中断。如果buf使用spinlock,那么就我们就永远收不到来自磁盘的中断,也就无法从磁盘读取数据。(至少对单核cpu是这样)
    • 所以buf使用sleep lock,持有锁的时候不关闭中断。且当在阻塞在acquiresleep的时候,cpu并没有空转,而是将cpu让出。
    • (当中断发生时,另一个process(记当前正在cpu上运行的process)会进入 对于读取cpu完成的中断处理函数,该handler会通过wakeup唤醒刚才在sleeplock上等待读取数据的process。 )