Linux文件系统

Linux的哲学就是“万物皆文件”,我们对设备的操作也需要通过文件实现,因此我们首先要学习Linux的文件系统。
首先,我们需要建立这样的认知:Linux操作系统中,我们的任何操作,最终都会映射到对CPU、内存、硬盘、其他外设等硬件的操作。
事实上,Linux的“万物皆文件”有一个例外,那就是网络设备,但这里我们先不讨论。
那么,Linux是如何将硬件最终变成一个文件的呢?那就是虚拟文件系统——VFS。VFS将各种设备抽象成文件,并提供了标准化的文件操作接口的系统调用,使得用户空间的程序不需要过度关注底层的细节,只需要对文件进行打开、关闭、读操作、写操作等标准化操作即可操作硬件设备。

系统调用

file_operations

file_operations

file_operations

用户空间程序

虚拟文件系统

特殊文件系统

文件系统

设备文件

块设备驱动

块设备

字符设备驱动

字符设备

用户空间程序通过系统调用对VFS中的文件进行操作时,VFS会通过相应调用下层的file_operations,因此,我们写驱动程序的时候,就要按照VFS的要求和标准,实现相应的file_operations供VFS调用。

文件操作

用户空间可以通过C语言提供的库函数对文件进行操作,也可以直接通过Linux提供的系统调用对文件进行操作。
这里只给出常用的函数原型,具体使用方法这里不赘述,读者可以自行搜索或查询手册,相应资料非常丰富。

系统调用

// 创建文件
int creat(const char *filename, mode_t mode);
// 打开文件
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
// 读写文件
int read(int fd, const void *buf, size_t length);
int write(int fd, const void *buf, size_t length);
// 移动读写指针
int lseek(int fd, offset_t offset, int whence);
// 关闭文件
int close(int fd);

C语言库函数

// 创建和打开文件
file *fopen(const char *path, const char *mode);
// 文件读写
int fgetc(file *stream);
int fputc(int c, file *stream);
char *fgets(char *s, int n, file *stream);
int fputs(const char *s, file *stream);
int fprintf(file *stream, const char *format, ...);
int fscanf (file *stream, const char *format, ...);
size_t fread(void *ptr, size_t size, size_t n, file *stream);
size_t fwrite (const void *ptr, size_t size, size_t n, file *stream);

设备控制

除了标准的打开、关闭、读写操作外,有些设备可能提供特殊的操作,或者支持设置设备的一些额外参数,比如一个声卡,对声卡的标准读写,就是把音乐数据传输到声卡,但声卡可能支持设置通道数据、音频处理方法等配置,这时候,就能通过设备控制接口实现。

int fcntl(int fd, int op, ...);	// Linux提供的标准命令接口,没那么灵活
// 更灵活的设备控制,更好支持设备自定义的命令,但是更复杂
int ioctl(int fd, unsigned long op, ...);  /* glibc, BSD */
int ioctl(int fd, int op, ...);            /* musl, other UNIX */

file_operations

上面我们说了,VFS通过file_operations与设备驱动进行沟通,我们写驱动的关键之一就是构造相应的file_operations。
在内核中,file_operations定义如下:

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;

file_operations有很多操作,但是我们不需要了解和实现所有的操作,对于一个简单的设备,最关键的操作就是打开、关闭、读、写、设备控制,对应被调用的成员函数是:

  • 打开:int (*open) (struct inode *, struct file *);
  • 关闭:int (*release) (struct inode *, struct file *);
  • 读:ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
  • 写:ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
  • 设备控制:long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
    在这里插入图片描述

inode 与 file 结构体

VFS使用inode结构体来管理文件,每个文件对应一个inode,inode结构体存储了文件基本信息,对于设备文件,inode也会存储文件对应的设备结构体。
以下是inode结构体的定义,对初学者来说不需要深入理解,有个概念即可:

struct inode {
	umode_t			i_mode;
	unsigned short		i_opflags;
	kuid_t			i_uid;
	kgid_t			i_gid;
	unsigned int		i_flags;

#ifdef CONFIG_FS_POSIX_ACL
	struct posix_acl	*i_acl;
	struct posix_acl	*i_default_acl;
#endif

