文件管理和IO重定向
7 种文件类型
1 | - 普通文件 |
普通文件
普通文件分为三类:
- 纯文本文件 ASCII:使用
cat
可以查看纯文本文件 - 二进制文件 binary:使用
od
或者hexdump
命令以 8 进制或者 16 进制查看 - 数据格式文件 data
有些程序运行的过程中会读取某些特定格式的文件,那些特定格式的文件可以被成为数据文件(data file)。举例来说,我们的 Linux 在使用登录的时候,都会将登录的数据记录在/var/log/wtmp
文件内,该文件就是一个 data file,它可以被last
和who
命令读取内容.
目录文件
块设备 和 字符设备
块设备是硬件设备, 以块(block, 在 EXT4 文件系统中, 一个 block 通常为 4K)为单位, 应用程序通过随机访问的形式读取数据
最常见的块设备是硬盘,还有软盘等。注意这些都是挂载文件系统的设备, 文件系统就像块设备的通用语言.
字符设备文件以字节流的方式进行访问,由字符设备驱动程序来实现这种特性。字符终端、串口和键盘等就是字符设备。可以顺序读取,但通常不支持随机存取。
区分块设备和字符设备最简单的方法就是看数据访问的方式,能随机访问获取数据的是块设备,必须按字节顺序访问的是字符设备
符号链接文件
符号链接文件又叫软连接,软连接相当于一个快捷方式
软(symbolic 或 soft)链接
文件 A 和文件 B 的 inode 号码虽然不一样,但是文件 A 的内容是文件 B 的路径。读取文件 A 时,系统会自动将访问者导向文件 B。因此,无论打开哪一个文件,最终读取的都是文件 B。这时,文件 A 就称为文件 B 的”软链接”(soft link)或者”符号链接(symbolic link)。
这意味着,文件 A 依赖于文件 B 而存在,如果删除了文件 B,打开文件 A 就会报错:”No such file or directory”。这是软链接与硬链接最大的不同:文件 A 指向文件 B 的文件名,而不是文件 B 的 inode 号码,文件 B 的 inode”链接数”不会因此发生变化。
1 | ln -s filename [linkname] |
- 软链接的内容是它引用文件的路径
- 可以对目录创建软链接
- 可以跨分区创建软链接
Linux 下使用ln -s
设置软连接,Windows 下则是mklink /J
硬(hard)链接
硬链接本质上就给一个文件起一个新的名称,不是符号链接,写在这里主要是为了对照软连接
1 | ln filename [linkname] |
inode 信息中有一项叫做”链接数”,记录指向该 inode 的文件名总数
- 创建硬链接后,和原文件的 inode 还是一样的
- 新建硬链接, inode 中的”链接数” + 1
- rm 删除文件, inode 中的”链接数” - 1,当”链接数”为 0,才是真正删除此文件
- 不能跨域驱动器或分区创建硬链接
- 不支持对目录创建硬链接
管道文件 (FIFO 文件)
管道文件主要用于进程间通信
管道都是一端写入,另一端读取,它们是单方向数据传输的,它们的数据都是直接在内存中传输的,管道是进程间通信的一种方式,例如父进程写,子进程读
管道分为 匿名管道 和 命令管道:
- 匿名管道只能在有亲缘关系(父子或者兄弟)的进程之间创建
- 命名管道可以使用
mkfifo
或者mknod
命令创建
1 | mkfifo pipe # 创建一个名为pipe的命名管道 |
套接字文件
套接字用来实现两端通信,可以实现双向管道的进程间通信功能。不仅如此,套接字还能通过网络实现跨主机的进程间通信功能
套接字需要成对才有意义,也就是分为两端,每一端都有用于读、写的文件描述符(或文件句柄),相当于两根双向通信的管道。
套接字根据协议族的方式分为两大类:网络套接字(AF_INET 类型,根据 ipv4 和 ipv6 分为 inet4 和 inet6)和Unix Domain 套接字(AF_UNIX 类型)。当然,从协议族往下,套接字可细分为很多种类型,例如 INET 套接字可以分为 TCP 套接字、UDP 套接字、链路层套接字、Raw 套接字等等。其中网络套接字是网络编程的基础和核心。
对于单机的进程间通信,使用 Unix Domain 套接字比 Inet 套接字更好,因为 Unix Domain 套接字没有网络通信组件,也就是少了很多网络功能,它更加轻量级。实际上,某些语言在某些操作系统平台上实现的管道功能就是通过 Unix Domain 来实现的,可想而知其高效率。例如:nginx 和 PHP-FPM 的进程间通信有 TCP 和 UNIX Domain Socket 两种方式,其中 TCP 是 IP 加端口,可以跨服务器;而 UNIX Domain Socket 不经过网络,只能用于 Nginx 跟 PHP-FPM 都在同一服务器的场景,效率更高
文件元数据
每个文件的属性信息,比如:文件的大小、时间、类型等,称为文件的元数据(meta data)
每个文件都有三个时间,atime、mtime、ctime,使用 stat 命令可以查看。
atime:access time 访问时间
- 当使用 cat 命令查看文件内容时 atime 不改变;
- 使用 wq 退出 vim 时修改 atime;
- 使用 q!退出 vim 时不改变 atime;
- 使用 echo 往文件写内容不改变 atime;
- 使用 sed -i 修改文件改变 atime;
mtime:modify time 修改时间,当文件内容被修改时,mtime 发生改变
ctime:change time 变化时间,文件的 inode 被修改时,ctime 发生改变。
注意:只要 mtime 发生改变,ctime 肯定也同步发生改变,如果文件较大,ctime 可能会延迟几毫秒
文件节点表结构
广义上,一个文件由三部分组成:目录项、索引节点(inode)、数据块
- 目录项:dirent,包含文件名和索引节点号,索引节点号指向索引节点表(inode table)中对应的索引节点
- 索引节点:inode,包含文件的元数据以及数据块指针
- 数据块:包含文件的具体内容
inode 的大小
硬盘格式化的时候,操作系统自动将硬盘分为两个区域。一个是数据区,存放文件数据;另一个是 inode 区(inode table),存放 inode。
每个 inode 节点的大小,一般是 128 字节或 256 字节。inode 节点的总数,在格式化时就给定,一般是每 1KB 或每 2KB 就设置一个 inode。假定在一块 1GB 的硬盘中,每个 inode 节点的大小为 128 字节,每 1KB 就设置一个 inode,那么 inode table 的大小就会达到 128MB,占整块硬盘的 12.8%。
inode 结构
inode 由上图中的左边区域组成,数据块由右边区域构成。不同的文件大小,通过多层级的间接指针协同完成。
直接块指针:有 12 个,一个块大小为 4KB,所以直接指针可以保存 12 × 4 = 48KB 的文件
间接块指针:指向一个块(4KB),每个指针占用 4 个字节,所以这个块可以拆分成 1024 个指针,那么它可以保存数据 1024*4KB=4MB
双重间接块指针:同理可得它可以存储的数据为 1024*4MB=4GB
三级指针可以储存文件数据大小为 1024*4GB=4TB
一个指针占几个字节?
https://blog.csdn.net/IOSSHAN/article/details/88944637
目录也是文件,打开目录,实际上就是打开目录文件。
目录文件也是由目录项、inode、数据块组成。
目录的数据块中存储的是一系列目录项的列表,是其下的文件的所有目录项。每个目录项,由两部分组成:包含文件名,以及该文件名对应的 inode 号码。
打开文件和 inode
每个 inode 都有一个号码,操作系统用 inode 号码来识别不同的文件
这里值得重复一遍:Unix/Linux 系统内部不使用文件名,而使用 inode 号码来识别文件。对于系统来说,文件名只是 inode 号码便于识别的别称或者绰号
打开一个文件分三步走:
1 | 1. 名称与inode编号关联,系统找到这个文件名对应的inode号码 |
cp 和 inode
1 | 1. 分配一个空闲的inode号,在inode table中生成新条目 |
rm 和 inode
1 | 1. 链接数递减,当减到0,就释放inode号 |
根据 3,可以得出,删除文件实际上是修改目录的内容,所以删除文件必须要有目录的写权限,所以删除文件和文件的权限没有关系,和文件的父目录的权限有关,创建文件同理
mv 和 inode
如果 mv 命令的目标和源在相同的文件系统
1 | 1. 创建新的目录项,新名称对应到inode号 |
如果 mv 命令的目标和源在不同的文件系统:mv 相当于 cp + rm
1 | # 删除大文本文件 |
IO 重定向和管道
打开的文件都有一个 fd:file descriptor(文件描述符)
Linux 给程序提供了三种 IO 设备:
1 | 标准输入:STDIN,0,默认接受来自终端窗口的输入 |
2>&1
这里的&类似 php 中表示变量引用的用法,放在>后面的&,表示重定向的目标不是一个文件,而是一个文件描述符
2>&1
表示标准错误拷贝了标准输出的行为,注意是行为,不仅仅是路径。如果标准输出是 > a.log,那么标准错误输出也是 > a.log,如果标准输出是 >> a.log,那么标准错误输出也是 >> a.log
&>file
&>file
是一种特殊的用法,也可以写成 >&file
,二者的意思完全相同,都等价于1>file 2>&1
,此处的&>
或者>&
视作整体,不能分开
标准输入重定向 单行重定向 <
利用<可以将标准输入重定向
1 | [root@4710419222 test]# cat a.log |
多行重定向 <<终止词
这个很好理解, 类似 php 的长字符串<<<EOT
1 | [root@4710419222 test]# cat > a.log <<EOT |
管道
| 是管道符,管道符左边命令的标准输出发送给管道符右边命令的标准输入
1 | 命令1 | 命令2 | 命令3 | ... |
注意:所以管道符右面只能跟一个命令,因为管道符右面会打开一个子进程,多个命令只会执行第一个,后面的命令会退出子进程再执行
如果跟多个命令,命令之间用分号间隔,用{}或者()把多个命令包裹起来,确保多个这多个命令是在同一个进程下执行
1 | [root@centos8 scripts]#echo 1 2 | read x y ; echo x=$x y=$y |
注意: 标准错误默认通过管道转发, 可利用2>&1
或|&
实现
1 | [root@4710419222 test]# ll c.log |
重定向中的 - 符号
1 | # 将下载文件内容输出到标准输出, 而不保存文件 |
文件描述符(fd)
Linux 中一切都是文件。文件描述符 fd 是内核为了高效管理已被打开的文件所创建的索引,是一个非负整数,在文件 open 时产生。程序刚刚启动的时候,0 是标准输入,1 是标准输出,2 是标准错误。如果此时去打开一个新的文件,它的文件描述符会是 3。
系统为每一个进程维护了一个文件描述符表,该表的值都是从 0 开始的,由于进程级文件描述符表的存在,不同的进程中会出现相同的文件描述符,它们可能指向同一个文件,也可能指向不同的文件