ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Linux权限与进程管理实战:从rwx到生产环境避坑指南

Linux权限与进程管理实战:从rwx到生产环境避坑指南

Linux 这个系列写到第三篇,后台催更的消息已经攒了一屏。前两篇我们把文件系统、目录结构、基础命令这些"地基"打完了,这一篇要动真格的了——权限管理、用户与用户组、进程控制、软件包管理,这四块是区分"会用 Linux"和"敢在生产环境敲命令"的分水岭。很多人卡在小白阶段上不去,不是因为命令记不住,而是因为不理解权限模型和进程模型这两套底层逻辑,遇到Permission denied就只会sudo一把梭,遇到进程卡死就只会重启机器。这篇内容就是冲着解决这类问题来的,适合已经能熟练敲ls、cd、cp但一碰到权限报错、进程僵死、装软件报依赖冲突就发怵的朋友。我会把每个知识点背后的"为什么"讲透,再配上可以直接抄作业的命令和踩坑记录,读完你至少能做到:看到权限位能秒算数字、能自己设计用户组方案、能定位并干掉异常进程、能搞定软件源和依赖问题。

1. 权限模型:从 rwx 到数字的完整推演

1.1 为什么 Linux 要把权限设计成三组九位

刚接触 Linux 的人看到-rwxr-xr--这种字符串第一反应是"这什么天书"。其实这套设计非常朴素:Linux 是多用户系统,一个文件天然涉及三类人——文件所有者(owner)、所属组(group)、其他人(others),每类人对文件有三种操作可能——读(r)、写(w)、执行(x)。三乘三,就是九位。这个模型的精妙之处在于它用极小的信息量覆盖了绝大多数访问控制场景,而且检查顺序是固定的:先看你是不是所有者,是就按所有者权限判;不是再看你在不在所属组,在就按组权限判;都不是才落到其他人权限。这个"短路判断"逻辑决定了后面很多坑的成因,比如你明明在某个组里,但文件所有者是你,那组权限对你根本不生效。

我见过不少新手把chmod 777当成万能药,本质是没理解这个判断顺序。777 意味着所有人类别都拿到读写执行,等于把门拆了。正确的做法是先想清楚"谁需要什么权限",再精确赋值。举个实际场景:一个部署脚本需要被运维组执行但不能被其他人读,那应该是750(所有者 rwx,组 r-x,其他人无权限),而不是 777。

1.2 数字权限的换算方法与心算技巧

数字权限的规则很简单:r=4,w=2,x=1,每组三位相加。但实际工作中要求你能快速心算,而不是每次都掰手指。我的经验是记住几个"锚点值":

数字权限串典型用途
644rw-r--r--普通文件默认值,所有人可读,仅所有者可写
755rwxr-xr-x目录、可执行脚本默认值
600rw-------私钥、敏感配置文件
640rw-r-----配置文件,组内可读
750rwxr-x---组内可执行的脚本
700rwx------私有目录

心算技巧:把每组当成一个 0-7 的数字,rwx全有是 7,r-x是 5,r--是 4,rw-是 6,--x是 1。看到754立刻反应成rwxr-xr--。练熟之后,chmod 640 config.ini这种操作就是肌肉记忆。

注意:目录的x权限含义和文件不同。对目录来说,r是"能列出里面有什么"(ls),x是"能进入并访问里面的条目"(cd、访问子路径),w是"能在里面创建删除文件"。只有r没有x的目录,你能看到文件名但打不开,这个坑非常经典。

1.3 chmod、chown、chgrp 三件套的正确用法

chmod改权限,chown改所有者和组,chgrp只改组。实际工作中chown用得比chgrp多,因为chown user:group file一条命令能同时改所有者和组。几个高频用法:

# 递归改目录及其下所有内容的所有者 chown -R deploy:www-data /var/www/app # 给脚本加执行权限(符号法,比数字法更直观) chmod u+x deploy.sh chmod go-w deploy.sh # 递归给目录加权限但只对目录生效(find 配合) find /var/www -type d -exec chmod 755 {} \; find /var/www -type f -exec chmod 644 {} \;

