第十章:进程与服务管理:化身系统指挥官

第十章:进程与服务管理:化身系统指挥官

摘要: 在前面的章节中,我们已经搭建并优化了开发环境。然而,一个真正的工匠不仅要会使用工具,更要能洞察工具箱内部的运作状态。本章将带你深入 Linux 的“引擎室”——进程与服务管理。我们将学习如何精准地定位、诊断和管理系统中的每一个活动进程,掌握与它们“沟通”的正确方式。更重要的是,我们将攻克“终端关闭,任务中断”、“端口被占用”等一系列开发者日常的最高频痛点,并最终学会使用现代化的 systemd,像一个真正的运维专家那样,管理需要长期稳定运行的后台服务。

本章学习地图:
在本章中,我们将循序渐进,建立对系统活动全局掌控的能力:

  1. 首先,我们将掌握进程的 侦查与诊断,学会如何从系统的万千活动中,快速定位到我们关心的目标。
  2. 接着,我们将学习 信号的艺术,理解如何与进程进行“优雅”或“强制”的沟通,确保服务的平稳启停。
  3. 然后,我们将攻克开发者最常见的痛点:任务的持久化,使用 nohup 和终极武器 tmux 构建一个永不掉线的开发会话。
  4. 之后,我们将化身 网络侦探,解决“端口被占用”这一高频难题。
  5. 最后,我们将接触现代 Linux 系统的基石——systemd 服务管理,学习像运维专家一样管理和部署后台应用。

10.1. 侦查与诊断:透视系统脉搏

痛点背景:
想象这个场景:你的应用突然响应迟缓,系统风扇狂转,CPU 负载升高;或者,你刚刚在后台启动了一个数据处理脚本,现在想确认它是否还在正常运行。在这些时刻,我们首先需要一套精准的侦查工具来定位问题进程,看穿系统当前的脉搏。

静态快照: 经典组合拳 ps aux | grep

ps (process status) 是查看进程状态的基石命令。它会给你拍下一张当前系统进程的“静态快照”。单独使用 ps 信息有限,我们通常会附带 aux 选项:

  • a: 显示所有用户的进程。
  • u: 以用户为中心,显示更详细的信息。
  • x: 同时显示没有控制终端的进程(比如系统后台服务)。

动手操作:初探进程列表

直接运行 ps aux 会输出海量的信息,通常我们会用管道符 | 将结果传递给 grep 来进行过滤。例如,我们想查找 WSL 自带的 SSH 服务进程 sshd

1
ps aux | grep sshd

这个输出告诉我们很多信息:

  • 第一行就是我们想找的 /usr/sbin/sshd 服务进程,它的所有者是 root,进程 ID (PID) 是 10PID 是进程的唯一身份证号,后续我们与进程交互都将依赖它。
  • 第二行是 grep 命令本身。因为我们执行的 grep sshd 这个动作本身也是一个进程,所以它也被自己找到了。这是一个经典的小干扰。

实时仪表盘: htop (top 的终极替代品)

ps 提供的都是“静态快照”,如果你想实时监控系统状态,就需要一个动态的“仪表盘”。传统的工具是 top,但它的界面简陋,操作也不够直观。在 2025 年,我们毫无疑问应该选择它的终极替代品:htop .

htop 提供了一个彩色的、可交互的界面,让你能用更直观的方式排序、过滤、甚至直接管理进程。

动手操作:安装并体验 htop

第一步:安装 htop

htop 通常不是默认安装的,我们需要手动安装一下。

1
sudo apt update && sudo apt install htop -y

第二步:启动 htop

直接在终端输入命令即可。

1
htop

你的整个终端窗口会被 htop 的交互界面接管。

htop 核心功能导览

你不需要记住所有功能,但以下几个是开发者排查问题的“利器”:

  • 实时监控: 顶部的 CPU(每个核心一个进度条)、Mem(内存)、Swp(交换空间)使用率让你对系统负载一目了然。
  • 排序: 用鼠标直接点击 CPU%MEM% 这些列标题,或者按 F6,可以快速找到最耗费资源的应用。
  • 过滤 (F4): 按下 F4,在底部输入 sshd,进程列表会立即只显示与 sshd 相关的进程。
  • 树状视图 (F5): 按下 F5,进程会以树状结构展示,清晰地显示出谁是谁的父进程,对于理解复杂应用的进程关系非常有帮助。
  • 发送信号/杀死进程 (F9): 选中一个进程,按下 F9,会弹出一个信号菜单(我们下一节会详细讲),你可以选择发送 SIGTERM (15) 来优雅地关闭它,或者在它卡死时发送 SIGKILL (9) 来强制终结。

当你完成侦查后,按下 qF10 即可退出 htop

现在,我们已经掌握了从静态到动态、从粗略到精确的各种进程侦查手段。接下来,我们将学习如何使用这些侦查结果,与进程进行“沟通”。


10.2. 信号的艺术:与进程的优雅沟通

痛点背景: 想象一下,一个正在向数据库写入重要数据的应用程序,或者一个正在处理用户上传文件的大型 Web 服务。如果此时我们粗暴地“拔掉电源”,极有可能导致数据文件损坏、数据库记录不一致或产生难以清理的临时文件。我们需要一种机制,能够通知进程:“请准备一下,你即将关闭”,让它有机会完成收尾工作,这就是 Linux 信号机制的价值所在。


10.2.1. 核心信号理论

信号 (Signal) 是 Linux/Unix 系统中进程间通信的一种基本方式。它是一个异步的通知,由内核发送给某个进程,以告知其发生了某个事件。进程可以捕获并处理大部分信号,执行自定义的清理逻辑。

对于开发者和系统管理员而言,必须掌握以下三个最关键的信号:

  • SIGINT 2: Interrupt Signal,中断信号。它等同于我们在终端中按下 Ctrl+C。它通知前台进程“用户希望中断你”,大部分程序会捕获这个信号并立即终止。

  • SIGTERM 15: Terminate Signal,终止信号。这是最常用、最“优雅”的关闭信号,也是 kill 命令的 默认信号。它像一封正式的解雇通知,告诉进程“请你终止”,并给予进程时间来执行清理操作,如保存进度、关闭文件句柄、释放资源等。这是我们应该首选的进程关闭方式

  • SIGKILL 9: Kill Signal,杀死信号。这是一个“终极”信号,它由内核直接执行,会立即、强制地终止进程。进程 无法捕获、阻塞或忽略 此信号。这相当于直接切断电源,进程没有任何机会进行清理。

🤔 思考一下

为什么 SIGKILL 的编号是 9SIGTERM15

信号编号是预定义的,并没有特殊的含义。但一个有趣的记忆法是:kill -9 就像是“九死一生”中的那个“死”,是最后的手段;而 15 则相对温和。


10.2.2. 信号发送工具

掌握了理论后,我们来看看发送这些信号的具体命令。

kill

这是最基础的命令,通过 进程 ID (PID) 来精确地向单个进程发送信号。

语法: kill -<信号编号或名称> <PID>

实战场景: 假设我们在上一节中找到的 npm run dev 进程(PID 为 8765)无响应了。

1
2
3
4
5
6
7
8
# 首先,尝试发送默认的 SIGTERM 信号,给它一个优雅退出的机会
kill 8765

# 如果等待几秒后,进程依然存在(可用 pgrep 8765 检查)
# 再发送最后的 SIGKILL 信号
kill -9 8765
# 或者使用信号名称,效果相同
kill -SIGKILL 8765

pkill & killall

当处理按名称分类的一组进程,或者不想先用 pgrep 查找 PID 时,pkillkillall 就派上了用场。

  • pkill <name>: 根据进程名 部分匹配 来发送信号。
  • killall <process_name>: 根据进程名 精确匹配 来发送信号。

pkill 非常适合处理由主进程衍生出多个子进程的场景。

实战场景: 假设一个 Node.js 应用启动了多个工作进程。

1
2
# 优雅地关闭所有包含 "node" 关键字的进程
pkill -SIGTERM node

killall 在你确定要杀死所有同名的、独立的进程时非常有用。

实战场景: 比如系统中意外地运行了多个 my_script.sh 实例。

1
2
# 强制杀死所有名为 my_script.sh 的进程
killall -9 my_script.sh

使用警告:kill -9 是最后的防线,而非首选方案!

滥用 kill -9 是一个坏习惯。它应该只在进程完全僵死、对 SIGTERM 毫无响应的情况下使用。在生产环境中,优先使用 systemctl stopkill 命令,给应用一个安全关闭的机会,是专业运维的基本素养。

分类关键项核心描述
核心信号SIGTERM (15)(首选) 优雅终止信号,给予进程清理时间。kill 命令默认发送此信号。
SIGINT (2)中断信号,等同于 Ctrl+C
SIGKILL (9)(慎用) 强制杀死信号,由内核执行,进程无法拒绝。
发送工具kill <PID>(最常用) 通过 PID 精确控制单个进程。
pkill <name>通过进程名 部分匹配 来批量控制进程。
killall <name>通过进程名 精确匹配 来批量控制进程。

10.3. 任务持久化(一):简单的后台执行与 nohup

痛点背景: 您是否经历过这样的场景:通过 SSH 连接到服务器,启动了一个需要运行数小时的数据迁移脚本,然后因为网络波动连接中断,导致整个任务前功尽弃?或者,您在本地 WSL 中启动了一个 Web 应用的编译打包任务,却因为不小心关闭了 Windows Terminal 窗口而被迫重来?这就是任务持久化的必要性所在。


10.3.1. 临时后台任务:&, jobs, fg, bg

最简单的让任务在后台运行的方式,是在命令末尾加上一个 & 符号。

1
2
# 启动一个后台睡眠任务,它会持续 300 秒
sleep 300 &

这里的 [1]作业号 (Job ID),而 12345 是我们熟悉的 进程号 (PID)。这个作业现在就在当前 Shell 的后台运行,不会阻塞我们继续输入其他命令。我们可以使用 jobs 命令来查看当前 Shell 会话中的所有后台作业。

1
2
# 查看当前会话的后台作业
jobs -l

我们可以通过 fg (foreground) 和 bg (background) 命令,结合作业号(需要加 % 前缀),来灵活地调度它们:

  • fg %1: 将作业号为 1 的任务调回 前台 运行。此时终端会被该任务占据,按下 Ctrl+Z 可以 暂停 它。
  • bg %1: 让一个被暂停的作业(例如通过 Ctrl+Z)在 后台 继续运行。

关键理解: 这种 & 方式创建的后台任务与当前终端会话是 绑定 的。一旦关闭这个终端窗口,系统会发送一个 SIGHUP (Hangup) 信号给该会话的所有子进程,导致它们全部终止。


10.3.2. 断线保护神:nohup

为了解决关闭终端会导致后台任务中断的问题,nohup (No Hangup) 命令应运而生。它的核心作用就是 让进程忽略 SIGHUP 信号

nohup 的标准用法几乎总是与 & 结合,并且强烈建议重定向输出流,否则所有输出都会默认写入到一个名为 nohup.out 的文件中。

语法: nohup <command> > <log_file> 2>&1 &

  • nohup <command>: 使用 nohup 来运行你的命令。
  • > <log_file>: 将标准输出(stdout)重定向到指定的日志文件。
  • 2>&1: 将标准错误(stderr)重定向到与标准输出相同的地方。&1 表示“标准输出的文件描述符”。
  • &: 将整个命令放入后台执行。

实战场景: 我们有一个耗时的编译脚本 build.sh,希望它在后台运行并将所有日志记录到 build.log

1
2
# 以后台、忽略挂断信号的方式运行脚本,并合并所有输出到 build.log
nohup ./build.sh > build.log 2>&1 &

现在,即使我们关闭当前的终端,build.sh 进程(PID 为 13579)依然会在后台稳定运行,直到它自己执行完毕。我们可以随时通过 tail -f build.log 来实时监控它的进展。

nohup 非常适合那些启动后就不再需要交互的“一次性”批处理任务。但它的缺点也很明显:我们无法再回到那个任务的交互界面中去。如果需要一个可以随时“断开”和“重连”的持久化工作区,那么就需要下一节的终极武器 tmux

关键项核心描述注意事项
command &将命令放入当前 Shell 的后台 临时 运行。关闭 Shell 会导致任务终止。
jobs查看当前 Shell 会话中的后台作业列表。
fg %<job_id>将后台作业调回前台。
bg %<job_id>让一个已暂停的作业在后台继续运行。
nohup command &(推荐) 让命令 持久化 后台运行,忽略 SIGHUP 挂断信号。强烈建议配合 > log_file 2>&1 使用,否则输出会写入 nohup.out

