0%

Linux账号与文件权限

Linux 中的账号与组、文件权限、文件描述符。


用户账号与组

UID与GID

Linux 系统根据 ID 来识别不同的账号,每个登录的用户至少都会取得两个 ID :一个是用户 ID (User ID ,简称 UID),一个是组 ID (Group ID ,简称 GID)。

文件也是利用 UID 与 GID 辨别它的拥有者与组。使用 ls -l 查看文件信息时,系统会依据 /etc/passwd/etc/group 的内容, 找到 UID / GID 对应的账号与组名再显示出来。

账号相关文件

passwd文件

查看 /etc/passwd 文件内容:

1
2
3
4
5
6
# root用户
$ cat /etc/passwd | grep ^root
root:x:0:0:root:/root:/bin/zsh
# 普通用户
$ cat /etc/passwd | grep ^sannaha
sannaha:x:1000:1000:演示用户:/opt/home/sannaha:/bin/sh

passwd 文件中,每行对应一个用户,每行信息被 : 分隔为以下 7 个部分:

  1. 账号名称
  2. 口令:早期 Unix 系统将口令存储在 passwd 文件中,现在出于安全考虑改为存储在 /etc/shadow 中,此处只留下 x
  3. UID:用户 ID,其中 root 用户的 UID 为 0。在 CentOS 7 中,通常使用 1 ~ 999 作为系统账号,1000 及以上为普通账号;
  4. GID:组 ID,其中 root 组的 GID 为 0
  5. GECOS 字段:可以存放用户的描述信息;
  6. 主目录:用户的家目录,登录后所在的工作目录,也是使用 cd ~ 时进入的目录;
  7. Shell:登录后使用的 Shell,默认为 bash。

shadow文件

早期 Unix 系统将口令存储在 /etc/passwd 中,但由于程序需要读取 passwd 来了解不同账号的权限,因此 passwd 文件必须配置为所有用户可读(-rw-r--r--),存在安全问题。因此后来将口令改为存储在 /etc/shadow 文件中,可以使用 root 用户查看:

1
2
3
4
5
6
# root用户
$ cat /etc/shadow | grep ^root
root:$6$fSEeQmGG7/TRdUf7$yARd2J6gwlVSnbJJWIvelPVGja2viBV8DiTD1GJPmNdwgHY/GPYJP4wLjkPkHjCI/sQdIl8O1sFBqGUfWXih0/::0:99999:7:::
# 普通用户
$ cat /etc/shadow | grep ^sannaha
sannaha:!!:18848:0:99999:7:::

shadow 文件中,同样是每行对应一个用户,每行信息被 : 分隔为以下 9 个部分:

  1. 账号名称
  2. 口令:真正存储账号口令的字段,存储的是加密后的口令。
  3. 最近口令变更日期:这个日期以距离 1970 年 1 月 1日的天数来表示,比如 18848 代表 2021 年 8 月 9 日。
  4. 口令不可变更的天数:用来设置账号口令在变更后多少天才能再次变更,防止用户频繁修改密码。
  5. 口令需要再次变更的天数:用来设置账号口令在变更后多少天内需要再次变更,经常修改密码或许是个好习惯。
  6. 口令需要再次变更前的警告天数:用来设置账号口令在需要变更日期前多少天显示警告提示。
  7. 口令过期后的宽限天数:在过了第 5 字段指定的口令需要再次变更的天数后,口令进入过期状态,在宽限天数之内如果使用口令尝试登录系统时会被系统强制要求修改口令。如果过了宽限天数,口令就会失效,再也无法使用该口令登录系统。注意,此时账号仍然存在。
  8. 账号失效日期:和第 3 字段指定的最近口令变更日期一样,账号失效日期同样以距离 1970 年 1 月 1日的天数来表示。过了账号失效日期后,无论口令是否过期,账号都无法使用。

Tips:计算口令变更日期

1
2
$ date -d "+18848 day 19700101" +"%F" 
2021-08-09

group文件

查看 /etc/group 文件内容:

1
2
3
4
$ cat /etc/group | grep ^root
root:x:0:
$ cat /etc/group | grep ^sannaha
sannaha:x:1000:

