今日は「BSDカーネルの設計と実装 読書会 (14)」に参加してきたのですが、この中でreadv/writev、aio_read/aio_writeの説明の箇所でそういえばLinuxはsys_writeもext4だと最終的にはaio_write()を呼び出して、データもiovecだった気が・・・と思ったので再確認。
ネットでmanページを見ていて気づいたのはFreeBSDのaio_XXX()のセクションは2、Linuxの場合はセクション3ということに気づいたりもしました。
では順番にコードを見ていきます。見ているLinuxカーネルはv3.10.10です。
まずはsys_write()から。
sys_write()の引数は基本的にmanページにあるものと同一ですね。
書き込みするためにvfs_write()を呼んでいます。
486SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf, 487 size_t, count) 488{ 489 struct fd f = fdget(fd); 490 ssize_t ret = -EBADF; 491 492 if (f.file) { 493 loff_t pos = file_pos_read(f.file); 494 ret = vfs_write(f.file, buf, count, &pos); 495 file_pos_write(f.file, pos); 496 fdput(f); 497 } 498 499 return ret; 500}
vfs_write()はsys_write()と同じくfs/read_write.cにあります。
様々な処理を飛ばして、445行目からのif/else分でどのwrite()を呼ぶかが決まってますね。file構造体のファイルオペレーション構造体のwrite()へのポインタがセットされて入ればそれを、セットされていなければdo_sync_write()が使われます。
430ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos) 431{ 432 ssize_t ret; ・・・ 441 ret = rw_verify_area(WRITE, file, pos, count); 442 if (ret >= 0) { 443 count = ret; 444 file_start_write(file); 445 if (file->f_op->write) 446 ret = file->f_op->write(file, buf, count, pos); 447 else 448 ret = do_sync_write(file, buf, count, pos); 449 if (ret > 0) { 450 fsnotify_modify(file); 451 add_wchar(current, ret); 452 } ・・・ 458}
ext4を見る前に先にdo_sync_write()を見てみます。
最初に書き込むバッファとそのサイズをiovec構造体にセットし、kiocb構造体にファイルのポジション、書き込むバッファサイズをki_left、ki_nbytesにセットし、ファイルシステム固有のaio_write()を呼び出す。
ということで、ファイルシステム固有のwrite()がなければiovec、kiocbをセットしてファイルシステム固有のaio_write()を呼び出しています。
383ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos) 384{ 385 struct iovec iov = { .iov_base = (void __user *)buf, .iov_len = len }; 386 struct kiocb kiocb; 387 ssize_t ret; 388 389 init_sync_kiocb(&kiocb, filp); 390 kiocb.ki_pos = *ppos; 391 kiocb.ki_left = len; 392 kiocb.ki_nbytes = len; 393 394 ret = filp->f_op->aio_write(&kiocb, &iov, 1, kiocb.ki_pos); 395 if (-EIOCBQUEUED == ret) 396 ret = wait_on_sync_kiocb(&kiocb); 397 *ppos = kiocb.ki_pos; 398 return ret; 399} 400
ext4はどこでファイルオペレーション構造体を設定しているかというとfs/ext4/file.cの最後のほうです。
write()はdo_sync_writeを設定しているのでext4固有ではなく先ほど見たdo_sync_write()ですね。ext4はwrite()に関しては固有の処理は無く、aio_write(ext4_file_write)のほうで処理が行われます。
625 626const struct file_operations ext4_file_operations = { 627 .llseek = ext4_llseek, 628 .read = do_sync_read, 629 .write = do_sync_write, 630 .aio_read = generic_file_aio_read, 631 .aio_write = ext4_file_write, 632 .unlocked_ioctl = ext4_ioctl, 633#ifdef CONFIG_COMPAT 634 .compat_ioctl = ext4_compat_ioctl, 635#endif 636 .mmap = ext4_file_mmap, 637 .open = ext4_file_open, 638 .release = ext4_release_file, 639 .fsync = ext4_sync_file, 640 .splice_read = generic_file_splice_read, 641 .splice_write = generic_file_splice_write, 642 .fallocate = ext4_fallocate, 643};
余談としてf_opはどこで設定されているのかというとdo_dentry_open()っぽいですね。
inode構造体のi_fopがファイル構造体のf_opにセットされます。
664static int do_dentry_open(struct file *f, 665 int (*open)(struct inode *, struct file *), 666 const struct cred *cred) ・・・ 696 f->f_op = fops_get(inode->i_fop);
inode構造体のi_fopにext4_file_operationsを設定しているのは2ヶ所で1つはext4_create()、
2235static int ext4_create(struct inode *dir, struct dentry *dentry, umode_t mode, 2236 bool excl) 2237{ 2238 handle_t *handle; ・・・ 2250 err = PTR_ERR(inode); 2251 if (!IS_ERR(inode)) { 2252 inode->i_op = &ext4_file_inode_operations; 2253 inode->i_fop = &ext4_file_operations; ・・・ 2264}
もう一つはext4_iget()です。
4134struct inode *ext4_iget(struct super_block *sb, unsigned long ino) ・・・ 4320 if (S_ISREG(inode->i_mode)) { 4321 inode->i_op = &ext4_file_inode_operations; 4322 inode->i_fop = &ext4_file_operations; 4323 ext4_set_aops(inode); 4324 } else if (S_ISDIR(inode->i_mode)) { ・・・
ではwriteに戻ってext4のext4_file_write()はというとext4_file_writeです。fs/ext4/file.cにあります。
この関数の193~196行めのとこにあるif文でファイルがO_DIRECT付きで開かれたかどうかで処理が変わります。通常のファイルならgeneric_file_aio_write()を使うことが主だと思いますが。
167static ssize_t 168ext4_file_write(struct kiocb *iocb, const struct iovec *iov, 169 unsigned long nr_segs, loff_t pos) 170{ 171 struct inode *inode = file_inode(iocb->ki_filp); 172 ssize_t ret; 173 ・・・ 193 if (unlikely(iocb->ki_filp->f_flags & O_DIRECT)) 194 ret = ext4_file_dio_write(iocb, iov, nr_segs, pos); 195 else 196 ret = generic_file_aio_write(iocb, iov, nr_segs, pos); 197 198 return ret; 199}
generic_file_aio_write()はmm/filemap.cにあり、そこからさらに同ファイルの__generic_file_aio_write()を呼びとなっていきますが、大体の流れはこのような感じでext4の場合は書き込むバッファはiovecにセットし、f_op->write()を使う場合でも実際の処理としてはf_op->aio_write()と同じとなっていますね。