Chapter 39 Interpolation: documentation and directories

第 39 章 插叙:文件和目录

目前为止有两项关键操作系统技术:

  • 进程,虚拟化的CPU

  • 地址空间,虚拟化的内存

这两个抽象的共同作用下,程序运行时好像他在自己的私立空间里...使得系统编程变得容易

虚拟化拼图还有更为关键的一块:持久存储 persistent storage...体现为文件

1. 文件和目录

存储虚拟化形成了两个关键的抽象:

  • 文件 file 线性字节数组

  • 目录 directory

1.1. 文件的低级名称

用户不知道 叫 inode number

1.2. 目录

低级名称和用户可读名称不一致

用户可读名称通常更为具象,可以编码为字符

而低级名称更容易存储

在底层通过构造映射来进行记录。用户可以构建任意的目录树 directory tree 或 目录层次结构 directory hierarchy。

2. 文件系统接口

创建、访问、删除文件的接口...

3. 创建文件

3.1. open() 调用

传入 O_CREAT 程序创建一个新文件

In this example

  • the second parameter creates the file (O_CREAT) if it does not exist

  • ensures that the file can only be written to (O_WRONLY)

  • if the file already exists, truncates it to a size of zero bytes thus removing any existing content (O_TRUNC).

它的返回值

文件描述符 file descriptor 是一个整数,每个进程私有的 相当于一种权限 capability 一个不透明的句柄

一旦有这样的对象可以用其他方法来访问 如下

4. 读写文件

strace 跟踪系统调用

Why does the first call to open() return 3, not 0 or perhaps 1 as you might expect?

As it turns out, each running process already has three files open, standard input (which the process can read to receive input), standard output (which the process can write to in order to dump information to the screen), and standard error (which the process can write error messages to). These are represented by file descriptors 0, 1, and 2, respectively. Thus, when you first open another file (as cat does above), it will almost certainly be file descriptor 3.

对于大型文件,可能会重复调用 read() 和 write() system call...

5. 读取和写入 但不按顺序

先前介绍的都是 sequential,现在介绍 random

5.1. Function Prototype

off_t lseek(int fildes, off_t offset, int whence);

参数:

  1. 文件描述符 fd

  2. 偏移量

  3. whence

    1. If whence is SEEK_SET, the offset is set to offset bytes.

    2. If whence is SEEK_CUR, the offset is set to its current location plus offset bytes.

    3. If whence is SEEK_END, the offset is set to the size of the file plus offset bytes.

可以通过指定偏移量来完成读取和写入操作

6. 用fsync()立即写入

write 只是会接受写入请求,而且不是实时的,而是有一个缓存机制,并且极少数情况下会丢失

然而DBMS等程序需要开发正确的恢复协议要求能够强制写入磁盘

6.1. fsync()

强制将所有 dirty data写入磁盘

  1. 当调用write函数时,数据首先被写入到操作系统的页缓存(page cache)中。页缓存是内存中的一块区域,用于缓存文件数据,以提高读写性能。

  2. 同步到磁盘是指将内存中的数据(通常是页缓存中的数据)强制写入到物理磁盘中。这个操作通常由 fsync 或sync系统调用完成。

7. 文件重命名

rename 系统调用(char * old, char * new)

用 strace 跟踪 mv 可以发现...

将文件的新版本写入临时名称(foot.txt.tmp),使用fsync()将其强制写入磁盘... and then, when the application is certain the new file metadata and contents are on the disk, rename the temporary file to the original file’s name. This last step atomically swaps the new file into place, while concurrently deleting the old version of the file, and thus an atomic file update is achieved.

8. 获取文件信息

文件的各种信息

8.1. test

Each file system usually keeps this type of information in a structure called an inode1 . We’ll be learning a lot more about inodes when we talk about file system implementation. For now, you should just think of an inode as a persistent data structure kept by the file system that has information like we see above inside of it. All inodes reside on disk; a copy of active ones are usually cached in memory to speed up access.

9. 删除文件

