Select Page
$ big
…………………………………………………………………………………………………………………………………………………
wrote 16523 sectors
done; ok
$

Noice.


big.c tries to create a file as large as possible. The while loop breaks when write() fails.

  while(1){
    *(int*)buf = sectors;
    int cc = write(fd, buf, sizeof(buf));
    if(cc <= 0)
      break;
    sectors++;
	if (sectors % 100 == 0)
		printf(2, ".");
  }

writei() checks for file size:

  if(off + n > MAXFILE*BSIZE)
    return -1;

MAXFILE (defined in fs.h) is currently (NDIRECT + NINDIRECT), which expands into 140 (12 + 128). This means we can’t create a file larger than 140 sectors. Our goal is to make it 16523. How?

bmap() is a function that maps a “logical” block index into a “physical” one. It works like pgdir_walk() with the create flag enabled.

A dinode contains an array, addrs[13]. The indexes of the first 12 blocks of a dinode are directly stored in addrs. Addresses of block number 13 to 140, however, are stored in a separate block (512 bytes per block, 4 bytes per entry, therefore 128 of them), whose physical index is stored in the last entry of addrs.

This is very similar to how we break up page tables into different layers: We don’t want these metadata to eat up too much resources. A block index is 4-byte long, which means if we simply use a long flat array to describe all the 140 sectors, the metadata itself occupies 560 bytes, which is a huge overhead for small files. Instead we reserve the last entry in addrs for the index of a “block table”, and only allocate the table when we really need it.

Now we need the same array to be able to represent even more blocks. The solution is simple: Introducing yet another layer of indirection. Previously, addrs contains 12 direct indexes and 1 indirect index. Now, we change it to: 11 direct indexes, 1 singly-indirect index, and 1 doubly-indirect index. The new addrs array will be able to describe a file as large as 11 + 128 + (128 * 128) = 16523 sectors.

That’s the plan! For that to work, we first need to change some macro definitions in fs.h:

#define NDINDIRECT (NINDIRECT * NINDIRECT)
#define MAXFILE (NDIRECT + NINDIRECT + NDINDIRECT)

And more importantly, modify bmap() to make it support doubly-indirect indexing:

// Return the disk block address of the nth block in inode ip.
// If there is no such block, bmap allocates one.
static uint
bmap(struct inode *ip, uint bn)
{
  uint addr, *a;
  struct buf *bp;

  if(bn < NDIRECT){
    if((addr = ip->addrs[bn]) == 0)
      ip->addrs[bn] = addr = balloc(ip->dev);
    return addr;
  }
  bn -= NDIRECT;

  // Time to introduce some new terms, like PD and PT.
  // Let's call them BD (Block Directory) and BT (Block Table)...

  if(bn < NINDIRECT){
    if((addr = ip->addrs[NDIRECT]) == 0)      // BT is not allocated yet.
      ip->addrs[NDIRECT] = addr = balloc(ip->dev);
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    if((addr = a[bn]) == 0){                  // Block is not allocated yet.
      a[bn] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);
    return addr;
  }
  bn -= NINDIRECT;

  if(bn < NDINDIRECT){
    if((addr = ip->addrs[NDIRECT+1]) == 0)    // BD is not allocated yet.
      ip->addrs[NDIRECT+1] = addr = balloc(ip->dev);
    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    if((addr = a[bn / NINDIRECT]) == 0){      // BT is not allocated yet.
      a[bn / NINDIRECT] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);

    bp = bread(ip->dev, addr);
    a = (uint*)bp->data;
    if((addr = a[bn % NINDIRECT]) == 0){      // Block is not allocated yet.
      a[bn % NINDIRECT] = addr = balloc(ip->dev);
      log_write(bp);
    }
    brelse(bp);
    return addr;
  }

  panic("bmap: out of range");
}

That’s it! It took my machine about one minute to create then read that big file, though… 🙁