创建写入文件例子
有待复习
例子
分为3个阶段
- 创建文件
- 将”hi”写入文件
- 将”\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 33 : 将filex inode标记为将要被使用
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 45 : 寻找free的datablock 并更新bitmap
write “\n” to file
- write 595 : 上一步在bitmap中找到的datablock的free data block是595
- sys_write -> filewrite -> writei -> copyin -> log_write保存修改
- write 33 : 更新inode.size;
- write 595 : 上一步在bitmap中找到的datablock的free data block是595
并发举例: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");
}
- 也即在保护struct buf.data[BSIZE]。要访问data[BSIZE],必须先acquiresleep(&b->lock);
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。 )