φ(.. )メモシテオコウ sys_write()からext4のwrite()あたりのめも

今日は「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に戻ってext4ext4_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()と同じとなっていますね。