关于 loop device

我们平时挂载一个img文件一般是通过mount -o loop来挂载,而它实际上等价于下面两步:

losetup /dev/loop0 example.img
mount /dev/loop0 /home/you/dir

我们可以看 util-linux-ng 源代码中的 mount/mount.c 文件,在 loop_check() 里有这么一段代码:

[c]

    if (!*loopdev || !**loopdev)
      *loopdev = find_unused_loop_device();
    if (!*loopdev)
      return EX_SYSERR;     /* no more loop devices */
    if (verbose)
      printf(_("mount: going to use the loop device %sn"), *loopdev);

    if ((res = set_loop(*loopdev, *loopfile, offset, sizelimit,
                        opt_encryption, pfd, &loop_opts))) {

[/c]

第一步是把文件和某个空闲的loop设备相关联起来,这里是 /dev/loop0。用的是系统调用ioctl(LOOP_SET_FD),这样以来对 /dev/loop0 的读写就会转化成对 example.img 的读写了。

第二步就容易理解了,和挂载普通块设备没什么区别了。mount之所以把这两步合为一步是想让你省去手工搜索空闲的loop设备。

现在看看它是怎么工作的:调用 LOOP_SET_FD 的时候内核会把 img 对应的 struct file 关联到设备对应的 lo->lo_backing_file 上去。同时,内核启动一个内核线程来监控 /dev/loopX 的读写请求(loop_thread()),对于每一个 bio,它都会进行相应的转换,对应到对 lo->lo_backing_file 上的读写。以写为例,我们可以看do_lo_send_write():

[c]
static int do_lo_send_write(struct loop_device lo, struct bio_vec bvec,
loff_t pos, struct page *page)
{
int ret = lo_do_transfer(lo, WRITE, page, 0, bvec->bv_page,
bvec->bv_offset, bvec->bv_len, pos >> 9);
if (likely(!ret))
return __do_lo_send_write(lo->lo_backing_file,
page_address(page), bvec->bv_len,
pos);
printk(KERN_ERR “loop: Transfer error at byte offset %llu, “
“length %i.n”, (unsigned long long)pos, bvec->bv_len);
if (ret > 0)
ret = -EIO;
return ret;
}

[/c]

而__do_lo_send_write() 直接就调用 file->f_op->write() 了。