struct files_struct {
大多數情況, 避免動
/* count為文件表files_struct的引用計數 */
atomic_t count;
/* 文件描述符表 */
/*
為什么有兩個fdtable呢?這是內核的一種優化策略。fdt為指針, 而fdtab為普通變量。一般情況下,
fdt是指向fdtab的, 當需要它的時候, 才會真正動態申請內存。因為默認大小的文件表足以應付大多數
情況, 因此這樣就可以避免頻繁的內存申請。
這也是內核的常用技巧之一。在創建時, 使用普通的變量或者數組, 然后讓指針指向它, 作為默認情況使
用。只有當進程使用量超過默認值時, 才會動態申請內存。
*//*
struct fdtable __rcu *fdt;
struct fdtable fdtab;
* written part on a separate cache line in SMP
*/
/* 使用____cacheline_aligned_in_smp可以保證file_lock是以cache
line 對齊的, 避免了false sharing */
spinlock_t file_lock ____cacheline_aligned_in_smp;
/* 用於查找下一個空閑的fd */
int next_fd;
/* 保存執行exec需要關閉的文件描述符的位圖 */
struct embedded_fd_set close_on_exec_init;
/* 保存打開的文件描述符的位圖 */
struct embedded_fd_set open_fds_init;
/* fd_array為一個固定大小的file結構數組。struct file是內核用於文
件管理的結構。這里使用默認大小的數組, 就是為了可以涵蓋
態分配 */
struct file __rcu * fd_array[NR_OPEN_DEFAULT];
};
struct file
- {
struct list_head f_list; /*所有打開的文件形成一個鏈表*/
struct dentry *f_dentry; /*指向相關目錄項的指針*/
struct vfsmount *f_vfsmnt; /*指向VFS安裝點的指針*/
struct file_operations *f_op; /*指向文件操作表的指針*/
mode_t f_mode; /*文件的打開模式*/
loff_t f_pos; /*文件的當前位置*/
unsigned short f_flags; /*打開文件時所指定的標志*/
unsigned short f_count; /*使用該結構的進程數*/
unsigned long f_reada, f_ramax, f_raend, f_ralen, f_rawin;
/*預讀標志、要預讀的最多頁面數、上次預讀后的文件指針、預讀的字節數以及預讀的頁面數*/
int f_owner; /* 通過信號進行異步I/O數據的傳送*/
unsigned int f_uid, f_gid; /*用戶的UID和GID*/
int f_error; /*網絡寫操作的錯誤碼*/
unsigned long f_version; /*版本號*/
void *private_data; /* tty驅動程序所需 */
};
內核中,對應於每個進程都有一個文件描述符表,表示這個進程打開的所有文件。文件描述表中每一項都是一個指針,指向一個用於 描述打開的文件的數據塊———file對象,file對象中描述了文件的打開模式,讀寫位置等重要信息,當進程打開一個文件時,內核就會創建一個新的 file對象。需要注意的是,file對象不是專屬於某個進程的,不同進程的文件描述符表中的指針可以指向相同的file對象,從而共享這個打開的文件。 file對象有引用計數,記錄了引用這個對象的文件描述符個數,只有當引用計數為0時,內核才銷毀file對象,因此某個進程關閉文件,不影響與之共享同 一個file對象的進程.
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
long do_sys_open(int dfd, const char __user *filename, int flags, int mode)
{
struct open_flags op;
/* flags為用戶層傳遞的參數, 內核會對flags進行合法性檢查, 並根據mode生成新的flags值賦給 lookup */
int lookup = build_open_flags(flags, mode, &op);
/* 將用戶空間的文件名參數復制到內核空間 */
char *tmp = getname(filename);
int fd = PTR_ERR(tmp);
if (!IS_ERR(tmp)) {
/* 未出錯則申請
新的文件描述符 */
fd = get_unused_fd_flags(flags);
if (fd >= 0) {
/* 申請新的文件管理結構file */struct file *f = do_filp_open(dfd, tmp, &op, lookup);
if (IS_ERR(f)) {
put_unused_fd(fd);
fd = PTR_ERR(f);
} else {
/* 產生文件打開的通知事件 */
fsnotify_open(f);
/* 將文件描述符fd與文件管理結構file對應起來, 即安裝 */
fd_install(fd, f);
}
}
putname(tmp);
}
return fd;
}
int alloc_fd(unsigned start, unsigned flags)
{
struct files_struct *files = current->files;//獲取當前進程的對應包含文件描述符表的結構
unsigned int fd;
int error;
struct fdtable *fdt;
/* files為進程的文件表, 下面需要更改文件表, 所以需要先鎖文件表 */
spin_lock(&files->file_lock);
repeat:
/* 得到文件描述符表 */
fdt = files_fdtable(files);
/* 從start開始, 查找未用的文件描述符。在打開文件時, start為0 */
fd = start;
/* files->next_fd為上一次成功找到的fd的下一個描述符。使用next_fd, 可以快速找到未用的文件描述符;*/
if (fd < files->next_fd)
fd = files->next_fd;
/*
當小於當前文件表支持的最大文件描述符個數時, 利用位圖找到未用的文件描述符。
如果大於max_fds怎么辦呢?如果大於當前支持的最大文件描述符, 那它肯定是未
用的, 就不需要用位圖來確認了。
*/
if (fd < fdt->max_fds)
fd = find_next_zero_bit(fdt->open_fds->fds_bits,
fdt->max_fds, fd);
/* expand_files用於在必要時擴展文件表。何時是必要的時候呢?比如當前文件描述符已經超過了當
前文件表支持的最大值的時候。 */
error = expand_files(files, fd);
if (error < 0)
goto out;
/*
* If we needed to expand the fs array we
* might have blocked - try again.
*/
if (error)
goto repeat;
/* 只有在start小於next_fd時, 才需要更新next_fd, 以盡量保證文件描述符的連續性。*/
if (start <= files->next_fd)
files->next_fd = fd + 1;
/* 將打開文件位圖open_fds對應fd的位置置位 */
FD_SET(fd, fdt->open_fds);
/* 根據flags是否設置了O_CLOEXEC, 設置或清除fdt->close_on_exec */
if (flags & O_CLOEXEC)
FD_SET(fd, fdt->close_on_exec);
else
FD_CLR(fd, fdt->close_on_exec);
error = fd;
#if 1
/* Sanity check */
if (rcu_dereference_raw(fdt->fd[fd]) != NULL) {
printk(KERN_WARNING "alloc_fd: slot %d not NULL!\n", fd);
rcu_assign_pointer(fdt->fd[fd], NULL);
}
#endif
out:
spin_unlock(&files->file_lock);
return error;
}
void fd_install(unsigned int fd, struct file *file)
{
struct files_struct *files = current->files;//獲得進程文件表(包含文件描述符表)
struct fdtable *fdt;
spin_lock(&files->file_lock);
/* 得到文件描述符表 */
fdt = files_fdtable(files);
BUG_ON(fdt->fd[fd] != NULL);
/*
將文件描述符表中的file類型的指針數組中對應fd的項指向file。
這樣文件描述符fd與file就建立了對應關系
*/
rcu_assign_pointer(fdt->fd[fd], file);
spin_unlock(&files->file_lock);
}
SYSCALL_DEFINE1(close, unsigned int, fd)
{
struct file * filp;
/* 得到當前進程的文件表 */
struct files_struct *files = current->files;
struct fdtable *fdt;
int retval;
spin_lock(&files->file_lock);
/* 通過文件表, 取得文件描述符表 */
fdt = files_fdtable(files);
/* 參數fd大於文件描述符表記錄的最大描述符, 那么它一定是非法的描述符 */
if (fd >= fdt->max_fds)
goto out_unlock;
/* 利用fd作為索引, 得到file結構指針 */
filp = fdt->fd[fd];
/*
檢查filp是否為NULL。正常情況下, filp一定不為NULL。
*/
if (!filp)
goto out_unlock;
/* 將對應的filp置為0*/
rcu_assign_pointer(fdt->fd[fd], NULL);
/* 清除fd在close_on_exec位圖中的位 */
FD_CLR(fd, fdt->close_on_exec);
/* 釋放該fd, 或者說將其置為unused。*/
__put_unused_fd(files, fd);
spin_unlock(&files->file_lock);
/* 關閉file結構 */
retval = filp_close(filp, files); //這里將引用計數
/* can't restart close syscall because file table entry was cleared */
if (unlikely(retval == -ERESTARTSYS ||
retval == -ERESTARTNOINTR ||
retval == -ERESTARTNOHAND ||
retval == -ERESTART_RESTARTBLOCK))
retval = -EINTR;
return retval;
out_unlock:
spin_unlock(&files->file_lock);
return -EBADF;
}
EXPORT_SYMBOL(sys_close);
static void __put_unused_fd(struct files_struct *files, unsigned int fd)
{
/* 取得文件描述符表 */
struct fdtable *fdt = files_fdtable(files);
/* 清除fd在open_fds位圖的位 */
__FD_CLR(fd, fdt->open_fds);
/* 如果fd小於next_fd, 重置next_fd為釋放的fd */
if (fd < files->next_fd)
files->next_fd = fd;
}