最后那两条find命令是我强烈建议你背下来的。很多人chmod -R 755一把梭,结果把目录下的普通文件也变成了可执行,虽然不致命但很不规范,某些安全扫描会报警。用find按类型分别赋权才是正解。

1.4 特殊权限位:SUID、SGID、Sticky 到底什么时候用

九位之外还有三位特殊权限,很多人学完就忘,因为不知道用在哪。我用最直白的话解释:

  • SUID(4):加在可执行文件上,任何人执行它时都临时拥有文件所有者的权限。典型例子是passwd命令——普通用户改自己密码需要写/etc/shadow,靠的就是 passwd 的 SUID。你自己写的脚本别乱加 SUID,这是提权漏洞的重灾区。
  • SGID(2):加在目录上,目录里新建的文件自动继承目录的所属组。团队协作目录必备,比如/shared/project设成2775,组内任何人建的文件都归这个组,省得天天chgrp。
  • Sticky(1):加在目录上,目录里的文件只有所有者本人能删。/tmp就是1777,所以你能在 /tmp 建文件但删不掉别人的。

设置方法是在数字前加一位:chmod 2775 shared_dir、chmod 1777 /tmp。符号法则是chmod g+s dir、chmod +t dir。

实操心得:团队共享目录的标准配置是chmod 2775加chmod g+s,再配合把成员都加进同一个组。这样新人进来不用教,建的文件天然就是组内可协作的。我踩过的坑是忘了 SGID,结果组员 A 建的文件组是 A 的私有组,组员 B 死活改不了,排查半天。

2. 用户与用户组:多用户环境的地基

2.1 用户账号背后的三个文件

Linux 的用户信息不是存在数据库里,而是三个纯文本文件,理解它们等于理解了用户系统:

  • /etc/passwd:存用户基本信息,每行七段,冒号分隔。格式是用户名:x:UID:GID:描述:家目录:登录shell。第二段x是密码占位符,真实密码哈希在 shadow 里。
  • /etc/shadow:存密码哈希和密码策略,只有 root 能读。字段包括哈希、最后修改天数、最小间隔、最大有效期、警告天数等。
  • /etc/group:存组信息,格式组名:x:GID:组成员列表。

UID 有几个约定值要记住:0是 root,1-999通常是系统账号(服务用),1000起步是普通用户。你自己建用户时系统会自动从 1000 往后分配。

2.2 useradd 与 adduser 的区别及参数详解

Debian/Ubuntu 系有adduser(交互式,友好),RedHat/CentOS 系主要用useradd(底层,参数多)。useradd的关键参数:

# 建用户并指定家目录、shell、附加组 useradd -m -d /home/deploy -s /bin/bash -G www-data,docker deploy # -m 创建家目录(很多发行版默认不建,这是大坑) # -d 指定家目录路径 # -s 指定登录 shell # -G 附加组(逗号分隔,不加空格) # -g 主组(只能一个)

建完用户必须设密码:passwd deploy。这里有个高频坑:useradd不加-m时家目录不会创建,用户登录后cd ~报错,shell 提示符变成-bash-4.2$,新手一脸懵。所以我的习惯是永远带-m。

删除用户用userdel -r deploy,-r连家目录和邮件一起删。不加-r会留下孤儿家目录,时间长了磁盘被占满。

2.3 用户组管理与主组附加组的区别

每个用户有且只有一个主组(primary group),可以有零到多个附加组(supplementary group)。新建文件时,文件的所属组默认是用户的主组,不是附加组——这个细节决定了协作目录必须用 SGID 或者手动 chgrp。

# 建组 groupadd devops # 把用户加入组(-a 必须加,否则会覆盖原有附加组!) usermod -aG devops deploy # 查看用户所属组 groups deploy id deploy # 从组里移除用户 gpasswd -d deploy devops

血泪教训:usermod -G不加-a会把用户的所有附加组重置成你指定的那一个。我曾经用usermod -G docker deploy想加 docker 组,结果把 deploy 从 www-data 组里踢了出去,网站直接 403。正确写法永远是usermod -aG。

2.4 sudo 配置:别再把用户直接塞进 sudo 组了

