本文主要是介绍『 Linux 』目录与软硬链接 (万字详解),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
文章目录
- 如何理解目录
- 目录项
- 目录中的权限问题
- 根目录
- Dentry缓存
- 文件的增删改查与文件系统关系
- 软硬链接
- 软链接
- 硬链接
如何理解目录
目录是一个文件存在其对应独立的Inode;
$ stat dirFile: ‘dir’Size: 4096 Blocks: 8 IO Block: 4096 directory
Device: fd01h/64769d Inode: 2360193 Links: 2
Access: (0775/drwxrwxr-x) Uid: ( 1002/ _XXX) Gid: ( 1002/ _XXX)
Access: 2024-06-03 19:07:42.139437045 +0800
Modify: 2024-06-03 19:07:40.931391471 +0800
Change: 2024-06-03 19:07:40.931391471 +0800Birth: -
文件的实质是内容与属性的结合;
struct inode {uint16_t i_mode; // 文件类型和权限uint16_t i_uid; // 文件所有者的用户IDuint32_t i_size; // 文件大小(字节)/* ... */uint32_t i_flags; // 文件标志uint32_t i_block[15]; // 数据块指针// 其他文件系统特定的信息
};
当访问文件时需要通过Inode中的数据块指针数组字段中的元素直接或间接访问数据块中的数据;
目录文件的Inode中的数据块指针指向的内容称为目录项;
目录项
目录项是目录文件的Inode中的数据块指针数组直接或间接指向的内容;
是属于目录文件的内容;
目录项中包含了当前目录下的所有文件名以及Inode之间的关系;
struct ext2_dir_entry {uint32_t inode; // inode编号uint16_t rec_len; // 目录项长度uint8_t name_len; // 文件名长度uint8_t file_type; // 文件类型char name[]; // 文件名
};/// 例: 目录项1 文件名"file.txt"
/*{inode: 10,rec_len: 16,name_len: 9,file_type: 1, // 普通文件name: "file1.txt"}
*/
以先描述后组织的关系保存当前目录下所有文件的基本信息;
-
目录项的作用
-
文件名到Inode的映射
目录想主要作用是将文件名映射到对应的Inode编号;
虽然是以目录项的形式存在,但是以抽象来看的话是一个
KeyValue
结构;通过目录项使得文件系统能够通过文件名找到对应文件的Inode,从而访问文件的元数据和内容;
-
目录结构的组织
目录想使得文件系统能够组织和管理目录结构;
通过目录项用户可以浏览和访问目录中的文件和该目录文件中的其他子目录文件;
-
假设需要访问一个目录文件为/aa/bb/cc/dd.txt
,需要进行以下操作:
- 获取根目录(
/
)的inode:- 从inode表中获取根目录的inode;
- 访问根目录的数据块:
- 通过根目录的inode指针,找到根目录的数据块;
- 在根目录的数据块中查找名为
aa
的目录项,获取其对应的inode编号;
- 访问
aa
目录:- 通过
aa
目录的inode编号,找到aa
目录的inode; - 读取
aa
目录的inode,获取其指向的数据块指针; - 通过
aa
目录的inode指针,找到aa
目录的数据块; - 在
aa
目录的数据块中查找名为bb
的目录项,获取其对应的inode编号;
- 通过
- 访问
bb
目录:- 通过
bb
目录的inode编号,找到bb
目录的inode; - 读取
bb
目录的inode,获取其指向的数据块指针; - 通过
bb
目录的inode指针,找到bb
目录的数据块; - 在
bb
目录的数据块中查找名为cc
的目录项,获取其对应的inode编号;
- 通过
- 访问
cc
目录:- 通过
cc
目录的inode编号,找到cc
目录的inode; - 读取
cc
目录的inode,获取其指向的数据块指针; - 通过
cc
目录的inode指针,找到cc
目录的数据块; - 在
cc
目录的数据块中查找名为dd.txt
的目录项,获取其对应的inode编号;
- 通过
- 访问
dd.txt
文件:- 通过
dd.txt
文件的inode编号,找到dd.txt
文件的inode; - 读取
dd.txt
文件的inode,获取其指向的数据块指针; - 通过
dd.txt
文件的inode指针,找到文件的实际数据块,读取文件内容;
- 通过
目录中的权限问题
-
为什么同一个目录下不能有同名文件?
目录是文件,其Inode中的指针数组中数组中指针指向的数据块中存放的数据为目录项;
目录项为当前目录下所有文件的文件名与Inode编号的映射关系(以结构体方式存在);
这个映射关系为类似一个
KeyValue
的形式;若是
Key
值存在重复则无法有效找到对应文件的Inode从而不能正确找到需要访问的文件;
文件将通过自身的文件属性控制不同用户对该文件的操作权限;
而目录文件也是文件,其在文件系统中有独立的Inode,所以目录同样存在权限;
$ ll
total 4
drwxrwxr-x 2 _XXX _XXX 4096 Jun 3 19:07 dir
-rw-rw-r-- 1 _XXX _XXX 0 Jun 3 19:07 log.txt
-
权限类型
每个文件和目录都有三个权限类型:读( r ),写( w )和执行( x );
这些权限分别适用于文件所有者(user),文件所属组(group)和其他用户(others);
-
权限表示
权限通常用一个9位的字符串表示,如上图为例的
rwxrwxr-x
与rw-rw-r--
(第一个字符表示文件类型,d
表示目录文件);前三位表示文件所有者的权限;
中间三位表示文件所属组的权限;
最后三位表示其他用户的权限;
对于普通文件而言其文件权限如下:
-
读权限( r )
允许用户读取文件的内容;
若是没有读权限用户但有写与执行权限时可以对文件进行修改与执行但无法利用类似的
cat
命令将文件内容进行展示;$ cat log.txt cat: log.txt: Permission denied
-
写权限( w )
允许用户修改文件的内容;
若是没有写权限但是有读权限与执行权限时可以利用类似
cat
的命令读取文件内容与执行文件但是无法对文件内容进行增删改等写入操作;$ chmod 550 log.txt $ cat log.txt hello $ echo "world\n" > log.txt bash: log.txt: Permission denied
-
执行权限( x )
允许用户执行文件(如果文件是可执行程序或是脚本);
若是没有执行权限则无法运行文件;
$ ll total 8 d-wx-wx-wx 2 _XXX _XXX 4096 Jun 3 19:07 dir -rw-rw-rw- 1 _XXX _XXX 6 Jun 4 10:55 log $ ./log bash: ./log: Permission denied
对于目录文件如下:
-
读权限( r )
允许用户查看目录中的文件列表 (包括目录文件) ;
若是没有读权限但有写权限和执行权限时可以进入目录并在目录中进行增删改等操作但无法使用
ls
等命令读取该目录中的文件列表;$ chmod 330 dir $ ls dir/ ls: cannot open directory dir/: Permission denied
-
写权限( w )
允许用户在目录中创建,删除和重命名文件和子目录;
若是没有写权限则但有读和执行权限时可以进入目录并使用
ls
等命令展示当前目录的内容但无法对目录中的文件进行增删改查等操作;$ chmod 550 dir $ touch dir/test.txt touch: cannot touch ‘dir/test.txt’: Permission denied
-
执行权限( x )
允许用户进入该目录 ;
若是没有执行权限时即使拥有其他权限也无法对目录进行任何操作(目录文件中执行权限
x
是所有操作的前提);$ chmod 660 dir $ cd dir bash: cd: dir: Permission denied $ ll dir ls: cannot access dir/dir: Permission denied ls: cannot access dir/log.txt: Permission denied ls: cannot access dir/hello.txt: Permission denied ls: cannot access dir/test.txt: Permission denied total 0 d????????? ? ? ? ? ? dir -????????? ? ? ? ? ? hello.txt -????????? ? ? ? ? ? log.txt -????????? ? ? ? ? ? test.txt $ touch dir/makefile touch: cannot touch ‘dir/makefile’: Permission denied
根目录
在Linux中,由根目录至其他的所有的目录文件的整体构造被称为操作系统目录树;
-
操作系统目录树
操作系统目录树是文件系统的核心结构之一,以属性结构组织和管理文件和目录;
使得所有的文件和目录都已层次化的方式进行组织,且每个目录都可以包含子目录和文件;
-
根目录
根目录是整个操作系统目录树的起点也是最高顶点,通常为
/
;通常操作系统目录树的根目录
/
所在的文件系统被称为根文件系统;每个文件系统被创建时将默认存在一个根目录
/
; -
根文件系统
挂载至操作系统目录树中根目录
/
的文件系统被称为根文件系统;
当存在多个分区(文件系统)时,分区必须以挂载的形式挂载至操作系统目录树之中;
在进行分区时将会把分区格式化为一个文件系统,以便于存储文件和目录;
根目录也是一个目录,也是一个文件,在文件系统中有自己的Inode编号和属性,通常在创建一个文件系统时会创建对应的根目录,并在其InodeTable表中维护根目录的Inode信息;
而为了方便用户的操作,实际上每当进行一次分区(创建出一个文件系统)过后操作系统内核都会默认为其生成一个根目录/
;
但在创建一个新的分区后不能立即使用,必须以挂载的形式将其挂载至操作系统目录树之中;
一但挂载成功其文件系统的根目录将会变成挂载时所设置的名称,在文件系统中其算是一个根目录而在整体的操作系统目录树中并不是根目录;
文件系统的创建通常如下:
-
分区
采用分区工具将磁盘划分为一个或多个分区;
-
格式化分区
利用文件系统工具将分区格式化为一个特定的文件系统类型,其中文件系统的根目录的创建也属于格式化中的一环;
-
创建挂载点
在根文件系统中创建一个目录为挂载点;
-
挂载分区
将分区挂载至挂载点;
所以在一整个体系中只有一个根目录(根文件系统所挂载的目录);
对于各个文件系统的根目录而言,操作系统内核将选择一个文件系统作为根文件系统;
而将根文件系统挂载至/
的操作一般可以理解为告诉操作系统该目录将作为整个操作系统目录树的最高顶点(起点);
Dentry缓存
访问或操作任何一个文件/程序都要带路径并通过路径访问其目录项获得对应的Inode逐级进行访问;
这是一个效率过低的操作,无论进行任何操作都要从根目录开始寻找;
为了提高效率操作系统将进行一个Dentry缓存;
Dentry缓存是内核中的一个内存缓存,用于存储最近访问过的目录项,通常以结构体的形式存在;
每个Dentry结构体表示文件系统中的一个目录项,包含文件名和对应的Inode信息;
其结构信息可以参照如下:
struct dentry {atomic_t d_count; // 引用计数unsigned int d_flags; // 标志spinlock_t d_lock; // 自旋锁int d_mounted; // 挂载计数struct inode *d_inode; // 指向对应的inodestruct hlist_node d_hash; // 哈希链表节点struct dentry *d_parent; // 父目录的dentrystruct qstr d_name; // 文件名struct list_head d_lru; // LRU链表节点struct list_head d_child; // 子目录链表节点struct list_head d_subdirs; // 子目录链表头struct rcu_head d_rcu; // RCU头// 其他字段省略
};
这个缓存用于加速路径解析从而提高文件和目录的访问效率;
通过缓存目录项,文件系统可以快速查找文件和目录,而无需每次都从磁盘中读取目录项;
一般当进行一个目录或文件的访问通常会有以下操作:
-
路径解析
当用户访问文件或目录时,文件系统会解析路径并在Dentry缓存找找到对应的目录项;
如果在Dentry缓存中找到目录项,文件系统可以直接使用缓存中的Inode编号找到对应的Inode信息从而无需从磁盘中重新依次读取目录项;
-
缓存命中和未命中
-
缓存命中:
如果目录项在Dentry缓存中这种被称为缓存命中,文件可以直接使用缓存中的信息;
-
缓存未命中;
如果在Dentry缓存中未找到目录项,即目录项不在Dentry缓存中被称为缓存未命中;
此时文件系统需要从新从磁盘中读取目录项并添加到Dentry缓存中并进行使用同时方便后续访问时使用;
-
-
缓存管理
Dentry缓存使用引用计数等方式管理缓存项;
当缓存项的引用计数为
0
且缓存缓存空间不足时将会移除未使用时间最长的缓存想以释放空间;
文件的增删改查与文件系统关系
-
创建文件(增)
-
分配inode:
文件系统首先在inode表中找到一个空闲的inode,并将其分配给新文件;
初始化inode的元数据,包括文件类型、权限、所有者、时间戳等; -
创建目录项(dentry):
在目标目录中创建一个新的目录项(dentry),将新文件的名称与分配的inode关联起来;
更新目录的inode信息(如链接计数、修改时间等); -
分配数据块:
当文件内容被写入时,文件系统在数据区找到空闲的数据块,并将其分配给文件;
更新inode中的数据块指针,指向分配的数据块;
-
-
删除文件(删)
-
删除目录项(dentry):
文件系统在目标目录中找到对应的目录项,并将其删除;
更新目录的inode信息(如链接计数、修改时间等); -
释放inode:
文件系统将文件的inode标记为空闲,并从inode表中移除;
更新inode的元数据,清除文件类型、权限、所有者、时间戳等信息; -
释放数据块:
文件系统将文件占用的数据块标记为空闲,并从数据区中移除;
更新inode中的数据块指针,清除指向的数据块信息;
-
-
修改文件(改)
-
找到inode:
文件系统在目录中找到对应的目录项,获取文件的inode;
-
分配或重新分配数据块:
当文件内容被修改时,文件系统在数据区找到空闲的数据块,并将其分配给文件;
如果文件内容增加,需要分配新的数据块;如果文件内容减少,可能需要释放部分数据块;
更新inode中的数据块指针,指向新的或修改后的数据块; -
更新inode元数据:
更新inode的元数据,包括文件大小、修改时间等信息;
-
-
读取文件(查)
-
找到inode:
文件系统在目录中找到对应的目录项,获取文件的inode;
-
读取数据块:
根据inode中的数据块指针,文件系统读取文件的数据块;
将数据块中的内容读取到用户提供的缓冲区中; -
更新inode元数据:
更新inode的元数据,包括访问时间等信息;
-
软硬链接
在文件系统中,存在 “链接” 的概念;
指一个文件系统对象与其名称之间的关联;
在Linux中可以使用ln
命令为文件或目录创建链接;
其对应文档如下:
$ ln --h
Usage: ln [OPTION]... [-T] TARGET LINK_NAME (1st form)or: ln [OPTION]... TARGET (2nd form)or: ln [OPTION]... TARGET... DIRECTORY (3rd form)or: ln [OPTION]... -t DIRECTORY TARGET... (4th form)
In the 1st form, create a link to TARGET with the name LINK_NAME.
In the 2nd form, create a link to TARGET in the current directory.
In the 3rd and 4th forms, create links to each TARGET in DIRECTORY.
Create hard links by default, symbolic links with --symbolic.
By default, each destination (name of new link) should not already exist.
When creating hard links, each TARGET must exist. Symbolic links
can hold arbitrary text; if later resolved, a relative link is
interpreted in relation to its parent directory.Mandatory arguments to long options are mandatory for short options too.--backup[=CONTROL] make a backup of each existing destination file-b like --backup but does not accept an argument-d, -F, --directory allow the superuser to attempt to hard linkdirectories (note: will probably fail due tosystem restrictions, even for the superuser)-f, --force remove existing destination files-i, --interactive prompt whether to remove destinations-L, --logical dereference TARGETs that are symbolic links-n, --no-dereference treat LINK_NAME as a normal file ifit is a symbolic link to a directory-P, --physical make hard links directly to symbolic links-r, --relative create symbolic links relative to link location-s, --symbolic make symbolic links instead of hard links-S, --suffix=SUFFIX override the usual backup suffix-t, --target-directory=DIRECTORY specify the DIRECTORY in which to createthe links-T, --no-target-directory treat LINK_NAME as a normal file always-v, --verbose print name of each linked file--help display this help and exit--version output version information and exitThe backup suffix is '~', unless set with --suffix or SIMPLE_BACKUP_SUFFIX.
The version control method may be selected via the --backup option or through
the VERSION_CONTROL environment variable. Here are the values:none, off never make backups (even if --backup is given)numbered, t make numbered backupsexisting, nil numbered if numbered backups exist, simple otherwisesimple, never always make simple backupsUsing -s ignores -L and -P. Otherwise, the last option specified controls
behavior when a TARGET is a symbolic link, defaulting to -P.GNU coreutils online help: <http://www.gnu.org/software/coreutils/>
For complete documentation, run: info coreutils 'ln invocation
在文件系统中链接可以分为两种主要类型:
- 软链接
- 硬链接
当默认用ln
创建链接时创建的是硬链接,带-s
选项表示创建软链接;
软链接
软链接是一个符号链接,是一个特殊类型的文件,在文件系统当中存在一个独立的Inode;
其包含指向另一个文件或者目录的路径;
换一种说法就是软链接允许用户创建指向文件或目录的快捷方式从而使得能够通过多个位置来访问一个文件或者是目录;
$ ll -i
total 0
2360201 -rw-rw-r-- 1 _XXX _XXX 0 Jun 4 13:41 log.txt
$ ln -s log.txt soft-link
$ ll -i
total 0
2360201 -rw-rw-r-- 1 _XXX _XXX 0 Jun 4 13:41 log.txt
2360202 lrwxrwxrwx 1 _XXX _XXX 7 Jun 4 13:41 soft-link -> log.txt
软链接的特点如下:
-
软链接的Inode
软链接有自己的Inode编号,与目标文件的Inode编号不同故为一个独立的文件;
软链接的Inode中包含文件类型,权限,所有者,时间戳等元数据,但其数据块指针指向的存储目标文件路径的数据块而不是目标文件的实际数据块;
-
软链接的数据块
软链接的数据块存储的是目标文件或目录的路径 (绝对或者相对) ;
当访问软链接时,操作系统将会读取软链接的数据块从而获取目标文件的路径然后重定向到目标文件;
-
指向路径
软链接中存储的是目标文件或目录的路径而不是文件内容本身;
当访问软链接时操作系统将会自动重定向到目标文件或是目录;
$ cat log.txt ;cat soft-link $ echo "hello world\n" > soft-link $ cat log.txt ;cat soft-link hello world\n hello world\n
-
可以跨文件系统
软链接可以指向不同文件系统中的文件或是目录,类似指针一样,其存储的是路径信息;
-
可以指向不存在的文件
软链接可以指向一个当前不存在的文件或是目录;
这种链接被称为 “悬挂链接” 或是 “断链” ;
$ ll -i total 0 2360201 -rw-rw-r-- 1 _XXX _XXX 0 Jun 4 13:53 log.txt 2360202 lrwxrwxrwx 1 _XXX _XXX 7 Jun 4 13:41 soft-link -> log.txt $ rm log.txt $ ll -i total 0 2360202 lrwxrwxrwx 1 _XXX _XXX 7 Jun 4 13:41 soft-link -> log.txt
当删除
log.txt
后soft-link
将标红表示当前为 悬挂链接/断链 ; -
权限和大小
软链接本身有自己的权限和大小,但这些权限通常指影响链接文件本身而不影响目标文件;
软链接的大小通常是存储的路径长度;
硬链接
硬链接是软链接除外的另一种链接类型,允许多个文件名指向同一个文件数据块;
硬链接与源文件共享相同的Inode且并不属于一个单独的文件;
$ ll -i
total 0
2360201 -rw-rw-r-- 1 _XXX _XXX 0 Jun 4 13:59 log.txt
$ ln log.txt hard-link
$ ll -i
total 0
2360201 -rw-rw-r-- 2 _XXX _XXX 0 Jun 4 13:59 hard-link
2360201 -rw-rw-r-- 2 _XXX _XXX 0 Jun 4 13:59 log.txt
换句话说硬链接与目标文件实际上是一个不同名称的同文件,其对应的元信息相同;
硬链接提供了一种在文件系统中创建文件的多个入口点方法;
硬链接的特点如下:
-
共享Inode
硬链接与源文件共享相同的Inode编号,因此它们指向同一个数据块;
任何一个文件在Inode中存在一个引用计数,这个引用计数用来计数当前文件的硬链接数;
硬链接和源文件在文件系统中是完全等价的,删除任何一个不会影响数据的存在,只有当所有硬链接被删除后数据才会释放;
$ ll -i total 0 2360201 -rw-rw-r-- 1 _XXX _XXX 0 Jun 4 13:59 log.txt $ ln log.txt hard-link $ ll -i total 0 2360201 -rw-rw-r-- 2 _XXX _XXX 0 Jun 4 13:59 hard-link 2360201 -rw-rw-r-- 2 _XXX _XXX 0 Jun 4 13:59 log.txt
该代码中从左至右数第三个字段即为硬链接数,引用计数为
1
变为2
; -
不能夸文件系统
基于Inode相同的理论上;
硬链接只能在同一个文件系统中创建,不能跨文件系统;
-
相同的文件属性
硬链接与目标文件具有相同的Inode,故将会与原文件共享相同的文件属性(如权限,所有者等);
任何一个硬链接的修改都会影响所有指向统一Inode的硬链接;
-
不支持目录
硬链接的创建不支持目录的硬链接,具体原因是避免造成环的问题;
$ ln todel ln: ‘todel’: hard link not allowed for directory
若允许为目录创建硬链接将可能出现以下问题:
-
例:
/usr/home/bin/
,此时在bin
目录中创建一个指向usr
目录的硬链接,即/usr/home/bin/hard-link
当遍历到目录
/usr
时,根据路径继续访问/usr/home/bin/hard-link
,最终又会回到/usr
路径从而导致无限循环;
-
-
硬链接的应用场景
当一个目录被创建时其目录中会默认存在两个目录文件分别为
.
与..
分别代表当前目录和父目录;$ mkdir todel $ ls -ai todel/ 2360202 . 2360200 ..
因为目录不被允许创建硬链接,故在设置文件系统时为了方便用户进行访问将设置两个硬链接;
这篇关于『 Linux 』目录与软硬链接 (万字详解)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!