10.4. 任务持久化(二):终极武器 tmux 构建永不掉线的开发会话

在上一节中,我们学习了如何使用 nohup 来保护一个独立的、非交互式的后台任务。这对于执行批处理脚本非常有效。但现代开发工作流远不止于此:我们常常需要同时监控一个 Web 服务的日志、运行测试、并执行 Git 命令。nohup 无法为我们提供这样一个可随时离开并恢复的 交互式工作区。为此,我们需要 Linux 的终极武器——tmux

痛点背景: 想象一个典型的开发场景,你可能需要至少三个终端窗口:一个运行后端服务 npm run dev,一个运行前端服务 vite,第三个用于执行 git 命令或数据库查询。如果此时你需要重启电脑或切换到另一个项目,恢复这个工作布局将是一件繁琐的事情。tmux 允许我们创建一个可以随时“分离 (detach)”和“重连 (attach)”的虚拟工作空间,完美解决了这个问题。


10.4.1. 核心概念:tmux 的三层结构

tmux 的全称是 Terminal Multiplexer (终端复用器)。它的核心思想是在后台运行一个 tmux 服务进程,这个服务进程独立于任何终端窗口,并负责托管我们所有的工作会话。理解它的三层结构是掌握它的关键:

  • 会话 (Session): 一个独立的、包含多个窗口的 **工作区**。你可以为每个项目创建一个 Session,例如一个用于 `project-a`,一个用于 `project-b`。
  • 窗口 (Window): Session 内的 **标签页**。每个窗口都是一个全屏的 Shell 环境,你可以在一个窗口运行后端服务,在另一个窗口运行测试。
  • 窗格 (Pane): 窗口内的 **分屏**。你可以将一个窗口垂直或水平分割成多个窗格,以便同时查看和操作多个命令。

10.4.2. 核心工作流:分离与重连

tmux 的所有快捷键都需要先按下一个 前缀键,默认为 Ctrl+b。操作模式是:先按下并松开 Ctrl+b,然后再按下一个功能键。

步骤一:安装并创建新会话

首先,请确保 tmux 已经安装。

1
2
3
# 安装 tmux
sudo apt update
sudo apt install tmux -y

然后,创建一个名为 dev-session 的新会话。

1
2
# 创建新会话
tmux new -s dev-session

执行后,你会进入一个新的全屏界面,这标志着你已处于 tmux 的保护之下。

步骤二:在会话中工作并分离

现在,你可以在这个会话中运行任何耗时命令,例如启动一个开发服务器。

1
npm run dev

当你需要离开时,执行 tmux 的核心操作:分离会话。按下 Ctrl+b,松开,再按下 d (detach)。你会立刻返回到原来的终端,但 dev-session 及其中的任务仍在后台运行。

步骤三:查看与重连

你可以随时使用 tmux ls 命令,查看所有在后台运行的会话。

1
2
# 列出所有 tmux 会话
tmux ls

要重新进入会话,使用 attach 命令。

1
2
# 重新连接到名为 dev-session 的会话
tmux attach -t dev-session

你会发现,之前运行的任务及其所有输出都原封不动地呈现在眼前,仿佛你从未离开过。


10.4.3. 效率提升:窗口与窗格管理

tmux 的强大远不止于持久化,它更是一个强大的窗口管理器。

窗格 (Pane) 操作

Ctrl+b + %: 垂直分割当前窗格(左右分屏)。

Ctrl+b + ": 水平分割当前窗格(上下分屏)。

Ctrl+b + <方向键>: 在窗格之间进行导航。

Ctrl+b + x: 关闭当前窗格。

窗口 (Window) 操作

Ctrl+b + c: 创建一个新窗口 (create)。

Ctrl+b + w: 列出所有窗口,并提供交互式选择 (windows)。

Ctrl+b + p: 切换到上一个窗口 (previous)。

Ctrl+b + n: 切换到下一个窗口 (next)。

Ctrl+b + <数字 0-9>: 快速跳转到指定编号的窗口。

最佳实践: 为一个项目创建一个 tmux 会话。在 0 号窗口运行核心服务,并将其分割成服务日志和 Git 操作两个窗格。在 1 号窗口运行单元测试。在 2 号窗口连接数据库。这样就构建了一个功能完备且永不掉线的开发“驾驶舱”。

