系统调用—Read


read简介

系统调用read的作用是:从与文件描述符fd相关联的文件中读取 n bytes个字节的数据,并把它放入到数据缓冲区buf中。

概要:

read函数在<unistd.h>头文件中定义。

原型是:ssize_t read(int fd, void *buf, size_t count);

说明:

read()函数尝试从文件描述符fd中读取count个字节到buf开头的缓冲区中。

如果count=0;read返回0,并且没有其他结果,如果count>SSIZE_MAX,结果未指定。

ssize_t:有符号整型,与long类似。typedef long size_t

size_t:无符号的ssize_t,:typedef unsigned long size_t

返回值

调用成功返回读取的字节数,文件指示符指到对应的位置。这个返回值可能会比count,比如以下情况:

当文件的整体字节比count小时,读到文件尾。

我们从管道或者终端读取

我们读取时被一个信号打断了等等情况。

调用失败的时候返回-1;并且errno会被设置,这时候文件的指示符位置变化与否是未知的。

**open**:打开或创建文件,返回文件描述符(File Descriptor)。

**read**:从文件描述符读取数据到缓冲区。

**write**:将缓冲区数据写入文件描述符对应的文件。


read源码

搜索sys_read:https://elixir.bootlin.com/linux/v6.8.1/source/fs/read_write.c#L627

// fs/read_write.c
// 输入参数:
// fd:文件描述符(用户进程打开的文件句柄)
// buf:用户空间缓冲区指针(数据将被读取到这里)
// count:请求读取的字节数
// 输出结果:读取到的字节数

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
return ksys_read(fd, buf, count);
}

ssize_t ksys_read(unsigned int fd, char __user *buf, size_t count)
{
// 根据 fd 获取对应的文件描述符结构,并获取引用次数
struct fd f = fdget_pos(fd);
// 初始化返回值为无效文件描述符错误
ssize_t ret = -EBADF;

if (f.file) { // 检查文件有效性
// 获取文件当前位置指针
loff_t pos, *ppos = file_ppos(f.file);
if (ppos) {
pos = *ppos; // 保存当前文件位置(文件偏移量)
ppos = &pos; // 将 ppos 指向局部变量 pos
}
// 调用虚拟文件系统(VFS)读取,成功则返回读取的字节数
ret = vfs_read(f.file, buf, count, ppos);
if (ret >= 0 && ppos)
f.file->f_pos = pos; // 读取成功后更新位置
fdput_pos(f); // 释放 fdget_pos 获取的引用计数,即释放引用
}
return ret;
}

看看struct fd,这里的fd看起来是文件的实体

struct fd {
struct file *file; // 文件指针
unsigned int flags; // 引用计数
};

那么ksys_read的传入参数中的无符号整数的fd又是什么呢,fd是进程中文件数组的索引,用于找到对应的file

img

回来继续看file_ppos,它返回的pos还不是很懂,pposposition pointer的缩写,文件的位置指针,file_ppos函数首先看文件的类型(普通文件or流式文件),如果是普通文件就返回文件位置pos,否则返回NULL

/* file_ppos returns &file->f_pos or NULL if file is stream */
static inline loff_t *file_ppos(struct file *file)
{
return file->f_mode & FMODE_STREAM ? NULL : &file->f_pos;
}

嗯,pos变量只是被声明了,并没有初始化

loff_t pos, *ppos = file_ppos(f.file);

// 等价代码
loff_t pos; // 声明一个 loff_t 类型变量 pos(未初始化)
loff_t *ppos = file_ppos(f.file); // 声明并初始化指针 ppos

顺便去看看loff_t,貌似是一个long long类型,表示 文件偏移量(file offset),用于处理大文件的读写和定位操作

typedef __kernel_loff_t		loff_t;

typedef long long __kernel_loff_t; // 64位有符号整数

那接下来就是看看读取文件内容并存放到缓存区的核心函数vfs_read

// fs/read_write.c
// 输入参数:
// file:内核文件对象指针,包含文件的元数据和操作方法。
// buf:用户空间缓冲区指针,用于存储读取到的数据。
// count:请求读取的字节数。
// pos:读取位置指针(NULL 表示从当前文件位置读取)。
// 输出结果:读取到的字节数

ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;

// 检查文件是否以可读模式打开
if (!(file->f_mode & FMODE_READ))
return -EBADF;
// 检查文件是否允许读取
if (!(file->f_mode & FMODE_CAN_READ))
return -EINVAL;
// 验证用户空间缓冲区是否合法
if (unlikely(!access_ok(buf, count)))
return -EFAULT;

// 验证读取区域是否合法
ret = rw_verify_area(READ, file, pos, count);
if (ret)
return ret; // 失败则返回错误码
if (count > MAX_RW_COUNT)
count = MAX_RW_COUNT; // 限制单次读取大小

// 调用具体读取方法
if (file->f_op->read) // 传统读取方法
ret = file->f_op->read(file, buf, count, pos);
else if (file->f_op->read_iter) // 迭代读取方法
ret = new_sync_read(file, buf, count, pos);
else // 无读取支持(文件系统未实现读取方法)
ret = -EINVAL;
// 读取后处理
if (ret > 0) {
fsnotify_access(file); // 触发文件访问通知,通知用户空间或监控程序
add_rchar(current, ret); // 统计当前进程的读取字节数
}
inc_syscr(current); // 统计当前进程的系统调用读取次数
return ret;
}

read是传统的同步读取接口,而read_iter是为了支持异步I/O和更复杂的迭代操作设计的。新的代码可能更倾向于使用read_iter,但为了兼容旧代码,内核保留了read方法。因此,在vfs_read中,优先检查是否有read实现,如果没有,再尝试read_iter。继续往下追,看看主要的read函数,这里的read就是调用了具体文件系统的read函数,以ext4为例

// /fs/ext4/file.c
static ssize_t ext4_file_read_iter(struct kiocb *iocb, struct iov_iter *to)
{
struct inode *inode = file_inode(iocb->ki_filp);

if (unlikely(ext4_forced_shutdown(inode->i_sb)))
return -EIO;

if (!iov_iter_count(to))
return 0; /* skip atime */

#ifdef CONFIG_FS_DAX
if (IS_DAX(inode))
return ext4_dax_read_iter(iocb, to);
#endif
if (iocb->ki_flags & IOCB_DIRECT)
return ext4_dio_read_iter(iocb, to);

return generic_file_read_iter(iocb, to);
}

总结一下,read系统调用干了什么事情,找到文件file的位置,调用虚拟文件系统函数vfs_read读取文件内容,vfs_read经过一系列验证文件和缓冲区的合法性,然后调用具体的文件系统实体的read函数,将磁盘文件中的数据读取到内核缓冲区,最后复制到用户空间缓冲区。


参考资料:

知乎:https://zhuanlan.zhihu.com/p/364617329(神中神,强烈推荐)