先说一个反直觉的现象。我见过不少人把Linux命令背得滚瓜烂熟,能一口气说出几十条参数,但真正遇到线上问题时,却对着top和ss的输出发懵,不知道下一步该做什么。反过来,有些看起来“只会几个命令”的资深同行,往往能从一条命令的输出里读出两三层信息,迅速定位问题。差别不在记性,而在对底层模型的理解深度——Linux核心操作,本质上是在操作文件、进程、权限和网络套接字这四类抽象对象,命令只是暴露这些对象的接口。这篇内容不会写成命令字典,而是把日常运维和开发里最常用、也最容易被用错的那批核心操作拆开讲透:数值权限与特殊位、进程的观测与干预、磁盘空间与inode的定位、网络排查的合理顺序、日志与定时任务,以及如何把这些操作沉淀成自动化脚本。适合刚入职的运维、写后端服务的开发同学,也适合那些自认为“命令会用但一遇问题就乱”的从业者。
1. 先理解模型:Linux核心操作到底在操作什么
1.1 一切皆文件,但文件不只是“文件”
Linux哲学中的“一切皆文件”,是很多人挂在嘴边却理解错的一句话。这里说的“文件”,其实是一套统一抽象:普通文件、目录、管道、设备、socket,甚至进程间通信用的文件描述符,都提供统一的打开、读、写、关闭接口。对你来说,这意味着当你学会了针对普通文件的权限管理、路径操作和重定向,后面学其他对象时会省很多力气。比如ss -tlnp查到的“端口”,底层其实也落到一个可以被进程持有的文件描述符上;管道|只是创建一个匿名的文件对象让两个进程共享。类比来说,Linux系统就像一家管理严格的公司:文件是资料柜里的档案,进程是正在办公的员工,权限是门禁卡,网络套接字是电话分机。命令则是你去前台办事的窗口,窗口数量有限,但背后接管的业务对象几乎无穷。
1.2 进程、权限与套接字:三个概念的快速建模
进程可以理解为“一个正在运行的程序实例”。每个进程有唯一PID、父进程PPID、状态、资源占用等属性。大多数“服务挂了”的排查,本质上就是先找到进程,再判断它是死是活。权限用ugo(用户、组、其他人)配合rwx(读、写、执行)控制对文件对象的访问,这个概念延伸到目录、设备、socket时规则会变化,但底层逻辑一致。网络套接字可以比拟为“电话分机”,每个网络服务先在某个端口上“开户绑定”,然后通过这个套接字收发数据。排查网络问题,第一步永远是搞清“分机有没有在服务台登记”,而不是着急抓包。
1.3 为什么我判断一个人Linux水平不看他背了多少命令
直接说一个判断方法:随机挑一个系统对象,问“它在哪里、怎么观察、怎么变更、权限怎么控制、出问题会怎样”。如果对方能基于概念回答,大概率思路清晰;如果开始默背选项拼参数,多半是“说明书型选手”。在后面的实操里,我会一直把这个思维链路带出来,命令是入口,模型才是目的地。
2. 文件与权限:从数值权限到特殊位的实战细节
2.1 为什么权限值不是随便写的:rwx本质是二进制位
权限有三类:读r,权重4;写w,权重2;执行x,权重1。组合之后得到0-7的数,实际上就是一个三位二进制的结果:7对应111,表示读、写、执行全开;6对应110,表示读+写,无执行;5对应101,表示读+执行,无写。所以chmod 754 file的含义是:属主可读可写可执行(4+2+1=7),属组可读可执行(4+1=5),其他人只读(4)。这套计算并不复杂,但很多人有一个误区:以为x只是“可运行程序”的标记。其实对目录来说,x代表“能够进入目录”;只有r没有x,你能列出文件名但进不去;只有x没有r,能进入目录但看不到里面有什么。给web目录配权限时习惯用7或5开头,正是因为需要通过x让进程进入目录。
2.2 重定向、sudo与权限的组合坑:为什么sudo cat > 文件 会失败
有件事我见过不止三个人踩:某开发者想用sudo cat /var/private.txt > /home/user/out.txt来“以root身份读,再重定向写”。结果系统提示Permission denied。根源在于重定向符号>是当前shell干的活,不是sudo cat干的活。shell会以当前用户的身份去创建或清空out.txt,当前用户没有写权限当然失败。正确姿势有两种:
# 让sudo覆盖整个命令行 sudo bash -c 'cat /var/private.txt > /home/user/out.txt' # 或者用tee cat /var/private.txt | sudo tee /home/user/out.txt > /dev/null类似的还有“为什么sudo vi能打开文件,保存却报错”。编辑器在保存时可能就地改名或创建临时交换文件,这些操作受限于当前用户的目录权限和文件所有权,而不仅是“能不能打开原文件”。遇到这类问题,先分清哪个进程在执行“创建”动作,哪个用户在创建文件,思路就清楚了。
2.3 目录的删除权限逻辑:你可能一直理解错了
删除文件需要的权限不是“对文件的写权限”,而是“对文件所在目录的写+执行权限”。这是新手最常见的误解之一。假设在/tmp/share目录里有一个属主为其他用户的文件,只要你有该目录的写和执行权限,理论上就能删除它——除非该目录设置了sticky bit。粘滞位(sticky bit,chmod +t或数字1)专门用在这种场景:/tmp、/var/tmp都开了粘滞位,所以即使任何人都能在/tmp里写文件,也不能顺手删掉别人刚建的文件。也就是说,sticky位的作用是“共享目录中,用户只能删除自己创建的文件/目录”。
其他特殊位同样重要:
- setuid(4):程序运行时的有效用户ID变成文件属主。典型例子是
/usr/bin/passwd有setuid位,普通用户才能修改自己的密码(因为改密码要写/etc/shadow)。 - setgid(2):目录拥有此位后,新创建的文件会继承目录的属组,团队共享目录常用它保证组内协作文件归属一致。
# 给目录加setgid,使新建文件都归该目录属组 chmod g+s /data/team # 给目录加粘滞位 chmod +t /data/public提醒一句:setuid放在自写脚本或自制二进制上要非常谨慎。配置错了等于把提权入口随手发给所有能执行这个文件的人,且多数发行版会忽略脚本上的setuid位,所以不要指望用脚本做提权工具。
3. 进程管理:从ps、top到kill的完整观测与干预链路
3.1 ps和top:看懂进程的“体检报告”
ps aux是最常用的静态快照,关键列包括:USER(谁启动的)、PID(哪个实例)、STAT(现在是死是活)、%CPU/%MEM(资源占用)、START(何时启动)、TIME(累计消耗的CPU时间)、COMMAND(具体命令)。STAT状态码要拿捏住几个:S代表可中断睡眠,R代表运行/可运行,T是停止,Z是僵尸;附加字符如<表示高优先级,+表示前台进程组,s表示会话首进程——通常是守护进程。看到一堆s时别急着认为“服务在空转”,守护进程等待事件循环时有大量s状态很正常。
top是动态视图。注意三点:load average三个值分别表示1分钟、5分钟、15分钟的系统负载,单核机器超过1.0说明CPU过载,多核机器要除以核数理解;按P按CPU排序、按M按内存排序、按T按运行时间,日常最常用P;top -p PID可以单独盯某个进程,top -u user过滤某个用户。另外别混淆TIME+与%CPU:TIME+是累计消耗的CPU时间,不是实时占用。我见过有人看到某个进程TIME+很高就判死刑,其实它可能只是运行时间特别长,当前并不吃CPU。
3.2 为什么有些进程“杀不死”:信号机制与D状态、Z状态
kill的底层是给目标进程发信号。默认发SIGTERM(15),进程可以自行决定如何响应,正常表现为优雅退出;kill -9发的是SIGKILL(9),内核强制终止,进程没有任何机会善后。这里有个几乎所有人都会踩的误区:尽量先用普通kill,等几秒不好再用-9。因为直接-9可能让服务来不及释放锁、写不完缓冲数据,甚至留下脏数据。好习惯是:先用kill PID,观察几秒,确实没反应再kill -9 PID。
真正“杀不死”的是两类状态:
- D状态(不可中断睡眠):进程正在等待内核I/O完成,比如卡在慢盘或阻塞的NFS上。它不会处理任何信号,只能等底层操作完成,或者物理拔盘/重启系统。
kill -9对它无效。 - Z状态(僵尸进程):进程已经退出,只剩一个PCB残影留在进程表里,等待父进程调用wait回收。僵尸没有信号可处理,所以你
kill不到。解决思路是找父进程,然后重启父进程,让init/systemd接管回收。
# 查找僵尸进程及父进程PID ps -eo pid,ppid,stat,comm | grep Z如果僵尸的父进程是systemd(1号进程),通常系统会自动清理;如果长时间挂着不消失,多半是某个父进程没有正确回收子进程退出状态,需要重启那个父进程,或重启宿主机。
3.3 让进程后台稳定运行:nohup、setsid与systemd-run
临时起一个后台任务,最常用nohup cmd > /tmp/my.log 2>&1 &。但很多人没意识到:nohup只忽略SIGHUP(挂断)信号,不保证完全脱离终端。如果当前shell是交互式,你在shell里按Ctrl+C,进程照样收到SIGINT;如果shell非交互式,关闭终端也可能造成进程组异常。更地道的做法是:
# 完全脱离当前会话,成为新会话leader setsid cmd # 交给systemd托管,可观察、可停止 systemd-run --unit=myjob.service bash -c 'sleep 100; echo done'systemd-run是我比较推荐的方式。它把任务注册进systemd,之后可以用systemctl status myjob看状态,用journalctl -u myjob看日志,用systemctl stop myjob回收进程。在模拟项目X里,我曾把一批批量导入任务全部改成systemd托管,比裸nohup的可观测性高太多。
4. 磁盘与文件系统:df、du与inode耗尽的定位思路
4.1 df和du为什么对不上:被删除文件仍占空间的经典案例
df -h看的是文件系统挂载点的整体占用,du -sh统计的是目录下所有文件的大小叠加。两者相等是巧合,不相等是常态。有个非常隐蔽的问题:后台进程打开了日志文件并持续写入,随后logrotate把它“删除”(实际是unlink,但进程仍然持有文件句柄)。此时du怎么都找不到这个文件——目录里已经没有名字了,但df显示空间一直没释放。排查命令:
lsof +L1 | grep deleted看到deleted状态的文件描述符后,定位到持有它的进程,重启进程或让它关掉句柄,空间就回来了。这个问题我帮人排查过两次,每次都是“磁盘满了但不知道谁占的”,du扫遍全盘没结果,lsof +L1一照面就解决。
4.2 分区挂载与fstab:为什么重启后找不到新磁盘
有些人把数据盘挂在/data,结果服务器一重启,/data变成空目录或者显示之前的内容不见了。大概率是/etc/fstab没配好,或者配置的是设备名/dev/sdb1,但Linux启动时设备顺序变化导致挂载失败。生产环境建议这样处理:
# 查磁盘UUID blkid /dev/sdb1 # 在/etc/fstab中用UUID挂载 UUID=xxxx-xxxx /data ext4 defaults,noatime 0 2挂载选项也别无脑用默认值。defaults实际包含rw、suid、dev、exec、auto、nouser、async等,并不包含noatime。在意写入性能和SSD寿命的话,通常会主动加上noatime。配好之后执行mount -a检查有没有报错,再df -h确认挂载成功。否则数据盘挂载失败,所有写入都落到根分区的空目录/data上,根分区悄悄写满是迟早的事。
4.3 inode耗尽:另一种“磁盘满”
磁盘空间明明充足,创建新文件时报错“No space left on device”——第一反应要查df -i /,如果Inodes已用100%,思路就要切换。inode是文件系统为每个文件/目录分配的元数据索引,大量小文件会提前耗尽inode,而不是占满数据块。常见重灾区:/var/spool下的邮件队列、/var/cache缓存目录、容器挂载层、消息队列产生的临时文件。定位办法:
# 统计整个根分区文件总数 find / -xdev -type f | wc -l # 按时间清理/tmp下超过30天的文件 find /tmp -xdev -type f -mtime +30 -delete清理后记得再df -i验证。如果目录里全是几KB的缓存文件,删除后inode会立刻释放,效果立竿见影。
4.4 安全定位并删除大文件的实操顺序
直接rm -rf大目录风险很高,我的习惯是递减法定位:
# 第一步:看根目录下哪层冒头 du -h --max-depth=1 / | sort -hr | head -10 # 第二步:逐层深入,锁定具体大文件 # 第三步:删除前确认文件是否被进程占用 lsof 文件路径 # 或者用fuser fuser -v 文件路径 # 第四步:删除后再确认 df -h“删了但没释放”的情况就是4.1里的deleted文件,必须处理进程而不是只删文件名。另外,删除日志文件时不要只rm,很多服务会继续往旧文件句柄里写;推荐truncate -s 0 /path/to/log清空而非删除,否则磁盘块被句柄占住依然不释放。
5. 网络排查三板斧:端口、路由与抓包的正确顺序
5.1 第一步看ss:别再张口就是netstat了
ss直接读取内核socket信息,速度快输出全,很多发行版netstat甚至默认不安装。常用的三招:
# 看所有TCP监听端口及对应进程 ss -tlnp # 看所有TCP连接状态及进程 ss -atnp # 看连接状态统计,比如TIME_WAIT数量 ss -s很多人看到TIME_WAIT多就慌。首先明白,TIME_WAIT是主动关闭方在等待2MSL(Linux默认约60秒),目的是保证旧连接的迟滞包不会污染新连接。高并发服务有几千个TIME_WAIT很正常,通常不用处理。真想优化,优先做应用侧长连接复用,而不是去开某些内核参数,改坏了容易引发连接复用错乱,尤其在NAT环境会出现各种诡异问题。
5.2 ping和traceroute的边界:通与不通都别急着下结论
ping通只能说明ICMP可达,不代表业务端口正常;ping不通也不说明业务不可用——很多网络节点会主动丢弃ICMP,但TCP流量完全正常。traceroute的定位是看路径上每一跳的延迟与丢包,找出“网络断在哪一段”。在模拟项目X里我遇到过跨机房访问超时的情况,本机怎么测都正常。用traceroute发现中间某跳延迟飙升到几百毫秒,再结合服务端抓包,最终锁定是中间设备的MTU设置不合理导致分片重组。这就是典型的“先定位层面,再锁定节点”的排查方式。
5.3 curl与tcpdump:从应用层到数据链路层的兜底
curl适合验证应用层能否工作,几个常用参数:
# 输出握手过程与响应头 curl -v https://example.com/health # 忽略自签名证书,测试环境用 curl -k https://192.168.1.10/status # 临时把域名解析到指定IP做联调 curl --resolve api.example.com:443:10.0.0.2 https://api.example.com/health如果curl都正常但对方坚持说有问题,就需要抓包:
tcpdump -i eth0 -nn port 8080 -c 200 -w /tmp/8080.pcap抓到包后看SYN/ACK/RST序列,基本能判断三次握手有没有完成。还要区分两种常见报错:“Connection refused”表示请求到达主机,但该端口没有进程监听(或防火墙直接回了RST);“timed out”表示数据包要么被防火墙静默丢弃,要么目标IP根本不可达。后者要看路由和防火墙,前者先查服务监听。
5.4 一条完整的服务不可用排查链路
实际排查时我会按层递进,不浪费体力:
- 本机
ss -tlnp | grep 8080确认进程在监听; - 本机
curl -v 127.0.0.1:8080确认服务本地健康; - 其他机器
telnet 目标IP 8080验证跨节点连通; - 不行就
traceroute 目标IP看网络路径; - 再不行就两边同时抓包对比。
这套链路基本覆盖80%的“服务连不上”问题。很多人一上来就抓包,我反而觉得浪费,先把端口层和应用层验证清楚,抓包才有明确的对比目标。
6. 日志、定时任务与Shell化:让核心操作变成自动化能力
6.1 journalctl:别再去/var/log/messages里大海捞针
systemd环境下,日志基本统一由journald管理。落实这几个用法就够用了:
# 跟踪某服务实时日志 journalctl -u myservice -f # 看最近一小时的日志 journalctl --since "1 hour ago" -u myservice # 只看本次启动以来的错误级日志 journalctl -p err -b # 结构化输出,便于程序化筛选 journalctl -u myservice -o json-pretty有一个坑要提:journald默认把日志存在内存,重启会丢。如果希望历史日志持久化到/var/log/journal,需要修改配置启用持久化存储,或者把重要日志另行落盘。我遇到过几次需要排查历史故障但日志只剩开机的记录,都是从这里开始的。
6.2 crontab的坑:PATH、环境与输出
定时任务“偶尔不生效”的重灾区基本都在环境差异。cron执行时的PATH极简,不加载~/.bashrc,HOME、SHELL也不一定是预期值。所以脚本里最好:
- 开头显式
export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin - 使用绝对路径调用命令,不依赖PATH推导
- 把输出重定向到文件或syslog,而不是默认的本地邮件(默认邮件没人看,还容易攒垃圾)
另一个容易忽略的点是时区。服务器是UTC时,0 3 * * *指UTC凌晨3点,北京时间是上午11点;跨时区团队协作时,务必在任务配置里写清楚时区基准,或者统一把服务器时区拨到业务预期值。
6.3 把核心操作写成“一键巡检”脚本
学了再多的命令,不沉淀成可复用脚本,遇到重复问题还是低效。我常年在模拟项目X里维护一个约30行的巡检脚本,把df、free、ps、ss、journalctl揉在一起:
#!/usr/bin/env bash # 模拟项目X一键巡检 export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin echo "== 磁盘 ==" df -h / | tail -1 echo "== 内存 ==" free -h | head -2 echo "== CPU占用TOP ==" ps aux --sort=-%cpu | head -4 echo "== 监听端口 ==" ss -tlnp | tail -5需要告警的话就加判断,比如load超过4时报红,再用IM机器人推送。这类脚本本身就是一个“Linux核心操作”的集合体:命令只会在你反复使用时变成肌肉记忆,脚本则是这些记忆的最可靠载体。
6.4 沉淀自己的命令资产:alias与函数
最后分享个人习惯。我会把高频命令写进~/.bashrc,变成alias或函数,而不是每次都敲完整命令:
# 按时间倒序看当前目录最近文件 alias lsrc='ls -lt --time-style=long-iso | head -20' # 查找大文件:findbig 2 /var 表示查看/var下两层内最大的文件 findbig() { du -h --max-depth="${1:-2}" "${2:-.}" 2>/dev/null | sort -hr | head -20; } # 精简版df,忽略临时文件系统 mydf() { df -h | grep -vE 'tmpfs|udev|overlay'; }这种积累往往是“看起来像老手”和“看起来像新手”的分水岭。当你把查找大文件、看监听端口、查服务日志这些动作压缩成一句话,操作效率的差别会肉眼可见地拉开。
我的体会是,Linux核心操作不是一本需要从头背到尾的命令手册,而是你在真实故障中反复使用的“排查链+处理链+自动化链”的集合。文件与权限是地基,进程与磁盘是日常主战场,网络是疑难杂症的来源,日志和定时任务则把一切串成自动化能力。把这些链条一条条打通,才算真正入了门。