目的:
正文目录:
1. Linux 的两大抽象
2. 文件类型
3. 文件描述符
4. 通用文件模型:简介
4.1 演示 demo
4.2 相关要点: 与 VFS 的关系
5. 通用文件模型:文件描述符和打开文件的关系
5.1 相关的内核数据结构
5.2 列举几种打开文件的情景
Linux 系统的大多数文件是普通文件或目录,但是也有另外一些文件类型,具体包括如下几种:
在 Linux,可以用 ls/stat 命令 和 stat() 系统调用确定文件类型。
$ ls -li
12587634 drwxr-xr-x 26 root root 4096 Mar 16 07:49 1.opensource
27396428 lrwxrwxrwx 1 root root 12 Nov 17 2017 Link to ssd_dvd -> /mnt/ssd_dvd
12582945 -rw-r--r-- 1 root root 665 Jul 10 18:47 minicom.log
$ stat minicom.log
File: ‘minicom.log‘
Size: 665 Blocks: 8 IO Block: 4096 regular file
Device: 822h/2082d Inode: 12582945 Links: 1
Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2020-01-09 09:44:07.101177618 +0800
Modify: 2020-07-10 18:47:20.073532673 +0800
Change: 2020-07-10 18:47:20.073532673 +0800
在 Linux 中,文件必须先打开才能访问。对于内核而言,所有打开的文件都通过文件描述符 ( file descriptor,简称fd ) 引用。文件描述符是一个非负整数。当打开一个现有文件或创建一个新文件时,内核向进程返回一个文件描述符。当读、写一个文件时,使用 open() 或 creat() 返回的文件描述符标识该文件,将其作为参数传送给 read() 或 write()。
Linux 通用文件模型最为显著的特性之一就是 I/O 通用性。也就是说,同一套系统调用 open()、read()、write()、close() 等所执行的 I/O 操作,可施之于所有文件类型,包括设备文件在内。应用程序发起的I/O请求,内核会将其转化为相应的文件系统操作,或者设备驱动程序操作,以此来执行针对目标文件或设备的I/O操作。因此,采用这些系统调用的程序能够处理任何类型的文件。
演示 demo (copy.c):
int main(int argc, char *argv[])
{
int inputFd, outputFd, openFlags;
mode_t filePerms;
ssize_t numRead;
char buf[BUF_SIZE];
if (argc != 3 || strcmp(argv[1], "--help") == 0)
usageErr("%s old-file new-file\n", argv[0]);
/* Open input and output files */
inputFd = open(argv[1], O_RDONLY);
if (inputFd == -1)
errExit("opening file %s", argv[1]);
openFlags = O_CREAT | O_WRONLY | O_TRUNC;
filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP |
S_IROTH | S_IWOTH; /* rw-rw-rw- */
outputFd = open(argv[2], openFlags, filePerms);
if (outputFd == -1)
errExit("opening file %s", argv[2]);
/* Transfer data until we encounter end of input or an error */
while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0)
if (write(outputFd, buf, numRead) != numRead)
fatal("write() returned error or partial write occurred");
if (numRead == -1)
errExit("read");
if (close(inputFd) == -1)
errExit("close input");
if (close(outputFd) == -1)
errExit("close output");
exit(EXIT_SUCCESS);
}
运行效果:
$ ./copy test test.old
$ ./copy test /dev/tty
$ ./copy /dev/tty abc.txt
相关要点:
struct inode {
umode_t i_mode;
...
const struct file_operations *i_fop;
...
}
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 *);
...
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);
...
} __randomize_layout;
每个进程在进程表 (process table) 中都有一个记录项 (process table entry),即 struct task_struct,内核用它来描述一个进程。在 struct task_struct 中包含了一张打开文件描述符表 (open file descriptors table),由 struct files_struct 里的 struct fdtable 来表示 (Linux-4.14):
struct task_struct {
...
/* Filesystem information: */
struct fs_struct *fs;
/* Open file information: */
struct files_struct *files;
-> struct fdtable *fdt;
...
}
每个文件描述符包含:
struct fdtable {
...
struct file **fd; /* current fd array */
unsigned long *close_on_exec;
...
};
内核为所有打开文件维持一张打开文件表。每个打开文件表项包含:
inode 结构体和 vnode 结构体名称虽然不同,但是 2 者其实是同一个概念,它们都用于描述存储在硬盘中的文件系统的 inode 数据。注意区别内存里的 inode 结构体对象和硬盘中的 inode 数据。
每个打开文件都有一个 inode 对象。inode 对象包含了:
struct inode {
...
/* Stat data, not accessed from path walking */
unsigned long i_ino;
...
/* former ->i_op->default_file_ops */
const struct file_operations *i_fop;
}
这些信息是在打开文件时从硬盘上读入内存的,所以,文件的所有相关信息都是随时可用的。即 inode 对象包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。
上述三张表的完整关系如下:
两个独立进程各自打开了同一文件,则有如下关系:
第一个进程在文件描述符 3 上打开该文件,而另一个进程在文件描述符 4 上打开该文件。打开该文件的每个进程都获得各自的一个打开文件表项,但对一个给定的文件只有一个 inode 节点表项。
之所以每个进程都获得自己的打开文件表项,是因为这可以使每个进程都有它自己的对该文件的当前偏移量。
dup() 用来复制一个现有的文件描述符。
$ man 2 dup
#include <unistd.h>
int dup(int oldfd);
dup(1)后的内核数据结构:
dup() 返回的新文件描述符与参数 oldfd 共享同一个打开文件表项。
假定所用的描述符是在fork之前打开的,如果父进程和子进程写同一描述符指向的文件,但又没有任何形式的同步,如使父进程等待子进程,那么它们的输出就会相互混合。
有相同爱好的可以进来一起讨论哦:企鹅群号:1046795523
学习视频资料:http://www.makeru.com.cn/course/details/2058?s=143793
原文:https://www.cnblogs.com/jinwenyi/p/13491380.html