group 文件中,每行对应一个组,每行信息被 : 分隔为以下 4 个部分:

  1. 组名
  2. 组口令:该配置通常是提供给群组管理员使用,一般不配置。组口令现在存储到 /etc/gshadow 中,此处只留下 x
  3. GID:组 ID,对应 passwd 中的第 4 个字段;
  4. 加入该组的账号名称:一个账号可以加入多个组,一个组可以包含多个账号,账号名称之间用 , 分隔。

gshadow文件

查看 /etc/gshadow 文件内容:

1
2
3
4
$ cat /etc/gshadow | grep ^root
root:::
$ cat /etc/gshadow | grep ^sannaha
sannaha:!::

gshadow 文件中,每行对应一个组,每行信息被 : 分隔为以下 4 个部分:

  1. 组名
  2. 组口令:真正存储组口令的字段,存储的是加密后的口令;
  3. 组管理员账号
  4. 加入该组的账号名称:与 group 文件中相同,一个账号可以加入多个组,一个组可以包含多个账号,账号名称之间用 , 分隔。

文件关系

这些文件关联的重点是 /etc/passwd,可以使用一个简单的图示来表示 UID / GID /口令之间的关系:

账号相关文件之间的关系

RUID、EUID与SUID

  • RUID:真实用户 ID,当用户登录一个 UNIX 系统后就唯一确定,也就是登录用户的 UID。
  • EUID:有效用户 ID,用于系统决定用户对文件资源的访问权限。
  • SUID:设置用户 ID,它与文件绑定而非用户,用于对外权限的开放(提权)。对于某一个文件,当设置文件权限后,可以使本没有相应权限的用户执行这个程序时,可以访问没有访问权限的资源。同理,SGID 也是这个意思,详见 *十二位文件权限 *

用户账号

用户登录流程

用户登录的时候,Linux 系统做了以下工作:

  1. 查看/etc/passwd中是否有该账号。如果没有则退出,如果有则去/etc/group将该账号对应的 UID 与 GID 读出来。此外,该账号的 home 目录与 shell 配置也一并读出来
  2. 核对口令表。Linux 会去/etc/shadow找出对应的账号与 UID,然后核对一下输入的口令与里头的口令是否相符
  3. 如果一切都 OK 的话,就进入 Shell 控管的阶段

/etc/passwd文件结构

//TODO 第十四章、Linux 账号管理与 ACL 权限配置

添加用户账号

添加用户账号,会在系统中创建一个新账号,为新账号分配 UID、GID,并执行创建主目录和邮件目录,设置登录 Shell 等操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 格式
useradd [选项] 用户名
useradd -D
useradd -D [选项]

选项:
-b, --base-dir BASE_DIR 设置存放新账户主目录的基目录
-c, --comment COMMENT 新账户的 GECOS 字段,可以存放用户的描述信息
-d, --home-dir HOME_DIR 将指定目录作为新账户的主目录
-D, --defaults 显示默认的 useradd 配置
-e, --expiredate EXPIRE_DATE 设置新账户的过期日期
-f, --inactive INACTIVE 设置新账户的密码不活动期
-g, --gid GROUP 设置新账户主组的名称或 ID
-G, --groups GROUPS 设置新账户的附加组列表
-m, --create-home 创建用户的主目录
-M, --no-create-home 不创建用户的主目录
-p, --password PASSWORD 设置新账户加密后的密码。此处设置的并非明文密码,而是对密码加密后的结果,这是在故意刁难,因为并不推荐以这种方法设置密码,推荐的是添加新账户后使用 passwd 来设置密码
-r, --system 创建一个系统账户
-R, --root CHROOT_DIR chroot 到的目录
-s, --shell SHELL 新账户的登录 shell
-u, --uid UID 新账户的用户 ID
-U, --user-group 创建与用户同名的组
-Z, --selinux-user SEUSER 为 SELinux 用户映射使用指定 SEUSER

-d 目录 指定用户主目录,如果此目录不存在,则同时使用-m选项,可以创建主目录。
-g 用户组 指定用户所属的用户组。
-G 用户组,用户组 指定用户所属的附加组。
-s Shell文件 指定用户的登录Shell。
-u 用户号 指定用户的用户号,如果同时有-o选项,则可以重复使用其他用户的标识号。