分类快捷键/命令核心描述
会话管理tmux new -s <name>创建一个指定名称的新会话。
tmux ls列出所有后台运行的会话。
tmux attach -t <name>(最常用) 重连到指定名称的会话。
Ctrl+b d(最常用) 从当前会话中分离 (Detach)。
窗口管理Ctrl+b c创建一个新窗口 (Create)。
Ctrl+b w列出并选择窗口 (Windows)。
Ctrl+b p / n切换到上一个 (Previous) / 下一个 (Next) 窗口。
窗格管理Ctrl+b %垂直分割窗格 (Vertical)。
Ctrl+b "水平分割窗格 (Horizontal)。
Ctrl+b <方向键>在窗格间导航。

10.5. 网络侦探:解密端口占用之谜

通过 tmux,我们已经为我们的开发工作流建立了一个坚不可摧的“堡垒”。在这个堡垒中,我们最常做的就是启动各种服务,尤其是 Web 服务。然而,当你满怀期待地敲下 npm run devdocker-compose up 时,一个熟悉的错误往往会无情地跳出来:“Address already in use” 或 “Port 8080 is already in use”。

痛点背景: 端口被占用是开发过程中最常见的“拦路虎”之一。它可能是一个先前未被正常关闭的应用实例,一个后台僵死的进程,甚至是另一个你已经忘记了的项目。此时,我们迫切需要一个侦探工具,能立刻告诉我们:“究竟是哪个进程占用了这个端口?”


在 Linux 系统中,网络连接也被抽象为一种文件,因此,我们可以使用文件查看工具来一探究竟。

10.5.1. 首选利器:lsof

lsof (List Open Files) 是一个功能极其强大的诊断工具。虽然名字是“列出打开的文件”,但配合 -i 参数,它就能化身为网络侦探。

语法: sudo lsof -i :<port_number>

  • sudo: 查看网络端口信息通常需要 root 权限。
  • -i: 表示仅列出网络连接相关的“文件” (Internet sockets)。
  • :<port_number>: 指定你想要查询的端口号。

实战场景: 假设我们无法启动一个需要使用 8080 端口的应用。

1
sudo lsof -i :8080

lsof 的输出结果非常人性化,一目了然:

  • COMMAND: 进程的命令名 (java)
  • PID: 进程 ID (15234)
  • USER: 运行该进程的用户 (prorise)
  • NAME: 具体的连接信息,*:8080 (LISTEN) 表示它正在监听所有网络接口的 8080 端口。

现在,元凶已经找到!PID 为 15234java 进程就是占用者。我们就可以用上一节学到的知识来处理它:kill 15234


10.5.2. 现代替代品:ss

ss (Socket Statistics) 是一个更现代、更高性能的工具,专为查询网络套接字而设计,通常被认为是经典 netstat 命令的继任者。在系统繁忙时,ss 的查询速度通常比 lsof 更快。

语法: sudo ss -tlpn | grep <port_number>

  • -t: 仅显示 TCP sockets。
  • -l: 仅显示正在监听 (Listen) 的 sockets。
  • -p: 显示使用该 socket 的进程信息。
  • -n: 以数字形式显示地址和端口,不做域名解析(这会更快)。

实战场景: 同样是调查 8080 端口。

1
sudo ss -tlpn | grep 8080

ss 的输出虽然信息密集,但也非常清晰,直接在 users 部分告诉了我们进程名 (java) 和 PID (15234)。

命令核心描述适用场景
sudo lsof -i :<port>(推荐) 列出打开指定网络端口的进程,输出可读性高。日常开发、手动排查问题的首选。
sudo ss -tlpn | grep <port>更快、更现代的套接字统计工具,性能更优。编写自动化脚本、或在超高负载服务器上排查。

10.6. 现代服务管理:systemdsystemctl 入门

