程序可以理解为硬盘上的普通二进制文件;进程是加载到内存中的二进制文件,除了加载到内存中的二进制文件外,还附有所有对于该二进制文件描述信息的结构体,描述该进程的结构体叫PCB(进程控制块),在这就不在讨论。对于程序与进程,也就可以简单地理解为是否有PCB(进程控制块)。下面我们再来讨论PCB与file_struct的关系。
在每一个PCB中,都有一个文件描述符表,通过文件描述符索引指向file_struct(系统打开文件表)。
文件描述符在形式上是一个非负整数,实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表,当程序打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。也就是说,一个程序能够访问文件是因为给这个程序分配了文件描述符。
下面我们来讨论file_struct里面具体有哪些内容, file结构体定义在linux系统中的(/kernels/include/linus/fs.h)文件中。
file_struct结构如下
struct file { union { struct list_head fu_list; //文件对象链表指针linux/include/linux/list.h struct rcu_head fu_rcuhead; //RCU(Read-Copy Update)是Linux 2.6内核中新的锁机制 } f_u; struct path f_path; //包含dentry和mnt两个成员,用于确定文件路径 #define f_dentry f_path.dentry //f_path的成员之一,当统的挂载根目录 const struct file_operations //*f_op; 与该文件相关联的操作函数 atomic_t f_count; //文件的引用计数(有多少进程打开该文件) unsigned int f_flags; //对应于open时指定的flag mode_t f_mode; //读写模式:open的mod_t mode参数 off_t f_pos; //该文件在当前进程中的文件偏移量 struct fown_struct f_owner; //该结构的作用是通过信号进行I/O时间通知的数据。 unsigned int f_uid, f_gid; //文件所有者id,所有者组id struct file_ra_state f_ra; //在linux/include/linux/fs.h中定义,文件预读相关 unsigned long f_version; #ifdef CONFIG_SECURITY void *f_security; #endif void *private_data; #ifdef CONFIG_EPOLL struct list_head f_ep_links; spinlock_t f_ep_lock; #endif struct address_space *f_mapping; };
其中重要参数参数介绍如下:
f_flags:表示打开文件的权限
f_pos:表示当前读写文件的位置
f_count:这个是一个相对来说比较重要的参数,表示打开文件的引用计数,如果有多个文件指针指向它,就会增加f_count的值。
f_mode:设置对文件的访问模式,例如:只读,只写等。
当然其中还定义了许多结构体等内容,这里就不在深究,下面我们来讨论一个fd与files_struct的关系。files_struct不同于file_struct。在这里要区分清楚。
file_operations
当我们打开一个文件时,操作系统为了管理所打开的文件,都会为这个文件创建一个file结构体,而file结构体中的f_op指针又指向file_operations结构体,这个结构体中的成员除了struct module* owner 其余都是函数指针,file_operation就是把系统调用和驱动程序关联起来的关键数据结构。这个结构的每一个成员都对应着一个系统调用。读取file_operation中相应的函数指针,接着把控制权转交给函数,从而完成了Linux设备驱动程序的工作。
我们先来看看file_operations结构体的实现和相关成员的介绍
struct file_operations { struct module *owner; //指向拥有该模块的指针; loff_t (*llseek) (struct file *, loff_t, int); //llseek 方法用作改变文件中的当前读/写位置, 并且新位置作为(正的)返回值. ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); //用来从设备中获取数据. 在这个位置的一个空指针导致 read 系统调用以 -EINVAL("Invalid argument") 失败. 一个非负返回值代表了成功读取的字节数( 返回值是一个 "signed size" 类型, 常常是目标平台本地的整数类型). ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); //发送数据给设备. 如果 NULL, -EINVAL 返回给调用 write 系统调用的程序. 如果非负, 返回值代表成功写的字节数. ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); //初始化一个异步读 -- 可能在函数返回前不结束的读操作. ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); //初始化设备上的一个异步写. int (*readdir) (struct file *, void *, filldir_t); //对于设备文件这个成员应当为 NULL; 它用来读取目录, 并且仅对**文件系统**有用. unsigned int (*poll) (struct file *, struct poll_table_struct *); int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 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 *); //mmap 用来请求将设备内存映射到进程的地址空间. 如果这个方法是 NULL, mmap 系统调用返回 -ENODEV. int (*open) (struct inode *, struct file *); //打开一个文件 int (*flush) (struct file *, fl_owner_t id); //flush 操作在进程关闭它的设备文件描述符的拷贝时调用; int (*release) (struct inode *, struct file *); //在文件结构被释放时引用这个操作. 如同 open, release 可以为 NULL. int (*fsync) (struct file *, struct dentry *, int datasync); //用户调用来刷新任何挂着的数据. int (*aio_fsync) (struct kiocb *, int datasync); int (*fasync) (int, struct file *, int); int (*lock) (struct file *, int, struct file_lock *); //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 **); };
文件描述符(file_struct)是操作系统用来管理文件的数据结构,当我们创建一个进程时,会创建文件描述符表,进程控制块PCB中的fs指针指向文件描述符表,当我们创建文件时,会为指向该文件的指针FILE*关联一个文件描述符并添加在文件描述符表中。在文件描述符表中fd相当于数组的索引,FILE*相当于数组的内容吗,指向一个文件结构体
————————————————
每个进程用一个files_struct结构来记录文件描述符的使用情况,这个files_struct结构称为用户打开文件表,它是进程的私有数据。
files_struct结构在include/linux/fdtable.h中定义如下:
struct files_struct { atomic_t count; /* 共享该表的进程数 */ rwlock_t file_lock; /* 保护以下的所有域,以免在tsk->alloc_lock中的嵌套*/ int max_fds; /*当前文件对象的最大数*/ int max_fdset; /*当前文件描述符的最大数*/ int next_fd; /*已分配的文件描述符加1*/ struct file ** fd; /* 指向文件对象指针数组的指针 */ fd_set *close_on_exec; /*指向执行exec( )时需要关闭的文件描述符*/ fd_set *open_fds; /*指向打开文件描述符的指针*/ fd_set close_on_exec_init;/* 执行exec( )时需要关闭的文件描述符的初 值集合*/ fd_set open_fds_init; /*文件描述符的初值集合*/ struct file * fd_array[32];/* 文件对象指针的初始化数组*/ };
/* * 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]; };
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; struct fdtable *next; };
在struct files_struct中包括一个struct fdtable变量实例和一个struct fdtable类型指针,
而struct fdtable中的成员变量close_on_exec,open_fds,fd又分别指向struct files_struct中的成员close_on_exec_init,open_fds_init和fd_array。
即一个结构中嵌套了另一个结构,被嵌套结构中的成员又反过来引用了属主的成员,这样看起来有些重复和让人迷惑。
在files_struct结构的初始化时,能更清晰的看出这种重复。如内核第一个进程(即进程init)的files_struct静态初始化:
————————————————
struct files_struct init_files = { .count = ATOMIC_INIT(1), .fdt = &init_files.fdtab, .fdtab = { .max_fds = NR_OPEN_DEFAULT, .fd = &init_files.fd_array[0], .close_on_exec = (fd_set *)&init_files.close_on_exec_init, .open_fds = (fd_set *)&init_files.open_fds_init, .rcu = RCU_HEAD_INIT, }, .file_lock = __SPIN_LOCK_UNLOCKED(init_task.file_lock), }; ————————————————
参考博文:https://blog.csdn.net/gmy2016wiw/article/details/72594093