- 轉載自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; } } } ............................................................. } |