每個file
結構體都指向一個file_operations
結構體,這個結構體的成員都是函數指針,指向實現各種文件操作的內核函數。比如在用戶程序中read
一個文件描述符,read
通過系統調用進入內核,然后找到這個文件描述符所指向的file
結構體,找到file
結構體所指向的file_operations
結構體,調用它的read
成員所指向的內核函數以完成用戶請求。在用戶程序中調用lseek
、read
、write
、ioctl
、open
等函數,最終都由內核調用file_operations
的各成員所指向的內核函數完成用戶請求。file_operations
結構體中的release
成員用於完成用戶程序的close
請求,之所以叫release
而不叫close
是因為它不一定真的關閉文件,而是減少引用計數,只有引用計數減到0才關閉文件。對於同一個文件系統上打開的常規文件來說,read
、write
等文件操作的步驟和方法應該是一樣的,調用的函數應該是相同的,所以圖中的三個打開文件的file
結構體指向同一個file_operations
結構體。如果打開一個字符設備文件,那么它的read
、write
操作肯定和常規文件不一樣,不是讀寫磁盤的數據塊而是讀寫硬件設備,所以file
結構體應該指向不同的file_operations
結構體,其中的各種文件操作函數由該設備的驅動程序實現。
每個file
結構體都有一個指向dentry
結構體的指針,“dentry”是directory entry(目錄項)的縮寫。我們傳給open
、stat
等函數的參數的是一個路徑,例如/home/akaedu/a
,需要根據路徑找到文件的inode。為了減少讀盤次數,內核緩存了目錄的樹狀結構,稱為dentry cache,其中每個節點是一個dentry
結構體,只要沿着路徑各部分的dentry搜索即可,從根目錄/
找到home
目錄,然后找到akaedu
目錄,然后找到文件a
。dentry cache只保存最近訪問過的目錄項,如果要找的目錄項在cache中沒有,就要從磁盤讀到內存中。
每個dentry
結構體都有一個指針指向inode
結構體。inode
結構體保存着從磁盤inode讀上來的信息。在上圖的例子中,有兩個dentry,分別表示/home/akaedu/a
和/home/akaedu/b
,它們都指向同一個inode,說明這兩個文件互為硬鏈接。inode
結構體中保存着從磁盤分區的inode讀上來信息,例如所有者、文件大小、文件類型和權限位等。每個inode
結構體都有一個指向inode_operations
結構體的指針,后者也是一組函數指針指向一些完成文件目錄操作的內核函數。和file_operations
不同,inode_operations
所指向的不是針對某一個文件進行操作的函數,而是影響文件和目錄布局的函數,例如添加刪除文件和目錄、跟蹤符號鏈接等等,屬於同一文件系統的各inode
結構體可以指向同一個inode_operations
結構體。
inode
結構體有一個指向super_block
結構體的指針。super_block
結構體保存着從磁盤分區的超級塊讀上來的信息,例如文件系統類型、塊大小等。super_block
結構體的s_root
成員是一個指向dentry
的指針,表示這個文件系統的根目錄被mount
到哪里,在上圖的例子中這個分區被mount
到/home
目錄下。
file
、dentry
、inode
、super_block
這 幾個結構體組成了VFS的核心概念。對於ext2文件系統來說,在磁盤存儲布局上也有inode和超級塊的概念,所以很容易和VFS中的概念建立對應關 系。而另外一些文件系統格式來自非UNIX系統(例如Windows的FAT32、NTFS),可能沒有inode或超級塊這樣的概念,但為了能mount
到Linux系統,也只好在驅動程序中硬湊一下,在Linux下看FAT32和NTFS分區會發現權限位是錯的,所有文件都是rwxrwxrwx
,因為它們本來就沒有inode和權限位的概念,這是硬湊出來的
static const struct file_operations socket_file_ops = {
.owner = THIS_MODULE,
.llseek = no_llseek,
.aio_read = sock_aio_read,
.aio_write = sock_aio_write,
.poll = sock_poll,
.unlocked_ioctl = sock_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_sock_ioctl,
#endif
.mmap = sock_mmap,
.open = sock_no_open, /* special open code to disallow open via /proc */
.release = sock_close,
.fasync = sock_fasync,
.sendpage = sock_sendpage,
.splice_write = generic_splice_sendpage,
.splice_read = sock_splice_read,
};
file = alloc_file(&path, FMODE_READ | FMODE_WRITE,
&socket_file_ops);
struct file *alloc_file(struct path *path, fmode_t mode,
const struct file_operations *fop)
{
struct file *file;
/* 申請一個file */
file = get_empty_filp();
if (!file)
return NULL;
file->f_path = *path;
file->f_mapping = path->dentry->d_inode->i_mapping;
file->f_mode = mode;
/* 將自定義的文件操作函數指針結構體賦給file->f_op */
file->f_op = fop;
……
}