示例:

1
2
3
4
$ useradd -b /opt/home -c "普通演示用户" -s /bin/sh sannaha
$ cat /etc/passwd | grep sannaha
sannaha:x:1000:1000:普通演示用户:/opt/home/sannaha:/bin/sh

删除用户账号

删除用户账号就是要将/etc/passwd等系统文件中的该用户记录删除,必要时还删除用户的主目录。

删除一个已有的用户账号使用userdel命令,其格式如下:

1
userdel 选项 用户名

常用的选项是 -r,它的作用是把用户的主目录一起删除。

例如:

1
# userdel -r sam

此命令删除用户sam在系统文件中(主要是/etc/passwd, /etc/shadow, /etc/group等)的记录,同时删除用户的主目录。

修改用户账号

修改用户账号就是根据实际情况更改用户的有关属性,如用户号、主目录、用户组、登录Shell等。

修改已有用户的信息使用usermod命令,其格式如下:

1
usermod 选项 用户名

常用的选项包括-c, -d, -m, -g, -G, -s, -u以及-o等,这些选项的意义与useradd命令中的选项一样,可以为用户指定新的资源值。

另外,有些系统可以使用选项:-l 新用户名

这个选项指定一个新的账号,即将原来的用户名改为新的用户名。

例如:

1
# usermod -s /bin/ksh -d /home/z –g developer sam

此命令将用户sam的登录Shell修改为ksh,主目录改为/home/z,用户组改为developer。

修改用户账号口令

用户管理的一项重要内容是用户口令的管理。用户账号刚创建时没有口令,但是被系统锁定,无法使用,必须为其指定口令后才可以使用,即使是指定空口令。

指定和修改用户口令的Shell命令是passwd。超级用户可以为自己和其他用户指定口令,普通用户只能用它修改自己的口令。命令的格式为:

1
passwd 选项 用户名

可使用的选项:

  • -l 锁定口令,即禁用账号。
  • -u 口令解锁。
  • -d 使账号无口令。
  • -f 强迫用户下次登录时修改口令。

如果默认用户名,则修改当前用户的口令。

例如,假设当前用户是sam,则下面的命令修改该用户自己的口令:

1
2
3
4
$ passwd 
Old password:******
New password:*******
Re-enter new password:*******

如果是超级用户,可以用下列形式指定任何用户的口令:

1
2
3
# passwd sam 
New password:*******
Re-enter new password:*******

普通用户修改自己的口令时,passwd命令会先询问原口令,验证后再要求用户输入两遍新口令,如果两次输入的口令一致,则将这个口令指定给用户;而超级用户为用户指定口令时,就不需要知道原口令。

为了系统安全起见,用户应该选择比较复杂的口令,例如最好使用8位长的口令,口令中包含有大写、小写字母和数字,并且应该与姓名、生日等不相同。

为用户指定空口令时,执行下列形式的命令:

1
# passwd -d sam

此命令将用户 sam 的口令删除,这样用户 sam 下一次登录时,系统就不再允许该用户登录了。

passwd 命令还可以用 -l(lock) 选项锁定某一用户,使其不能登录,例如:

1
# passwd -l sam

这里可能新建组:#groupadd group及groupadd adm

文件权限

查看文件权限

我们使用 ls -l 命令查看文件列表时,可以看到显示了 7 列信息,其中第 1 列信息中包含了文件权限:

1
2
3
4
5
6
7
8
9
10
# 第一列表示文件权限等信息
# 第二列表示链接数量,表示链接到该文件 inode 号码的文件数量
# 第三列表示拥有者
# 第四列表示所属群组
# 第五列表示文档容量大小,单位字节
# 第六列表示文档最后修改时间
# 第七列表示文档名称,以点.开头的是隐藏文档
$ ls -l
drwxr-xr-x. 2 root root 6 8月 5 15:47 dir
-rw-r--r--. 1 root root 0 8月 5 15:47 file

第 1 列信息中的首位表示的是文件类型:

  • -:普通文件;
  • b:块设备文件;
  • c:字符设备文件;
  • d:目录;
  • l:链接文件;
  • p:命名管道文件;
  • s:套接字文件。