	const struct inode_operations	*i_op;
	struct super_block	*i_sb;
	struct address_space	*i_mapping;

#ifdef CONFIG_SECURITY
	void			*i_security;
#endif

	/* Stat data, not accessed from path walking */
	unsigned long		i_ino;
	/*
	 * Filesystems may only read i_nlink directly.  They shall use the
	 * following functions for modification:
	 *
	 *    (set|clear|inc|drop)_nlink
	 *    inode_(inc|dec)_link_count
	 */
	union {
		const unsigned int i_nlink;
		unsigned int __i_nlink;
	};
	dev_t			i_rdev;
	loff_t			i_size;
	struct timespec64	i_atime;
	struct timespec64	i_mtime;
	struct timespec64	i_ctime;
	spinlock_t		i_lock;	/* i_blocks, i_bytes, maybe i_size */
	unsigned short          i_bytes;
	u8			i_blkbits;
	u8			i_write_hint;
	blkcnt_t		i_blocks;

#ifdef __NEED_I_SIZE_ORDERED
	seqcount_t		i_size_seqcount;
#endif

	/* Misc */
	unsigned long		i_state;
	struct rw_semaphore	i_rwsem;

	unsigned long		dirtied_when;	/* jiffies of first dirtying */
	unsigned long		dirtied_time_when;

	struct hlist_node	i_hash;
	struct list_head	i_io_list;	/* backing dev IO list */
#ifdef CONFIG_CGROUP_WRITEBACK
	struct bdi_writeback	*i_wb;		/* the associated cgroup wb */

	/* foreign inode detection, see wbc_detach_inode() */
	int			i_wb_frn_winner;
	u16			i_wb_frn_avg_time;
	u16			i_wb_frn_history;
#endif
	struct list_head	i_lru;		/* inode LRU list */
	struct list_head	i_sb_list;
	struct list_head	i_wb_list;	/* backing dev writeback list */
	union {
		struct hlist_head	i_dentry;
		struct rcu_head		i_rcu;
	};
	atomic64_t		i_version;
	atomic64_t		i_sequence; /* see futex */
	atomic_t		i_count;
	atomic_t		i_dio_count;
	atomic_t		i_writecount;
#if defined(CONFIG_IMA) || defined(CONFIG_FILE_LOCKING)
	atomic_t		i_readcount; /* struct files open RO */
#endif
	union {
		const struct file_operations	*i_fop;	/* former ->i_op->default_file_ops */
		void (*free_inode)(struct inode *);
	};
	struct file_lock_context	*i_flctx;
	struct address_space	i_data;
	struct list_head	i_devices;
	union {
		struct pipe_inode_info	*i_pipe;
		struct block_device	*i_bdev;
		struct cdev		*i_cdev;
		char			*i_link;
		unsigned		i_dir_seq;
	};

	__u32			i_generation;

#ifdef CONFIG_FSNOTIFY
	__u32			i_fsnotify_mask; /* all events this inode cares about */
	struct fsnotify_mark_connector __rcu	*i_fsnotify_marks;
#endif

#ifdef CONFIG_FS_ENCRYPTION
	struct fscrypt_info	*i_crypt_info;
#endif

#ifdef CONFIG_FS_VERITY
	struct fsverity_info	*i_verity_info;
#endif

	void			*i_private; /* fs or device private pointer */
} __randomize_layout;

而进程打开一个文件后,使用file结构体来管理进程打开的文件,file文件保存了inode信息、进程读写文件的信息(如读写位置、打开模式)。

struct file {
	union {
		struct llist_node	fu_llist;
		struct rcu_head 	fu_rcuhead;
	} f_u;
	struct path		f_path;
	struct inode		*f_inode;	/* cached value */
	const struct file_operations	*f_op;

	/*
	 * Protects f_ep_links, f_flags.
	 * Must not be taken from IRQ context.
	 */
	spinlock_t		f_lock;
	enum rw_hint		f_write_hint;
	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;
	errseq_t		f_wb_err;
} __randomize_layout
  __attribute__((aligned(4)));	/* lest something weird decides that 2 is OK */

打开文件时,进程会获得一个整数类型的文件描述符,一个文件描述符fd对应一个打开的文件,对文件进行操作时,可以使用文件描述符来标识当前操作的是哪个文件。

更多推荐