Linux存储栈(一)

系统调用

这里推荐《Linux系统编程》,里面有非常具体的参数介绍和注意事项。

最常用的有以下几个。

write,open,close

open字面意思为打开文件,文件加载到内存中以文件描述符号的形式存在,通过文件描述符这个中间人完成所有对文件的操作,文件描述符在内存中代表文件,通过操作文件描述符间接操作文件。

malloc,mmap

malloc用来进行用户空间的内存申请。mmap可以将文件从offset的位置开始长度为length的一个块映射到内存区域,程序可以通过访问内存的方式访问文件。因为他们都建立了内存的实际映射,用户空间的地址都是虚拟地址,虚拟地址和物理地址的转换的时间不可避免,但与read/write相比,使用mmap的方式对文件进行访问,带来的一个显著好处就是可以减少一次用户空间到内核空间的复制,在某些场景下,如访问音频、视频等大文件,可以带来性能的提升。

文件系统

Linux中文件的概念并不局限于普通的磁盘文件,而是指由字节序列构成的信息载体,I/O设备、socket等也被包括在内。因为有了文件的存在,所以需要衍生出文件系统去进行组织和管理文件,而为了支持各种各样不同的文件系统,所以有了虚拟文件系统的出现。

虚拟文件系统屏蔽了不同文件系统的差异,提供统一的接口。用户空间可以直接使用函数调用而不需要考虑文件系统的实现差异。

文件系统的抽象概念:文件(file),索引节点(inode),目录项(dentry),安装节点(mount point)

准确的说文件+索引节点才是真正的文件,为什么这么说,因为文件本身的数据记录在文件file,而相关信息如(访问权限,大小,拥有者,创建时间)这些信息记录在索引节点inode,数据和控制信息分开存放

超级块

所有文件系统都需要实现超级块对象,该对象存储文件系统的信息。而文件系统会在系统初始化时就构建好,mkfs制作文件系统的,第一步就是构建超级块,超级块记录了文件系统的原始的重要信息。

文件,目录项,文件索引

索引节点(inode)本质上是用来存储文件,是作为存储文件的基本单位,其结构体中自然包含文件的相关信息(访问权限,大小,创建时间等)。目录项是用来索引文件。文件加载到内存中一般是以文件描述符号的形式存在,通过操作文件描述符间接操作文件,而不是直接操作文件。

内核API定义为

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iopoll)(struct kiocb *kiocb, bool spin);
int (*iterate) (struct file *, struct dir_context *);
int (*iterate_shared) (struct file *, struct dir_context *);
__poll_t (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
unsigned long mmap_supported_flags;
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *file, int mode, loff_t offset,
loff_t len);
void (*show_fdinfo)(struct seq_file *m, struct file *f);
#ifndef CONFIG_MMU
unsigned (*mmap_capabilities)(struct file *);
#endif
ssize_t (*copy_file_range)(struct file *, loff_t, struct file *,
loff_t, size_t, unsigned int);
loff_t (*remap_file_range)(struct file *file_in, loff_t pos_in,
struct file *file_out, loff_t pos_out,
loff_t len, unsigned int remap_flags);
int (*fadvise)(struct file *, loff_t, loff_t, int);
bool may_pollfree;
} __randomize_layout;

inode数据结构的定义在<linux/fs.h>,一个索引节点代表一个文件(仅当索引节点仅当文件被访问时,才会在内存创建),万物皆文件,当然也可以代表pipe,cdev,bdev

1
2
3
4
5
union {
struct pipe_inode_info *i_pipe;
struct block_device *i_bdev;
struct cdev *i_cdev;
};

对于索引节点的操作,都是控制信息类,在Xv6内核实验中就有一个实现symlink,涉及对inode中信息的判断,为了添加链接,需要判断类型,确保链接到文件上,而不是目录。以及连接不能成环。

目录项本质上也是文件,那么目录+索引节点,为了方便查找查找,其数据结构体定义在<linux/dcache.h>,如果遍历时再读入并解析,会浪费时间,所以一般都将目录项对象缓存在目录项缓存(dcache).

与进程相关的数据结构

file_struct,定义在<linux/fdtable.h>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* Open file table structure
*/
struct files_struct {
/*
* read mostly part
*/
atomic_t count;
bool resize_in_progress;
wait_queue_head_t resize_wait;
struct fdtable __rcu *fdt;
struct fdtable fdtab;
/*
* written part on a separate cache line in SMP
*/
spinlock_t file_lock ____cacheline_aligned_in_smp;
unsigned int next_fd;
unsigned long close_on_exec_init[1];
unsigned long open_fds_init[1];
unsigned long full_fds_bits_init[1];
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};

这里面有一个成员 file 里面存储了所有代开文件对象。