给普通用户提权,最省事的做法是usermod -aG sudo deploy(Ubuntu)或usermod -aG wheel deploy(CentOS)。但这等于给了全部 root 权限,生产环境不够精细。更规范的做法是编辑/etc/sudoers(用visudo命令,它会做语法检查,别直接 vim):

# 允许 deploy 用户免密执行特定命令 deploy ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx, /usr/bin/systemctl status nginx # 允许 devops 组执行所有命令但需要密码 %devops ALL=(ALL) ALL

visudo的价值在于保存时会校验语法,语法错了会提示你重编辑,避免把 sudoers 写坏导致所有人都提不了权——这个事故我见过一次,最后只能进单用户模式救。

3. 进程管理:看清系统里在跑什么

3.1 ps、top、htop 的分工与选择

三个工具各有定位:ps是快照,top是动态刷新,htop是 top 的增强版(需额外安装,但强烈建议装)。日常排查我基本只用两条命令:

# 查看所有进程的完整信息(BSD 风格,最常用) ps aux # 查看进程树,看清父子关系 ps -ef --forest # 按 CPU 排序看前 10 ps aux --sort=-%cpu | head -11

ps aux各列含义要能秒读:USER 是运行用户,PID 是进程号,%CPU/%MEM 是资源占用,STAT 是状态(R 运行、S 睡眠、D 不可中断、Z 僵尸、T 停止),COMMAND 是命令。STAT 里带+表示前台进程,带s表示会话 leader,带l表示多线程。

top的交互键值得记:P按 CPU 排序,M按内存排序,k杀进程,1展开每个 CPU 核心,q退出。htop则直接支持鼠标点击、F 键操作,看进程树和杀进程都更直观。

3.2 前台、后台、守护进程与作业控制

一个命令跑起来默认占着终端,这叫前台进程。加&放后台,Ctrl+Z挂起当前前台进程,bg让挂起的进程在后台继续,fg把它拉回前台,jobs看当前 shell 的作业列表。这套作业控制是 shell 层面的,和系统进程管理是两回事。

# 后台跑一个耗时任务 tar -czf backup.tar.gz /data & # 挂起当前任务 Ctrl+Z # 查看作业 jobs -l # 把作业 1 放后台继续 bg %1 # 拉回前台 fg %1

真正要长期运行的服务,得用nohup或setsid脱离终端,否则终端一关进程就收到 SIGHUP 挂掉:

nohup ./long_task.sh > task.log 2>&1 &

2>&1是把标准错误重定向到标准输出,再一起进日志文件,不然错误信息会丢。更现代的做法是用systemd管理服务,或者用tmux/screen会话,这个后面单独讲。

3.3 信号机制与 kill 的正确姿势

kill不是"杀死",是"发信号"。默认发SIGTERM(15),进程可以捕获它做清理再退出。kill -9发SIGKILL,进程无法捕获,直接被内核干掉,来不及清理临时文件、释放锁。所以原则是:先 15 后 9。

# 优雅终止 kill 1234 # 等几秒没反应再强杀 kill -9 1234 # 按名字杀(慎用,可能误杀) pkill -f "python.*worker" # 杀整个进程组 kill -- -1234

pkill -f是按完整命令行匹配,比pkill 进程名精确,但也更容易误伤——比如你pkill -f python可能把系统里所有 python 进程都干掉。我的习惯是先pgrep -af 关键词确认匹配到了哪些,再决定杀不杀。

3.4 僵尸进程与不可中断进程的排查

STAT为Z的是僵尸进程——子进程已退出但父进程没调wait()回收,只剩一个进程表项。僵尸本身不占资源,但大量堆积会耗尽 PID。解决办法不是杀僵尸(它已经死了杀不动),而是找到它的父进程,让父进程回收或者杀掉父进程让 init 接管。

STAT为D的是不可中断睡眠,通常在等 IO(磁盘、网络文件系统)。这种进程kill -9也没用,因为它不响应信号,只能等 IO 完成或者重启。看到一堆 D 状态进程,先查磁盘是不是卡了、NFS 是不是挂了。

# 找僵尸进程及其父进程 ps aux | awk '$8 ~ /Z/ {print $2, $11}' ps -o pid,ppid,stat,cmd -p <僵尸PID>

4. 软件包管理:告别依赖地狱

