转载:https://blog.csdn.net/u010325193/article/details/90495544
mmap与read/write操作文件的区别
基础知识: 磁盘,文件系统,内存。
每个进程中都有一个 user 文件描述符表,指向一个全局的文件表,
文件表表项有一个指向内存inode的指针,每个inode唯一标识一个文件。
struct task_struct {
struct files_struct *files; // 指向一个全局的文件表
... ...
};
// Open file table structure
struct files_struct {
// read mostly part
atomic_t count;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
// written part on a separate cache line in SMP
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT]; // 【 file结构体 】
};
struct file {
union {
struct llist_node fu_llist;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;
#define f_dentry f_path.dentry
struct inode *f_inode; /* cached value */ //【 inode 】
const struct file_operations *f_op;
/*
* Protects f_ep_links, f_flags.
* Must not be taken from IRQ context.
*/
spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
struct mutex f_pos_lock;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
struct list_head f_tfile_llink;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
} __attribute__((aligned(4))); /* lest something weird decides that 2 is OK */
struct fdtable {
unsigned int max_fds;
struct file __rcu **fd; /* current fd array */
unsigned long *close_on_exec;
unsigned long *open_fds;
struct rcu_head rcu;
};
如果同时有多个进程打开同一文件,它们的 user 文件描述符表项指向不同的文件表项,
但这些文件表项会指向同一个inode。
内核会为每个文件单独维护一个 page cache
用户进程对于文件的大多数读写操作会直接作用到 page cache 上,
内核会选择在适当的时候将 page cache 中的内容写到磁盘上
(当然可以手工 fsync 控制回写),这样可以大大减少磁盘的访问次数,从而提高性能。
page cache中保存用户进程访问过的该文件的内容,这些内容以页为单位保存在内存中,
当用户需要访问文件中的某个偏移量上的数据时,内核会以偏移量为索引,找到相应的内存页,
如果该页没有读入内存,则需要访问磁盘读取数据。
为了提高页的查询速度,同时节省 page cache 数据结构占用的内存,
linux内核用树来保存 page cache 中的页。
mmap和read/write的区别:
read/write系统调用:
访问文件,涉及到用户态到内核态的转换
读取硬盘文件中的对应数据
内核会采用预读的方式,
比如需要访问100字节,内核实际会按照4KB(内存页大小)存储在page cache中
将 read 指定的数据,从 page cache 中拷贝到用户缓冲区中
mmap系统调用:
将硬盘文件映射到用户内存中,
将page cache中的页直接映射到用户进程地址空间中,
进程直接访问自身地址空间的虚拟地址来访问page cache中的页,
这样会并涉及 page cache 到用户缓冲区之间的拷贝,
mmap系统调用与read/write调用的区别:
mmap只需要一次系统调用,后续操作不需要系统调用
访问的数据不需要在 page cache 和用户缓冲区之间拷贝
当频繁对一个文件进行读取操作时,mmap会比read高效一些。
page cache
从磁盘文件中读取的内容都会存在 page cache 中,
但当关闭这个文件时, page cache 中内容会立马释放掉吗?
否,磁盘的读取速度比内存慢太多,
若能命中 page cache ,可以显著提升性能,后续如有对该文件的操作,系统就可以快速响应。
当然,这些文件内容也不是一直存在 page cache 中,
一般只要系统有空闲物理内存,内核都会拿来当缓存使用,
但当物理内存不够用,内存会清理出部分 page cache 应急,
所以程序对于物理内存的使用,能省则省,交给内核使用,作用很大。
write 调用只是将数据写到 page cache 中,
并将其标记为 dirty 就返回,磁盘I/O通常不会立即执行,
好处:
减少磁盘的回写次数,提供吞吐率,
不足:
机器一旦意外挂掉,page cache中的数据就会丢失。
安全性较高的程序会在每次write后,调用 fsync 立即将 page cache 中的内容回写到磁盘中。