痛点背景: 您在 WSL 中开发一个 Node.js 项目。通常的工作流是:打开 tmux,进入项目目录,运行 npm start,应用成功启动。但这种“手工作坊”式的管理方式,在稍微严肃一点的开发场景中,其弊端会立刻显现:

  1. 脆弱性: 应用因为一个未捕获的异常而崩溃。您不会收到任何通知,只能在发现服务无响应后,手动回到终端,重新执行 npm start
  2. 非持久性: 您关闭了 WSL 或重启了 Windows。当您再次打开终端时,必须记得到每个项目目录(后端、缓存服务等)去手动重启服务。这个过程极其繁琐且容易出错。
  3. 环境鸿沟: 在生产服务器上,运维人员绝不会用 tmuxnohup 来运行核心应用。所有服务都由 systemd 统一管理,以确保其健壮性和可维护性。您本地的开发方式与生产环境严重脱节,这会隐藏很多潜在问题。

systemd 和它的命令行工具 systemctl,正是解决以上所有问题的专业方案。它能将您的应用提升为“一等公民”,像管理 Nginx、Docker、数据库一样,来管理您的应用程序。


10.6.1. 实战 DEMO:将 Node.js 应用服务化

让我们通过一个完整的例子,来体验 systemd 的威力。

第 1 步:准备一个简单的 Node.js 应用

假设我们有一个简单的 Express 应用。在您的 WSL 主目录中,创建一个新项目:

1
2
3
4
5
6
7
8
# 创建项目目录并进入
mkdir my-awesome-app && cd my-awesome-app

# 初始化 Node.js 项目
npm init -y

# 安装 express
npm install express

现在,创建应用入口文件 app.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 使用 cat 创建一个简单的 app.js 文件
# 您也可以用 VS Code 或其他编辑器创建
cat > app.js << EOF
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
console.log('Request received at', new Date().toISOString());
res.send('Hello from systemd-managed app!');
});

// 一个模拟崩溃的路由,用于测试自动重启
app.get('/crash', (req, res) => {
console.error('Crashing application intentionally!');
res.send('Crashing...');
process.exit(1); // 强制退出进程
});

app.listen(port, () => {
console.log(`Example app listening on port ${port}`);
});
EOF

现在,我们用**“老办法”**运行它:

1
2
node app.js
# 输出: Example app listening on port 3000

应用正在运行。但这很脆弱,关闭这个终端,应用就停止了。

第 2 步:编写 systemd 服务单元文件 (.service)

这是核心步骤。我们需要创建一个配置文件,告诉 systemd 如何启动、停止、重启我们的应用。这种配置文件被称为单元文件 (Unit File)

所有用户自定义的系统服务单元文件,都应该放在 /etc/systemd/system/ 目录下。

1
2
3
# 使用 sudo 和您的首选编辑器创建一个新的服务文件
# 这里我们使用 nano,您也可以用 vim
sudo nano /etc/systemd/system/my-app.service

在编辑器中,粘贴以下内容。请务必根据您的实际情况修改 UserWorkingDirectory

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
[Unit]
Description=My Awesome Node.js App
# 表示此服务应该在网络可用后启动
After=network.target

[Service]
# 替换 'your_username' 为您在 WSL 中的用户名 (用 `whoami` 命令查看)
User=your_username
Group=your_username

# 替换为您的项目绝对路径 (在项目目录中用 `pwd` 命令查看)
WorkingDirectory=/home/your_username/my-awesome-app

# 启动命令。使用 `which node` 找到 node 的绝对路径,这更可靠
ExecStart=/usr/bin/node /home/your_username/my-awesome-app/app.js

# 关键!设置服务在失败时总是自动重启
Restart=always

# (可选) 为应用注入环境变量
# Environment=NODE_ENV=production
# Environment=PORT=3000

[Install]
# 定义服务应该在哪个“目标”下启用。multi-user.target 是常规多用户环境的通用选择
WantedBy=multi-user.target

关键配置项解析:

  • [Unit]: 定义了服务的元数据和依赖关系。Description 是服务的简短描述,After 定义了启动顺序。
  • [Service]: 定义了服务的核心行为。
    • User/Group: 出于安全考虑,服务不应该以 root 用户运行。
    • WorkingDirectory: 这是执行命令前 cd 到的目录,对于依赖相对路径的应用至关重要。
    • ExecStart: 启动服务的唯一命令。
    • Restart=always: 这是 systemd 的“杀手级特性”。一旦进程因为任何原因(崩溃或被杀掉)退出,systemd 都会立即尝试重新启动它。
  • [Install]: 定义了当服务被 enable (设置开机自启) 时的行为。