4.1 两大阵营:apt 与 yum/dnf 的对应关系

Debian/Ubuntu 用apt(底层是 dpkg),RedHat/CentOS/Fedora 用yum或新版dnf(底层是 rpm)。命令对照表:

操作apt (Debian系)dnf/yum (RedHat系)
更新软件列表apt updatednf check-update
升级已装软件apt upgradednf upgrade
安装apt install 包名dnf install 包名
卸载apt remove 包名dnf remove 包名
搜索apt search 关键词dnf search 关键词
查包信息apt show 包名dnf info 包名
查文件属于哪个包dpkg -S 文件路径rpm -qf 文件路径
列出已装包dpkg -lrpm -qa

最后两行是排查神器。比如你发现系统里有个/usr/bin/xxx不知道哪来的,dpkg -S /usr/bin/xxx一查就知道属于哪个包。

4.2 软件源配置与换源的注意事项

apt的源在/etc/apt/sources.list和/etc/apt/sources.list.d/下,dnf的源在/etc/yum.repos.d/下。换源能显著提速,但要注意版本代号必须匹配——Ubuntu 20.04 是 focal,22.04 是 jammy,24.04 是 noble,写错了apt update直接报 404。

# 备份原源 cp /etc/apt/sources.list /etc/apt/sources.list.bak # 编辑后必须更新索引 apt update

注意:换源后如果apt update报 GPG 签名错误,通常是源的密钥没导入,或者源地址和系统版本不匹配。别急着删密钥,先确认版本代号对不对。

4.3 依赖冲突的排查思路

依赖冲突是包管理最烦人的问题,典型报错是"unmet dependencies"或"held broken packages"。排查顺序我总结成三步:

  1. 看完整报错:apt install失败时别只看最后一行,往上翻,它会告诉你哪个包依赖哪个版本没满足。
  2. 查是谁占着:apt-cache policy 包名看可用版本和已装版本,apt-mark showhold看有没有被 hold 住的包。
  3. 修复:apt --fix-broken install尝试自动修复,apt install -f同理。还不行就dpkg --configure -a把没配置完的包配完。

RedHat 系对应的是dnf repoquery --requires 包名查依赖,dnf history看操作历史,出问题可以dnf history undo 序号回滚。

4.4 编译安装与包管理的取舍

有些软件源里没有,或者需要特定编译选项,就得源码编译。流程是./configure && make && make install,但这里有几个坑:

  • ./configure报缺库,得先装-dev或-devel包(如libssl-dev)。
  • make install默认装到/usr/local,不会进包管理器数据库,卸载只能手动删,容易留垃圾。
  • 编译安装的软件不受包管理器管,升级要重新编译。

我的建议是:能用包管理器就用包管理器,实在没有再编译,而且编译时用--prefix指定独立目录(如/opt/软件名),卸载时整个目录删掉就干净了。

5. 实操演练:从零搭一个多用户协作环境

5.1 需求拆解与方案设计

假设一个模拟项目组要在同一台机器上协作开发,需求是:三个开发者账号、一个共享目录、共享目录里所有人可读写但只能删自己的文件、开发者能重启指定服务但不能干别的。这个需求覆盖了前面所有知识点,我们一步步落地。

方案设计:建一个devteam组,三个用户都加入;共享目录/srv/shared设2775加 SGID 保证新建文件归组;再加 Sticky 位保证只能删自己的;sudo 配置只允许重启 nginx。

5.2 完整命令序列与验证

# 1. 建组和用户 groupadd devteam for u in dev1 dev2 dev3; do useradd -m -s /bin/bash -G devteam $u echo "$u:初始密码" | chpasswd done # 2. 建共享目录并设权限 mkdir -p /srv/shared chown root:devteam /srv/shared chmod 3775 /srv/shared # 3 = SGID + Sticky # 3. 配置 sudo visudo # 添加:%devteam ALL=(ALL) NOPASSWD: /usr/bin/systemctl restart nginx # 4. 验证 su - dev1 touch /srv/shared/test1.txt ls -l /srv/shared/test1.txt # 组应该是 devteam su - dev2 rm /srv/shared/test1.txt # 应该报错,因为 Sticky 位

