作爲文件的使用者,進程理所當然地將要使用的文件記錄於自己的控制塊。另外,由於進程所對應的程序也是一個文件,因此進程控制塊還必須記錄這個文件的相關信息。由於操作系統要對系統所以進程提供服務,因此操作系統還要維護一個記錄所有進程打開文件的總表。
進程與其打開文件的關係
如果說文件管理系統是文件的管理者與提供者,那麼進程就是文件系統中文件的使用者。即,文件管理系統與進程之間是服務與客戶之間的關係。
文件對象
當進程通過系統調用open()打開一個文件時,該系統調用找到這個文件後,會把文件封裝到一個file結構的實例中提供給進程,這個實例稱爲file對象。
file結構的定義如下:
struct file {
union {
struct list_head fu_list; //所有打開文件的鏈表
struct rcu_head fu_rcuhead;
} f_u;
struct vfsmount *f_vfsmnt; //文件目錄的VFS安裝點指針
struct dentry *f_dentry; //文件的dentry
#define f_dentry f_path.dentry
#define f_vfsmnt f_path.mnt
const struct file_operations *f_op; //指向文件操作函數集的這孩子很
spinlock_t f_lock; /* f_ep_links, f_flags, no IRQ */
atomic_long_t f_count; //記錄訪問本文件的進程數目的計數器
unsigned int f_flags; //訪問類型
fmode_t f_mode; //訪問模式
loff_t f_pos; //文件的當前讀寫位置
struct fown_struct f_owner;
const struct cred *f_cred; //id信息
struct file_ra_state f_ra;
u64 f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
void *private_data;
#ifdef CONFIG_EPOLL
struct list_head f_ep_links;
#endif
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};
其中,指針f_cred是記錄文件所有者ID,文件所有者所在用戶組ID的。其cred的結構如下:
struct cred {
atomic_t usage;
uid_t uid; /* real UID of the task */
gid_t gid; /* real GID of the task */
uid_t suid; /* saved UID of the task */
gid_t sgid; /* saved GID of the task */
uid_t euid; /* effective UID of the task */
gid_t egid; /* effective GID of the task */
uid_t fsuid; /* UID for VFS ops */
gid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
* keys to */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
struct thread_group_cred *tgcred; /* thread-group shared credentials */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
};
這就使得一個文件可能面臨三種用戶的訪問:文件所有者、同組用戶、其他用戶。內核在處理一個進程或用戶訪問某一個文件的請求時,要根據進程的uid、fid和訪問模式來確定該進程是否具有訪問這個文件的權限。對於一個文件的用戶來說,有讀、寫和執行三種文件操作,因此形成了三種權限,這三種權限與三種用戶就共有9種組合,即文件的訪問權限可以用9個二進制來表示,並將其保存在文件的dentry中。
結構中的f_ops記錄了進程對文件讀寫位置的當前值,它可以通過調用函數llseek()進行移動。
值得注意的是,結構體中的指針f_op指向結構file_operations,該結構中封裝了對於文件進行操作函數。該結構在文件include/linux/fs.h的定義如下:
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 (*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);
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 *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
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 *);
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 **);
};
從結構中可以看到,結構中是一系列函數的指針,這裏有熟悉的read()、write()、open()、close()等函數的指針。進程就是通過調用這些函數來訪問一個文件的,所以有人說file_operations結構是Linux虛擬文件系統與進程之間的主要接口。
文件描述符
弄清楚了系統管理file對象的方法之後,下面就進一步介紹每個進程對它自己訪問的file對象的管理方法。
Linux用一個數組來管理進程打開的文件的file對象,數組中的每一個元素都存放在一個指向進程所打開的文件的file對象。既然用一個數組來存放file對象,那麼用數組的下標來訪問文件就是一個順理成章的方法,於是Linux就把數組元素的下標叫做該數組元素所對應的文件的文件描述符,該描述符就是系統對文件的標識,這個數組也叫做文件描述符數組,其示意圖如下:
內核通過調用系統調用函數dup()、dup2()和fctl()可以使數組中的多個元素指向同一個文件的file對象。也就是說,在Linux中,同一個文件可以有多個文件描述符。
進程打開文件表
前面看到,文件描述符數組中存放了一個進程所訪問的所有文件,把這個文件描述符數組和這個數組在系統中的一些動態信息組合在一起,就會形成一個新的數據結構——進程打開文件表。
進程打開文件表files_struct結構在文件include/linux/fdtable.h中的定義如下:
struct files_struct {
atomic_t count; //引用計數
struct fdtable *fdt;
struct fdtable fdtab;
spinlock_t file_lock ____cacheline_aligned_in_smp;
int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT]; //文件描述符表指針
};
顯然,這個結構應該屬於進程的數據,所以進程控制塊用指針files指向它。
struct task_struct{
...
struct files_struct *files;
...
}
進程與其打開文件之間的關係如下圖所示:
因爲進程對於的程序也是一個文件,這個文件的位置在哪呢?
進程在文件系統所在的位置叫做進程與文件系統的關係。Linux用結構fs_struct來描述進程在文件系統中的位置。結構fs_struct在文件include/linux.fs_struct.h中的定義如下:
struct fs_struct {
int users;
rwlock_t lock;
int umask;
int in_exec;
struct vfsmount *rootmnt, *pwdmnt, *altrootmnt; //與安裝有關的指針
struct dentry *root, *pwd, *altroot; //與進程位置有關的指針
};
其中,pwd指向進程的當前所在目錄;root指向進程的根目錄;altroot是進程的替換目錄。如下所示:
另外三個指針rootmnt、pwdmnt和altrootmnt則對應地指向上面三個目錄的結構vfsmount,該結構存放了目錄的一些安裝信息。
系統打開和關閉文件表
作爲文件對象的提供者,操作系統必須對已經打開和使用過後已被關閉的文件進行記錄。內核通過維護兩個雙向循環鏈表來管理這些被打開的文件和被關閉的文件:一個專門記錄打開文件,另一個專門記錄關閉文件。凡是file對象的f_count域不爲NULL的文件,都被鏈入打開系統文件鏈表;而爲NULL的文件,都被鏈入系統關閉文件鏈表。
當VFS需要一個file對象時,將調用函數get_empty_file()優先在系統關閉文件鏈表中摘取,如果鏈表中file對象的數目已經爲限制的底線NR_RESERVED_FILES,則系統就另行分配file對象所需要的內存空間。
總結起來,就是: