qinghuan
← 工程实践 / 全部笔记

单元 15 / 工程实践

P0 · 必会主线L0 · 零基础

Linux 入门排障:先读路径、权限、进程和服务状态

先核对服务实际使用的账号、路径和 JDK,再逐层看权限、进程、端口和请求结果,找到第一处失败。

本篇目录 · 按需要跳转

读完这篇,你能做什么

能用只读命令定位 Java 进程,区分权限错误、进程退出和应用不可用。

学习理由、准备要求与知识点

为什么学它

后端服务运行在操作系统中,先能读进程、权限和服务状态才有条件排查部署问题。

先读这些内容

知识点清单

  • 路径与当前目录
  • 用户和组
  • 读写执行权限
  • 目录遍历权限
  • PID 与命令行
  • 进程状态
  • 服务状态
  • 退出码与业务健康

运行前先确认命令语境为 Ubuntu 24.04 Bash,systemctl 需 systemd;Java 17 权限算法仅模拟普通用户的传统九位模式,不包含 root、ACL、链接或安全模块。Java 编译验证不等于 Linux 现场验证。JDK 源码核验 Microsoft 17.0.20.1,上游 jdk-17.0.20-ga。

01先弄懂它是什么

排查 Linux 上的 Java 服务,先确认当前用户、目录和实际运行的程序。终端能读某个文件,不代表服务账号也能读;终端 java 指向的 JDK,也不代表后台服务用的就是它。先用 pwd、id、ls -ld 和 ps 记录现场,再顺着现象检查。本篇以 Ubuntu 24.04 和 Bash 为范围,systemctl 只适合使用 systemd 的环境。

文件和目录的读写执行权限,含义不完全相同。目录的读权限关系到列出名字,执行权限关系到能否沿路径继续寻找,写权限关系到创建或删除目录项。能否删文件主要还要看父目录及其他限制。访问 /opt/demo/app.jar 时,如果某层父目录不允许搜索,即使 jar 本身可读,也可能打不开。

进程是正在执行的程序实例,具有 PID、父进程、用户、地址空间和打开的文件等状态。线程共享所属进程的部分资源并承担执行。服务管理器则维护自己的单位状态、启动策略和依赖关系。进程存在、服务 active、端口监听和业务请求成功是不同证据层,不能用其中一个替代全部。

文中这几个词,先认一认

搜索权限
目录上的执行权限,允许按名字穿过这层目录继续查找里面的路径。
有效身份
系统判断权限时采用的用户和组身份;某些检查还涉及文件系统身份等具体规则。
服务就绪
服务已具备处理目标请求的条件,比仅仅看到进程还在多一步。

还有陌生词?到术语小抄里看一个具体例子 ↗

02看看它怎么工作

观察路径时依次运行 ls -ld /opt /opt/demo 和 ls -l /opt/demo/app.jar,检查每一层所有者、组与模式。用 id 确认当前账户及附加组,再与服务配置中的用户对应。遇到符号链接,还要考虑解析后的实际目标与其父目录;不能仅对屏幕上最初的路径字符串做权限推断。

基础 Linux 权限会先判断你属于哪一类,再使用那一组权限,不会把所有者、所属组和其他人的权限相加。你是文件所有者,就看 owner 那组;即使 other 允许读取,也不能拿它补上 owner 没有的读权限。

判断组时,还要看用户加入的附加组。真实系统可能另有逐用户授权的 ACL、进程能力、挂载选项,以及 SELinux 或 AppArmor 的安全规则。本题算法只计算前面这套基础权限,不能代替实际系统的完整判断。

进程问题可以从 ps -eo pid,ppid,user,stat,etime,args 观察身份和命令行,再用 systemctl status myapp.service --no-pager 与 journalctl -u myapp.service --since "10 minutes ago" 关联同一时间窗口。

名称需替换为自己的练习服务。命令行可能含敏感参数,排障记录应只保留必要字段,日志不能原样公开。

进程不存在时先查退出原因、启动路径与依赖;存在但请求失败时,继续检查监听地址、端口、配置、依赖连接和实际响应。修改前提出具体假设,例如服务账户不能穿过父目录,然后只调整与证据一致的所有者或最小权限。chmod 777 会扩大可写范围,并不能修复错误 JDK 路径、数据库配置或端口绑定。