编辑完成后,按 Ctrl+X,然后按 YEnter 保存并退出 nano

第 3 步:使用 systemctl 指挥您的服务

现在,我们的“菜谱”已经写好,是时候让“大厨” systemd 来烹饪了。

  1. 重新加载 systemd 配置
    每当您创建或修改一个 .service 文件后,都需要执行此命令,让 systemd 重新读取其配置。

    1
    sudo systemctl daemon-reload
  2. 启动您的服务

    1
    2
    sudo systemctl start my-app.service
    执行后没有任何输出?这正是 Unix “没有消息就是好消息”的哲学。
1
2
3
4
5
    
3. **检查服务状态 (最常用命令)**
这是诊断和查看服务所有信息的首要命令。
```bash
sudo systemctl status my-app.service
您应该会看到类似下面的输出:
1
2
3
4
5
6
7
8
9
10
11
12
● my-app.service - My Awesome Node.js App
Loaded: loaded (/etc/systemd/system/my-app.service; disabled; vendor preset: enabled)
Active: active (running) since Thu 2025-09-18 14:30:00 UTC; 5s ago
Main PID: 12345 (node)
Tasks: 7 (limit: 4684)
Memory: 25.4M
CPU: 150ms
CGroup: /system.slice/my-app.service
└─12345 /usr/bin/node /home/your_username/my-awesome-app/app.js

Sep 18 14:30:00 my-wsl-machine systemd[1]: Started My Awesome Node.js App.
Sep 18 14:30:00 my-wsl-machine node[12345]: Example app listening on port 3000
从这里您可以看到服务是 `active (running)`,它的主进程 PID,以及**最新的几行日志**,这对于快速排错至关重要!
  1. 测试自动重启
    打开另一个终端窗口,用 curl 访问 /crash 路由来模拟应用崩溃:

    1
    curl http://localhost:3000/crash

    现在,立刻回到第一个终端,再次检查服务状态:

    1
    sudo systemctl status my-app.service

    您会惊奇地发现,服务的 Active 状态可能短暂地变为 activating,然后迅速回到 active (running),但 Main PID 已经变了!systemd 在检测到进程退出后,瞬间就把它拉起来了。这就是服务的健壮性。

  2. 查看完整的服务日志
    status 只显示最新几行日志。要查看全部或实时跟踪日志,请使用 journalctl

    1
    2
    3
    4
    5
    # 查看 my-app 服务的所有日志
    sudo journalctl -u my-app.service

    # 实时跟踪日志 (类似 tail -f)
    sudo journalctl -u my-app.service -f
  3. 设置开机自启
    现在服务已经可以稳定运行了,我们希望每次 WSL 启动时它都自动运行。

    1
    sudo systemctl enable my-app.service

    输出: Created symlink /etc/systemd/system/multi-user.target.wants/my-app.service → /etc/systemd/system/my-app.service.
    enable 命令本质上是创建了一个符号链接,告诉 systemd 在启动时加载这个服务。

    对应的,取消开机自启的命令是:

    1
    sudo systemctl disable my-app.service
  4. 停止与重启服务

    1
    2
    3
    4
    5
    # 停止服务
    sudo systemctl stop my-app.service

    # 重启服务 (通常在更新代码或配置后使用)
    sudo systemctl restart my-app.service

10.6.2. 总结:从手工作坊到现代化运维的飞跃

通过上面的 Demo,我们已经完成了从手动到自动的质变。

对比维度手动管理 (tmux/nohup)systemd 现代化管理
启动方式手动进入目录,执行 node app.jssudo systemctl start my-app
进程崩溃进程死亡,服务中断,需手动重启自动重启,服务自愈
系统重启所有服务丢失,必须全部手动重开自动启动 (如果 enabled),服务持久化
状态监控ps aux | grep node,信息零散systemctl status my-app,状态、PID、内存、日志一目了然
日志查看重定向到文件 > app.log 2>&1,需要手动管理journalctl -u my-app,结构化、统一的日志系统
生产对齐完全脱节与生产环境完全一致的标准实践

掌握 systemdsystemctl,意味着您已经开始用运维工程师的视角来思考和管理您的应用,这是从“能写代码”到“能部署和维护可靠服务”的关键一步。