ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Linux进程管理实战:从PS命令到IPC通信与故障排查

Linux进程管理实战:从PS命令到IPC通信与故障排查 说实话Linux学到第五节很多人都会卡在一个坎上前面文件操作、权限管理都熟了但一旦涉及“进程”——系统里那些看不到摸不着、却又真实跑着的东西——就开始发懵。进程到底是个啥为什么ps能看到一堆乱七八糟的PID为什么kill -9有时候都不管用面试题里最常见的“说说Linux进程间通信方式”又该怎么答这一节学习笔记我打算把进程相关的内容从头到尾捋一遍。不是照搬man手册而是按我实际运维和开发中“真会用到的那些点”来写顺带把面试常问的坑也一起填了。无论你是在虚拟机里折腾Linux的新手还是准备跳槽的运维看完这篇至少遇到进程管理相关的问题心里能有个清晰的框架。1. 先理清概念进程不是程序别搞混了1.1 程序与进程的区别菜谱与做菜我特别喜欢用“菜谱和做菜”来解释程序和进程的关系。程序就是你硬盘上的那个可执行文件比如/usr/bin/nginx。它是一堆静态的指令和数据躺在磁盘上不占用CPU不消耗内存就像一个菜谱放在书架上什么都不干。进程则是这个文件被系统加载到内存里、开始真正“执行”起来之后的那个动态实体。同一个菜谱可以五百个人同时照着做菜就有五百个做菜的过程同理一个nginx程序文件可以被多次启动在系统里跑出多个nginx进程。关键点在哪进程不是那个“文件本身”而是“文件运行起来后的一切”包括它占用的内存、持有的文件描述符、执行到哪一行代码了、它的PID号……这些都是进程的范畴。所以“杀进程”不会删除磁盘上的程序文件只是把那个“做菜的过程”暂停了。1.2 进程在系统里的“身份证”PID与PPID每个进程都有唯一的一个进程ID也就是我们常说的PID可以理解成进程的身份证号。PID从1开始递增用完会复用所以不要假设“PID是永远唯一的”它只是在某个时刻唯一。除了PID还有个必须认识的PPID——父进程ID。Linux的进程管理是一个“树形结构”所有进程最终都追根溯源到PID为1的那个进程在传统Systemd系统里叫systemd。每次你在终端执行一条命令其实都是Shell比如bash这个进程帮你fork出一个子进程去干活子进程的PPID就是bash的PID。这个父子进程关系在实际排查问题时会很有用。比如你启动了一个后台任务结果关掉终端之后任务也死了这就是因为终端关闭时给子进程发了SIGHUP信号。理解了PPID你就明白为什么nohup这个命令能解决这个问题了——它就是在忽略SIGHUP信号的前提下启动子进程。1.3 进程状态别被STAT列吓到ps命令里STAT那一栏新手经常看得一头雾水R、S、D、Z、T都是什么意思我整理了个速查表状态编码含义说明RRunning/Runnable正在运行或在运行队列中等待CPUSInterruptible Sleep可中断睡眠最常见在等待某个事件或资源DUninterruptible Sleep不可中断睡眠通常在等待磁盘IO这种最难杀ZZombie僵尸进程子进程结束但父进程没有正确回收TStopped已停止比如用CtrlZ暂停的任务注意D状态它特别值得说道。D状态的进程正在等待内核层面的IO操作完成比如磁盘读写卡住你用kill -9都杀不掉因为内核根本不给你处理信号的机会。遇到D状态堆积基本就是底层存储或IO出问题了优先检查磁盘和挂载。2. 查看进程ps、top的各种实用姿势2.1 ps命令静态快照三个常用姿势ps是process status的缩写用来查看某一瞬间的进程快照。命令的“姿势”很多但日常运维我就常用三个ps -efSystem V风格。每行一个进程关键字段有UID哪个用户启动的、PID、PPID、CCPU占用、STIME启动时间、TTY关联终端、TIME累计CPU时间、CMD命令。ps auxBSD风格。它和ps -ef展示的信息差不多但多了%CPU、%MEM、VSZ、RSS这些内存相关字段。VSZ是虚拟内存大小RSS是实际物理内存占用面试偶尔会问到务必记住。ps -elf比-ef多了个F进程标志和WCHAN内核等待的地址或函数名排查内核问题时更有用。实操中我习惯直接用ps aux因为它能直观看到CPU和内存峰值然后配合排序去抓罪魁祸首# 查看当前CPU占用最高的10个进程 ps aux --sort-%cpu | head -11 # 查看当前内存占用最高的10个进程 ps aux --sort-%mem | head -11 # 过滤某个关键词相关的进程 ps aux | grep nginx最后一行的grep nginx本身也会出现在结果里这个细节新手容易困惑别慌这是正常的。想排除它就用grep -v grep或者干脆用pgrep来替代。2.2 top/htop动态监控比ps更“活”ps看的是“那一瞬间”top则每隔几秒刷新一次是动态视角。我遇到CPU飙高、系统卡顿的问题第一反应就是敲top。进来后重点看三块第一行当前时间、系统运行了多久、几个用户在线、load average后面的三个数。这三个数分别是1分钟、5分钟、15分钟的平均负载。单看数字没意义得配合CPU核数看假设4核机器load average在4以下说明没事超过4说明任务排队了。第二行进程总数running几个sleeping几个zombie几个。zombie就是僵尸进程只要有就得留意是不是有父进程没写wait逻辑。进程列表按CPU或MEM排序默认按CPU排。想按内存排序按一下M键想杀进程按k然后输PID。top原生界面比较朴素我建议装上htop——它的彩色显示把CPU、内存占用条画得明明白白支持鼠标操作按F6可以选排序字段按F9直接选信号发kill。看着顺眼效率也高一条命令装好# CentOS/RHEL系 sudo yum install -y htop # Ubuntu/Debian系 sudo apt install -y htop2.3 快速定位进程pgrep、pidof如果已知进程名想知道PID用ps管道grep也能做但更简单的是# 查找进程名包含nginx的PID pgrep nginx # 带上完整命令行 pgrep -a nginx # pidof直接查某程序的所有PID pidof nginx # 输出示例1888 1887 1886依启动顺序而定 # 想看某个PID是从哪个目录启动的进程id是12345 ls -l /proc/12345/cwd提到/proc这个目录顺便多说一句。/proc不是真实磁盘目录而是内核暴露给用户的内存文件系统。每个PID对应一个子目录里面存着这个进程的很多“天机”cwd是当前工作目录exe是真正运行的二进制文件路径environ是环境变量fd是打开的文件描述符。排查问题时/proc里翻一翻往往能找到关键线索。3. 进程管理核心操作启动、暂停、终止与优先级3.1 前后台切换与作业控制在Linux终端里一个进程是前台还是后台直接影响你的使用体验。在命令后面加个就把它扔到后台运行了比如python manage.py runserver 。后台任务会用方括号输出一个作业号比如[1]后面跟着PID。然后有几个常用控制键和命令CtrlZ把当前前台进程暂停不是终止转为后台的“停止”状态。jobs查看当前Shell的后台作业列表。fg %作业号或fg直接回车把最近一个后台作业调回前台。bg %作业号让暂停的后台作业在后台继续跑。比较麻烦的场景是“退出终端之后任务怎么办”。普通后台任务会随终端关闭被SIGHUP信号干掉解决办法用nohupnohup python app.py app.log 21 nohup的原意就是“不挂断”让进程忽略终端挂断信号再配合重定向把输出写进日志。还有更“正道”的方案是用setsid直接把进程从当前会话中“解绑”让它成为独立的会话首进程。不过日常里nohup加已经够用了。3.2 信号机制与kill命令为什么别动不动就kill -9Linux进程间的控制底层靠“信号”。kill命令本质也不是“杀”而是“发信号”。很多新手一上来就kill -9其实是最粗鲁的做法容易留下后遗症。先看几个最常用的信号信号值信号名行为场景1SIGHUP终端挂断重新读取配置而不停止进程2SIGINT中断类似CtrlC正常中断9SIGKILL强制杀死无法被捕获直接由内核处置15SIGTERM终止kill默认信号请求进程“自行退出”日常建议这样用# 先发SIGTERM给进程一个善后的机会 kill 1234 # 等待几秒发现还没退出再考虑SIGKILL kill -9 1234为什么不要一上来就kill -9因为SIGKILL不可被进程捕获它没有机会保存数据、关闭文件、清理临时资源。对数据库、缓存这类有持久化需求的进程直接强杀容易损坏数据文件。很多服务其实支持热重载比如nginx -s reload实际上就是给它发SIGHUP信号让它重新读配置而不是重启进程。3.3 进程优先级调整nice与reniceLinux内核调度器决定“先让谁用CPU”的时候会看进程的nice值。nice值的范围是-20到19数字越小优先级越高。普通用户只能把nice值调高降低优先级只有root才能调低提高优先级。这也合理否则普通用户把进程优先级全调到-20系统就成垃圾了。启动时设置优先级用nice运行时调整用renice# 以nice值为-5启动某任务需要root权限 nice -n -5 ./heavy_task # 把PID为1234的进程优先级调整为10 renice -n 10 -p 1234 # 设置完后用ps确认 ps -o pid,ni,comm -p 1234在top界面里按r键也能交互式修改某个进程的nice值效果是一样的。3.4 修改进程名称比想象中更实用的小技巧有时候你把一个Python脚本丢到后台ps一看所有python程序都叫“python3”根本分不清谁是谁。这时候“修改进程名”就有用处了。最简单的做法是启动前用exec -a把进程argv[0]改成自定义名称exec -a my_task python3 long_running_script.py 再看进程列表就会多出一个叫my_task的进程。但它本质还是python3在跑只是“名字”换了个马甲。这种方法适合shell包装脚本。在更底层、更正式的场合可以用prctl这个系统调用C语言里调PR_SET_NAME或者直接改/proc/PID/comm文件需要权限。不过对于日常脚本exec -a这个技巧已经能解决我大部分困惑了真的简单又实用。4. 进程间通信IPC必须了解的五种方式4.1 IPC是什么为什么要学它进程和进程之间就像一个个独立运行的“部门”彼此默认是隔离的。但现实工作中某个模块算完的数据要交给另一个模块去处理这两个进程怎么交换数据这就是进程间通信IPCInterProcess Communication要解决的问题。面试题里“Linux进程间通信方式有哪些”几乎是必考题我盘点一下常用的六种通信方式特点典型场景管道Pipe/命名管道单向字节流简单适合父子进程或兄弟进程命令之间的竖线连接信号Signal异步通知信息量小适合发事件通知通知进程退出或重载配置消息队列内核维护的消息链表可以双向少量结构化消息传递共享内存速度最快两个进程直接读写同一块内存大量数据高频读写信号量用于同步和互斥不传数据控制多个进程对共享资源的访问套接字Socket网络通信的统一接口也支持本机通信跨机器或本机服务通信4.2 管道其实你每天都在用管道是Linux里最“家常便饭”的通信方式。命令行里的竖线就是管道# 把ps的输出交给grep去筛选 ps aux | grep nginx # 再传给wc统计行数 ps aux | grep nginx | wc -l它的本质是前一个进程写stdout后直接作为后一个进程的stdin开一个“单向水管”把数据从一个进程导向另一个进程。除了这种匿名管道只适合父子进程还有命名管道FIFO用mkfifo创建。两个互不相关的进程可以通过这个特殊“文件”来交流数据一个写一个读mkfifo /tmp/my_fifo # 终端A写 echo hello /tmp/my_fifo # 终端B读 cat /tmp/my_fifo这种情况只要有一头没准备好另一头就会阻塞等待特性比较特殊。4.3 共享内存与消息队列、信号量共享内存我愿称它为IPC里的“性能王者”。它把同一块物理内存映射到多个进程的地址空间里数据写进去对方直接能看到零拷贝速度最快。但它也有个“副作用”多个进程同时写同一块内存会发生竞争所以必须配信号量来保证互斥。信号量本身不传输数据它就像一个“厕所门锁”谁抢到了锁谁才能进去用资源。消息队列则像一个“邮箱”进程往里投递带类型标签的消息接收方按照类型取用。好处是消息自带边界不用考虑两个进程写数据时的粘连问题。4.4 Socket不只跨机器同机器也能用很多人觉得Socket是网络编程的东西忽略了它也是进程间通信的一种重要方式。本机里的服务之间要通信比如Redis和业务程序常用TCP或Unix Domain Socket。Unix Domain Socket比TCP少了网络协议栈的层层打包解包开销同一台机器上性能更好也常见于Nginx和PHP-FPM的通信配置里。考察IPC时如果只说管道和消息队列会显得视野不够把Socket补上才算完整。5. 脚本实战与故障排查进程管理在真实世界的模样5.1 用脚本实现简单“守护进程”工作中最常见的场景是线上某个服务挂了你要用脚本把它拉起来。一个简单的守护脚本可以这样写#!/bin/bash PROC_NAMEmy_server CMD/opt/app/my_server while true; do # 检查进程是否存在 if pgrep -f $PROC_NAME /dev/null 21; then : else echo $(date %F %T) $PROC_NAME down, restarting... /var/log/guard.log nohup $CMD /opt/app/server.log 21 fi sleep 5 done核心逻辑是每5秒用pgrep -f检查进程名是否存在不存在就重新启动。这里有个细节值得注意pgrep -f是匹配完整命令行不是只匹配进程名这样能避免“进程名刚好被其他命令包含”的误判。但这个简单方案有个问题如果进程只是卡死但没退出仍然会被pgrep检测到所以真正生产环境的守护逻辑会更复杂比如同时检测端口响应或心跳文件。5.2 故障案例一僵尸进程怎么处理发现僵尸进程STAT为Z时先别慌着kill。僵尸进程本身不耗CPU但它占用着内核进程表项累积多了会导致系统无法创建新进程。导致僵尸的根本原因是子进程先结束父进程却没有调用wait/waitpid去回收它的退出码。处理办法是# 找到僵尸进程及其PID和PPID ps ajx | awk $3Z {print $2, $3} # 看看它的父进程是谁 ps -ef | grep PPID如果父进程是系统服务比如PID 1的systemd它一般会自动清理。如果父进程是你的应用那说明应用代码里没有正确处理子进程回收应该去修代码而不是治标。临时想清掉僵尸可以让父进程去死僵尸进程被init进程“收养”init会自动回收。5.3 故障案例二进程Kill不掉D状态比起僵尸进程更让运维头疼的是D状态。之前我遇到过一台机器上好几个进程进入D状态kill -9怎么敲都没反应top一看全部D。这类问题的排查思路很明确D状态意味着进程卡在内核IO上重点查系统层面的资源而非进程本身比如# 查看挂载情况是否有IO错误 dmesg | tail -20 # 看磁盘IO是否异常 iostat -x 2 2那次我排查到最后其实是NFS挂载的服务端网络抖动导致本地进程一直在等IO回包。恢复网络后D状态进程自己就退了。在那之前再怎么kill都是白搭因为信号压根无法交付给处于不可中断睡眠的进程。5.4 面试高频问题速查进程与线程的区别为什么总被问在这场笔记的最后我想把几个面试中最常被问到的进程相关高频题整理成速查表平时记一记面试时能扛好一阵子问题简述常见坑进程和线程的区别进程是资源分配的基本单位有独立地址空间线程是同进程内轻量级执行单元共享进程的内存空间只说“线程更轻”不够要提共享内存一个进程能创建多少线程受内存和pid_max限制普通用户还有RLIMIT_NPROC限制别死记数字要讲动态限制为什么要区分父子进程父进程负责下属进程管理资源回收依赖PPID关系提到孤儿进程和僵尸进程更完整fork等于复制进程吗fork是写时复制COW开始时共享物理内存写时才复制说“完全复制”是经典错误僵尸进程会一直存在吗父进程回收或父进程退出后由init抚养并回收只说“存在”不答后续会不完整我个人的体会是进程管理这块知识特别适合“顺着一条线去学”先从ps命令认识进程长什么样再学kill如何控制它再研究优先级和通信方式最后落到脚本和故障排查上。这条线捋顺了Linux系统管理的底子就打得比较牢了。前面提到的exec -a、pgrep -f、双信号尝试退出那个小套路都是我在实际工作里踩过坑之后沉淀出来的方法希望对正在学Linux的你也有用。
返回列表