服务停止还涉及优雅退出与资源回收。正常停止请求给应用清理机会,强制终止则可能让尚未完成的工作中断。观察超时、子进程和服务管理器策略,再决定是否升级终止方式。重启之后仍需确认启动的是预期制品与配置,并用业务请求验证,不能只看到新 PID 就认为故障已经解决。

正在加载流程图…

由文件系统到进程再到请求逐层收集证据,各层回答不同问题。
查看流程图源码(Mermaid)
flowchart TD
 A["确认用户与当前目录"] --> B["检查路径各级权限"]
 B --> C["确认进程身份和启动命令"]
 C --> D["读取服务与日志状态"]
 D --> E["检查监听和真实请求"]
 E --> F["按证据最小修改并复测"]

按顺序看,每一步做了什么

  1. 确认环境、用户和目标路径。
  2. 沿父目录链检查搜索权限。
  3. 确认 PID、用户、启动参数与制品。
  4. 按服务时间窗口关联日志。
  5. 修改后重复原失败请求并记录结果。

03对照实现代码

ProcessBuilder.start:Java 到操作系统进程的边界 (新标签页)↗

Microsoft OpenJDK 17.0.20.1;java.base/java/lang/ProcessBuilder.java · java.lang.ProcessBuilder#start()

以下为原创 Java 17 教学实现,不是原样 JDK 源码;只保留文中明确讨论的机制,省略优化、兼容及内部接口。

核心机制代码 · java
import java.nio.file.Path;
import java.util.*;
public final class SourceModel {
  static ProcessBuilder prepare(Path directory,List<String> arguments) {
    if(arguments.isEmpty()) throw new IllegalArgumentException("empty command");
    List<String> copy=new ArrayList<>();
    for(String arg:arguments) copy.add(Objects.requireNonNull(arg)); // 固定参数快照。
    ProcessBuilder builder=new ProcessBuilder(copy); // 参数分开传递,不拼成 shell 命令。
    builder.directory(directory.toFile()); // 工作目录是独立配置,不等于修改全局目录。
    builder.redirectErrorStream(true); // 若真正启动,调用方仍需消费输出并管理进程。
    return builder; // 原创配置模型:返回构建器,不启动外部程序。
  }
}

本机 ProcessBuilder.start 入口委托私有 start 方法,后者把命令列表转换并克隆为数组,再执行进一步校验和平台调用。这说明构建命令与启动进程之间存在明确边界;设置 builder 的目录不等于当前 Java 进程已经改变目录,也不意味着子程序已执行成功。

Java 可以通过 ProcessBuilder 与操作系统交互,但实际 Linux 权限检查和进程创建发生在内核与平台实现中。教学代码只展示 Java 客户端如何准备参数,不是 Linux 内核的 Java 重写。Windows 上编译模型能够检查 Java 语法,不能证明 Ubuntu 的账户、ACL 或服务行为。

把可执行文件与每个参数分别传入,可以避免无意引入 shell 展开语义。若显式调用 bash -c,输入又会被当作 shell 程序解释,参数数组本身不再自动消除字符串拼接风险。实际运行工具时还应选择可信可执行路径,并对用户可控制的参数制定业务约束。

start 返回 Process,只表示得到了子进程对象,不表示命令已经结束。还要读取标准输出和错误、等待结果并处理取消;管道没人读,子程序可能因写不出去而卡住。退出码怎样解释由命令定义,工具通常还要设超时,并明确终止时是否处理它启动的后代进程。

代码先调用谁,再调用谁

  1. 准备独立命令参数
  2. ProcessBuilder 配置目录和重定向
  3. start 校验并调用平台实现
  4. 操作系统执行权限与进程创建
  5. 调用方消费输出并检查完成结果

04动手写一个例子

普通用户沿目录链的读取权限判定

模拟无 root、无 ACL、无符号链接及安全模块的传统九位权限。读取文件需要每个父目录可搜索,且文件适用类别有读位。输入都是内存对象,不读取或修改真实权限。

输入 1
父目录 0700 属于别人,文件 0644
输出 1
拒绝
输入 2
父目录 0755,文件所属组可读且用户在该组
输出 2
允许

把完整检查拆成两个步骤:先为每个节点根据用户与组选择适用的三个位,再检查该操作需要的位。搜索需要一,读取需要四。用位与判断是否包含所有需要的权限,避免把八进制模式当作十进制数解释。

每个父目录都必须可搜索,因此可以遇到第一处拒绝就返回 false;只有全部通过,才检查目标文件读权限。这是路径条件的合取,不是各级权限求和。文件类型也在输入校验中检查,防止把普通文件误放到父目录链中。

