目录
[git 的三板斧](#git 的三板斧)
[一、Git 的核心定义](#一、Git 的核心定义)
[二、操作流程与 Git 核心指令解析](#二、操作流程与 Git 核心指令解析)
[三、Git 核心三层结构(工作区→暂存区→版本库)](#三、Git 核心三层结构(工作区→暂存区→版本库))
四、核心总结
[进程 和 fork函数](#进程 和 fork函数)[二、fork 函数的核心解析](#二、fork 函数的核心解析)
总结
[Linux 进程的状态](#Linux 进程的状态)[Linux 进程核心基础状态](#Linux 进程核心基础状态)
[运行状态 R](#运行状态 R)
[一、ps axj | head -1 && ps axj | grep myproc 命令解析](#一、ps axj | head -1 && ps axj | grep myproc 命令解析)
[二、进程运行状态(STAT 列)解析](#二、进程运行状态(STAT 列)解析)
[kill 进程](#kill 进程)
[一、./myproc & 指令的核心解析](#一、./myproc & 指令的核心解析)
[二、kill -9 4499 指令的核心解析](#二、kill -9 4499 指令的核心解析)
[睡眠状态 S](#睡眠状态 S)
[一、睡眠状态(STAT 列 S)的核心解析](#一、睡眠状态(STAT 列 S)的核心解析)
[深度睡眠状态 D](#深度睡眠状态 D)
[一、深度睡眠状态 D(Disk Sleep/Uninterruptible Sleep)核心定义](#一、深度睡眠状态 D(Disk Sleep/Uninterruptible Sleep)核心定义)
[二、D 态的核心特征(与可中断睡眠 S 态对比)](#二、D 态的核心特征(与可中断睡眠 S 态对比))
[三、D 态的典型触发场景](#三、D 态的典型触发场景)
[四、为什么 D 态无法被 kill -9 终止?](#四、为什么 D 态无法被 kill -9 终止?)
[五、D 态进程的排查与处理](#五、D 态进程的排查与处理)
[停止状态 T](#停止状态 T)
[一、kill -19 5087 命令解析](#一、kill -19 5087 命令解析)
[二、kill -18 5087 命令解析](#二、kill -18 5087 命令解析)
[三、停止状态(STAT 列 T)核心解析](#三、停止状态(STAT 列 T)核心解析)
[僵尸状态 Z](#僵尸状态 Z)
[一、僵尸进程(Zombie Process)核心定义](#一、僵尸进程(Zombie Process)核心定义)
六、核心总结
[vim 的批量化修改](#vim 的批量化修改)
进程优先级[二、PRI(new) = PRI(old) + NI 公式解析](#二、PRI(new) = PRI(old) + NI 公式解析)
[三、NI(Nice 值)的调整区间 [-20, 19]](#三、NI(Nice 值)的调整区间 [-20, 19])
[一、环境变量(Environment Variable)的核心解析](#一、环境变量(Environment Variable)的核心解析)
[二、printf("PATH: %s\n", getenv("PATH")); 代码解析](#二、printf("PATH: %s\n", getenv("PATH")); 代码解析)
[一、strcpy(who, getenv("USER")); 代码解析](#一、strcpy(who, getenv("USER")); 代码解析)
git 的三板斧
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test]$ git clone https://gitee.com...
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test]$ ll
total 16
drwxrwxr-x 3 ranjiaju ranjiaju 4096 Dec 14 14:19 learning-linux
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Dec 13 16:53 process
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Dec 10 11:25 processbar
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Dec 14 11:26 test_1
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test]$ cp -rf processbar learning-linux/
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test]$ cd learning-linux/
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ learning-linux]$ ll
total 12
drwxrwxr-x 2 ranjiaju ranjiaju 4096 Dec 14 14:25 processbar
-rw-rw-r-- 1 ranjiaju ranjiaju 853 Dec 14 14:19 README.en.md
-rw-rw-r-- 1 ranjiaju ranjiaju 942 Dec 14 14:19 README.md
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ learning-linux]$ git add .
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ learning-linux]$ git commit -m "提交模拟实现进程进度条的代码"
*** Please tell me who you are.
Run
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
to set your account's default identity.
Omit --global to set the identity only in this repository.
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ learning-linux]$ git config --global user.email "XXXXXX@qq.com"
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ learning-linux]$ git config --global user.name "XXX"
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ learning-linux]$ git commit -m "提交模拟实现进程进度条的代码"
[master 4e5f524] 提交模拟实现进程进度条的代码
5 files changed, 45 insertions(+)
create mode 100644 processbar/main.c
create mode 100644 processbar/makefile
create mode 100644 processbar/processBar.c
create mode 100644 processbar/processBar.h
create mode 100755 processbar/processbar
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ learning-linux]$ git push
warning: push.default is unset; its implicit value is changing in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the current behavior after the default changes, use:
git config --global push.default matching
To squelch this message and adopt the new behavior now, use:
git config --global push.default simple
See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)
Username for 'https://gitee.com': XXX
Password for 'https://ran-jiaju@gitee.com':
Counting objects: 9, done.
Delta compression using up to 2 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 4.91 KiB | 0 bytes/s, done.
Total 8 (delta 0), reused 0 (delta 0)
remote: Powered by GITEE.COM [1.1.23]
remote: Set trace flag cc448d8a
To https://gitee.com/ran-jiaju/learning-linux.git
5f16097..4e5f524 master -> master
一、Git 的核心定义
Git 是一款 分布式版本控制系统(Distributed Version Control System,DVCS),核心作用是:
- 追踪文件 / 代码的修改记录(谁改、改了什么、什么时候改);
- 管理代码的版本(可回滚到任意历史版本,避免代码丢失 / 误改);
- 支持本地独立完成版本管理(无需联网),也能与远程仓库(如 Gitee、GitHub)同步,实现多人协作开发;
- 核心优势:分布式(本地有完整版本库)、轻量、高效、分支管理灵活。
你提供的操作是 Git 最典型的 "从远程克隆仓库→本地新增文件→提交到本地版本库→推送到远程仓库" 流程,下面结合每一步操作拆解 Git 核心概念和指令。
二、操作流程与 Git 核心指令解析
1. git clone https://gitee.com...:克隆远程仓库到本地
- 核心作用 :将 Gitee 上的远程仓库(
learning-linux)完整复制到本地/home/ranjiaju/test目录,同时建立本地仓库与远程仓库的关联(记录远程仓库地址、分支映射等); - 关键特性 :克隆后本地会生成
.git隐藏目录(Git 版本库核心,存储所有版本记录、配置、分支信息),且默认关联远程仓库的master分支。
2. cp -rf processbar learning-linux/:本地工作区新增文件
- Git 中的「工作区(Working Directory)」:就是你能看到的本地目录(如
learning-linux/),是日常编辑、新增 / 删除文件的区域; - 这一步是在工作区新增
processbar目录及文件,属于 "未被 Git 追踪的修改"(Git 此时还不知道这些文件存在)。
3. git add .:将工作区修改添加到暂存区
- 暂存区(Staging Area/Index):Git 中工作区和版本库之间的 "过渡层",用于临时存放 "准备提交的修改";
- 指令解析:
git add:将指定文件 / 目录的修改(新增、修改、删除)从工作区提交到暂存区;.:通配符,代表当前目录下所有未被 Git 追踪的文件 / 修改(即新增的processbar目录及所有文件);
- 核心目的:告诉 Git"这些修改是我要提交的,先暂存起来"。
4. 第一次 git commit -m "提交备注" 失败:缺少用户身份配置
- commit(提交) :将暂存区的修改 "正式保存" 到本地版本库(Repository),生成一个唯一的版本哈希值(如后续的
4e5f524),是 Git 版本管理的核心操作; - 失败原因:Git 要求每一次提交必须记录 "提交者身份(用户名 + 邮箱)",用于追溯修改人,首次使用需先配置;
-m "备注":指定提交说明,必须填写(或用-amend补填),用于描述本次提交的内容(如 "新增进度条代码"),便于后续追溯版本。
5. git config --global user.email/name:配置 Git 身份
- 核心作用 :设置 Git 的全局提交者身份(用户名 + 邮箱);
--global:全局配置(所有本地 Git 仓库都生效);若省略,则仅当前learning-linux仓库生效;user.email/user.name:Git 提交记录中显示的邮箱和用户名(需与 Gitee 账号的邮箱 / 用户名一致,否则推送可能权限异常);
- 配置后,Git 会将身份信息存储在
~/.gitconfig(全局)或仓库.git/config(本地)文件中,后续提交会自动关联该身份。
6. 第二次 git commit -m "备注":提交到本地版本库
-
执行成功后输出解读:
[master 4e5f524] 提交模拟实现进程进度条的代码 5 files changed, 45 insertions(+) create mode 100644 processbar/main.c ...master:提交到本地master分支(Git 默认主分支);4e5f524:本次提交的唯一哈希值(版本 ID,可通过该值回滚 / 查看该版本);5 files changed:本次提交修改了 5 个文件;create mode...:新增了这些文件,100644/100755是文件权限(Git 会记录文件权限);
-
核心结果:暂存区的
processbar相关文件被永久保存到本地版本库,可通过git log查看提交记录。
7. git push:将本地提交推送到远程仓库(Gitee)
- 核心作用 :把本地
master分支的提交(4e5f524)同步到 Gitee 上的远程仓库,实现代码 "本地→远程" 的备份 / 共享; - 关键细节解读:
-
用户名 / 密码验证:Git 推送远程仓库需验证 Gitee 账号密码(也可配置 SSH 免密登录);
-
警告信息 :
push.default是 Git 推送策略配置,默认值从matching改为simple(Git 2.0 后),仅影响推送规则(simple只推送当前分支到远程同名分支,更安全),不影响本次推送; -
推送日志 :
To https://gitee.com/ran-jiaju/learning-linux.git 5f16097..4e5f524 master -> master表示将本地
master分支从5f16097版本更新到4e5f524版本,并推送到远程master分支。
-
三、Git 核心三层结构(工作区→暂存区→版本库)
操作完整体现了 Git 的核心数据流转逻辑:
远程仓库(Gitee)
↓(git clone)
本地工作区(learning-linux/processbar 新增文件)
↓(git add .)
本地暂存区(记录要提交的文件)
↓(git commit)
本地版本库(.git 目录,永久保存提交记录)
↓(git push)
远程仓库(Gitee 同步更新)
- 工作区 :日常编辑文件的目录,Git 不主动追踪,需通过
git add加入暂存区; - 暂存区 :临时存放待提交的修改,可多次
git add累积修改,再一次性git commit; - 版本库:Git 的核心,存储所有提交记录、分支、标签等,是本地版本管理的 "数据库"。
四、核心总结
Git 的核心价值是版本追踪与远程同步:
- 本地操作(
add/commit):完成代码的版本管理,即使断网也能独立开发; - 远程操作(
clone/push):实现代码的共享、备份、多人协作; - 你本次操作是 Git 最基础的 "从远程克隆→本地新增代码→提交→推送" 流程,覆盖了 Git 日常使用的核心指令,也是多人协作中 "提交代码到远程仓库" 的标准步骤。
补充:若需拉取远程仓库的最新代码,可使用 git pull;若需查看提交记录,用 git log;若需回滚版本,用 git reset --hard <版本哈希>。
进程 和 fork函数
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ process]$ pwd
/home/ranjiaju/test/process
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ process]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 80 Dec 13 16:41 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 604 Dec 13 16:50 process.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ process]$ cat process.c
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
printf("begin: 我是一个进程, pid: %d, ppid: %d\n", getpid(), getppid());
pid_t id = fork();
if(id == 0)
{
// 子进程
while(1)
{
printf("我是子进程, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
else if(id > 0)
{
// 父进程
while(1)
{
printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
else
{
}
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ process]$ cat makefile
process:process.c
gcc process.c -o process
.PHONY:clean
clean:
rm -rf process
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ process]$ make
gcc process.c -o process
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ process]$ ./process
begin: 我是一个进程, pid: 3227, ppid: 2355
我是父进程, pid: 3227, ppid: 2355
我是子进程, pid: 3228, ppid: 3227
我是父进程, pid: 3227, ppid: 2355
我是子进程, pid: 3228, ppid: 3227
我是父进程, pid: 3227, ppid: 2355
我是子进程, pid: 3228, ppid: 3227
我是子进程, pid: 3228, ppid: 3227
我是父进程, pid: 3227, ppid: 2355
我是子进程, pid: 3228, ppid: 3227
我是父进程, pid: 3227, ppid: 2355
我是父进程, pid: 3227, ppid: 2355
我是子进程, pid: 3228, ppid: 3227
// ......
一、进程的核心解析
进程(Process)是 Linux 系统中 程序的运行实例 ,是操作系统资源分配和调度的基本单位 ------ 程序(如 process.c 是静态文本文件)被编译为可执行文件(process)并运行后,会成为一个独立的进程,拥有唯一的进程 ID(PID)、独立的内存空间、CPU 时间片等系统资源。
核心特性(结合示例):
- PID(Process ID) :进程的唯一标识(示例中父进程 PID 为 3227,子进程 PID 为 3228),可通过
getpid()函数获取当前进程的 PID; - PPID(Parent Process ID) :父进程的 PID(示例中子进程 PPID 为 3227,即父进程 PID;父进程 PPID 为 2355,即启动该进程的终端进程 PID),可通过
getppid()函数获取; - 独立性:不同进程拥有独立的运行环境,即使是父子进程,也各自独立执行代码逻辑;
- 并发执行:多个进程(如示例中的父子进程)会被操作系统调度,交替占用 CPU 资源,因此输出顺序可能不固定。
二、fork 函数的核心解析
fork() 是 Linux 下的 系统调用函数 (头文件 <unistd.h>/<sys/types.h>),核心作用是 从当前进程(父进程)创建一个新进程(子进程),是实现进程创建的核心方式。
1. 核心特性:
- 复制式创建:子进程会完整复制父进程的内存空间、代码段、数据段等(采用 "写时复制" 机制,仅当父子进程修改数据时才真正拷贝,节省资源);
- 一次调用,两次返回 :
fork()调用后,父进程和子进程都会继续执行后续代码,但返回值不同:- 父进程:返回子进程的 PID(正整数,示例中
id > 0分支); - 子进程:返回 0(示例中
id == 0分支); - 失败时:返回 -1(示例中
else分支未处理失败场景);
- 父进程:返回子进程的 PID(正整数,示例中
- 并发执行:父子进程创建后,由操作系统调度器决定执行顺序,无固定先后(示例中输出顺序交替,体现此特性)。
2. 函数调用的关键逻辑(结合代码):
pid_t id = fork(); // 创建子进程,父进程返回子进程PID,子进程返回0
这行代码是进程分裂的 "分水岭":代码执行到 fork() 前,只有 1 个进程(父进程);执行后,分裂为 2 个独立进程,分别进入不同分支执行。
三、代码运行结果的逐行解析
示例中代码运行后输出分为三个核心阶段,结合逻辑拆解如下:
1. 初始阶段:begin 仅打印一次
begin: 我是一个进程, pid: 3227, ppid: 2355
- 原因:
fork()调用前,仅父进程运行,因此printf("begin: ...")仅执行一次; - 数值含义:此时进程 PID 为 3227(父进程),PPID 为 2355(启动该进程的终端进程 PID)。
2. 进程分裂后:父子进程各自循环打印
我是父进程, pid: 3227, ppid: 2355
我是子进程, pid: 3228, ppid: 3227
- 父进程分支(
id > 0):进入while(1)循环,每秒打印自身 PID(3227)和 PPID(2355); - 子进程分支(
id == 0):进入while(1)循环,每秒打印自身 PID(3228)和 PPID(3227,即父进程 PID); - 输出顺序不固定:操作系统并发调度父子进程,因此 "父进程 / 子进程" 的打印顺序无规律(示例中出现 "父→子→父→子""子→父→父→子" 等交替情况)。
3. 循环持续执行
代码中父子进程均为 while(1) 死循环 + sleep(1),因此会持续每秒打印一次进程信息,直到手动终止(如按 Ctrl+C)。
四、核心补充细节
- 资源占用 :父子进程是独立进程,各自占用 CPU 时间片,
sleep(1)让进程休眠 1 秒,降低 CPU 占用率; - 进程终止 :若手动终止父进程(如
kill 3227),子进程的 PPID 会变为 1(init 进程 PID),成为 "孤儿进程",由 init 进程接管; - fork 执行逻辑 :
fork()后父子进程共享代码段,但数据段是 "写时复制"------ 若父子进程修改同一变量,各自修改的是私有副本,互不影响。
总结
- 进程是程序的运行实例,由 PID/PPID 唯一标识,是系统资源分配的基本单位;
fork()是创建子进程的核心系统调用,一次调用返回两次,父子进程并发执行;- 代码运行结果中,
begin仅打印一次(fork()前),父子进程各自循环打印 PID/PPID,输出顺序因并发调度不固定。
Linux 进程的状态
Linux 进程核心基础状态
| 状态字符 | 含义 |
|---|---|
| R(Running/Runnable) | 运行态 / 可运行态:进程要么正在占用 CPU 执行,要么处于就绪队列等待 CPU 调度(最常见的高占用状态) |
| S(Sleeping) | 睡眠态(可中断):进程等待某个事件完成(如 sleep、I/O 操作),可被信号唤醒,CPU 占用率极低 |
| D(Disk Sleep) | 磁盘睡眠态(不可中断):进程等待磁盘 I/O 完成,无法被信号唤醒,避免 I/O 中断导致数据丢失 |
| Z(Zombie) | 僵尸态:进程已终止,但父进程未回收其资源(PID、状态等),是 "空壳进程" |
| T(Stopped) | 停止态:进程被信号暂停(如 Ctrl+Z),可通过 kill -CONT <PID> 恢复运行 |
| X(Dead) | 死亡态:进程已完全终止,瞬间消失,无法通过 ps 捕获 |
运行状态 R
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ pwd
/home/ranjiaju/test/test_1
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 75 Dec 13 21:41 makefile
-rw-rw-r-- 1 ranjiaju ranjiaju 82 Dec 13 22:19 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ cat makefile
myproc:myproc.c
gcc myproc.c -o myproc
.PHONY:clean
clean:
rm -rf myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ cat myproc.c
#include<stdio.h>
#include<unistd.h>
int main()
{
while(1);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ make
gcc myproc.c -o myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ll
total 20
-rw-rw-r-- 1 ranjiaju ranjiaju 75 Dec 13 21:41 makefile
-rwxrwxr-x 1 ranjiaju ranjiaju 8432 Dec 13 22:23 myproc
-rw-rw-r-- 1 ranjiaju ranjiaju 82 Dec 13 22:19 myproc.c
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ./myproc
// ......
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 4474 4474 3744 pts/1 4474 R+ 1001 3:40 ./myproc
2355 4486 4485 2355 pts/0 4485 R+ 1001 0:00 grep --color=auto myproc
一、ps axj | head -1 && ps axj | grep myproc 命令解析
该命令是 Linux 下 组合式进程查询指令 ,核心作用是 "先显示进程查询的表头(字段说明),再精准过滤出与 myproc 相关的进程信息",便于清晰解读目标进程的完整属性,各组件拆解及输出解析如下:
1. 命令各组件核心含义
| 组件 | 功能拆解 | |
|---|---|---|
ps axj |
进程查询核心命令:- ps(Process Status):查看系统进程的状态信息;- a:显示所有用户的进程(非仅当前用户);- x:显示无终端(TTY)关联的进程(如后台守护进程);- j:显示与作业控制相关的字段(PPID、PID、PGID、SID 等),输出进程间的层级关系 |
|
| ` | `(管道符) | 数据重定向工具:将前一个命令的输出作为后一个命令的输入 |
head -1 |
提取 ps axj 输出的第一行(即表头行),用于标注后续进程信息的字段含义 |
|
&&(逻辑与) |
命令执行控制:仅当前一个命令(`ps axj | head -1`)执行成功时,才执行后一个命令 |
grep myproc |
过滤命令:从 ps axj 的输出中筛选出包含 myproc 字符串的行,定位目标进程 |
2. 输出内容逐行解析(结合示例)
示例输出分为 "表头 + 目标进程 + grep 自身进程" 三行,各字段及数值含义如下:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 4474 4474 3744 pts/1 4474 R+ 1001 3:40 ./myproc
2355 4486 4485 2355 pts/0 4485 R+ 1001 0:00 grep --color=auto myproc
| 字段 | 核心含义 | 示例值解析(./myproc 进程) |
|---|---|---|
| PPID | 父进程 PID(启动当前进程的进程 ID) | 3744(启动 ./myproc 的终端进程 PID) |
| PID | 当前进程的唯一标识(PID) | 4474(./myproc 进程的 PID) |
| PGID | 进程组 ID(同一进程组的进程共享作业控制) | 4474(./myproc 是进程组组长) |
| SID | 会话 ID(会话是进程组的集合,关联终端) | 3744(与启动终端的会话 ID 一致) |
| TTY | 进程关联的终端(pts/1/pts/0 为伪终端,对应不同的终端窗口) |
pts/1(./myproc 运行在 pts/1 终端) |
| TPGID | 终端前台进程组 ID(前台进程占用终端输入 / 输出) | 4474(./myproc 是终端前台进程) |
| STAT | 进程运行状态 | R+(前台可运行态) |
| UID | 进程所属用户 ID(1001 对应 ranjiaju 用户) |
1001(ranjiaju 运行该进程) |
| TIME | 进程占用 CPU 的总时间(分钟:秒) | 3:40(./myproc 已占用 3 分 40 秒 CPU) |
| COMMAND | 启动进程的原始命令 | ./myproc(可执行文件启动命令) |
⚠️ 注意:第三行是 grep myproc 自身的进程(COMMAND 列显示 grep --color=auto myproc),是执行查询命令时临时生成的进程,并非目标业务进程(./myproc)。
二、进程运行状态(STAT 列)解析
STAT 列是进程当前核心状态的标识,由 "基础状态 + 附加标识" 组成,示例中 ./myproc 和 grep 进程的 STAT 均为 R+,先解释 Linux 进程核心基础状态,再聚焦 R+ 的含义:
2. 示例中 R+ 的完整含义
R+ 是 "基础状态 + 附加标识" 的复合状态,核心解读:
- 基础状态
R:./myproc进程处于 "可运行态"------ 因代码中是while(1);空死循环,无任何阻塞操作(如sleep、磁盘 I/O、网络请求),会持续抢占 CPU 资源:要么正在 CPU 上执行,要么在就绪队列等待 CPU 调度,因此 STAT 始终为R; - 附加标识
+:表示进程是 "前台进程"------ 关联到终端(pts/1),占用终端的前台会话,可通过Ctrl+C直接终止;若通过./myproc &启动(后台运行),STAT 会显示R(无+)。
3. 补充:TIME 字段的特殊含义
示例中 ./myproc 的 TIME 为 3:40,说明该进程已累计占用 3 分 40 秒的 CPU 时间 ------ 因 while(1); 是无任何耗时操作的空循环,会 100% 占用一个 CPU 核心,TIME 会快速增长;而 grep 进程的 TIME 为 0:00,因是临时执行的短命令,占用 CPU 时间可忽略。
总结
ps axj | head -1 && ps axj | grep myproc:是 "显示表头 + 过滤目标进程" 的高效组合命令,可清晰查看myproc进程的 PID、PPID、运行状态、所属终端等全量属性;- 进程状态
R+:表示进程处于前台可运行态 ,因while(1);死循环无阻塞,进程持续抢占 CPU,是典型的高 CPU 占用状态; - 核心逻辑:
./myproc的空死循环导致其无法进入睡眠态(S),始终处于 "要么执行、要么等待 CPU" 的 R 态,且作为前台进程附加+标识。
kill 进程
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ./myproc &
[1] 4499
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 4499 4499 3744 pts/1 3744 R 1001 0:45 ./myproc
2355 4505 4504 2355 pts/0 4504 R+ 1001 0:00 grep --color=auto myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ kill -9 4499
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2355 4516 4515 2355 pts/0 4515 S+ 1001 0:00 grep --color=auto myproc
一、./myproc & 指令的核心解析
该指令是 Linux 下 将可执行程序以 "后台进程" 方式启动 的核心操作,& 是后台运行的关键符号,核心作用是让进程脱离终端前台会话,在后台持续运行,同时释放当前终端供用户输入其他命令。
1. 指令各部分拆解
| 组件 | 核心含义 |
|---|---|
./myproc |
执行当前目录下的可执行文件 myproc(与前台运行的指令一致) |
& |
后台运行标识:- 启动的进程进入 "后台作业" 队列,不再占用终端前台会话;- 终端立即返回提示符,可继续执行其他命令(无需等待进程结束);- 系统会输出 [1] 4499:[1] 是后台作业号,4499 是该进程的 PID(示例中可验证)。 |
2. 后台运行的核心特性(结合示例对比)
| 维度 | 前台运行(./myproc) |
后台运行(./myproc &) |
|---|---|---|
| 终端占用 | 终端被阻塞,无法输入任何命令(除非终止进程) | 终端不阻塞,可正常执行其他命令(如 ps、kill) |
| 终止方式 | 可通过 Ctrl+C 直接终止(前台进程响应终端信号) |
Ctrl+C 无效(仅作用于前台),需通过 kill 命令终止 |
| STAT 状态 | R+(+ 表示前台进程) |
R(无 + 标识,仅为可运行态) |
| 输出反馈 | 进程若有输出,直接打印到终端 | 进程输出仍会打印到终端(如需屏蔽可加 >/dev/null 2>&1) |
3. 示例中 ./myproc & 的执行反馈解析
[1] 4499
[1]:当前终端的第 1 个后台作业号(若再启动一个后台进程,作业号为[2]);4499:./myproc后台进程的 PID(后续kill命令需用此 PID 定位进程)。
二、kill -9 4499 指令的核心解析
kill 是 Linux 下 向进程发送信号以控制进程状态 的核心命令,kill -9 4499 是发送 "强制终止信号" 给 PID 为 4499 的进程,是最直接、无法被进程忽略的终止方式。
1. 指令各部分拆解
| 组件 | 核心含义 |
|---|---|
kill |
进程信号发送工具:默认发送 SIGTERM(信号 15,"优雅终止" 信号),进程可捕获并清理资源后退出 |
-9 |
指定发送的信号编号:9 对应 SIGKILL(强制终止信号),是 Linux 中最高优先级的终止信号,进程无法捕获、无法忽略、无法处理,操作系统会直接强制终止进程 |
4499 |
目标进程的 PID(需与 ps 查询到的 ./myproc 进程 PID 一致) |
2. SIGKILL(信号 9)的核心特性
- 强制性:无论进程处于何种状态(R 态、S 态等),只要有权限,操作系统会立即终止进程,无任何协商空间;
- 无资源清理 :进程无法执行退出前的清理逻辑(如关闭文件、释放内存、保存数据),适合终止 "无响应、高占用、无法优雅退出" 的进程(如示例中
while(1);空循环的进程); - 权限要求 :需是进程所属用户(示例中
ranjiaju)或 root 用户才能执行,普通用户无法终止其他用户的进程。
3. 示例中 kill -9 4499 的执行效果
执行后再次查询进程:
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
2355 4516 4515 2355 pts/0 4515 S+ 1001 0:00 grep --color=auto myproc
- 无
./myproc进程的记录,说明 PID 为 4499 的进程已被强制终止; - 仅剩余
grep自身的进程(STAT 为S+,因grep执行完成后进入短暂睡眠态)。
4. 补充:kill 命令的常见用法对比
| 命令 | 信号类型 | 特性 | 适用场景 |
|---|---|---|---|
kill 4499 |
SIGTERM(15) | 优雅终止,进程可捕获并清理资源 | 正常终止可响应的进程 |
kill -9 4499 |
SIGKILL(9) | 强制终止,进程无响应空间 | 无响应 / 高占用 / 死循环进程 |
kill -15 4499 |
SIGTERM(15) | 与 kill 4499 等效,显式指定优雅终止信号 |
脚本中明确终止逻辑 |
三、核心总结
./myproc &:将进程转为后台运行,释放终端但进程仍持续占用 CPU(STAT 为R),Ctrl+C无法终止,需用kill命令;kill -9 4499:发送 SIGKILL 强制终止信号给 PID 为 4499 的进程,是终止后台无响应进程的核心方式,执行后进程立即消失,无资源清理环节;- 关联逻辑:后台进程因脱离前台终端会话,无法通过终端快捷键终止,
kill -9是最直接的终止手段,也是处理while(1);这类高占用死循环进程的常用方式。
睡眠状态 S
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ cat myproc.c
#include<stdio.h>
#include<unistd.h>
int main()
{
int input = 0;
printf("Enter#:");
scanf("%d", &input);
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ make
gcc myproc.c -o myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ./myproc
Enter#:
// 等待用户输入
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 4800 4800 3744 pts/1 4800 S+ 1001 0:00 ./myproc
2355 4805 4804 2355 pts/0 4804 S+ 1001 0:00 grep --color=auto myproc
一、睡眠状态(STAT 列 S)的核心解析
Linux 进程的睡眠状态(Sleeping)是进程 主动让出 CPU 资源、暂停执行,等待某个外部事件完成 的状态,核心分为两类,示例中进程的 S+ 是最常见的「可中断睡眠态」:
| 睡眠态类型 | 标识 | 核心特征 |
|---|---|---|
| 可中断睡眠 | S | 进程等待外部事件(如用户输入、sleep 超时、磁盘 I/O 完成),可被信号(如 Ctrl+C)唤醒 / 终止,CPU 占用率为 0 |
| 不可中断睡眠 | D | 进程等待磁盘 I/O 等核心操作完成,无法被信号唤醒 / 终止(避免 I/O 中断导致数据丢失),仅见于内核级操作 |
示例中进程的 STAT 为 S+,其中:
S:核心状态 ------ 可中断睡眠态;+:附加标识 ------ 进程是前台进程(关联pts/1终端,占用前台会话)。
二、示例中进程进入睡眠态的核心原因(结合代码)
代码中 scanf("%d", &input); 是 阻塞式输入函数,触发进程进入可中断睡眠态,完整逻辑如下:
-
进程执行流程:
printf("Enter#:"); // 打印提示语,进程处于运行态(R) scanf("%d", &input); // 执行到此处,进程需要等待用户输入整数并回车------无输入时,进程无法继续执行 -
睡眠态触发逻辑:
scanf是 "阻塞调用":若无用户输入,进程会主动放弃 CPU 使用权,进入内核的 "等待队列",状态转为S(可中断睡眠);- 资源释放:睡眠期间,进程不占用任何 CPU 时间片(示例中
TIME字段为0:00,无 CPU 占用),CPU 可调度其他进程执行; - 唤醒条件:当用户在终端输入整数并按回车后,终端会向进程发送 "输入完成" 事件,进程被唤醒,从
S态转回R态,继续执行scanf读取输入,最终执行return 0退出。
三、睡眠态(S+)与之前运行态(R+)的核心对比
| 维度 | 本次示例(S+:scanf 阻塞) | 之前示例(R+:while (1) 死循环) |
|---|---|---|
| 核心行为 | 等待外部事件(用户输入),主动让出 CPU | 无等待事件,持续抢占 CPU 执行空循环 |
| CPU 占用 | 0%(TIME 字段不变) | 100%(TIME 字段快速增长) |
| 唤醒 / 终止 | 可通过 Ctrl+C 终止(信号唤醒) |
需 kill -9 强制终止(进程无响应信号) |
| 进程目的 | 等待用户交互,被动暂停 | 无目的循环,主动占用 CPU |
四、睡眠态的常见触发场景
除了 scanf 终端输入阻塞,以下场景也会让进程进入 S 态(可中断睡眠):
sleep(n):进程主动睡眠n秒,等待时间超时后自动唤醒;read()/write():读取 / 写入文件 / 网络套接字时,若数据未就绪(如网络未收到数据);wait():父进程等待子进程退出;pause():进程等待任意信号触发。
五、核心总结
示例中进程的 S+ 状态是「前台可中断睡眠态」:
- 进程因
scanf等待用户输入,主动让出 CPU,进入S态; - 睡眠期间 CPU 占用为 0,可通过
Ctrl+C发送终止信号(SIGINT)唤醒并终止进程; - 当用户输入整数后,进程被唤醒转为
R态,完成输入读取后退出。
这是 Linux 进程 "按需占用 CPU" 的核心机制 ------ 仅当进程有任务执行时占用 CPU,等待外部事件时主动睡眠,最大化系统资源利用率。
深度睡眠状态 D
一、深度睡眠状态 D(Disk Sleep/Uninterruptible Sleep)核心定义
D 状态(Disk Sleep,也叫「不可中断睡眠态」)是 Linux 进程特有的睡眠状态,专指进程因等待 底层磁盘 I/O(输入 / 输出)操作完成 而暂停执行,且在此期间 无法被任何信号(包括 kill -9 强制终止信号)中断或唤醒 的状态 ------ 内核设计该状态的核心目的是保证磁盘 I/O 操作的原子性,避免中途中断导致数据丢失、文件系统损坏。
二、D 态的核心特征(与可中断睡眠 S 态对比)
D 态是 Linux 中 "最特殊的睡眠态",与之前讲解的 S+(可中断睡眠)有本质区别,核心特征如下:
| 对比维度 | D 态(深度睡眠 / 不可中断睡眠) | S 态(可中断睡眠) |
|---|---|---|
| 中断性 | 完全不可中断:任何信号(kill/kill -9/Ctrl+C)都无法唤醒 / 终止进程,仅能等待磁盘 I/O 完成或系统重启 |
可中断:kill/Ctrl+C 可随时唤醒 / 终止进程 |
| 触发原因 | 仅与底层磁盘 I/O 强绑定(如读 / 写物理磁盘扇区、访问 swap 分区、磁盘挂载 / 卸载的内核级操作) | 等待终端输入、sleep、网络 I/O、进程等待等非磁盘核心操作 |
| CPU 占用 | 0%(主动让出所有 CPU 时间片,直到 I/O 完成) | 0%(同 D 态,无 CPU 占用) |
| 持续时间 | 通常极短(毫秒级,磁盘 I/O 完成即退出);若长时间(分钟 / 小时)处于 D 态,大概率是磁盘故障(如坏道、远程磁盘失联) | 可长可短(如 scanf 等待用户输入可卡数小时) |
| 数据安全目的 | 保证磁盘 I/O 原子性(避免中途中断导致数据丢失 / 文件系统损坏) | 无核心数据操作,中断无风险 |
kill -9 效果 |
完全无效,无法终止进程 | 可直接终止进程 |
三、D 态的典型触发场景
D 态仅出现在进程与物理磁盘 / 块设备的底层交互中,普通文件读写(命中内存缓存)不会触发,常见场景:
- 大文件裸盘读写 :执行
cp/dd复制大文件(如几十 GB)到机械硬盘 / SSD,进程会短暂进入 D 态(等待磁盘控制器返回 I/O 完成信号); - swap 分区交换:内存不足时,系统将进程数据写入 swap 分区(磁盘),进程会进入 D 态等待写入完成;
- 磁盘故障 / 失联:挂载的机械盘出现坏道、NFS / 挂载的远程磁盘突然断连,进程等待 I/O 响应但始终无结果,会长期卡在 D 态;
- 文件系统修复 :执行
fsck检查损坏的文件系统,进程会进入 D 态等待磁盘扇区读取完成。
四、为什么 D 态无法被 kill -9 终止?
Linux 内核对信号(包括 SIGKILL,即 kill -9 对应的信号)的处理逻辑是:进程必须处于 "可调度状态" 才能响应信号。
- D 态进程的调度被内核强制暂停,且内核为了保护磁盘 I/O 的完整性(比如正在写一个文件的物理块,中途中断会导致文件系统元数据损坏),会屏蔽所有信号 ------ 直到 I/O 操作完成(成功 / 失败),进程退出 D 态转为 R/S 态后,才能响应
kill -9。 - 这是内核的 "保护性设计":宁可让进程短暂卡住,也不允许中断核心磁盘 I/O 导致数据灾难。
五、D 态进程的排查与处理
1. 排查 D 态进程
用 ps 命令过滤出 D 态进程(STAT 列含 D):
ps axj | grep -E 'D|PID' # 显示表头+D态进程
结合 lsof 查看进程正在访问的磁盘 / 文件,定位 I/O 故障点:
lsof -p <D态进程PID> # 查看进程关联的文件/磁盘设备
2. 处理策略
- 短期 D 态(毫秒 / 秒级):无需处理,等待磁盘 I/O 完成后,进程会自动退出 D 态;
- 长期 D 态(分钟 / 小时级) :① 检查磁盘健康:
dmesg查看内核日志(是否有磁盘 I/O 错误)、smartctl检测机械盘 / SSD 坏道;② 检查挂载设备:若为远程磁盘(如 NFS),重启挂载服务或修复网络;③ 终极方案:若磁盘故障无法修复,仅能重启系统(这是终止长期 D 态进程的唯一方式)。
六、核心总结
D 态(Disk Sleep)是 Linux 为保护磁盘 I/O 原子性设计的 "不可中断睡眠态":
- 仅触发于底层磁盘 I/O 操作,短暂 D 态是正常现象;
- 长期 D 态 = 磁盘 / 挂载设备故障,且无法通过
kill -9终止,需排查硬件 / 网络问题; - 与 S 态的核心区别是 "不可中断性",本质是内核为了数据安全牺牲了进程的可操作性。
停止状态 T
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ cat myproc.c
#include<stdio.h>
#include<unistd.h>
int main()
{
while(1)
{
sleep(1);
printf("hello Linux\n");
}
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ make
gcc myproc.c -o myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ./myproc
hello Linux
hello Linux
hello Linux
// ......
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 5087 5087 3744 pts/1 5087 S+ 1001 0:00 ./myproc
2355 5096 5095 2355 pts/0 5095 S+ 1001 0:00 grep --color=auto myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ kill -19 5087
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 5087 5087 3744 pts/1 3744 T 1001 0:00 ./myproc
2355 5100 5099 2355 pts/0 5099 S+ 1001 0:00 grep --color=auto myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ kill -18 5087
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 5087 5087 3744 pts/1 3744 S 1001 0:00 ./myproc
2355 5106 5105 2355 pts/0 5105 S+ 1001 0:00 grep --color=auto myproc
一、kill -19 5087 命令解析
kill -19 5087 是向 PID 为 5087 的进程发送 SIGSTOP 信号(信号编号 19) ,核心作用是强制暂停进程执行,且该信号不可被进程捕获、不可忽略,是 Linux 中 "无条件暂停进程" 的核心指令。
1. 指令拆解与信号特性
| 组件 | 核心含义 |
|---|---|
kill |
信号发送工具,用于向指定进程传递控制信号 |
-19 |
指定信号编号:19 对应 SIGSTOP(暂停信号),属于「不可捕获、不可忽略」的内核级信号,进程无法通过代码屏蔽或处理 |
5087 |
目标进程 PID(示例中 ./myproc 的进程 ID) |
2. 执行效果(结合示例)
- 状态变化 :进程从
S+(前台可中断睡眠态)转为T(停止态)------ 原本进程因sleep(1)每秒在「运行(输出 hello Linux)→ 睡眠(等待 1 秒)」间切换,收到 SIGSTOP 后,所有执行立即停止,printf不再输出,sleep也暂停计时; - 资源特征 :进程 PID 保留(5087 不变),但完全停止占用 CPU(
TIME仍为 0:00),也不响应除SIGCONT(恢复信号)、SIGKILL(强制终止)外的任何信号; - 终端表现 :原本持续输出的
hello Linux立即停止,终端不再有新输出。
二、kill -18 5087 命令解析
kill -18 5087 是向 PID 为 5087 的进程发送 SIGCONT 信号(信号编号 18) ,核心作用是恢复被 SIGSTOP/SIGTSTP 暂停的进程,让进程从停止态回到之前的执行状态。
1. 指令拆解与信号特性
| 组件 | 核心含义 |
|---|---|
kill |
信号发送工具 |
-18 |
指定信号编号:18 对应 SIGCONT(继续 / 恢复信号),是唯一能唤醒 T 态进程的信号 |
5087 |
目标进程 PID(需与暂停的进程 PID 一致) |
2. 执行效果(结合示例)
- 状态变化 :进程从
T(停止态)恢复为S(后台可中断睡眠态)------ 原本暂停的while循环恢复执行,sleep(1)继续计时,printf重新每秒输出hello Linux; - 前台标识变化 :恢复后 STAT 从
S+变为S(无+标识),原因是进程暂停后脱离了终端前台会话,转为后台执行(仍关联终端,但不再占用前台输入); - 执行连续性 :进程恢复后从暂停的位置继续执行(如
sleep剩余的时间会继续计时,而非重新开始 1 秒)。
三、停止状态(STAT 列 T)核心解析
停止状态(Stopped)是 Linux 进程因收到 SIGSTOP(19)、SIGTSTP(Ctrl+Z,终端暂停信号)等信号,被强制暂停所有执行 的状态,核心特征如下:
1. 核心定义
- 进程完全停止执行代码(包括循环、睡眠、I/O 操作等),但进程实体仍存在(PID、内存空间等资源未释放);
- CPU 占用率为 0(示例中
TIME始终为 0:00),进程进入 "冻结" 状态,直到收到SIGCONT(18)恢复,或SIGKILL(9)终止。
2. 示例中 T 态的关键表现
- 进程 PID 仍为 5087,说明进程未被终止,仅暂停;
- 无
+标识,说明暂停后脱离前台会话; - 仅能通过
kill -18恢复,或kill -9强制终止(kill普通信号(15)无法终止T态进程)。
四、完整流程逻辑(结合代码)
示例中进程的状态变化链路:
S+(前台可中断睡眠:while循环+sleep,每秒输出)
↓(kill -19 5087)
T(停止态:所有执行暂停,无输出)
↓(kill -18 5087)
S(后台可中断睡眠:恢复循环,继续每秒输出,脱离前台)
代码层面:while(1) 循环中 sleep(1) 让进程 99% 的时间处于 S 态,仅在 printf 执行时短暂进入 R 态;kill -19 强制冻结整个循环,kill -18 解冻后循环从暂停点继续执行。
五、核心总结
kill -19 5087:发送 SIGSTOP 信号强制暂停进程,进程进入T态,所有执行停止;kill -18 5087:发送 SIGCONT 信号恢复暂停的进程,进程从T态转回S态,继续执行代码;- 停止状态
T:进程被信号强制冻结,PID 保留但无执行行为,仅能通过 SIGCONT 恢复或 SIGKILL 终止,是 Linux 中 "临时暂停进程" 的核心状态。
僵尸状态 Z
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ cat myproc.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
pid_t id = fork();
if(id == 0)
{
// 子进程
int cnt = 5;
while(cnt)
{
printf("我是子进程, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
exit(0);
}
else
{
// 父进程
while(1)
{
printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ./myproc
我是父进程, pid: 5919, ppid: 3744
我是子进程, pid: 5920, ppid: 5919, cnt: 5
我是父进程, pid: 5919, ppid: 3744
我是子进程, pid: 5920, ppid: 5919, cnt: 4
我是子进程, pid: 5920, ppid: 5919, cnt: 3
我是父进程, pid: 5919, ppid: 3744
我是父进程, pid: 5919, ppid: 3744
我是子进程, pid: 5920, ppid: 5919, cnt: 2
我是子进程, pid: 5920, ppid: 5919, cnt: 1
我是父进程, pid: 5919, ppid: 3744
我是父进程, pid: 5919, ppid: 3744
我是父进程, pid: 5919, ppid: 3744
// ......
// 前五秒
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 5919 5919 3744 pts/1 5919 S+ 1001 0:00 ./myproc
5919 5920 5919 3744 pts/1 5919 S+ 1001 0:00 ./myproc
2355 5924 5923 2355 pts/0 5923 S+ 1001 0:00 grep --color=auto myproc
// 五秒后
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps axj | head -1 && ps axj | grep myproc
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
3744 5919 5919 3744 pts/1 5919 S+ 1001 0:00 ./myproc
5919 5920 5919 3744 pts/1 5919 Z+ 1001 0:00 [myproc] <defunct>
2355 5928 5927 2355 pts/0 5927 S+ 1001 0:00 grep --color=auto myproc
一、僵尸进程(Zombie Process)核心定义
僵尸进程(STAT 列标记为 Z/Z+,后缀 <defunct>)是 Linux 中一种特殊的进程状态:子进程执行完毕并终止(如调用 exit(0)),但父进程未调用 wait()/waitpid() 等函数回收其子进程的退出状态、PID 等核心资源,导致子进程成为 "空壳"------ 进程实体已死(无代码执行、无资源占用),但 PID 仍被系统保留,无法被释放,成为僵尸进程。
二、僵尸进程的核心特征(结合示例)
示例中子进程(PID 5920)5 秒后转为 Z+ 态,体现了僵尸进程的所有核心特征:
| 特征 | 示例表现 |
|---|---|
| 进程状态标识 | STAT 列显示 Z+(Z=Zombie 僵尸态,+= 前台进程),后缀 <defunct>(已终止 / 失效) |
| 进程本质 | 子进程已执行 exit(0) 完全终止,无任何代码执行、无 CPU / 内存占用(TIME 始终为 0:00) |
| PID 资源占用 | 子进程 PID(5920)仍存在,系统未释放(内核保留 PID 供父进程查询退出状态) |
| 信号处理特性 | 无法被 kill -9 终止(进程已死,仅残留 PID 信息,信号无接收主体) |
| 触发核心原因 | 父进程(PID 5919)陷入 while(1) 死循环,未调用 wait()/waitpid() 回收子进程资源 |
三、示例中僵尸进程的触发完整逻辑
-
子进程正常运行阶段(前 5 秒) :子进程执行
cnt从 5 到 1 的循环,每秒打印信息并sleep(1),此时进程处于S+(前台可中断睡眠态),与父进程并发执行,状态正常。 -
子进程终止阶段(5 秒后) :子进程
cnt减至 0,执行exit(0)终止 ------ 此时子进程的代码执行完全停止,但其退出状态(如退出码 0)、PID 等信息会被内核保留(Linux 内核设计:子进程终止后,需父进程主动 "认领" 退出状态,内核才会释放 PID)。 -
父进程未回收,形成僵尸进程 :父进程陷入
while(1)死循环,未调用wait()/waitpid()等函数查询子进程的退出状态,内核无法释放子进程的 PID,因此子进程成为 "僵尸"------STAT 转为Z+,标注<defunct>,PID 5920 仍存在,但无任何实际执行行为。
四、僵尸进程的核心危害
僵尸进程本身不占用 CPU、内存等核心资源,但会占用 PID 资源(Linux 系统的 PID 数量有限,默认约 32768 个),长期积累会导致:
- PID 耗尽 :系统无法分配新的 PID,新进程(如
./myproc、ls等)无法创建; - 资源泄漏:内核为僵尸进程保留的退出状态、PID 等元数据会占用少量内核内存,大量僵尸进程会增加内核负担;
- 排查困难:僵尸进程无明显的 CPU / 内存占用,易被忽视,直到系统无法创建新进程才发现问题。
五、僵尸进程的解决方法
针对示例中父进程未回收子进程的问题,核心解决思路是让父进程主动 / 被动回收子进程资源:
1. 父进程主动回收(推荐)
修改父进程代码,添加 wait()/waitpid() 函数,主动获取子进程退出状态,内核会自动释放僵尸进程的 PID:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h> // 新增头文件
int main()
{
pid_t id = fork();
if(id == 0)
{
// 子进程逻辑不变
int cnt = 5;
while(cnt)
{
printf("我是子进程, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
exit(0);
}
else
{
// 父进程新增 wait() 回收子进程
int status;
waitpid(id, &status, 0); // 阻塞等待子进程退出,回收资源
while(1)
{
printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
2. 终止父进程(应急方案)
若父进程无回收逻辑,可通过 kill -9 <父进程PID> 终止父进程:
- 父进程终止后,其所有僵尸子进程会被系统的
init进程(PID 1)接管; init进程会定期调用wait()回收所有子进程资源,僵尸进程的 PID 会被释放。
3. 信号驱动回收(进阶)
父进程注册 SIGCHLD 信号处理函数,子进程退出时触发信号,在处理函数中调用 wait() 回收:
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<signal.h>
// 信号处理函数:回收子进程
void handler(int sig)
{
waitpid(-1, NULL, WNOHANG); // 非阻塞回收所有子进程
}
int main()
{
signal(SIGCHLD, handler); // 注册 SIGCHLD 信号处理
pid_t id = fork();
// 后续子进程/父进程逻辑不变
if(id == 0)
{
int cnt = 5;
while(cnt)
{
printf("我是子进程, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt);
cnt--;
sleep(1);
}
exit(0);
}
else
{
while(1)
{
printf("我是父进程, pid: %d, ppid: %d\n", getpid(), getppid());
sleep(1);
}
}
return 0;
}
六、核心总结
示例中的僵尸进程是子进程终止后父进程未回收资源导致的 "PID 残留":
- 僵尸进程(
Z+/<defunct>)无执行行为、无法被kill -9终止,仅占用 PID 资源; - 核心危害是 PID 耗尽,导致新进程无法创建;
- 解决核心是让父进程主动调用
wait()/waitpid(),或终止父进程让 init 接管回收。
这是 Linux 进程管理的核心规则:子进程终止后,父进程必须主动回收其退出状态,否则子进程会成为僵尸进程。
vim 的批量化修改
保证在命令模式中,并且光标在合适位置后:Ctrl+V,通过 H、J、K、L 移动,确定好范围后,Shift + I(Shift + D:批量化删除),就进入了批量化修改模式,此时光标就在初始那一行,输入你想要修改/添加的字符,再点击 Esc 就可以完成批量化修改
进程优先级
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ cat myproc.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
while(1)
{
printf("我是一个进程\n");
sleep(1);
}
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ make
gcc myproc.c -o myproc
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ./myproc
我是一个进程
我是一个进程
我是一个进程
我是一个进程
// ......
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_1]$ ps -al | head -1 && ps -al | grep myproc
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1001 7493 3744 0 80 0 - 1054 hrtime pts/1 00:00:00 myproc
一、进程优先级的核心定义
Linux 系统中进程优先级 是内核 CPU 调度器分配 CPU 时间片的核心依据 ------ 优先级越高(对应 PRI 数值越小),进程越容易被 CPU 调度,占用 CPU 时间片的机会越多;反之优先级越低(PRI 数值越大),进程越难被调度,仅在高优先级进程空闲时才能获得 CPU 资源。
进程优先级分为两类(与示例相关的是普通进程优先级):
| 优先级类型 | 数值范围 | 核心特征 |
|---|---|---|
| 实时优先级 | 0 - 99 | 内核级高优先级(如硬件中断、系统核心进程),普通用户无法调整,抢占性极强 |
| 普通优先级 | 100 - 139(内核实际值)/ 0 - 39(ps 显示偏移值) | 普通用户进程的优先级,ps -al 中显示的 PRI=80 是该类的默认基准值 |
示例中 ps -al 输出的 PRI 80 是普通进程的默认基准优先级(不同 Linux 发行版基准值可能为 80/120,本质是内核值的偏移显示),是优先级调整的基础。
二、PRI(new) = PRI(old) + NI 公式解析
该公式是 Linux 中调整普通进程优先级的核心规则 ,通过修改 NI(Nice 值)来改变进程实际调度优先级,各参数及逻辑如下:
1. 公式参数含义
| 参数 | 核心含义 | 示例取值 |
|---|---|---|
PRI(old) |
进程的原始 / 基准优先级(普通进程默认值为 80,示例中未调整时 PRI(old)=80) |
80 |
NI |
优先级偏移量(Nice 值),用户可手动调整的唯一参数 | 示例中 NI=0 |
PRI(new) |
调整后进程的实际调度优先级(决定 CPU 调度顺序) | 80 + 0 = 80 |
2. 公式核心逻辑
NI是 "优先级增量":NI < 0(负数):PRI(new)变小 → 进程优先级升高(更易被 CPU 调度);NI = 0:PRI(new) = PRI(old)→ 优先级不变(示例默认状态);NI > 0(正数):PRI(new)变大 → 进程优先级降低(更难被 CPU 调度)。
- 示例验证:
- 若将示例进程的
NI设为-5(root 权限),则PRI(new)=80-5=75(优先级升高); - 若将
NI设为10(普通用户可操作),则PRI(new)=80+10=90(优先级降低)。
- 若将示例进程的
三、NI(Nice 值)的调整区间 [-20, 19]
NI 是用户唯一可调整的优先级参数,取值严格限制在 -20(最小)到 19(最大)之间,核心原因和取值影响如下:
1. 区间限制的核心目的
- 防止资源滥用 :若允许
NI无限制调整,低权限用户可能将进程优先级调至最高,抢占所有 CPU 资源,导致系统卡死; - 权限分级控制 :
- 普通用户:仅能设置
NI ≥ 0(即只能降低进程优先级,如NI=1~19),无法抢占系统资源; - root 用户:可设置
NI < 0(即升高进程优先级,如NI=-1~-20),满足核心业务进程的调度需求。
- 普通用户:仅能设置
2. 区间内不同 NI 值的影响
| NI 值 | PRI (new)(基准 80) | 优先级等级 | 操作权限 | 核心效果 |
|---|---|---|---|---|
| -20 | 60 | 普通进程最高 | root | 进程几乎优先占用 CPU,仅让位于实时进程 |
| 0 | 80 | 默认等级 | 所有用户 | 均衡调度,示例中进程的默认状态 |
| 19 | 99 | 普通进程最低 | 所有用户 | 进程仅在其他所有进程空闲时,才会被 CPU 调度 |
3. 调整 NI 的常用命令
- 启动时调整 :
nice -n <NI值> ./myproc(如nice -n -5 ./myproc,需 root 权限); - 运行中调整 :
renice -n <NI值> -p <进程PID>(如renice -n 10 -p 7493,调整示例进程的 NI 为 10)。
四、结合示例输出的完整解析
示例中 ps -al 输出:
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
0 S 1001 7493 3744 0 80 0 - 1054 hrtime pts/1 00:00:00 myproc
核心字段与优先级相关的解读:
PRI 80:进程原始基准优先级为 80,无调整;NI 0:Nice 值为默认 0,因此PRI(new)=80+0=80,按默认优先级调度;C 0:CPU 占用率为 0(因进程中sleep(1)使其大部分时间处于睡眠态S,不参与 CPU 调度,优先级仅影响进程被唤醒后获取 CPU 的顺序);S:进程处于可中断睡眠态(sleep(1)等待超时),此时优先级无实际调度意义,仅当进程被唤醒(printf执行时)才按PRI(new)参与 CPU 竞争。
五、核心总结
- 进程优先级决定 CPU 调度顺序,
PRI数值越小,优先级越高; PRI(new) = PRI(old) + NI是优先级调整的核心规则:NI为负升高优先级,为正降低优先级,为 0 不变;NI的区间[-20, 19]是系统的安全限制:普通用户仅能调大NI(降低优先级),root 可调小NI(升高优先级),防止资源滥用。
优先级的调整仅影响普通进程的调度顺序,不会改变进程的运行状态(如睡眠态 S 不会因优先级升高而被唤醒),仅在进程进入可运行态 R 时生效。
环境变量
认识环境变量
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ ll
total 8
-rw-rw-r-- 1 ranjiaju ranjiaju 129 Dec 14 17:09 code.c
-rw-rw-r-- 1 ranjiaju ranjiaju 65 Dec 14 17:06 makefile
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ cat code.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
printf("PATH: %s\n", getenv("PATH"));
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ make
gcc code.c -o code
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ ./code
PATH: /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/ranjiaju/.local/bin:/home/ranjiaju/bin
一、环境变量(Environment Variable)的核心解析
环境变量是 Linux 系统 / Shell 维护的 全局键值对(KEY=VALUE)配置,本质是为进程运行提供 "全局可用的配置信息",核心特征和作用如下:
1. 核心定义与特性
- 全局可访问 :环境变量由操作系统管理,所有进程(包括你编译的
./code进程)启动时会继承父进程(如终端 Shell)的环境变量,无需手动传递即可在进程内获取; - 键值对结构 :格式为
KEY=VALUE(如PATH=/usr/local/bin:/usr/bin),KEY是变量名(大写规范),VALUE是变量值(可包含多个路径,用:分隔); - 动态可修改 :可通过 Shell 指令(
export/unset)修改,也可在程序内通过库函数(putenv/setenv)调整; - 核心作用:为进程提供运行时依赖的系统配置(如查找可执行文件的路径、语言编码、临时目录等),避免硬编码路径,提升程序通用性。
2. 重点:PATH 环境变量(示例核心)
PATH 是最常用的环境变量,核心作用是:系统执行命令 / 可执行文件时,会按 PATH 中列出的目录顺序,查找对应名称的可执行文件。
- 示例中
./code输出的PATH值:/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/ranjiaju/.local/bin:/home/ranjiaju/bin; - 实际意义:
- 你在终端执行
gcc/make/ls等命令时,系统会依次在/usr/local/bin、/usr/bin等目录中查找这些命令的可执行文件; - 若没有
PATH,执行gcc code.c需写全路径(如/usr/bin/gcc code.c),无法直接简写; - 自定义可执行文件若想全局调用,可将其目录加入
PATH(如export PATH=$PATH:/home/ranjiaju/test_code)。
- 你在终端执行
二、printf("PATH: %s\n", getenv("PATH")); 代码解析
这行代码的核心是 在 C 程序中获取并打印 PATH 环境变量的值,拆解如下:
1. 函数 / 语法拆解
| 代码片段 | 核心含义 |
|---|---|
getenv("PATH") |
① 所属库:C 标准库函数(需包含头文件 <stdlib.h>);② 作用:根据环境变量名("PATH"),从进程继承的环境变量中获取对应的值;③ 返回值:char* 类型字符串(示例中返回 /usr/local/bin:...);④ 异常:若变量名不存在,返回 NULL(如 getenv("XXX"))。 |
printf(...) |
① 格式化输出函数(需包含头文件 <stdio.h>);② PATH: %s\n:格式化字符串,%s 是字符串占位符,用于填充 getenv 返回的 PATH 值;③ 输出效果:将 PATH 变量名和值拼接为 PATH: 路径列表 并换行打印。 |
2. 代码执行逻辑(结合示例)
- 进程启动:
./code进程从终端 Shell 继承所有环境变量(包括PATH); - 调用
getenv("PATH"):从进程的环境变量表中查找PATH键,返回其值(字符串); - 调用
printf:将PATH变量名和值格式化输出到终端,即示例中看到的PATH: /usr/local/bin:...。
3. 关键补充
- 头文件依赖:代码中
#include<stdlib.h>是getenv的必需头文件,#include<stdio.h>是printf的必需头文件;<unistd.h>在此代码中未使用(可删除); - 与 Shell 指令的关联:程序中
getenv("PATH")获取的值,和在终端执行echo $PATH输出的结果完全一致(因为进程继承了 Shell 的环境变量); - 其他环境变量获取:同理,可通过
getenv("HOME")获取用户主目录、getenv("LANG")获取语言编码、getenv("PWD")获取当前工作目录。
三、示例输出的核心解读
PATH: /usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/ranjiaju/.local/bin:/home/ranjiaju/bin
这行输出是当前 ./code 进程继承的 PATH 环境变量的完整值:
- 各目录的作用:
/usr/local/bin:系统本地安装的软件(如手动编译的gcc);/usr/bin:系统默认命令(如ls/gcc/make);/usr/local/sbin//usr/sbin:系统管理员命令(如service/systemctl);/home/ranjiaju/.local/bin//home/ranjiaju/bin:当前用户自定义的可执行文件目录;
- 路径分隔符:多个目录用
:分隔,系统查找时按从左到右的顺序,找到第一个匹配的可执行文件即停止。
四、核心总结
- 环境变量是系统为进程提供的全局配置,
PATH是其中最核心的变量,决定了系统查找可执行文件的路径; getenv("PATH")是 C 程序中获取环境变量的核心方式,能直接读取进程继承的环境变量值;printf("PATH: %s\n", ...)是将获取到的PATH值格式化输出,直观展示当前进程的PATH配置;- 这行代码的本质是 "打通程序内与系统级配置的桥梁",让程序能动态获取系统环境,而非硬编码固定路径。
不同的用户执行环境变量相关代码的区别
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ cat code.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char who[32];
strcpy(who, getenv("USER"));
if(strcmp(who, "root") == 0)
{
printf("当前用户是root\n");
}
else
{
printf("当前用户是 %s\n", who);
}
return 0;
}
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ make
gcc code.c -o code
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ ./code
当前用户是 ranjiaju
[ranjiaju@iZ2vc15k23y9vpuyi3tiqzZ test_code]$ su -
Password:
Last login: Sun Dec 14 17:33:58 CST 2025 on pts/0
[root@iZ2vc15k23y9vpuyi3tiqzZ ~]# whoami
root
[root@iZ2vc15k23y9vpuyi3tiqzZ ~]# cd /
[root@iZ2vc15k23y9vpuyi3tiqzZ /]# cd home/ranjiaju/test/test_code
[root@iZ2vc15k23y9vpuyi3tiqzZ test_code]# ll
total 20
-rwxr-xr-x 1 root root 8688 Dec 14 17:34 code
-rw-rw-r-- 1 ranjiaju ranjiaju 395 Dec 14 17:31 code.c
-rw-rw-r-- 1 ranjiaju ranjiaju 65 Dec 14 17:06 makefile
[root@iZ2vc15k23y9vpuyi3tiqzZ test_code]# ./code
当前用户是root
一、strcpy(who, getenv("USER")); 代码解析
这行代码的核心是 获取当前用户的用户名环境变量,并将其拷贝到自定义字符数组中,是 C 程序中处理环境变量字符串的典型操作,拆解如下:
1. 各组件核心含义
| 代码片段 | 功能与细节 |
|---|---|
char who[32]; |
定义一个长度为 32 的字符数组,作为存储用户名的 "目标缓冲区":- 长度 32 足够容纳 Linux 用户名(通常不超过 16 位),避免缓冲区溢出;- 局部数组存储在进程栈区,生命周期为 main 函数执行期间。 |
getenv("USER") |
① 作用:从进程继承的环境变量中读取 USER 变量的值(USER 是 Linux 核心环境变量,存储当前登录用户的用户名);② 返回值:char* 类型字符串(如普通用户返回 "ranjiaju",root 返回 "root");③ 依赖:需包含 <stdlib.h> 头文件。 |
strcpy |
① 所属库:C 字符串操作函数(需包含 <string.h> 头文件);② 作用:将 getenv("USER") 返回的 "源字符串" 完整拷贝到 who 数组(目标缓冲区);③ 参数顺序:strcpy(目标地址, 源地址)(不可颠倒);④ 注意:strcpy 不检查目标缓冲区长度,若源字符串过长(超过 32 字符)会导致缓冲区溢出(示例中用户名短,无风险)。 |
2. 执行逻辑(结合示例)
- 进程启动时,从父进程(终端 Shell)继承
USER环境变量; getenv("USER")读取该变量的值(如ranjiaju/root),返回字符串首地址;strcpy将该字符串逐字节拷贝到who数组中,为后续strcmp比较做准备。
二、不同用户执行环境变量相关代码的核心区别
环境变量的核心特性是 "进程级继承 + 用户级隔离" :不同用户登录系统后,其 Shell 进程的环境变量(如 USER/HOME/PATH)会加载各自的配置,而 ./code 作为 Shell 的子进程,会完整继承父进程(Shell)的环境变量,因此不同用户执行同一代码,环境变量相关结果会不同。
1. 核心区别:环境变量的 "用户级隔离"
Linux 为不同用户维护独立的环境变量配置,登录时 Shell 会加载专属配置文件,导致核心环境变量差异:
| 维度 | 普通用户 ranjiaju 执行 |
root 用户执行 |
|---|---|---|
USER 环境变量值 |
ranjiaju(继承自普通用户 Shell 的 USER 变量) |
root(继承自 root Shell 的 USER 变量) |
| 环境变量配置来源 | 加载 ~/.bashrc/~/.profile(/home/ranjiaju/ 下) |
加载 /root/.bashrc//root/.profile(root 主目录下) |
| 代码执行结果 | printf 输出 当前用户是 ranjiaju |
printf 输出 当前用户是 root |
2. 延伸:其他核心环境变量的用户差异(补充理解)
除了 USER,不同用户的核心环境变量也会不同,这些差异会直接影响代码执行行为:
| 环境变量 | 普通用户 ranjiaju 值 |
root 用户值 | 影响 |
|---|---|---|---|
HOME |
/home/ranjiaju(用户主目录) |
/root(root 主目录) |
代码中 getenv("HOME") 会返回不同路径,影响文件读写的默认目录 |
PATH |
包含 /home/ranjiaju/bin 等用户目录 |
包含 /sbin//usr/sbin 等系统目录 |
root 的 PATH 包含管理员命令(如 service),普通用户可能无权限执行 |
UID |
1001(普通用户 UID) | 0(root 专属 UID) | 代码中 getuid() 可获取,决定进程的系统权限(root 进程可操作所有文件) |
3. 关键细节:su - 的作用(示例中切换用户的核心)
示例中执行 su - 而非 su,是切换用户时的核心操作:
su:仅切换用户身份,但继承原用户的 Shell 环境变量 (如USER仍为ranjiaju);su -:切换用户身份 + 加载目标用户的完整环境变量配置 (root 的 Shell 会重新加载/root/.bashrc,USER设为root);这也是为什么su -后执行./code能正确识别 root 用户的核心原因。
4. 环境变量与进程权限的关系
环境变量本身不决定进程权限,但权限会影响环境变量的配置和使用:
- 普通用户的环境变量仅对自身进程有效,无法修改系统级环境变量(如
/etc/profile); - root 用户可修改所有用户的环境变量配置,且其进程能访问普通用户无权限的环境变量关联资源(如
/sbin目录下的命令); - 示例中
./code的可执行文件权限被改为root:root(ll中显示1 root root 8688),但不影响环境变量读取 ------ 环境变量继承自 Shell,与可执行文件的属主无关。
三、核心总结
strcpy(who, getenv("USER"));:是 "读取USER环境变量 + 字符串拷贝" 的组合操作,将当前用户的用户名存入自定义数组,为后续用户判断提供数据;- 不同用户执行环境变量相关代码的核心区别:
- 环境变量是 "用户级隔离" 的,不同用户的 Shell 会加载专属配置,核心变量(
USER/HOME/PATH)值不同; - 程序进程会继承父 Shell 的环境变量,因此同一代码在不同用户下执行,读取到的环境变量值不同,输出结果也不同;
su -是切换用户并加载完整环境变量的关键,保证环境变量与用户身份一致。
- 环境变量是 "用户级隔离" 的,不同用户的 Shell 会加载专属配置,核心变量(