We’ve removed a bunch of unrelated cruft from the traced output, leaving just a single call to the mysteriously-named system call unlink(). As you can see, unlink() just takes the name of the file to be removed, and returns zero upon success. But this leads us to a great puzzle: why is this system call named unlink? Why not just remove or delete? To understand the answer to this puzzle, we must first understand more than just files, but also directories.

其中 第111行unlinkat(AT_FDCWD, "a.txt", 0) = 0

10. 创建目录

mkdir 系统调用

10.1. 小心强大的指令

rm 提供了一个强大命令,也说明太多权力是一个问题,要一次删除一堆文件可以用 rm *...有时候可以用 rm -rf *如果在 root 就麻烦了

10.2. 目录条目

空目录也包含两个:

  • .引用自身条目

  • ..引用父目录的条目

ls -a 可以看到

11. 读取目录

ls 来读取 -l 和 不-l 也有区别

11.1. CODE

11.2. OUTPUT

输出 inode 号 和 文件名称...

12. 删除目录

rmfir() 调用,但是要求此时目录是空的:只包含...

13. 硬链接

ln 指令 相当于之前用过的创造快捷方式...?

通过两个人类可读名称指向同一个文件(同一个inode号的文件)

14. 符号连接

symbolic link 符号连接 有时称为 soft link 软连接

ln -s 标识...

14.1. 硬链接

创建硬链接link1指向file.txt

  • 删除file.txt后,link1仍然可以访问内容“Hello, World!”,因为它们共享同一个文件内容

14.2. 软链接

创建软链接link2指向file.txt

  • 删除file.txt后,link2就变成一个坏链接,无法访问任何内容。

15. 创建并挂载文件系统

mkfs() 调用

make file system

15.1. 创建文件系统 mkfs()

目标挂载点 mount point

规划硬盘分区...存储文件

15.2. 挂载 mount()

挂载点是一个目录,比如 /mnt/mydisk 。这个目录就像是一个“入口”。

  • 想象你有一个硬盘分区(比如/dev/sdb/),上面已经用mkfs创建了文件系统。

  • 这个硬盘分区就像是一栋房子,里面有房间(文件和目录),但你现在还不能进去。

  • mount就像是在房子和外面的街道之间修了一条路,让你可以通过某个入口(目录)进入这栋房子。

16. 作业(编码)

16.1. Stat

Write your own version of the command line program stat, which simply calls the stat() system call on a given file or directory. Print out file size, number of blocks allocated, reference (link) count, and so forth. What is the link count of a directory, as the number of entries in the directory changes? Useful interfaces: stat(), naturally

CODE

OUTPUT

16.2. List Files

Write a program that lists files in the given directory. When called without any arguments, the program should just print the file names. When invoked with the -l flag, the program should print out information about each file, such as the owner, group, permissions, and other information obtained from the stat() system call. The program should take one additional argument, which is the directory to read, e.g., myls -l directory. If no directory is given, the program should just use the current working directory. Useful interfaces: stat(), opendir(), readdir(), getcwd().

CODE

OUTPUT

16.3. Tail

Write a program that prints out the last few lines of a file. The program should be efficient, in that it seeks to near the end of the file, reads in a block of data, and then goes backwards until it finds the requested number of lines; at this point, it should print out those lines from beginning to the end of the file. To invoke the program, one should type: mytail -n file, where n is the number of lines at the end of the file to print. Useful interfaces: stat(), lseek(), open(), read(), close().

CODE

OUTPUT

有个 bug,n和实际输出的行数不对应...不知道怎么修了

16.4. Recursive Search

Write a program that prints out the names of each file and directory in the file system tree, starting at a given point in the tree. For example, when run without arguments, the program should start with the current working directory and print its contents, as well as the contents of any sub-directories, etc., until the entire tree, root at the CWD, is printed. If given a single argument (of a directory name), use that as the root of the tree instead. Refine your recursive search with more fun options, similar to the powerful find command line tool. Useful interfaces: figure it out.

CODE

OUTPUT

Last updated