一、引言:Linux 为什么要控制默认权限?
在前两篇中,我们学习了 Linux 的文件属性、基本权限,以及如何使用 chmod、chown 和 chgrp 修改权限。
但还有两个问题值得继续思考:
- 使用
touch创建一个文件时,我们没有指定权限,为什么文件仍然会自动获得一组权限? - 在一个允许所有用户写入的公共目录中,为什么用户可以创建自己的文件,却不应该随意删除其他用户的文件?
第一个问题涉及 umask:新建文件和目录的默认权限如何产生。
第二个问题涉及 Sticky Bit(粘滞位):如何在允许共享的同时限制删除行为。
这两个知识点看似独立,实际上体现了 Linux 权限设计的两个目标:
- 创建资源时,默认权限不能过度开放。
- 共享资源时,不能只考虑协作便利,还要考虑资源保护。
二、umask:新建文件的权限是怎么来的?
2.1 先观察一个现象
在 Ubuntu 终端中执行:
bash
umask
假设输出:
text
0002
这表示当前 Shell 的文件创建权限掩码为 0002。
接下来创建一个文件和一个目录:
bash
touch test.txt
mkdir test_dir
ls -l test.txt
ls -ld test_dir
在没有其他权限机制干预的常见情况下,可以观察到:
text
-rw-rw-r-- test.txt
drwxrwxr-x test_dir
对应的权限数字分别是:
text
test.txt -> 664
test_dir -> 775
问题来了:我们没有执行 chmod,为什么它们会得到这些权限?
原因是,创建文件和目录时,程序会向操作系统提出创建请求,并提供一个期望的初始权限模式。内核再结合当前进程的 umask 等权限规则,确定新对象的权限。
因此,umask 不是事后修改权限的命令,而是影响新建文件和目录默认权限的掩码。
2.2 先回顾 rwx 的数字表示
Linux 使用三位二进制位表示一组基本权限:
| 权限 | 二进制 | 八进制数值 |
|---|---|---|
r |
100 |
4 |
w |
010 |
2 |
x |
001 |
1 |
每个文件通常分别设置拥有者、所属组和其他用户的权限。
例如:
text
-rw-rw-r--
拆分为:
text
rw- rw- r--
用户 用户组 其他用户
对应:
text
664
其中:
- 用户拥有读、写权限:
4 + 2 = 6 - 用户组拥有读、写权限:
4 + 2 = 6 - 其他用户只有读权限:
4
理解了这个基础,才能继续理解 umask 为什么能够屏蔽权限。
2.3 文件和目录的起始权限为什么不同?
在常见的 Linux 文件创建机制中,用于计算默认权限的基础模式通常是:
| 对象类型 | 基础模式 | 含义 |
|---|---|---|
| 普通文件 | 666 |
rw-rw-rw- |
| 目录 | 777 |
rwxrwxrwx |
这里的基础模式不是说最终创建出来的文件一定具有这些权限,而是计算时使用的基础值。
为什么普通文件的基础模式不是 777?
因为普通文件默认不应自动获得执行权限。一个刚刚创建的文本文件通常只是数据,不应该仅仅因为它被创建出来,就默认成为可执行文件。
目录则不同:目录的 x 权限与进入、搜索目录有关,因此目录的基础模式通常包含执行权限。
注意:程序可以请求不同的创建模式,文件系统也可能受到 ACL 等机制影响。因此,这里的 666 和 777 是理解常见默认权限计算的基础模式,不代表所有创建操作都必然采用它们。
三、umask 的核心:不是减法,而是位运算
3.1 umask 究竟屏蔽了什么?
可以先记住一句话:
umask 中对应权限位为 1 的位置,表示该权限位不应出现在最终权限中。
例如:
text
umask = 0002
最后一位的八进制数字 2,对应二进制:
text
010
它代表屏蔽其他用户权限组中的写权限位。
于是,新建文件时,基础模式中的其他用户写权限就会被屏蔽。
这也解释了为什么 umask 0002 通常会让新建文件的权限从 666 变为 664。
3.2 为什么公式是 mode & ~umask?
常见的权限计算表达式为:
c
final_mode = requested_mode & ~umask;
这里涉及两个位运算符:
~:按位取反。&:按位与。
先看按位与的规则:
| A | B | A & B |
|---|---|---|
| 0 | 0 | 0 |
| 0 | 1 | 0 |
| 1 | 0 | 0 |
| 1 | 1 | 1 |
按位与的核心特点是:只有两边都为 1,结果才是 1。
因此,如果我们希望某个权限位被保留,就需要让基础模式和掩码取反后的对应位都为 1。
如果掩码中的某个权限位是 1,取反之后就会变成 0。基础模式与这个 0 进行按位与,结果只能是 0,对应权限便被屏蔽了。
这就是 umask 的底层逻辑。
3.3 手动推导:umask 0002
先看新建目录。
基础模式:
text
777 = 111 111 111
掩码:
text
002 = 000 000 010
取反后,只保留我们需要讨论的低九个权限位:
text
~002 = 111 111 101
执行按位与:
text
111 111 111 777:目录基础模式
& 111 111 101 ~002:屏蔽写权限位
-------------
111 111 101 775:最终权限
因此:
text
777 & ~002 = 775
再看普通文件。
text
110 110 110 666:文件基础模式
& 111 111 101 ~002
-------------
110 110 100 664:最终权限
因此:
text
666 & ~002 = 664
这里的二进制演算只展示权限所使用的九个基本权限位。实际计算机中的按位取反还会涉及更高位,所以不能把无限位宽的 ~002 直接理解为一个只有九位的数;我们展示的是权限位范围内的结果。
3.4 为什么不能直接做减法?
看到:
text
666 - 002 = 664
777 - 002 = 775
你可能会觉得,直接相减不就行了吗?
但这只是部分例子中碰巧得到相同结果,不能作为通用算法。
原因在于:umask 表示的是需要屏蔽哪些权限位,而不是需要从权限数字中减去多少。
例如:
text
基础模式:666
umask: 022
正确结果:
text
666 & ~022 = 644
如果用普通减法:
text
666 - 022 = 644
这次结果相同。
但再看:
text
基础模式:666
umask: 003
按照位屏蔽规则:
003表示屏蔽其他用户组的写权限和执行权限对应的位。- 普通文件基础模式
666本身没有执行权限,因此屏蔽执行权限不会产生额外变化。 - 屏蔽其他用户的写权限后,最终权限为
664。
而直接做八进制减法:
text
666 - 003 = 663
得到的结果反而让其他用户失去了读权限,并没有正确表达"屏蔽写和执行权限"的含义。
结论:权限掩码操作的是二进制权限位,因此必须按照位运算规则理解,不能将其等同于数字减法。
四、在 Ubuntu 中验证 umask
4.1 查看当前掩码
bash
umask
如果想看到符号形式,可以执行:
bash
umask -S
输出形式可能类似:
text
u=rwx,g=rwx,o=rx
具体输出以当前 Shell 环境为准。
4.2 临时修改掩码
可以在当前终端中执行:
bash
umask 0002
然后创建测试对象:
bash
touch umask_file
mkdir umask_dir
ls -l umask_file
ls -ld umask_dir
在常见默认创建模式下,预期分别为:
text
-rw-rw-r-- umask_file
drwxrwxr-x umask_dir
对应:
text
664
775
如果你想验证不同掩码的效果,可以尝试:
bash
umask 0022
touch file_0022
mkdir dir_0022
ls -l file_0022
ls -ld dir_0022
常见结果:
text
file_0022 -> 644
dir_0022 -> 755
再尝试:
bash
umask 0077
touch private_file
mkdir private_dir
ls -l private_file
ls -ld private_dir
常见结果:
text
private_file -> 600
private_dir -> 700
这时新建文件只有拥有者能够读写,新建目录只有拥有者具有完整的基本权限。
4.3 umask 修改后会永久生效吗?
通常不会。
直接执行:
bash
umask 0077
会修改当前 Shell 的掩码,当前 Shell 创建的子进程通常也会继承相应的掩码状态。退出该 Shell 后,重新登录时的掩码可能由启动配置重新设置。
如果确实需要长期修改,可以根据自己的 Shell 环境,在对应的用户启动配置文件中设置。但在学习阶段,建议先在当前终端实验,不要随意修改全局配置。
特别注意:不要在不理解影响范围的情况下,把严格或过于宽松的 umask 设置写进系统级配置。
五、从默认权限转向共享目录:出现了什么矛盾?
理解 umask 后,我们再看第二个问题。
假设一台 Linux 服务器上有多个普通用户,他们都需要在一个公共目录中创建临时文件。
如果公共目录不允许其他用户写入,那么协作就会受限。
但如果为了方便协作,直接允许所有用户读、写、进入这个目录,又会带来新的问题:
用户可能删除自己并不拥有的文件。
这里有一个很容易误解的知识点:
删除目录中的文件,通常主要检查的是父目录的权限,而不是被删除文件本身的写权限。
5.1 为什么删除文件看的是目录权限?
例如,目录结构如下:
text
/shared/
└── alice.txt
假设:
/shared允许某个用户写入并搜索。alice.txt属于 Alice。- 该用户不是 Alice,也不是 root。
即使 alice.txt 本身没有授予该用户写权限,如果该用户对父目录具有足够的权限,且没有其他限制,仍然可能删除这个目录项。
为什么?
从文件系统的角度看,删除文件并不等同于打开文件并修改内容。删除操作需要修改的是父目录中的目录项,也就是让目录不再记录这个文件名与对应对象之间的关联。
因此:
- 修改文件内容,主要检查文件自身的写权限。
- 删除或重命名目录中的条目,主要检查父目录的写权限和搜索权限。
这就是为什么"文件不可写"并不一定意味着"文件不可删除"。
5.2 用流程图理解删除权限
#mermaid-svg-QDIAng2O5meAnjgu{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-QDIAng2O5meAnjgu .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-QDIAng2O5meAnjgu .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-QDIAng2O5meAnjgu .error-icon{fill:#552222;}#mermaid-svg-QDIAng2O5meAnjgu .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-QDIAng2O5meAnjgu .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-QDIAng2O5meAnjgu .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-QDIAng2O5meAnjgu .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-QDIAng2O5meAnjgu .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-QDIAng2O5meAnjgu .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-QDIAng2O5meAnjgu .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-QDIAng2O5meAnjgu .marker{fill:#333333;stroke:#333333;}#mermaid-svg-QDIAng2O5meAnjgu .marker.cross{stroke:#333333;}#mermaid-svg-QDIAng2O5meAnjgu svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-QDIAng2O5meAnjgu p{margin:0;}#mermaid-svg-QDIAng2O5meAnjgu .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-QDIAng2O5meAnjgu .cluster-label text{fill:#333;}#mermaid-svg-QDIAng2O5meAnjgu .cluster-label span{color:#333;}#mermaid-svg-QDIAng2O5meAnjgu .cluster-label span p{background-color:transparent;}#mermaid-svg-QDIAng2O5meAnjgu .label text,#mermaid-svg-QDIAng2O5meAnjgu span{fill:#333;color:#333;}#mermaid-svg-QDIAng2O5meAnjgu .node rect,#mermaid-svg-QDIAng2O5meAnjgu .node circle,#mermaid-svg-QDIAng2O5meAnjgu .node ellipse,#mermaid-svg-QDIAng2O5meAnjgu .node polygon,#mermaid-svg-QDIAng2O5meAnjgu .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-QDIAng2O5meAnjgu .rough-node .label text,#mermaid-svg-QDIAng2O5meAnjgu .node .label text,#mermaid-svg-QDIAng2O5meAnjgu .image-shape .label,#mermaid-svg-QDIAng2O5meAnjgu .icon-shape .label{text-anchor:middle;}#mermaid-svg-QDIAng2O5meAnjgu .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-QDIAng2O5meAnjgu .rough-node .label,#mermaid-svg-QDIAng2O5meAnjgu .node .label,#mermaid-svg-QDIAng2O5meAnjgu .image-shape .label,#mermaid-svg-QDIAng2O5meAnjgu .icon-shape .label{text-align:center;}#mermaid-svg-QDIAng2O5meAnjgu .node.clickable{cursor:pointer;}#mermaid-svg-QDIAng2O5meAnjgu .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-QDIAng2O5meAnjgu .arrowheadPath{fill:#333333;}#mermaid-svg-QDIAng2O5meAnjgu .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-QDIAng2O5meAnjgu .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-QDIAng2O5meAnjgu .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QDIAng2O5meAnjgu .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-QDIAng2O5meAnjgu .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QDIAng2O5meAnjgu .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-QDIAng2O5meAnjgu .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-QDIAng2O5meAnjgu .cluster text{fill:#333;}#mermaid-svg-QDIAng2O5meAnjgu .cluster span{color:#333;}#mermaid-svg-QDIAng2O5meAnjgu div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-QDIAng2O5meAnjgu .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-QDIAng2O5meAnjgu rect.text{fill:none;stroke-width:0;}#mermaid-svg-QDIAng2O5meAnjgu .icon-shape,#mermaid-svg-QDIAng2O5meAnjgu .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-QDIAng2O5meAnjgu .icon-shape p,#mermaid-svg-QDIAng2O5meAnjgu .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-QDIAng2O5meAnjgu .icon-shape .label rect,#mermaid-svg-QDIAng2O5meAnjgu .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-QDIAng2O5meAnjgu .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-QDIAng2O5meAnjgu .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-QDIAng2O5meAnjgu :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
是
否
用户尝试删除文件
检查父目录权限
是否具有必要的写权限和搜索权限
通常拒绝操作
是否存在粘滞位等额外限制
通常允许删除目录项
继续检查删除者身份
是否为允许删除的用户
允许删除
拒绝删除
这个流程是帮助理解的简化模型。实际操作还可能受到 ACL、只读挂载、文件系统属性等因素影响。
现在,问题就明确了:
我们希望用户可以在公共目录中创建自己的文件,但不能随意删除别人的文件。
这正是粘滞位要解决的核心问题。
六、Sticky Bit:让公共目录既能共享,又能保护文件
6.1 什么是粘滞位?
Sticky Bit,中文通常称为"粘滞位",是一种特殊权限位。
在现代 Linux 中,它最常见的用途是保护设置了该权限的共享目录。
当目录设置了粘滞位后,即使用户对目录拥有写权限,也不能因此随意删除或重命名其他用户的文件。
在典型的目录场景中,允许删除目录项的身份包括:
- 文件的拥有者。
- 目录的拥有者。
- 超级用户 root。
当然,操作仍然需要满足其他适用的权限与系统规则。
6.2 粘滞位在权限字符串中如何显示?
观察一个常见的权限字符串:
text
drwxrwxrwt
最后的 t 就是粘滞位的标志。
普通目录权限的最后一位通常对应其他用户的执行权限;设置粘滞位后:
- 如果其他用户执行权限也存在,通常显示为小写
t。 - 如果其他用户执行权限不存在,则通常显示为大写
T。
例如:
text
drwxrwxrwt
drwxrwxrwT
这两个字符串都体现了粘滞位,但对应的执行权限情况不同。
6.3 为什么 /tmp 通常有粘滞位?
Linux 系统中的 /tmp 通常用于存放临时文件。
多个程序、多个用户可能同时使用这个目录,因此它需要允许不同用户创建临时文件。
可以查看:
bash
ls -ld /tmp
常见输出类似:
text
drwxrwxrwt ... /tmp
重点看末尾的 t。
/tmp 通常具有:
- 所有者的完整权限。
- 所属组和其他用户的读、写、执行权限。
- 粘滞位。
这意味着普通用户可以在其中创建临时文件,但不能仅仅因为目录可写,就随意删除其他用户拥有的文件。
因此,/tmp 展示了一种典型的权限设计思路:
允许共享目录本身,同时对目录项的删除和重命名增加身份限制。
七、如何设置粘滞位?
7.1 使用符号方式
假设我们创建一个用于实验的目录:
bash
mkdir sticky_demo
chmod 777 sticky_demo
ls -ld sticky_demo
此时,权限通常类似:
text
drwxrwxrwx ... sticky_demo
设置粘滞位:
bash
chmod +t sticky_demo
再次查看:
bash
ls -ld sticky_demo
预期看到:
text
drwxrwxrwt ... sticky_demo
最后的 t 表示粘滞位已设置。
这里使用的是自己创建的实验目录,不需要修改系统目录,也不需要执行危险的全局权限修改。
7.2 使用数字方式
Linux 也支持使用特殊权限位的数字表示。
粘滞位对应的数值为 1,写在普通三位权限数字之前。
例如:
bash
chmod 1777 sticky_demo
其中:
text
1 777
│ └── 普通权限:rwxrwxrwx
└──── 粘滞位
执行后,目录的权限通常会显示为:
text
drwxrwxrwt
需要注意,特殊权限位与普通的 rwx 权限位属于不同的概念,不要把 1777 理解成四组普通读写执行权限。
八、umask 和粘滞位有什么本质区别?
这两个知识点都与 Linux 权限有关,但它们解决的是不同阶段的问题。
| 对比项 | umask | Sticky Bit |
|---|---|---|
| 主要作用 | 控制新建对象的默认权限 | 限制共享目录中的删除、重命名行为 |
| 主要生效时机 | 创建文件或目录时 | 对设置了粘滞位的目录进行相关操作时 |
| 关注对象 | 新建对象的基本权限位 | 目录中的文件条目及相关用户身份 |
| 常见使用方式 | umask 0022 |
chmod +t directory |
| 典型场景 | 避免新建文件默认过度开放 | 保护 /tmp 一类共享目录中的文件 |
可以这样理解:
umask关注的是:一个对象刚创建出来时,默认应该开放哪些权限?- 粘滞位关注的是:一个目录允许多人共同使用时,谁可以删除其中的文件?
一个控制创建阶段的默认权限,一个在共享目录场景中增加删除与重命名的限制。二者互不替代。
九、常见误区总结
误区一:umask 是从默认权限中直接减去一个数字
错误。
umask 表示需要屏蔽的权限位。应从位运算角度理解:
text
最终权限 = 请求的基础权限 & ~umask
误区二:新建文件一定是 666,新建目录一定是 777
不准确。
它们是常见计算场景中的基础模式。最终权限还取决于创建请求、umask 以及可能存在的其他权限机制。
误区三:文件没有写权限,就一定不能删除
错误。
删除文件通常主要检查父目录的写权限和搜索权限;设置粘滞位后,还需要检查相关用户身份。
误区四:目录有粘滞位,就不能删除里面的文件
错误。
粘滞位并不是禁止所有删除操作,而是限制哪些用户可以删除或重命名目录中的条目。文件拥有者、目录拥有者和 root 通常仍然可以删除符合条件的文件。
误区五:只要设置了粘滞位,其他权限就不重要了
错误。
粘滞位不会代替目录的基本权限检查。用户仍然需要满足适用的访问条件,粘滞位是在相关权限检查之外增加的一层限制。
十、总结
本篇学习了 Linux 权限管理中的两个重要机制。
第一,umask 通过屏蔽权限位影响新建文件和目录的默认权限。理解它的关键不是记住几个数字,而是理解按位取反与按位与的运算逻辑。
第二,Sticky Bit 用于解决共享目录中的权限矛盾。它允许用户在公共目录中协作,同时限制普通用户随意删除或重命名其他用户的文件。
回到最初的两个问题:
- 为什么新建文件会自动获得一组权限?因为创建请求的基础权限会受到
umask等规则的影响。 - 为什么公共目录可以允许多人写入,却又保护其他人的文件?因为粘滞位能够在共享目录中增加身份方面的限制。
Linux 权限管理并不是简单地给文件设置几个 rwx 标志,而是围绕资源访问、默认安全性和多用户协作建立起来的一套机制。
当我们能够从"为什么需要这条规则"出发,再推导它的行为,就不再只是记住命令,而是在理解操作系统的设计思想。