2 ~ 10 位代表的是文件权限,每 3 个一组表示不同的用户所对应权限(u/g/o):

  • 主权限(User):当前文件所属用户的权限,在 Linux 中每一个文件都有所属的用户。
  • 组权限(Group):当前文件所属用户所在组的其它成员的权限。
  • 其它用户权限(Other):跟文件所属用户不同组的其它用户的权限。

文件权限信息

每一组包含的 3 位字符分别对应读、写、执行(r/w/x)。用二进制表示的话,读(100)为 4,写(010)为 2,执行(001)为 1

1
2
3
4
5
6
7
8
rwx	7	# 具备读写执行权限(421) 
rw- 6 # 具备读写权限(420)
r-x 5 # 具备读和执行权限(401)
r-- 4 # 具备只读权限(400)
-wx 3 # 具备写和执行权限(021)
-w- 2 # 具备写权限(020)
--x 1 # 具备执行权限(001)
--- 0 # 不具备读写执行权限(000)

第 1 列信息末尾的 . 表示文件带有 SELinux 的安全上下文,在开启了 SELinux 模式下创建文件就会带有这个标记。如果使用的是 ACL 权限管理则会带有 + 标记。

十二位权限

在 Linux 的实现中,文件权限其实使用了 12 个二进制位来表示。除了常用的 9 位权限外,还有 3 位特殊权限经常用于提权。对应 u/g/o,有 SUID/SGID/sticky:

十二位权限 11 10 9 8 7 6 5 4 3 2 1 0
含义 S G T r w x r w x r w x
说明 SUID SGID sticky
  • SUID(Set User ID)位:冒险位,执行该文件时,获得该文件属主账号对应的身份。与文件绑定,用字母表示是 Ss,用数字表示是 4。举例说明,普通用户可以使用 passwd 命令修改自己的密码,就是用到了 SUID,这样就相当于 root 用户在执行该命令。
  • SGID(Set Group ID)位:强制位,执行该文件时,获得该文件属组账号对应的身份与文件绑定。用字母表示是 Ss,用数字表示是 2。举例说明,如果一个目录拥有强制位,那么任何用户在该目录里所创建的任何文件的属组都会继承该目录的属组。
  • sticky 位:粘滞位,执行该文件时,获得其他用户组账号对应的身份。用字母表示是 Tt,用数字表示是 1。举例说明,如果一个公共的目录拥有粘滞位,那么该目录下的文件只能被 root 和文件创建者所删除,每个用户只能管理自己的文件。

在以十个字符表示文件权限信息时,SUID 位 / SGID 位 / sticky 位体现在 u/g/o 的执行权限位上:如果该位置上已有执行权限,则以小写字母的方式表示;否则,以大写字母表示:

1
2
3
4
5
6
7
-rwxr-xr-x # 表示没有设置suid/sgid/sticky
-rwsr-xr-x # 表示设置了suid,且属主用户有执行权限
-rwSr-xr-x # 表示设置了suid,但属主用户没有执行权限
-rwxr-sr-x # 表示设置了sgid,且属组用户有执行权限
-rwxr-Sr-x # 表示设置了sgid,但属组用户没有执行权限
-rwxr-xr-t # 表示设置了sticky,且其他用户有执行权限
-rwxr-xr-T # 表示设置了sticky,但其他用户没有执行权限

权限管理

修改文件权限

使用 chmod 修改文件权限:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 创建文件,查看文件权限
$ touch file
$ ls -l
-rw-r--r--. 1 root root 0 8月 9 10:48 file

# 设置文件权限,用数字表示权限
# 数字长度可为1~4位,对应十二位文件权限中的特殊权限/u/g/o
# 长度为1位时设置的是o,2位时设置的是g/o,3位时设置的是u/g/o,4位时设置的是特殊权限/u/g/o
# 数字的取值范围为0~7,对应权限的各种组合
# 普通权限中r=4,w=2,x=1,特殊权限中suid=4,sgid=2,sticky=1
$ chmod 755 file
$ ls -l
-rwxr-xr-x. 1 root root 0 8月 9 10:48 file

