1.7 写入文件

Linux中写入文件操作,最常用的就是write函数,其原型如下:

ssize_t write(int fd, const void *buf, size_t count);

write尝试从buf指向的地址,写入count个字节到文件描述符fd中,并返回成功写入的字节数,同时将文件偏移向前移动相同的字节数。

write有可能写入比指定count少的字节数。

1.7.1 write源码跟踪

write的源码与read的很相似,位于read_write.c中,代码如下:

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,size_t, count)

{

struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
/* 得到

file管理结构指针 */

file = fget_light(fd, &fput_needed);
if (file) {
/* 得到当前的文件偏移 */
loff_t pos = file_pos_read(file);
/* 利用

VFS写入 */

ret = vfs_write(file, buf, count, &pos);
/* 更新文件偏移量

*/

file_pos_write(file, pos);
/* 释放文件管理指针

file */

fput_light(file, fput_needed);
}
return ret;

}

进入vfs_write,代码如下:

ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)

{

ssize_t ret;
/* 检查文件是否为写入打开

*/

if (!(file->f_mode & FMODE_WRITE))
return -EBADF;
/* 检查文件是否支持打开操作

*/

if (!file->f_op || (!file->f_op->write && !file->f_op->aio_write))
return -EINVAL;
/* 检查用户给定的地址范围是否可读取*/
if (unlikely(!access_ok(VERIFY_READ, buf, count)))
return -EFAULT;
/* 验证文件从pos起始是否可以写入count个字节数并返回可以写入的字节数 */
ret = rw_verify_area(WRITE, file, pos, count);
if (ret >= 0) {
/* 更新写入字节数

*/

count = ret;
/*

如果定义write操作,则执行定义的write操作如果没有定义write操作,则调用do_sync_write—其利用异步aio_write来完成同步的write操作*/

if (file->f_op->write)
ret = file->f_op->write(file, buf, count, pos);
else
ret = do_sync_write(file, buf, count, pos);
if (ret > 0) {
/* 写入了一定的字节数,进行通知操作

*/

fsnotify_modify(file);
/* 增加进程读取字节的统计计数

*/

add_wchar(current, ret);
}
/* 增加进程系统调用的统计计数

*/

inc_syscw(current);
}
return ret;

}

write同样有部分写入的情况,这个与read类似,都是由具体实现来决定的。在此就不再深入探讨

write的部分写入的情况了。

1.7.2 追加写的实现

前面说过,文件的读写操作都是从当前文件的偏移处开始的。这个文件偏移量保存在文件表中,而每个进程都有一个文件表。那么当多个进程同时写一个文件时,即使对write进行了锁保护,在进行串行写操作时,文件依然不可避免地会被写乱。根本原因就在于文件偏移量是进程级别的。

当使用O_APPEND以追加的形式来打开文件时,每次写操作都会先定位到文件末尾,然后再执行写操作。

Linux下大多数文件系统都是调用generic_file_aio_write来实现写操作的。在generic_file_aio_write中,有如下代码:

mutex_lock(&inode->i_mutex);

blk_start_plug(&plug);

ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);

mutex_unlock(&inode->i_mutex);

这里有一个关键的语句,就是使用mutex_lock对该文件对应的inode进行保护,然后调用__generic_file_aio_write->generic_write_check。其部分代码如下:

if (file->f_flags & O_APPEND)

*pos = i_size_read(inode);

上面的代码中,如果发现文件是以追加方式打开的,则将从inode中读取到的最新文件大小作为偏移量,然后通过**__generic_file_aio_write**再进行写操作,这样就能保证写操作是在文件末尾追加的。

1.8 文件的原子读写

使用O_APPEND可以实现在文件的末尾原子追加新数据,Linux还提供pread和pwrite从指定偏移位置读取或写入数据。

它们的实现很简单,代码如下:

SYSCALL_DEFINE(pread64)(unsigned int fd, char __user *buf, size_t count, loff_t pos)

{

struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
if (pos < 0)
return -EINVAL;
file = fget_light(fd, &fput_needed);
if (file) {
ret = -ESPIPE;
if (file->f_mode & FMODE_PREAD)
ret = vfs_read(file, buf, count, &pos);
fput_light(file, fput_needed);
}
return ret;

}

看到这段代码,是不是有一种似曾相识的感觉?让我们再来回顾一下read的实现,代码如下所示。

/* 得到文件的当前偏移量 */

loff_t pos = file_pos_read(file);

/* 利用vfs进行真正的read */

ret = vfs_read(file, buf, count, &pos);

/* 更新文件偏移量 */

file_pos_write(file, pos);

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值