linux kernel中如何保證append寫的原子性

  1. 轉載自pagefault

先來描述一下,write系統調用的大體流程,首先內核會取得對應的文件偏移,然後調用vfs的write操作,而在vfs層的write操作的時候會調用對應文件系統的write方法,而在對應文件系統的write方法中aio_write方法,最終會調用底層驅動。這裏有一個需要注意的就是內核在寫文件的時候會加一把鎖(有些設備不會加鎖,比如塊設備以及裸設備).這樣也就是說一個文件只可能同時只有一個進程在寫。而且快設備是不支持append寫的。

而這裏append的原子操作的實現很簡單,由於每次寫文件只可能是一個進程操作(計算文件偏移並不包含在鎖裏面),而append操作是每次寫到末尾(其他類型的寫是先取得pos才進入臨界區,而這個時間內有可能pos已經被其他進程改變,而append的pos的計算是包含在鎖裏面的),因此自然append就是原子的了.

ok,接下來來看代碼。

首先來看write的系統調用,函數很簡單,就是取得當前文件的偏移,然後調用vfs的寫方法。最後更改文件的偏移。這裏要注意,取得文件偏移的方法並沒有加鎖,也就是說這裏存在競爭。

這裏有個要注意的就是POS,如果是append寫的話,後面的代碼會修改這個值,這裏先跳過,後面遇到我們會說明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
        size_t, count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;
 
    file = fget_light(fd, &fput_needed);
    if (file) {
//取得文件句柄的偏移
        loff_t pos = file_pos_read(file);
//寫文件。傳遞偏移量。
        ret = vfs_write(file, buf, count, &pos);
//更新偏移
        file_pos_write(file, pos);
        fput_light(file, fput_needed);
    }
 
    return ret;
}

接下來就是vfs_write,這個函數主要就是進行一些合法性判斷,然後調用具體文件系統的write方法,這裏要注意,write方法不一定會調用到文件系統的write方法,比如塊設備以及裸設備都會調用到blkdev_aio_write。

而file op的初始化在ext3_iget中的,也就是獲取超級塊的方法,可以看到如果是一般文件,則會被初始化爲ext3_file_inode_operations。

1
2
3
4
5
6
7
8
struct inode *ext3_iget(struct super_block *sb, unsigned long ino)
................................................................................
    if (S_ISREG(inode->i_mode)) {
//初始化
        inode->i_op = &ext3_file_inode_operations;
        inode->i_fop = &ext3_file_operations;
        ext3_set_aops(inode);
.................................................
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
.........................................................................
    ret = rw_verify_area(WRITE, file, pos, count);
    if (ret >= 0) {
        count = ret;
//調用具體的文件系統的方法。
        if (file->f_op->write)
            ret = file->f_op->write(file, buf, count, pos);
        else
            ret = do_sync_write(file, buf, count, pos);
..................................................................................
    }
 
    return ret;
}

我們主要來看ext3的實現,其他的文件系統基本差不多。下面就是ext3文件系統對應回調操作函數。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const struct file_operations ext3_file_operations = {
    .llseek     = generic_file_llseek,
//主要是下面4個
    .read       = do_sync_read,
    .write      = do_sync_write,
    .aio_read   = generic_file_aio_read,
    .aio_write  = generic_file_aio_write,
    .unlocked_ioctl = ext3_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = ext3_compat_ioctl,
#endif
    .mmap       = generic_file_mmap,
    .open       = dquot_file_open,
    .release    = ext3_release_file,
    .fsync      = ext3_sync_file,
    .splice_read    = generic_file_splice_read,
    .splice_write   = generic_file_splice_write,
};

可以看到它的write方法就是do_sync_write,在do_sync_write中會調用它自己的aio_write方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
...............................................
//如果不是塊設備才進入下面的處理
    if (!isblk) {
        /* FIXME: this is for backwards compatibility with 2.4 */
//調用i_size_read得到文件大小,從而定位append的位置。
        if (file->f_flags & O_APPEND)
                        *pos = i_size_read(inode);
 
        if (limit != RLIM_INFINITY) {
            if (*pos >= limit) {
                send_sig(SIGXFSZ, current, 0);
                return -EFBIG;
            }
            if (*count > limit - (typeof(limit))*pos) {
                *count = limit - (typeof(limit))*pos;
            }
        }
    }
...................................................
    return ret;
}

因此可以看到最關鍵的操作都是放在aio_write中,也就是generic_file_aio_write,這個函數我們可以看到在調用具體的實現__generic_file_aio_write之前會加一把鎖(i_mutex),這樣就保證了一個文件同時只會有一個進程來寫。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ssize_t generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
        unsigned long nr_segs, loff_t pos)
{
    struct file *file = iocb->ki_filp;
    struct inode *inode = file->f_mapping->host;
    ssize_t ret;
 
    BUG_ON(iocb->ki_pos != pos);
//加鎖
    mutex_lock(&inode->i_mutex);
//調用具體的實現
    ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);
//釋放鎖
    mutex_unlock(&inode->i_mutex);
 
    if (ret > 0 || ret == -EIOCBQUEUED) {
        ssize_t err;
 
        err = generic_write_sync(file, pos, ret);
        if (err < 0 && ret > 0)
            ret = err;
    }
    return ret;
}

上面可以看到先加鎖然後調用__generic_file_aio_write,而對應的blkdev_aio_write則是直接調用__generic_file_aio_write,也就是不用加鎖,下面就是內核裏面的註釋:

1
2
* It expects i_mutex to be grabbed unless we work on a block device or similar
* object which does not need locking at all.

然後來看__generic_file_aio_write的實現,這裏它調用了generic_write_checks,這個函數主要用來執行寫之前的一些檢測。

1
2
3
4
5
6
7
8
ssize_t __generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
                 unsigned long nr_segs, loff_t *ppos)
{
...................................................................
 
    err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
......................................................................
}

然後是generic_write_checks,這個函數就是做一些檢測,並且APPEND寫的原子性也是由這個函數進行控制的。

這裏會修改對應的pos,調用i_size_read來得到文件的大小,從而進行append操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
inline int generic_write_checks(struct file *file, loff_t *pos, size_t *count, int isblk)
{
    struct inode *inode = file->f_mapping->host;
    unsigned long limit = rlimit(RLIMIT_FSIZE);
 
        if (unlikely(*pos < 0))
                return -EINVAL;
 
    if (!isblk) {
        /* FIXME: this is for backwards compatibility with 2.4 */
//如果是append操作,則調用i_size_read得到文件大小,然後得到文件該寫的位置,這裏更改了pos的值.
        if (file->f_flags & O_APPEND)
//更改pos
                        *pos = i_size_read(inode);
 
        if (limit != RLIM_INFINITY) {
            if (*pos >= limit) {
                send_sig(SIGXFSZ, current, 0);
                return -EFBIG;
            }
            if (*count > limit - (typeof(limit))*pos) {
                *count = limit - (typeof(limit))*pos;
            }
        }
    }
.............................................................
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章