我正在创建一个非常简单的基于 sbull 的块 RAM 磁盘。
到目前为止,如果我使用 dd 读/写数据块,它工作正常,但是每当我尝试在其上安装文件系统(有时创建文件系统)时,我的驱动程序就会崩溃。
经过长达数周的调试,我终于发现了问题所在,尽管我真的找不到解决问题的方法。因此我的问题在这里:)
每当用户空间应用程序向带有偏移量的设备创建请求时,驱动程序将无法工作!让我向您展示源代码以澄清:
首先,我使用 mk_request 处理请求(不使用 request_queue):
static void escsi_mk_request(struct request_queue *q, struct bio *bio)
{
struct block_device *bdev = bio->bi_bdev;
struct escsi_dev *esd = bdev->bd_disk->private_data;
int rw;
struct bio_vec *bvec;
sector_t sector;
int i;
int err = -EIO;
printk("request received nr. sectors = %lu\n",bio_sectors(bio));
sector = bio->bi_sector;
if (bio_end_sector(bio) > get_capacity(bdev->bd_disk))
goto out;
if (unlikely(bio->bi_rw & REQ_DISCARD)) {
err = 0;
goto out;
}
rw = bio_rw(bio);
if (rw == READA)
rw = READ;
bio_for_each_segment(bvec, bio, i) {
unsigned int len = bvec->bv_len;
err = esd_do_bvec(esd, bvec->bv_page, len, bvec->bv_offset, rw, sector);
if (err) {
printk("err!\n");
break;
}
sector += len >> SECTOR_SHIFT;
}
out:
bio_endio(bio, err);
}
esd_do_bvec 函数:
static int esd_do_bvec(struct escsi_dev *esd, struct page *page,
unsigned int len, unsigned int off, int rw,
sector_t sector)
{
void *mem;
int err = 0;
unsigned int offset;
int i;
offset = off + sector * 512;
printk("ESD RW=%d, len=%d, off=%d, offset=%d, sector=%lu\n",rw,len,off,offset,sector);
mem = kmap_atomic(page);
if (rw == READ) {
memcpy(mem,esd->data+offset,len);
} else {
memcpy(esd->data+offset,mem,len);
}
kunmap_atomic(mem);
out:
return err;
}
好的,所以基本上当我使用 dd 读取或写入数据时,esd_do_bvec() 中的变量“off”始终为 0,无论我要写入的位置和字节数。文件系统显然总是以 4KB 的块执行 I/O,并且即使只需要替换一个字节也会写入一个完整的块。
我确信当没有偏移时读取和写入工作正常,因为我创建了一个与我的块 RAM 磁盘大小相同的文件并使用 dd 将整个文件转储到我的设备中,然后得到设备的输出(也使用dd),输入和输出文件完全一样。我还将相同的文件写入 brd(Linux 内核原始块 RAM 磁盘驱动程序),比较我的设备和 brd 设备的输出是相同的。
但是——在某些特定情况下,我尝试在我的设备上挂载或创建一个新的文件系统,它以某种方式获取带有偏移量的 I/O 请求,此时我的驱动程序失败了。我假设我没有正确处理偏移量。例如,当我尝试“mount -t ext2 /dev/esda”时:
linux-xjwl:/home/phil/escsi # mount /dev/esda -t ext2 /mnt/esda1/
mount: wrong fs type, bad option, bad superblock on /dev/esda,
missing codepage or helper program, or other error
In some cases useful info is found in syslog - try
dmesg | tail or so
linux-xjwl:/home/phil/escsi # dmesg|tail -n 10
[ 2239.275901] ESD RW=0, len=4096, off=0, offset=16384, sector=32
[ 2239.275947] request received nr. sectors = 8
[ 2239.275959] ESD RW=0, len=4096, off=0, offset=4096, sector=8
[ 2239.276516] request received nr. sectors = 8
[ 2239.276537] ESD RW=0, len=4096, off=0, offset=2097152, sector=4096
[ 2239.276606] request received nr. sectors = 8
[ 2239.276626] ESD RW=0, len=4096, off=0, offset=28672, sector=56
[ 2239.277535] request received nr. sectors = 2
[ 2239.277535] ESD RW=0, len=1024, off=1024, offset=2048, sector=2
[ 2239.277535] EXT4-fs (esda): VFS: Can't find ext4 filesystem
(ps:输出显示“EXT4”,但我使用“-t ext2”运行)
我检查了扇区 n 的内容。2 在我的设备中,它确实包含 ext2 元数据(因为我在尝试挂载之前运行了 mkfs.ext2,当然)。所以我认为偏移量有问题。到目前为止,我无法真正调试我的驱动程序,因为我无法提出会导致 I/O 请求带有偏移量的请求(例如,如果我尝试将单个字节写入我的设备,Linux 将读取整个块并只用一个不同的字节重写它)。
希望这对你来说不是一个太简单的问题。
在此先感谢,菲尔
请参阅下面彼得提供的答案。
如果您想知道 esd_do_bvec() 函数现在是什么样子,那么它来了:
static int esd_do_bvec(struct escsi_dev *esd, char *buf,
unsigned int len, int rw, sector_t sector)
{
int err = 0;
unsigned int offset;
// Please notice that we STILL have an offset to deal with, but
// this offset comes in sectors and needs to be converted to a
// a byte offset.
offset = sector << SECTOR_SHIFT; // or multiply by 512
//printk("ESD RW=%d, len=%d, off=%d, offset=%d, sector=%lu\n",rw,len,off,offset,sector);
if (rw == READ) {
memcpy(buf,esd->data+offset,len);
} else {
memcpy(esd->data+offset,buf,len);
}
return err;
}