验证要点:ls -l看新文件的组是不是 devteam(验证 SGID 生效),dev2 删 dev1 的文件应该被拒(验证 Sticky 生效),dev1 执行sudo systemctl restart nginx应该免密成功而sudo ls /root应该被拒(验证 sudo 精细控制)。

5.3 权限数字 3775 的拆解

3775是四位,第一位3是特殊位,3 = 2(SGID) + 1(Sticky),后三位775是常规权限:所有者 rwx,组 rwx,其他人 r-x。为什么组是 rwx 而不是 r-x?因为协作目录组内要能建文件删文件,需要 w 和 x。为什么其他人是 r-x?给非组员只读访问,如果完全隔离就设成3770。

这个数字不是拍脑袋来的,是需求倒推的:组内要协作所以组 rwx,要防误删所以加 Sticky,要文件归组所以加 SGID。每一步都有理由,这才叫"理解权限"而不是"背权限"。

6. 高频故障速查与避坑清单

6.1 权限类问题速查表

现象最可能原因排查命令解决
Permission denied 但权限看着对上级目录缺 x 权限namei -l 路径给路径上每级目录补 x
能 ls 不能 cd目录只有 r 没有 xls -ld 目录chmod +x 目录
脚本执行报 Permission denied没有 x 权限或挂载 noexecls -l 脚本;mountchmod +x 或换挂载点
组员改不了文件文件组不对或没 SGIDls -l 文件chgrp 或设 SGID
sudo 提示不在 sudoers用户没进 sudo/wheel 组groups 用户usermod -aG

namei -l这个命令值得单独说,它会逐级列出路径上每个目录的权限,一眼看出是哪一级卡住了。很多人排查权限只盯着目标文件,忘了上级目录的 x 权限,用namei -l /a/b/c/file一秒钟定位。

6.2 进程类问题排查流程

进程卡死先别急着kill -9。标准流程是:ps找到 PID 和 STAT,top -p PID看资源,strace -p PID看它卡在哪个系统调用(需要装 strace),lsof -p PID看它打开了哪些文件。如果是 D 状态,重点查 IO;如果是 R 但 CPU 不高,可能死锁;如果是 Z,找父进程。

lsof还有个高频用法是查端口占用:lsof -i:8080看谁占了 8080 端口,比netstat更直观。没有 lsof 就用ss -lntp | grep 8080。

6.3 我踩过的三个真实坑

第一个坑:给目录chmod -R 777之后,SSH 登录变慢。原因是家目录下的.ssh权限被改宽了,SSH 出于安全拒绝使用权限过宽的密钥,回退到密码认证。修复是把.ssh设回 700,authorized_keys设回 600。这个坑教会我:递归改权限前一定先想清楚哪些目录有特殊权限要求。

第二个坑:usermod -G没加-a,把用户的附加组冲掉了,导致服务起不来。这个前面提过,教训是改用户组永远用-aG。

第三个坑:kill -9干掉了一个正在写数据库的进程,导致数据文件损坏,恢复花了大半天。从此我定下规矩:数据库、消息队列这类有状态服务的进程,先发 TERM 等它自己优雅退出,实在不行再考虑 KILL,而且 KILL 前先看它有没有在写盘。

6.4 日常运维的几条硬规矩

  • 改任何系统配置文件前先cp 文件 文件.bak.日期,出问题能秒回滚。
  • rm -rf前先ls一遍确认路径,尤其是变量拼接的路径,变量为空时rm -rf $DIR/会变成rm -rf /。
  • 生产环境操作前先在测试机验证,chmod、chown、rm这三类命令执行前默念三秒。
  • 用history和script记录操作过程,出问题能复盘。
  • 定期df -h和du -sh *查磁盘,很多"服务莫名挂了"其实是磁盘满了。

这套东西写下来,其实核心就一句话:Linux 的每个设计都有它的道理,权限是为了安全,进程是为了隔离,包管理是为了可维护。你把这些"为什么"搞明白了,命令自然就记住了,遇到没见过的问题也能顺着原理推出来。下一篇我打算聊聊 shell 脚本和定时任务,那才是把日常操作自动化的关键,有兴趣的可以先把这篇的实操练熟。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进