算法显式拒绝超出 0777 的模式,以免读者误以为实现了 sticky、setuid 和 setgid 等特殊位。它还要求目标为普通文件。测试集中包括 owner 优先、附加组、other、父目录阻断、空父链和非法模式,结果只解释模型内的权限逻辑。

为什么这样写是对的继续处理第 i 层时,之前所有父目录均具有适用身份下的搜索权限。

时间 O(d),d 为父目录数,组成员查询预期 O(1)。额外空间 判定额外空间 O(1),输入组集合与路径链不计。

每个目录选择一次类别并检查一位,最终文件再检查一次;不枚举目录内容。

保存为 Main.java,运行 javac --release 17 -encoding UTF-8 Main.java,再运行 java Main。程序包含固定输入自测,断言失败时会抛出错误。

展开收起代码 · Main.java · 完整可运行程序Java · 34 行

长行可在代码区域内左右滚动,按 Tab 可把焦点移入代码区。

Main.java · 完整可运行程序 · Java
import java.util.*;
public class Main {
  record User(int uid,Set<Integer> groups) {}
  record Node(int owner,int group,int mode,boolean directory) {}
  static int bits(User u,Node n) {
    if(n.mode()<0 || n.mode()>0777) throw new IllegalArgumentException("mode");
    if(u.uid()==n.owner()) return (n.mode()>>6)&7; // 所有者优先,不与其他类别相加。
    if(u.groups().contains(n.group())) return (n.mode()>>3)&7;
    return n.mode()&7;
  }
  static boolean canRead(User u,List<Node> parents,Node file) {
    if(file.directory()) throw new IllegalArgumentException("regular file required");
    for(Node dir:parents) {
      if(!dir.directory()) throw new IllegalArgumentException("parent is not directory");
      if((bits(u,dir)&1)==0) return false; // 每一级父目录都需要搜索权限。
    }
    return (bits(u,file)&4)!=0; // 文件本身只检查本题需要的读位。
  }
  static void eq(boolean a,boolean b) {if(a!=b) throw new AssertionError();}
  public static void main(String[] args) {
    User u=new User(100,Set.of(10,20));
    Node open=new Node(1,1,0755,true);
    eq(canRead(u,List.of(open),new Node(100,1,0400,false)),true);
    eq(canRead(u,List.of(open),new Node(2,20,0040,false)),true);
    eq(canRead(u,List.of(open),new Node(2,30,0004,false)),true);
    eq(canRead(u,List.of(new Node(2,2,0700,true)),new Node(2,2,0644,false)),false);
    eq(canRead(u,List.of(open),new Node(100,1,0004,false)),false); // owner 无读位,other 不能补救。
    eq(canRead(u,List.of(),new Node(100,1,0400,false)),true);
    eq(canRead(u,List.of(open),new Node(2,2,0200,false)),false);
    try {canRead(u,List.of(),new Node(1,1,01000,false)); throw new AssertionError();}
    catch(IllegalArgumentException expected) {}
    System.out.println("PASS 8");
  }
}

这些特殊情况也要试一试

  • 所有者没有读权限时,不再尝试 other 类别。
  • 任一级目录无搜索权限就拒绝。
  • 特殊位和 ACL 不在模型中。
  • 目标必须为普通文件。
进阶选读 · 真实访问诊断器

场景基础模式允许,但应用仍得到权限错误。

做法结合实际服务身份、路径解析、ACL、挂载属性和安全模块日志逐层定位,报告证据与未知条件,不凭静态模式直接断言允许。

复杂度路径扫描随深度增长,系统查询和策略评估成本依赖环境。

好处与代价更贴近实际但受权限、竞态和平台差异影响,检查与实际打开之间还可能发生变化。

05项目里怎么用

应用账户应具备完成任务所需的最小目录和文件权限。部署制品通常无需由运行账户任意修改,日志与临时文件可放到明确可写的位置;将代码目录、数据目录和日志目录职责分开,能更容易解释权限。所有者错误应修正所有权或部署流程,而不是把所有路径开放为全员可写。

systemd 单位的 User、WorkingDirectory、ExecStart 和环境配置共同影响实际启动行为。交互式 shell 中成功的命令可能依赖本地 PATH 或环境文件,而服务并未继承它们。排查时对齐服务使用的绝对路径、JDK 和配置来源,避免为了让服务暂时启动而在多个位置复制不一致的配置。

