目录
3.2.5 GDT(Group Descriptor Table)
3.3 inode 和 Data Block的映射(简单说明)
1.理解硬盘
1.1 磁盘、服务器、机柜、机房
机械磁盘是计算机中唯一的一个机械设备,磁盘对于计算机来说是一种外设,机械磁盘的特点是容量大,价格便宜但是传输数据慢(相对于内存和cpu之间的传输)。
如下图,就是一块企业级的磁盘。
如下就是把上述磁盘的内部结构,数据就存储在像光盘一样的盘面上。
下图就是一个由一块一块的磁盘构成的服务器,每一块磁盘都是可拔插的。
将一个一个服务器放在一个柜子里面,这个柜子就叫做机柜,用于放置服务器的柜子。
将一个一个机柜集中起来统一放在一个房间里面,这个房间就叫做机房。
1.2 磁盘物理结构
1.3 磁盘的存储结构
在盘面上,围着主轴转的一个一个同心圆叫做磁道,每个磁道都可以分为若干个一小节一小节的区域,这个区域叫做扇区,扇区是有宽度的,扇区是磁盘存储数据的基本单位,一般为512字节,是一个块设备。
而一块磁盘上不仅仅只有一个盘面,如上图,这块磁盘有三个盘,则有6个盘面,每一个盘面相同的磁道,组成的柱状叫做柱面。
下图为多个盘面磁盘的示意图。
文件 = 内容 + 属性。内容和属性都是数据,无非就是占据多少个扇区和占据哪几个扇区的问题。 使用 fdisk -l 命令可以查看本地的磁盘情况。
扇区(sector)数:磁盘读写数据的最小单位,通常大小为512字节,每个磁道都被分成很多扇区,每个磁道的扇区数量相同。
磁头(head)数:每个盘面一般有上下两面,分别对应1个磁头。
磁道(track)数:磁道是从盘面外圈往内圈编号0磁道,1磁道...,靠近主轴的同心圆用于停靠磁头,不存储数据。
柱面(cylinder)数:磁道构成柱面,数量上等同与磁道个数。
圆盘(platter)数:就是盘片的数量。
磁盘容量 = 磁头数 * 磁道数 * 每个磁道的扇区数 * 每个扇区的字节数
知识点1:
如何定位一个扇区?
先确定在要访问哪一个柱面(cylinder),在确定磁头(header)来确定访问柱面上哪一个磁道,最后定位访问该磁道的哪一个扇区(sector)。这种定位方案叫做CHS地址定位。
1.4 磁盘的逻辑结构
1.4.1 理解逻辑结构
对于一个盘面来说,一个盘面就相当于一个”磁带“,当把“磁带”拉直,则就将一个盘面变成了一个线性结构。
磁盘本质上虽然是硬质的,但是逻辑上我们可以把磁盘想象成卷在一起的磁带,磁盘的逻辑存储结构可以类似于:
这样每一个扇区,就有了一个线性地址(数组下标),这种地址叫做LBA。
注:一个盘面的扇区编号从 ‘1’ 开始。
1.4.2 真实过程
柱面是一个逻辑上的概念,一个柱面就是由所有相同半径的磁道构成。磁盘物理上有很多盘面,但是在我们看来,逻辑上磁盘整体是由一个一个柱面卷起来的。
所以,磁盘的真是情况如下:
磁道 -- 某一盘面的某一个磁道展开:
柱面 -- 整个磁盘所有盘面的同一个磁道,即柱面展开:
柱面上的每个磁道的扇区个数是相同的,构成一个二维数组。
整个磁盘 -- 整个磁盘就是由若干个柱面构成,展开如下:
所以,整个磁盘就是多张二维的扇区数组表,可以看成一个三维数组。
所以寻址一个扇区,一是先找到哪一个柱面(cylinder),在确定柱面内的哪一个磁道(其实就是确定哪一个磁头(head)),最后确定磁头上的哪一个扇区(sector),这就叫做CHS寻址。
在计算机中,多维数组都可以被看作为一维数组:
所以每一个扇区都有一个下标,叫做LBA(Logical Block Address)地址,就是线性地址。
OS只需要使用LBA地址就可以了。LBA地址和CHS地址的相互转换由磁盘自己来做。
1.5 CHS地址和LBA地址的相互转换
CHS转成LBA:
LBA地址 = 柱面号C * 单个柱面的扇区总数 + 磁头号H * 单个磁道的扇区总数 + 扇区号S - 1
注:柱面和磁道都是从0开始编号的,扇区号通常从1开始,而LBA是数组下标,也是从0开始,所以上面的计算公式中,扇区号要减1。
柱面,磁道,扇区总数等信息,在磁盘内部会自动维护,上层开机的时候,会自动获取到这些参数信息。
LBA转成CHS:
柱面号C = LBA // 单个柱面的扇区总数
磁头号H = (LBA % 单个柱面的扇区总数)// 每个磁道的扇区数
扇区号S = (LBA % 每个磁道的扇区数)+ 1
注:"%"表示取余,"//"表示除取整。
从此之后,磁盘就是一个元素为扇区的一维数组,数组的下标就是每一个扇区的LBA地址,OS使用磁盘的时候就可以用一个数字寻址到磁盘扇区了。
2.引入文件系统
2.1 “块”概念
硬盘是典型的“块”设备,操作系统读取硬盘数据的时候,其实不会一个个扇区地读取,这样效率态度,而是一次性连续读取多个扇区,即一次性读取一个“块”(block)。
硬盘的每个分区(partition)是被划分成一个个的“块”,一个“块”的大小是由格式化的时候确定的,最常见的是4KB,即连续的八个扇区组成一个“块”。“块”是文件存取的最小单位。
每个扇区都有LBA,那么8个扇区为一个块,每个块号也能计算出来。
块号 = LBA // 8
LBA = 块号 * 8 + n (n是块内的第几个扇区)
2.2 “分区”概念
其实磁盘是可以被分成多个分区(partition)的,以Windows系统的观点看,可能会有一块磁盘并且将其分为C,D,E盘,那么C,D,E盘就是所谓的分区。分区从实质上说就是对硬盘的一种格式化。但是Linux的设备都是以文件形式存在,所有怎么分区的呢?
柱面是分区的最小单位。可以利用参考柱面号码的方式来进行分区,其本质就是设置每个区的起始柱面和结束柱面号码。
知识点1:
文件系统的载体是分区,当谈到使用什么文件系统的时候,最小的谈论单位为一个分区。
2.3 “inode”概念
文件数据(属性 + 内容)都存储在“块”中,那么很显然,文件的元信息(属性信息)也需要一个地方进行存储,比如文件的创建者、文件的创建日期、文件的大小等等。这种存储文件元信息的区域就叫做inode,中文译为“索引节点”。
inode是一个结构体,每一个文件都有对应的inode,里面包含了与该文件相关的一些信息。使用命令 ls -li 可以查看到每个文件的inode号。
ext2文件系统中inode结构体内容如下,下列内容看个大概,了解其内部存储哪些文件属性即可。
/*
* Structure of an inode on the disk
*/
struct ext2_inode
{
__le16 i_mode;
/* File mode */
__le16 i_uid;
/* Low 16 bits of Owner Uid */
__le32 i_size;
/* Size in bytes */
__le32 i_atime;
/* Access time */
__le32 i_ctime;
/* Creation time */
__le32 i_mtime;
/* Modification time */
__le32 i_dtime;
/* Deletion Time */
__le16 i_gid;
/* Low 16 bits of Group Id */
__le16 i_links_count; /* Links count */
__le32 i_blocks;
/* Blocks count */
__le32 i_flags;
/* File flags */
union
{
struct
{
__le32 l_i_reserved1;
} linux1;
struct
{
__le32 h_i_translator;
} hurd1;
struct
{
__le32 m_i_reserved1;
} masix1;
} osd1;
/* OS dependent 1 */
__le32 i_block[EXT2_N_BLOCKS]; /* Pointers to blocks */
__le32 i_generation;
/* File version (for NFS) */
__le32 i_file_acl; /* File ACL */
__le32 i_dir_acl; /* Directory ACL */
__le32 i_faddr;
/* Fragment address */
union
{
struct
{
__u8
l_i_frag;
/* Fragment number */
__u8
l_i_fsize; /* Fragment size */
__u16
i_pad1;
__le16 l_i_uid_high;
/* these 2 fields
*/
__le16 l_i_gid_high;
/* were reserved2[0] */
__u32
l_i_reserved2;
} linux2;
struct
{
__u8
h_i_frag;
/* Fragment number */
__u8
h_i_fsize; /* Fragment size */
__le16 h_i_mode_high;
__le16 h_i_uid_high;
__le16 h_i_gid_high;
__le32 h_i_author;
} hurd2;
struct
{
__u8
m_i_frag;
/* Fragment number */
__u8
m_i_fsize; /* Fragment size */
__u16
m_pad1;
__u32
m_i_reserved2[2];
} masix2;
} osd2;
/* OS dependent 2 */
};
/*
* Constants relative to the data blocks
*/
#define EXT2_NDIR_BLOCKS
12
#define EXT2_IND_BLOCK
EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK
(EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK
(EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS
(EXT2_TIND_BLOCK + 1)
知识点1:
Linux下文件的存储是属性和内容分开存储的。Linux下,保存文件属性的集合叫做inode,一个文件对应一个inode,inode内有一个唯一的标识符,叫做inode号。
知识点2:
文件名属性并未纳入到inode结构体中,inode的大小一般为128字节或者256字节,任何文件的内容大小可以不同,但是属性的大小一定是相同的。
现在,已经知道硬盘是典型的块设备,操作系统读取硬盘数据的时候,基本单位是块。分区也是由块组成的,但是块在分区中可不是随便排布的,还有就是inode又是放在哪些块中的,这些就是文件系统来进行管理的。
3.ext2文件系统
3.1 宏观认识
要想在硬盘上存储文件,必须先把硬盘格式化为某种格式的文件系统才能存储文件。文件系统的目的就是组织和管理硬盘中的文件。
在Linux系统中,最常见的是ext系列的文件系统。其早期版本为ext2,后来又发展处ext3和ext4。ext3和ext4是对ext2进行了增强,但是其核心设计并没有发生变化,所以下列以ext2来进行介绍。
ext2文件系统将整个磁盘分为若干个分区(partition),将每个分区划分为若干个相同大小的块组(block group),如下图所示。只要能管理一个分区就能管理所有分区,也就能管理所有磁盘文件。
上图中启动块(Boot Sector)的大小是确定的,为1KB,由PC标准规定,用来存储磁盘分区信息和启动信息,任何文件系统都不能修改启动块。启动块之后才是ext2文件系统的开始。
3.2 Block Group
ext2⽂件系统会根据分区的大小划分为数个Block Group。而每个Block Group都有着相同的结构组成。
3.2.1 数据区(Data Blocks)
Data Blocks就是数据区,存放文件内容,里面就是一个一个4KB的数据块。根据不同的文件类型由一下几种情况:
对于普通文件,文件的内容存储在数据块中。
对于目录,该目录下的所有文件名和目录名存储在该目录文件的数据块中,除了文件名外,ls -l 命令看到的其他信息都保存在文件的inode中。
数据块号按照分区划分,不可以跨分区。
知识点1:
一个文件的文件名没有保存在inode中,而是将该文件的文件名和该文件的inode的映射关系保存在该文件所处的的目录的数据块中。
所以在磁盘中存储没有目录的概念,而是所有的文件类型以及目录文件在磁盘中都保存为一个inode 和对应的数据块中。
3.2.2 i节点表(inode Table)
inode Table 中也是一个一个4KB的块,每个文件的属性信息就存储在inode Table的块中。一个文件一个inode结构体对象,一个inode对象128字节,一个4KB的块可以存储32个inode对象。
inode编号以分区为单位,整体划分,不可以跨分区。
3.2.3 块位图(Block Bitmap)
Block Bitmap中记录着Data Block中哪些数据块已经被占用,哪些数据块没有被占用。一个数据块被占用用一个bit位来记录,当该bit位置1,表示该bit位映射的数据块被占用,当该bit位置0,表示该bit位映射的数据未被占用。
3.2.4 inode位图(Inode Bitmap)
Inode Bitmap中记录着Inode Table中哪些inode被使用,哪些inode空闲可用。原理与Block Bitmap相同。
3.2.5 GDT(Group Descriptor Table)
GDT叫做块组描述符表,描述块组属性信息,整个分区分成多少个块组就对应有多少个块组描述符。每个块组描述符存储一个块组的描述信息。如这个块组中从哪里开始是inode Table,从哪里开始是Data Block,空闲的inode和数据块还有多少个等等。块组描述符在在每个块组的开头都有一份拷贝。
// 磁盘级blockgroup的数据结构
/*
* Structure of a blocks group descriptor
*/
struct ext2_group_desc
{
__le32 bg_block_bitmap;
/* Blocks bitmap block */
__le32 bg_inode_bitmap;
/* Inodes bitmap */
__le32 bg_inode_table;
/* Inodes table block*/
__le16 bg_free_blocks_count;
/* Free blocks count */
__le16 bg_free_inodes_count;
/* Free inodes count */
__le16 bg_used_dirs_count; /* Directories count */
__le16 bg_pad;
__le32 bg_reserved[3];
};
3.2.6 超级块(Super Block)
存放文件系统本身的结构信息,描述整个分区的文件系统信息。记录的信息主要有:block和inode的总量,未使用的block和inode的数量,一个block和inode的大小,最近一次挂载的时间,最近一次写入数据的时间,最近一次检验磁盘的时间等其他文件系统的相关信息。Super Block的信息被破坏,可以说整个分区的文件系统结构就被破坏了。
/*
* Structure of the super block
*/
struct ext2_super_block
{
__le32 s_inodes_count;
/* Inodes count */
__le32 s_blocks_count;
/* Blocks count */
__le32 s_r_blocks_count;
/* Reserved blocks count */
__le32 s_free_blocks_count;
/* Free blocks count */
__le32 s_free_inodes_count;
/* Free inodes count */
__le32 s_first_data_block; /* First Data Block */
__le32 s_log_block_size;
/* Block size */
__le32 s_log_frag_size;
/* Fragment size */
__le32 s_blocks_per_group; /* # Blocks per group */
__le32 s_frags_per_group; /* # Fragments per group */
__le32 s_inodes_per_group; /* # Inodes per group */
__le32 s_mtime;
/* Mount time */
__le32 s_wtime;
/* Write time */
__le16 s_mnt_count;
/* Mount count */
__le16 s_max_mnt_count;
/* Maximal mount count */
__le16 s_magic;
/* Magic signature */
__le16 s_state;
/* File system state */
__le16 s_errors;
/* Behaviour when detecting errors */
__le16 s_minor_rev_level; /* minor revision level */
__le32 s_lastcheck;
/* time of last check */
__le32 s_checkinterval;
/* max. time between checks */
__le32 s_creator_os;
/* OS */
__le32 s_rev_level;
/* Revision level */
__le16 s_def_resuid;
/* Default uid for reserved blocks */
__le16 s_def_resgid;
/* Default gid for reserved blocks */
__le32 s_first_ino;
/* First non-reserved inode */
__le16
s_inode_size;
/* size of inode structure */
__le16 s_block_group_nr;
/* block group # of this superblock */
__le32 s_feature_compat;
/* compatible feature set */
__le32 s_feature_incompat;
/* incompatible feature set */
__le32 s_feature_ro_compat;
/* readonly-compatible feature set */
__u8
s_uuid[16];
/* 128-bit uuid for volume */
char
s_volume_name[16]; /* volume name */
char
s_last_mounted[64];
/* directory where last mounted */
__le32 s_algorithm_usage_bitmap; /* For compression */
/*
* Performance hints. Directory preallocation should only
* happen if the EXT2_COMPAT_PREALLOC flag is on.
*/
__u8
s_prealloc_blocks; /* Nr of blocks to try to preallocate*/
__u8
s_prealloc_dir_blocks; /* Nr to preallocate for dirs */
__u16
s_padding1;
/*
* Journaling support valid if EXT3_FEATURE_COMPAT_HAS_JOURNAL set.
*/
__u8
s_journal_uuid[16]; /* uuid of journal superblock */
__u32
s_journal_inum;
/* inode number of journal file */
__u32
s_journal_dev;
/* device number of journal file */
__u32
s_last_orphan;
/* start of list of inodes to delete */
__u32
s_hash_seed[4];
/* HTREE hash seed */
__u8
s_def_hash_version; /* Default hash version to use */
__u8
s_reserved_char_pad;
__u16
s_reserved_word_pad;
__le32 s_default_mount_opts;
__le32 s_first_meta_bg;
/* First metablock block group */
__u32
s_reserved[190];
/* Padding to the end of the block */
};
知识点1:
超级块虽然是描述整个分区的文件系统信息,但是在每个块组的开头都有一份拷贝(第一个块组必须有,后面的块组可以没有)。为了保证文件系统在磁盘部分扇区出现物理问题的情况下还能正常工作,就必须保证文件系统的super block信息在这种情况下也能正常访问。所以一个文件系统的super block会在多个block group中进行备份,这些super block区域的内容都是相同一致的。
3.3 inode 和 Data Block的映射(简单说明)
inode内部存在 __le32 i_block[EXT2_N_BLOCKS];EXT2_N_BLOCKS=15。就是用来进行inode和block映射的。
前12个直接存储的是数据块的块编号,一级间接块索引表指针,存储一个指针指向一个块,假设一个块编号4个字节,则这个块中存储了4KB/4 = 1024个块的编号。同理,二级间接块索引表指针,指向的块,存储的是4KB/4 = 1024个块指针,每个指针指向一个块,指向的块中存储4KB/4 = 1024个块的编号。三级间接块索引表指针同理。
3.4 目录与文件名
目录也是文件,但是磁盘上没有目录的概念,只有 文件属性 + 文件内容 的概念。目录的属性是存储在inode当中,目录文件的内容是:该目录下所有的文件与inode号的映射关系。
访问文件,必须打开当前目录,根据文件名获得对应的inode号,然后进行文件访问。但是如何找到当前目录文件呢?当前目录文件的文件名和inode存储在上级目录文件中,所以要打开上级目录文件。所以访问任何一个文件的时候,都要从根目录开始做路径解析。
下面使用一段代码来查看当前目录文件的内容。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <dirent.h>
#include <sys/types.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
if (argc != 2)
{
fprintf(stderr, "Usage: %s <directory>\n", argv[0]);
exit(EXIT_FAILURE);
}
DIR *dir = opendir(argv[1]); // 系统调⽤,⾃⾏查阅
if (!dir)
{
perror("opendir");
exit(EXIT_FAILURE);
}
struct dirent *entry;
while ((entry = readdir(dir)) != NULL)
{
// 系统调⽤,⾃⾏查阅
// Skip the "." and ".." directory entries
if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
{
continue;
}
printf("Filename: %s, Inode: %lu\n", entry->d_name, (unsigned long)entry->d_ino);
}
closedir(dir);
return 0;
}
3.5 路径解析
访问任何一个文件,如 /home/whb/code/test/test/test.c ,都要从根目录开始,一次打开每一个目录,根据目录名,依次访问每个目录下指定的目录,知道访问到test.c,这个过程叫做Linux路径解析。访问文件必须要有路径。
根目录无需查找,系统开机之后就必须知道。
知识点1:
路径是谁提供的?
访问文件,都是通过 指令/软件 进行访问,本质都是进程进行访问,进程有CWD,所以路径是进程提供的。
3.6 路径缓存
访问任何文件如果都从/目录开始的话,原则上是可以的,但是每次访问都要打开若干个目录文件,等于是要做若干次磁盘IO操作,这样效率太慢了,所以Linux就会缓存历史路径结构。
打开一个文件时,在内核中,Linux系统就会将该路径结构缓存在内存中,下次再次访问该文件时,就不需要进行磁盘IO,而是直接从路径缓存中获取路径。
Linux中路径缓存结构是一颗多叉树的结构,在内核中维护树状路径结构的内核结构体叫做:struct dentry。
每个文件都要有对应的dentry节点,包括普通文件。这样所有被打开的文件,就可以在内存中形成整个树形结构。
整个树形节点也同时隶属于LRU(Last Recently Used)链表结构中,进行节点淘汰。
整个树形节点也同时隶属于Hash表,方便快速查找。
更重要的是,这个树形结构,整体构成了Linux的路径缓存结构,打开访问任何文件,都要先在这棵树下根据路径进行查找,找到就返回属性inode和内容,没有找到就从磁盘加载路径,添加到dentry树形结构中,缓存新路径。
struct dentry
{
atomic_t d_count;
unsigned int d_flags;
/* protected by d_lock */
spinlock_t d_lock;
/* per dentry lock */
struct inode *d_inode; //该文件对应的inode
/* Where the name belongs to - NULL is
* negative */
/*
* The next three fields are touched by __d_lookup. Place them here
* so they all fit in a cache line.
*/
struct hlist_node d_hash; //该文件对应的哈希节点
/* lookup hash list */
struct dentry *d_parent;
/* parent directory */
struct qstr d_name;
struct list_head d_lru;
/* LRU list */
/*
* d_child and d_rcu can share memory
*/
union
{
struct list_head d_child;
/* child of parent list */
struct rcu_head d_rcu;
} d_u;
struct list_head d_subdirs; /* our children */
struct list_head d_alias;
/* inode alias list */
unsigned long d_time;
/* used by d_revalidate */
struct dentry_operations *d_op;
struct super_block *d_sb;
/* The root of the dentry tree */
void *d_fsdata;
/* fs-specific data */
#ifdef CONFIG_PROFILING
struct dcookie_struct *d_cookie; /* cookie, if any */
#endif
int d_mounted;
unsigned char d_iname[DNAME_INLINE_LEN_MIN];
/* small names */
};
3.7 挂载分区
从之前来看,我们只要知道inode号就能去对应分区中的inode Table中找文件的属性,然后就能在Data Blocks中找到文件的内容。但是inode是不能跨分区的,如何才能知道在哪一个分区中呢?
分区一定是要和一个特定一个目录进行关联,通过进入这个目录,就相当于进入这个分区,这个概念就叫做挂载。
使用 df -h 命令可以查看系统中磁盘分区的挂载情况。
可以看到该电脑中只用一块磁盘被挂载了,并且挂载到了/根目录上。
下面做一个实验,模拟分区挂载和卸载的过程。
制作一个5M的磁盘块,来当做一个分区。
将该磁盘块格式化为ext4文件系统。
此时使用 df -h 命令并没有看到该分区被挂载。
挂载分区 ./disk.img 到 ./dir 目录中。
使用 umount 命令卸载分区。
分区写入文件系统是无法直接使用的,需要和指定的目录关联,进行挂载才能使用。所以,可以根据访问目标文件的“路径前缀”准确判断在哪一个分区。
知识点1:
/dev/loop0 在Linux系统中代表第一个循环设备(loop device)。循环设备也被称为回环设备或者loopback设备,是一种伪设备(pseudo-device),它允许将文件作为块设备(block device)来使用,磁盘就是一种块设备。这种机制使得可以将文件(比如ISO镜像文件)挂载(mount)为文件系统,就可以把它们当做物理硬盘分区或者外部存储设备一样。
3.9 文件系统总结
一个进程PCB中,fs_struct中存储根目录和当前文件的当前目录的信息,file_struct中有一个文件描述符表,其中是该进程打开的文件,文件描述符表中的指针指向每个被打开文件的files结构体,在files结构体中有存储该文件的操作方式结构体files_operations,用f_op指针指向。dentry_operations和inode_operations同理。
4.软硬链接
4.1 软链接
软链接是通过名字引用另外一个文件,但实际上,新的文件和被引用的文件的inode号不同,所以软链接的文件是一个独立的文件,软链接文件内容中保存着目标文件的路径。应用常见上可以想象成Windows系统中,桌面上的快捷方式。在Linux系统中,在当前目录中软链接一个比较深的目录的可执行文件,就可以在当前目录下通过软链接的形式执行目录比较深的可执行文件。
用 code-soft 与 code.c 进行软链接。
向code.c中写入一段字符串,并且查看code-soft,能查看到code.c当中的内容。
删除软链接文件可以使用 rm 命令,也可以使用 unlink 命令。
4.2 硬链接
硬链接本质上不是一个独立的文件,因为它没有独立的inode号。本质上是一组新的文件名和目标 inode号的映射关系。
硬链接可以用于对目标文件进行拷贝,上述图中文件属性显示的数字“2”就是该文件的硬链接数。但是本质上在磁盘中只有一份内容,这里也是采用引用计数的方式用于拷贝的。
目录中的 . 文件就是该目录的硬链接文件,所以一个目录文件最少有两个硬链接数,当目标目录下创建新的目录时,新的目录的 .. 文件也是目标目录的硬链接,所以一个目录的硬链接数 - 2就是该目录下的目录个数。
用户不能给目录建立硬链接,但是可以给目录建立软链接。. 和 .. 是Linux系统自己给目录建立的硬链接。
在Linux系统中路径是一种树状结构,如果允许用户建立目录的硬链接,就容易形成路径环问题,这样通过路径查找某一个文件的时候就可能进入死循环,而 . 和 .. 是Linux系统做过特殊处理的。. 和 .. 是用于用户方便命令行操作的。