|
写作目的:
正文目录: 1. Linux 的两大抽象; [0 R2 K# ^7 W( y 2. 文件类型 - g7 D0 x- D: B& y5 Y- [8 _ O& H9 Q 3. 文件描述符 ( S+ s3 q; c; k6 S( c" C6 _, }$ P' c 4. 通用文件模型:简介 4.1 演示 demo X3 h& `: ^5 r3 t* i9 x5 c' |8 A 4.2 相关要点: 与 VFS 的关系, P) G2 N6 H5 g1 s 5. 通用文件模型:文件描述符和打开文件的关系 5.1 相关的内核数据结构 5.2 列举几种打开文件的情景 1. Linux 的两大抽象
Linux 系统的大多数文件是普通文件或目录,但是也有另外一些文件类型,具体包括如下几种:
在 Linux,可以用 ls/stat 命令 和 stat() 系统调用确定文件类型。 $ ls -li , c( C, D3 M( V( k- L12587634 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: l \; W$ J) @$ M3 n$ K U/ m: S 12582945 -rw-r--r-- 1 root root 665 Jul 10 18:47 minicom.log g; \% B6 u0 I$ }5 X3 r1 A , V# l& r, X+ X. s $ stat minicom.log File: 'minicom.log'/ N# J* I1 n3 ^7 M Size: 665 Blocks: 8 IO Block: 4096 regular file% m4 Q ?( y$ _- ?0 d Device: 822h/2082d Inode: 12582945 Links: 1 Access: (0644/-rw-r--r--) Uid: ( 0/ root) Gid: ( 0/ root)! u; K4 i, K, { g. S$ r 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 +08006 u$ B9 x5 G6 r4 w3 j 3. 文件描述符 在 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[])( X+ r5 ], k# U! A( l/ }{0 U* H4 z5 q F- ~ int inputFd, outputFd, openFlags; mode_t filePerms;! Y/ f G7 N- ~$ X+ i ssize_t numRead;' }5 h* I% }/ e4 Z char buf[BUF_SIZE]; & g. Y; ~" X2 t3 q) X* K if (argc != 3 || strcmp(argv[1], "--help") == 0) usageErr("%s old-file new-file\n", argv[0]); /* Open input and output files */& N' s0 ?/ c. y5 T& Q0 h inputFd = open(argv[1], O_RDONLY); if (inputFd == -1)( m8 P- S4 j- {, c5 L errExit("opening file %s", argv[1]); openFlags = O_CREAT | O_WRONLY | O_TRUNC;) H3 L+ j( ]; Q8 O' p filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH; /* rw-rw-rw- */" d2 q$ ]8 j4 Y. v* _( { n7 d outputFd = open(argv[2], openFlags, filePerms);4 D# {8 D( { y! H" t+ U: u: @ if (outputFd == -1)1 w) T/ T/ _7 {4 X) n errExit("opening file %s", argv[2]);8 T6 x: P0 O' L6 C /* Transfer data until we encounter end of input or an error */; K' E# q+ M/ C9 r @# g) p 8 k" [7 P, s/ R2 \# _8 T7 |4 ? while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0) if (write(outputFd, buf, numRead) != numRead)* G, `: k D" P: e U# x fatal("write() returned error or partial write occurred"); if (numRead == -1) errExit("read");/ i2 x9 Z% |/ c) b1 S3 K if (close(inputFd) == -1) errExit("close input");; ~7 x9 \. d/ N9 K$ N6 ] if (close(outputFd) == -1)3 ]7 r- ~9 B- d* W8 ^' p" g errExit("close output"); exit(EXIT_SUCCESS);6 G6 \% k; @$ d2 O: k% G2 P } 运行效果: $ ./copy test test.old% e% L' c5 I: P6 Q6 t [+ z$ ./copy test /dev/tty $ ./copy /dev/tty abc.txt8 {: m. l) ]2 b2 y3 E* r* Z 相关要点:
umode_t i_mode;) s# @. l: w* A% \- M ... const struct file_operations *i_fop;& ], w6 o; \' J .../ J$ s- w1 d0 O C* o; @ } struct file_operations { struct module *owner; loff_t (*llseek) (struct file *, loff_t, int); N/ u# S3 t& s8 k2 A ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);! V7 p, |( V% w9 R 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);5 q4 o% u3 }' w! g( B int (*mmap) (struct file *, struct vm_area_struct *);/ a( s! Z4 y: |, f. V, R" y' ~ int (*open) (struct inode *, struct file *); int (*flush) (struct file *, fl_owner_t id); ... } __randomize_layout;$ S$ {) s1 {5 ?5 m. S+ F 5. 通用文件模型:文件描述符和打开文件的关系5.1 相关的内核数据结构内核使用 3 种数据结构来表示一个被打开的文件:
每个进程在进程表 (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 {...! w; T# s8 ~4 K9 e5 P /* Filesystem information: */ struct fs_struct *fs;7 I8 H" t0 c' Y( A # H- J3 P7 [3 B d1 X /* Open file information: */ b: s, C, W" _+ } struct files_struct *files;0 t7 R+ Y. J: I: I -> struct fdtable *fdt; ... } 每个文件描述符包含:
... struct file **fd; /* current fd array */' Z1 V6 o8 _0 S/ `# o% H) y5 | unsigned long *close_on_exec; ... };" F8 i' t* [5 b5 m$ E# D$ U
内核为所有打开文件维持一张打开文件表。每个打开文件表项包含:
inode 结构体和 vnode 结构体名称虽然不同,但是 2 者其实是同一个概念,它们都用于描述存储在硬盘中的文件系统的 inode 数据。注意区别内存里的 inode 结构体对象和硬盘中的 inode 数据。 3) 文件系统的 i-node 表 ( i-node table )每个打开文件都有一个 inode 对象。inode 对象包含了:
... /* Stat data, not accessed from path walking */ unsigned long i_ino;3 V" _6 T3 k/ g3 U( f* F % Q1 X8 O! E$ O0 R+ m ... /* former ->i_op->default_file_ops */ const struct file_operations *i_fop; } 这些信息是在打开文件时从硬盘上读入内存的,所以,文件的所有相关信息都是随时可用的。即 inode 对象包含了文件的所有者、文件长度、指向文件实际数据块在磁盘上所在位置的指针等。 上述三张表的完整关系如下:
两个独立进程各自打开了同一文件,则有如下关系:
第一个进程在文件描述符 3 上打开该文件,而另一个进程在文件描述符 4 上打开该文件。打开该文件的每个进程都获得各自的一个打开文件表项,但对一个给定的文件只有一个 inode 节点表项。 之所以每个进程都获得自己的打开文件表项,是因为这可以使每个进程都有它自己的对该文件的当前偏移量。 2) dup(1) 复制文件描述符dup() 用来复制一个现有的文件描述符。 $ man 2 dup#include <unistd.h>3 d, N- ^ ~ ?" l# ]/ v int dup(int oldfd); dup(1)后的内核数据结构:
dup() 返回的新文件描述符与参数 oldfd 共享同一个打开文件表项。 3) fork 之后父进程和子进程之间对打开文件的共享
9 Q# @! {3 o( t5 M+ |! n ; k4 v7 A' ^( p# Q# s假定所用的描述符是在fork之前打开的,如果父进程和子进程写同一描述符指向的文件,但又没有任何形式的同步,如使父进程等待子进程,那么它们的输出就会相互混合。
不好意思,这周身体不太舒服,文章拖更了,各位见谅。 鉴于大多数人的注意力无法在一篇文章里上集中太久,更多的内容请大家先自行去阅读吧,不是自己理解到的东西是消化不了的。有机会的话我会把更多的读书心得放在后面的文章。 |
| 顶一下 |
微信公众号
手机版