现场操作需要保留时间关联:失败请求发生时的进程、日志、端口与资源状态比相隔很久的截图更有解释力。重启可能清除瞬时线索,也会改变 PID,因此在可行范围内先采集必要只读证据,再执行有依据的修复。恢复后同时确认错误消失与关键行为正确,不将管理器状态当作最终健康结论。

容易踩的坑

  • 只看文件 0644 而漏查父目录搜索权限。
  • 将三类权限相加。
  • 终端 java 版本与服务实际 JDK 混为一谈。
  • 用 chmod 777 掩盖身份和路径设计错误。

几种做法,怎么选

表格有四列,窄屏可左右滑动查看代价和适用条件。

方案优势代价与限制适用条件
只读逐层检查保留现场且定位范围清楚需要理解证据含义首次排障
服务账户最小授权减少意外修改范围部署目录需合理规划长期运行服务
直接放宽全部权限可能快速改变表面现象扩大可写范围且原因不清不应作为常规修复策略

06检验是否读懂了

先自己想、自己写,再展开答案核对。场景题是假设的练习情境;遇到还没学过的内容,可以做完基础后再回来。

先用自己的话解释

01目录执行位代表什么?

展开参考答案与推导

主要关系到沿目录搜索后续路径分量,不等同于运行目录中的所有文件。列名字需要目录读权限,访问已知名字仍依赖父目录链的搜索条件。

02active 为什么不等于业务正常?

展开参考答案与推导

管理器状态只反映服务模型内的条件,应用可能依赖失败、配置错误或请求处理异常。需要监听与真实请求等更高层证据确认就绪。

03owner、group、other 如何选择?

展开参考答案与推导

先判断所有者,再判断匹配组,否则使用其他人类别,不能累加三个类别的权限。真实系统还会有 ACL 等额外规则,本模型不覆盖。

换个情况,还会处理吗

01自己的账户能运行 jar,服务账户失败怎么办?

展开参考答案与推导

对照服务用户、工作目录、JDK 路径和环境,逐层检查父目录与目标文件权限。不要只复制交互式终端命令或直接放宽所有权限。

02进程存在但端口不通如何继续?

展开参考答案与推导

核对是否监听预期地址和端口、初始化是否完成,再检查访问路径与应用日志;进程列表只能证明该实例存在,无法说明网络入口和依赖状态。

03模式位都允许但仍被拒绝?

展开参考答案与推导

检查真实路径和符号链接、ACL、挂载限制、安全模块以及服务身份差异。将模型外条件列为待核实项,不用基础位算法否定实际错误。

写代码试一试

01判定 owner 优先。

输入 / 条件
用户是 owner,模式 0004
输出 / 预期
拒绝读取

提示 不能尝试其他类别。

展开参考答案与推导

适用 owner 三位为零,所以没有读权限。other 的四不能为所有者提供补充,这个案例专门排除权限累加的错误实现。

02查找首个阻断目录。

输入 / 条件
0755、0700 属于其他人、文件 0644
输出 / 预期
第二级目录阻断

提示 沿链逐层检查。

展开参考答案与推导

第一层搜索允许,第二层适用 other 为零,立即拒绝。目标文件可读不改变路径无法解析的事实。

03附加组是否参与判断?

输入 / 条件
文件组 20,用户附加组包含 20,模式 0040
输出 / 预期
允许读取

提示 用组集合匹配。

展开参考答案与推导

用户不是 owner 时匹配所属组,适用组读位为四,因此允许。真实身份数据应来自服务账户,而不是当前观察者。

读完之后,独立完成

课后作业:自己做一遍

本篇共 10 道作业,先完成 8 道基础与综合任务,再按前置知识挑战进阶题。其中包含 1 道思维拓展,试着换个条件,看看原来的办法是否还管用。不附参考答案。

这次练什么

能用只读命令定位 Java 进程,区分权限错误、进程退出和应用不可用。

本篇重点加练

目录链决定真实可达性,进程身份则不能只依赖可能复用的 PID。

  • 目录遍历权限
  • PID 与命令行

重点内容会多练几次。先说清楚怎么做,再试试空数据、出错或条件改变时会发生什么。

先自己做,卡住时记下问题,再回来查。问答题写清“为什么、什么时候适用”;动手题留下输入、输出和运行步骤,方便下次重做。

