【Linux驱动开发】Linux文件系统 Linux文件系统Linux的哲学就是“万物皆文件”我们对设备的操作也需要通过文件实现因此我们首先要学习Linux的文件系统。首先我们需要建立这样的认知Linux操作系统中我们的任何操作最终都会映射到对CPU、内存、硬盘、其他外设等硬件的操作。事实上Linux的“万物皆文件”有一个例外那就是网络设备但这里我们先不讨论。那么Linux是如何将硬件最终变成一个文件的呢那就是虚拟文件系统——VFS。VFS将各种设备抽象成文件并提供了标准化的文件操作接口的系统调用使得用户空间的程序不需要过度关注底层的细节只需要对文件进行打开、关闭、读操作、写操作等标准化操作即可操作硬件设备。系统调用file_operationsfile_operationsfile_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结构体来管理文件每个文件对应一个inodeinode结构体存储了文件基本信息对于设备文件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对应一个打开的文件对文件进行操作时可以使用文件描述符来标识当前操作的是哪个文件。