$ chmod 7755 file
$ ls -l
-rwsr-sr-t. 1 root root 0 8月 9 10:48 file

# 设置文件权限,用字母表示权限
# 属主/属组/其他用户/所有身份用u/g/o/a表示
# 读/写/执行用r/w/x或X表示
# SUID/SGID/sticky用s/s/t表示
# 权限的新增/删除/设定用+/-/=表示
$ chmod u=rwx,go=rx file
-rwxr-xr-x. 1 root root 0 8月 9 10:48 file

$ chmod u=rwxs file
-rwsr-xr-x. 1 root root 0 8月 9 10:48 file

$ chmod u=rws file
-rwSr-xr-x. 1 root root 0 8月 9 10:48 file

$ chmod a=rwx file
-rwxrwxrwx. 1 root root 0 8月 9 10:48 file

$ chmod go-w file
-rwxr-xr-x. 1 root root 0 8月 9 10:48 file

修改文件拥有者

chown:变更文件拥有者,要被改变的组名必须要在 /etc/passwd 文件中存在

1
2
3
4
5
6
7
8
# 变更文件拥有者
chown zookeeper /var/lib/zookeeper/*

# 变更文件的拥有者和属组
chown zookeeper:zookeeper /var/lib/zookeeper/*

# 变更目录下及子目录下的所有文件的拥有者
chown -R zookeeper /var/lib/zookeeper/

修改文件属组

使用 chgrp 变更文件属组,属组要在 /etc/group 文件中存在:

1
2
3
4
5
# 变更文件属组
chgrp zookeeper /var/lib/zookeeper/*

# 变更目录下及子目录下的所有文件的属组
chown -R zookeeper /var/lib/zookeeper/

文件描述符

文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述符展开。

文件描述符概念

Linux 系统中,把一切都看做是文件,当进程打开现有文件或创建新文件时,内核向进程返回一个文件描述符,文件描述符就是内核为了高效管理已被打开的文件所创建的索引,用来指向被打开的文件,所有执行I/O操作的系统调用都会通过文件描述符。

文件描述符、文件、进程间的关系

  1. 描述:
  • 每个文件描述符会与一个打开的文件相对应
  • 不同的文件描述符也可能指向同一个文件
  • 相同的文件可以被不同的进程打开,也可以在同一个进程被多次打开
  1. 系统为维护文件描述符,建立了三个表:

文件描述符-三张表

  1. 通过这三个表,认识文件描述符:

文件描述符-三张表认识文件描述符

  • 在进程 A 中,文件描述符 1 和 30 都指向了同一个打开的文件句柄 #23,这可能是该进程多次对执行打开操作;
  • 进程 A 中的文件描述符 2 和进程 B 的文件描述符 2 都指向了同一个打开的文件句柄 #73,这种情况有几种可能:①进程 A 和进程 B 可能是父子进程关系;②进程 A 和进程 B 打开了同一个文件,且文件描述符相同(低概率事件);③ A、B 中某个进程通过 UNIX 域套接字将一个打开的文件描述符传递给另一个进程。
  • 进程 A 的描述符 0 和进程 B 的描述符 3 分别指向不同的打开文件句柄,但这些句柄均指向 i-node 表的相同条目 #1936,换言之,指向同一个文件。发生这种情况是因为每个进程各自对同一个文件发起了打开请求。同一个进程两次打开同一个文件,也会发生类似情况。

文件描述符限制

文件描述符也是一种资源,于是就有了文件描述符限制的规定。

文件描述符-文件描述符限制

永久修改用户级限制时有三种设置类型:

  1. soft 指的是当前系统生效的设置值
  2. hard 指的是系统中所能设定的最大值
  3. - 指的是同时设置了 soft 和 hard 的值

检查某个进程的文件描述符

  1. 查看 nginx 进程 id:
1
2
3
4
$ ps -ef | grep nginx
root 9066 1 0 Jun16 ? 00:00:00 nginx: master process nginx
nginx 9068 9066 0 Jun16 ? 00:00:21 nginx: worker process
root 27010 26976 0 17:37 pts/0 00:00:00 grep --color=auto --exclude-dir=.bzr --exclude-dir=CVS --exclude-dir=.git --exclude-dir=.hg --exclude-dir=.svn --exclude-dir=.idea --exclude-dir=.tox nginx
  1. 查看进程限制,Max open files 可以看到最大文件描述符的数量限制为 1024 个:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ cat /proc/9066/limits 
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 3651 3651 processes
Max open files 1024 4096 files
Max locked memory 65536 65536 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 3651 3651 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us

# 统计该进程用了多少个文件描述符
$ ll /proc/9066/fd | wc -l
10

实际应用过程中,如果出现 Too many open files, 可以通过增大进程可用的文件描述符数量来解决,但很多时候,并不是因为进程可用的文件描述符过少,而是因为程序 bug 打开了大量的文件连接而没有释放。程序申请的资源在用完后及时释放,才是解决 Too many open files 的根本之道。

Inode

原文链接:*理解inode *

inode是什么

理解 inode,要从文件储存说起。

文件储存在硬盘上,硬盘的最小存储单位叫做扇区(Sector)。每个扇区储存 512 字节(相当于 0.5KB)。

操作系统读取硬盘的时候,不会一个个扇区地读取,这样效率太低,而是一次性连续读取多个扇区,即一次性读取一个(block)。这种由多个扇区组成的块,是文件存取的最小单位。块的大小,最常见的是 4KB,即连续八个 sector 组成一个 block。

文件数据都储存在块中,那么很显然,我们还必须找到一个地方储存文件的元信息,比如文件的创建者、文件的创建日期、文件的大小等等。这种储存文件元信息的区域就叫做 inode,中文译名为”索引节点”。

每一个文件都有对应的 inode,里面包含了与该文件有关的一些信息。

inode的内容

inode 包含文件的元信息,具体来说有以下内容:

  • 文件的字节数;
  • 文件拥有者的 User ID;
  • 文件的 Group ID;
  • 文件的读、写、执行权限;
  • 文件的时间戳,共有三个:ctime 指 inode上一次变动的时间,mtime 指文件内容上一次变动的时间,atime 指文件上一次打开的时间;
  • 链接数,即有多少文件名指向这个 inode;
  • 文件数据 block 的位置。

查看文件的 inode 信息:

1
2
3
4
5
6
7
8
9
$ stat /bin/bash
File: ‘/bin/bash’
Size: 1089480 Blocks: 2128 IO Block: 4096 regular file
Device: fd02h/64770d Inode: 291512 Links: 1
Access: (0755/-rwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root)
Access: 2021-09-27 14:58:01.211373793 +0800
Modify: 2019-03-06 16:57:20.000000000 +0800
Change: 2019-10-17 14:03:06.567179709 +0800
Birth: -

总之,除了文件名以外的所有文件信息,都存在 inode 之中。至于为什么没有文件名,下文会有详细解释。

inode的大小

inode 也会消耗硬盘空间,所以硬盘格式化的时候,操作系统自动将硬盘分成两个区域。一个是数据区,存放文件数据;另一个是 inode区(inode table),存放 inode 所包含的信息。

每个 inode 节点的大小,一般是 128 字节或 256 字节。inode 节点的总数,在格式化时就给定,一般是每 1KB 或每 2KB 就设置一个 inode。假定在一块 1GB 的硬盘中,每个 inode 节点的大小为 128 字节,每 1KB 就设置一个 inode,那么 inode table 的大小就会达到 128MB,占整块硬盘的 12.8%。’’

查看每个硬盘分区的 inode 总数和已经使用的数量,可以使用 df -i 命令:

1
2
3
4
5
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
/dev/sdb1 781187840 138379 781049461 1% /app/data1
/dev/sdc1 781187840 137721 781050119 1% /app/data2
/dev/sdd1 781187840 138008 781049832 1% /app/data3

inode号码

每个 inode 都有一个号码,Unix/Linux 系统内部不使用文件名,而使用 inode 号码来识别文件。对于系统来说,文件名只是 inode 号码便于识别的别称或者绰号。

表面上,用户通过文件名,打开文件。实际上,系统内部这个过程分成三步:首先,系统找到这个文件名对应的 inode 号码;其次,通过 inode 号码,获取 inode 信息;最后,根据 inode 信息,找到文件数据所在的 block,读出数据。

使用 ls -i 命令,可以看到文件名对应的 inode 号码:

1
2
$ ls -i /etc/hosts
199 /etc/hosts

特殊文件

目录文件

Unix/Linux 系统中,目录(directory)也是一种文件。打开目录,实际上就是打开目录文件。

目录文件的结构非常简单,就是一系列目录项(dirent)的列表。每个目录项,由两部分组成:所包含文件的文件名,以及该文件名对应的inode号码。

使用 ls -i 命令,查看目录文件中文件名和 inode 号码:

1
2
3
4
5
$ ls -i /etc | grep hosts
5028 ghostscript
199 hosts
99 hosts.allow
100 hosts.deny

如果要查看目录中文件的详细信息,就必须根据 inode 号码访问 inode 节点,读取信息。使用 ls -l 命令列出目录文件中文件的详细信息:

1
2
3
4
5
$ ls -l /etc | grep hosts
drwxr-xr-x. 3 root root 18 Oct 12 2019 ghostscript
-rw-r--r--. 1 root root 2287 Apr 16 14:03 hosts
-rw-r--r--. 1 root root 370 Jun 7 2013 hosts.allow
-rw-r--r--. 1 root root 460 Jun 7 2013 hosts.deny

理解了上面这些知识,就能理解目录的权限。目录文件的读权限(r)和写权限(w),都是针对目录文件本身。由于目录文件内只有文件名和 inode 号码,所以如果只有读权限,只能获取文件名,无法获取其他信息,因为其他信息都储存在 inode 节点中,而读取 inode 节点内的信息需要目录文件的执行权限(x)。

硬链接

一般情况下,文件名和 inode 号码是“一一对应”关系,每个 inode 号码对应一个文件名。但是,Unix/Linux 系统允许多个文件名指向同一个 inode 号码。

这意味着:可以用不同的文件名访问同样的内容;对文件内容进行修改,会影响到所有文件名;但是,删除一个文件名,不影响另一个文件名的访问。这种情况就被称为硬链接(hard link)。

使用 ln 命令可以创建硬链接:

1
ln 源文件 目标文件

运行上面这条命令以后,源文件与目标文件的 inode 号码相同,都指向同一个 inode。inode 信息中有一项叫做“链接数”,记录指向该 inode 的文件名总数,这时就会增加 1。

反过来,删除一个文件名,就会使得 inode 节点中的”链接数”减 1。当这个值减到 0,表明没有文件名指向这个 inode,系统就会回收这个 inode 号码,以及其所对应 block 区域。

这里顺便说一下目录文件的“链接数”。创建目录时,默认会生成两个目录项:...。前者的 inode 号码就是当前目录的 inode 号码,等同于当前目录的硬链接;后者的 inode 号码就是当前目录的父目录的 inode 号码,等同于父目录的硬链接。所以,任何一个目录的硬链接总数,总是等于 2 加上它的子目录总数(含隐藏目录)。

软链接

除了硬链接以外,还有一种特殊情况。

文件 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 “链接数”不会因此发生变化。

使用 ln -s 命令可以创建软链接:

1
ln -s 源文文件或目录 目标文件或目录

inode的特殊作用

由于 inode 号码与文件名分离,这种机制导致了一些 Unix/Linux 系统特有的现象:

  1. 有时,文件名包含特殊字符,无法正常删除。这时,直接删除 inode 节点,就能起到删除文件的作用;
  2. 移动文件或重命名文件,只是改变文件名,不影响 inode 号码;
  3. 打开一个文件以后,系统就以 inode 号码来识别这个文件,不再考虑文件名。因此,通常来说,系统无法从 inode 号码得知文件名。

第 3 点使得软件更新变得简单,可以在不关闭软件的情况下进行更新,不需要重启。因为系统通过 inode 号码,识别运行中的文件,不通过文件名。更新的时候,新版文件以同样的文件名,生成一个新的 inode,不会影响到运行中的文件。等到下一次运行这个软件的时候,文件名就自动指向新版文件,旧版文件的 inode 则被回收。


参考资料