基础必做

  1. 作业 01排错实验

    在本地 Linux、WSL 或独立容器的临时目录建立脚本和数据文件,记录当前目录、绝对路径及执行用户组,再从另一目录运行脚本。

    提交内容
    环境说明与命令记录
    怎样算完成
    • 不改变系统目录权限
    • 区分相对路径和运行账户

    练到的知识点路径与当前目录 · 用户和组

  2. 作业 02代码推演

    文件本身可读,但它位于两层目录内。为三种目录权限组合写出访问判断,再仅在临时目录用测试账户或权限模型核对。

    提交内容
    权限矩阵与观测
    怎样算完成
    • 分别判断文件读写与目录遍历
    • 注明真实账户或模拟器边界

    练到的知识点读写执行权限 · 目录遍历权限

  3. 作业 03原理问答

    启动脚本返回 0,服务管理器显示运行中,但业务请求失败。分别定义退出码、服务状态、端口监听和业务健康能证明什么。

    提交内容
    状态与证据对照表
    怎样算完成
    • 包含启动成功但依赖失败
    • 不将任一信号替代完整健康验收

    练到的知识点服务状态 · 退出码与业务健康

  4. 作业 04代码推演

    日志文件通过符号链接指向另一目录,应用用户可访问链接所在目录却不一定能访问目标链。分别画出路径解析与权限检查需要的对象。

    提交内容
    目录链及访问规则
    怎样算完成
    • 区分链接和目标
    • 注明实际平台与挂载假设

    练到的知识点目录遍历权限

综合应用

  1. 作业 05面试场景

    同名 Java 进程有三个,只有一个属于当前实验。设计确认 PID、完整命令行、工作目录与进程状态的取证步骤。

    提交内容
    进程证据清单
    怎样算完成
    • 不凭进程名执行终止
    • 说明瞬时状态的观测局限

    练到的知识点PID 与命令行 · 进程状态

  2. 作业 06编程实践

    编写只读诊断脚本检查本地实验应用的路径、账户、权限、PID、监听与自建健康接口,输出每项成功、失败或未知。

    提交内容
    脚本与三组故障报告
    怎样算完成
    • 不修改权限或自动杀进程
    • 全部目标由明确参数限定

    练到的知识点路径与当前目录 · 用户和组 · 读写执行权限 · 目录遍历权限 · PID 与命令行 · 进程状态 · 服务状态 · 退出码与业务健康

  3. 作业 07面试场景

    诊断脚本记下一个 PID,十分钟后准备发信号,但原进程可能已结束。设计再次确认目标身份所需的字段。

    提交内容
    进程身份核验单
    怎样算完成
    • 包括命令行与启动信息
    • 不把 PID 持续相同当作同一任务

    练到的知识点PID 与命令行

  4. 作业 08编程实践

    在本地 Linux、WSL 或隔离容器临时目录创建链接与短命子进程,写只读脚本报告真实路径和目标进程身份;不自动修权限或终止进程。

    提交内容
    脚本与观察报告
    怎样算完成
    • 限制实验资源范围
    • 缺权限或目标消失报告为未知或失效

    练到的知识点目录遍历权限 · PID 与命令行

进阶挑战 · 掌握前置后选做

  1. 作业 09设计评审

    应用切换为专用服务账户后无法写日志,运维提议赋予所有目录完全权限。制定最小范围调查和修复验收计划。

    提交内容
    权限需求表与验证步骤
    怎样算完成
    • 逐个目录说明访问用途
    • 包含修复后业务及日志验证

    练到的知识点用户和组 · 读写执行权限 · 目录遍历权限 · PID 与命令行 · 服务状态 · 退出码与业务健康

思维拓展

先写出你的猜想,再用例子或实验验证。答案可以不同,但要说清依据、适用情况,以及还有什么没弄懂。

  1. 作业 10思维拓展 · 开放探究

    服务把日志目录作为可热更新配置,路径可能在检查后被更换。研究一次权限检查能支持多长时间的承诺,提出可验证的边界。

    提交内容
    时间竞态分析与研究计划
    怎样算完成
    • 区分检查对象和实际使用对象
    • 仅在本地临时目录演练

    练到的知识点目录遍历权限 · PID 与命令行

完成后,再检验一次

给每题标记“独立完成 / 查阅后完成 / 尚未完成”。查过资料或没做出的题,记下卡在哪里。隔一天换一组输入再试,能解释结果和出错原因,再把这个知识点标为掌握。

继续阅读

先看能帮你完成当前练习的部分。例子跑通后,再读更深入的解释。

打开资料库,找下一份学习材料 ↗