SYSCALL_DEFINE3(read, unsigned int, fd, char __user *, buf, size_t, count)
{
struct file *file;
ssize_t ret = -EBADF;
int fput_needed;
/* 通過文件描述符fd得到管理結構file */
file = fget_light(fd, &fput_needed);
if (file) {
/* 得到文件的當前偏移量 */
loff_t pos = file_pos_read(file);
/* 利用vfs進行真正的read */
ret = vfs_read(file, buf, count, &pos);
/* 更新文件偏移量 */
file_pos_write(file, pos);
/* 歸還管理結構file, 如有必要, 就進行引用計數操作*/
fput_light(file, fput_needed);
}
return ret;
}
ssize_t vfs_read(struct file *file, char __user *buf, size_t count, loff_t *pos)
{
ssize_t ret;
/* 檢查文件是否為讀取打開 */
if (!(file->f_mode & FMODE_READ))
return -EBADF;
/* 檢查文件是否支持讀取操作 */
if (!file->f_op || (!file->f_op->read && !file->f_op->aio_read))
return -EINVAL;
/* 檢查用戶傳遞的參數buf的地址是否可寫 */
if (unlikely(!access_ok(VERIFY_WRITE, buf, count)))
return -EFAULT;
/* 檢查要讀取的文件范圍實際可讀取的字節數 */
ret = rw_verify_area(READ, file, pos, count);
if (ret >= 0) {
/* 根據上面的結構, 調整要讀取的字節數 */
count = ret;
/*
如果定義read操作, 則執行定義的read操作
如果沒有定義read操作, 則調用do_sync_read—其利用異步aio_read來完成同步的read操作。
*/
if (file->f_op->read)
ret = file->f_op->read(file, buf, count, pos);
else
ret = do_sync_read(file, buf, count, pos);
if (ret > 0) {
/* 讀取了一定的字節數, 進行通知操作 */
fsnotify_access(file);
/* 增加進程讀取字節的統計計數 */
add_rchar(current, ret);
}
/* 增加進程系統調用的統計計數 */
inc_syscr(current);
}
return ret;
}
int udp_recvmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg,
size_t len, int noblock, int flags, int *addr_len)
……
ulen = skb->len - sizeof(struct udphdr);
copied = len;
if (copied > ulen)
copied = ulen;
……
mutex_lock(&inode->i_mutex);//加鎖
blk_start_plug(&plug);
ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);//發現文件是追加打開,直接從inode讀取最新文件大小作為偏移量
mutex_unlock(&inode->i_mutex); //解鎖
if (file->f_flags & O_APPEND)
*pos = i_size_read(inode);
int dup(int oldfd);
int dup2(int oldfd, int newfd);
SYSCALL_DEFINE1(dup, unsigned int, fildes)
{
int ret = -EBADF;
/* 必須先得到文件管理結構file, 同時也是對描述符fildes的檢查 */
struct file *file = fget_raw(fildes);
if (file) {
/* 得到一個未使用的文件描述符 */
ret = get_unused_fd();
if (ret >= 0) {
/* 將文件描述符與file指針關聯起來 */
fd_install(ret, file);
}
else
fput(file);
}
return ret;
}
void fd_install(unsigned int fd, struct file *file)
{
struct files_struct *files = current->files;
struct fdtable *fdt;
/* 對文件表進行保護 */
spin_lock(&files->file_lock);
/* 得到文件表 */
fdt = files_fdtable(files);
BUG_ON(fdt->fd[fd] != NULL);
/* 讓文件表中fd對應的指針等於該文件關聯結構file */
rcu_assign_pointer(fdt->fd[fd], file);
spin_unlock(&files->file_lock);
}
SYSCALL_DEFINE2(dup2, unsigned int, oldfd, unsigned int, newfd)
{
/* 如果oldfd與newfd相等, 這是一種特殊的情況 */
if (unlikely(newfd == oldfd)) { /* corner case */
struct files_struct *files = current->files;
int retval = oldfd;
/*
檢查oldfd的合法性, 如果是合法的fd, 則直接返回oldfd的值;
如果是不合法的, 則返回EBADF
*/
rcu_read_lock();
if (!fcheck_files(files, oldfd))
retval = -EBADF;
rcu_read_unlock();
return retval;
}
/* 如果oldfd與newfd不同, 則利用sys_dup3來實現dup2 */
return sys_dup3(oldfd, newfd, 0);}
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* inode number */
mode_t st_mode; /* protection */
nlink_t st_nlink; /* number of hard links */
uid_t st_uid; /* user ID of owner */
gid_t st_gid; /* group ID of owner */
dev_t st_rdev; /* device ID (if special file) */
off_t st_size; /* total size, in bytes */
blksize_t st_blksize; /* blocksize for file system I/O */
blkcnt_t st_blocks; /* number of 512B blocks allocated */
time_t st_atime; /* time of last access */
time_t st_atime; /* time of last access */
time_t st_mtime; /* time of last modification */
time_t st_ctime; /* time of last status change */
};
SYSCALL_DEFINE2(stat, const char __user *, filename,
struct __old_kernel_stat __user *, statbuf)
{struct kstat stat;
int error;
/* vfs_stat用於讀取文件元數據至stat */
error = vfs_stat(filename, &stat);
if (error)
return error;
/* 這里僅是從內核的元數據結構stat復制到用戶層的數據結構statbuf中 */
return cp_old_stat(&stat, statbuf);
}
int vfs_getattr(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
struct inode *inode = dentry->d_inode;
int retval;
/* 對獲取inode屬性操作進行安全性檢查 */
retval = security_inode_getattr(mnt, dentry);
if (retval)
return retval;
/* 如果該文件系統定義了這個inode的自定義操作函數, 就執行它 */
if (inode->i_op->getattr)
return inode->i_op->getattr(mnt, dentry, stat);
/* 如果文件系統沒有定義inode的操作函數, 則執行通用的函數 */
generic_fillattr(inode, stat);
return 0;
}
void generic_fillattr(struct inode *inode, struct kstat *stat)
{
stat->dev = inode->i_sb->s_dev;
stat->ino = inode->i_ino;
stat->mode = inode->i_mode;
stat->nlink = inode->i_nlink;
stat->uid = inode->i_uid;
stat->gid = inode->i_gid;
stat->rdev = inode->i_rdev;
stat->size = i_size_read(inode);
stat->atime = inode->i_atime;
stat->mtime = inode->i_mtime;
stat->ctime = inode->i_ctime;
stat->blksize = (1 << inode->i_blkbits);
stat->blocks = inode->i_blocks;
}