第13章 进程

背景:进程是正在运行程序的实例;内核通过时间片轮转实现多进程假象,调度器负责调度。本章聚焦 shell 脚本日常实操:查看进程、信号控制、trap捕获、系统调用追踪、任务调度、/proc虚拟文件。

13.1 进程建立

概念

新进程依靠内核系统调用 fork()、execve() 创建;进程调用 exit() 结束。 shell 执行外部命令就会创建新进程。

shell启动的进程自带特性

  1. 内核上下文:内核保存进程管理信息
  2. 私有虚拟地址空间:进程之间内存互相隔离,互不干扰
  3. 默认打开3个文件描述符:标准输入、标准输出、标准错误
  4. 交互终端启动的进程拥有控制终端,可以接收键盘信号(Ctrl‑C等)
  5. shell提前完成通配符展开,程序不需要自己处理
  6. 自带环境变量区域,程序可读取环境变量

配套命令

  • uptime:查看系统平均负载,输出 1分钟、5分钟、15分钟 负载
  • nice / renice:修改进程运行优先级

示例

1
2
uptime
#  1:51pm  up 298 day(s), 15:42, 32 users, load average: 3.51, 3.50, 3.55

13.2 进程列表

1.ps 查看进程

ps 两大流派:System‑V、BSD,很多系统两者都支持。

流派 命令 说明
System‑V ps -efl 完整长格式输出
BSD ps aux 常用,显示CPU、内存占用

关键字段

  • PID:进程唯一ID;shell变量 $$ 获取当前脚本PID
  • PPID:父进程ID;父进程退出,孤儿进程会被 PID=1(init)收养
  • USER/UID:进程所属用户
  • %CPU / %MEM:CPU、内存占用
  • COMMAND:执行的命令

2.top 动态实时监控进程

  • 默认按CPU占用从高到低排序,实时刷新
  • 可以按?查看top内置快捷键

shell作业控制(交互终端)

命令 作用
命令 & 后台运行进程
jobs 查看当前shell后台作业
fg %编号 后台进程切到前台
bg %编号 暂停进程放到后台继续跑
wait 等待后台进程结束;wait PID等待指定进程

前台进程快捷键

  • Ctrl‑C:INT信号,终止程序
  • Ctrl‑Z:SUSP信号,暂停程序
  • Ctrl‑\:QUIT,终止并生成core转储文件

示例脚本1:simple‑top(简易top)

原理:死循环,重复执行ps、排序、sleep休眠,模拟top效果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#! /bin/sh -
IFS='
'
PATH=/usr/ucb:/usr/bin:/bin
export PATH
HEADFLAGS="-n 20"
PSFLAGS="aux"
SLEEPFLAGS=5
SORTFLAGS="-k3nr -k1,1 -k2n"
HEADER="ps $PSFLAGS | head -n 1"
while true
do
    clear
    uptime
    echo "$HEADER"
    ps $PSFLAGS | sed -e 1d | sort $SORTFLAGS | head $HEADFLAGS
    sleep $SLEEPFLAGS
done

示例脚本2:puser,按用户统计进程数量

管道链路:ps输出 → sed去掉表头 → egrep过滤用户 → sort排序 → uniq计数 → awk格式化输出

 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
27
28
29
30
31
32
33
34
35
36
37
#! /bin/sh -
IFS='
'
PATH=/usr/local/bin:/usr/bin:/bin
export PATH
EGREPFLAGS=
while test $# -gt 0
do
    if test -z "$EGREPFLAGS"
    then
        EGREPFLAGS="$1"
    else
        EGREPFLAGS="$EGREPFLAGS|$1"
    fi
    shift
done
if test -z "$EGREPFLAGS"
then
    EGREPFLAGS="."
else
    EGREPFLAGS="^($EGREPFLAGS) "
fi
case "`uname -s`" in
    *BSD|Darwin) PSFLAGS="-a -e -o user,ucomm -x" ;;
    *)           PSFLAGS="-e -o user,comm" ;;
esac
ps $PSFLAGS |
sed -e 1d |
EGREP_OPTIONS= egrep "$EGREPFLAGS" |
sort -b -k1,1 -k2,2 |
uniq -c |
sort -b -k2,2 -k1nr,1 -k3,3 |
awk '
    user = (LAST == $2) ? "" : $2
    LAST = $2
    printf("%-15s\t%2d\t%s\n", user, $1, $3)
'

使用

1
2
./puser          #全部用户
./puser root     #只看root用户

13.3 进程控制与删除(信号signal)

基础概念

kill:不是直接杀死进程,是发送信号给进程。 进程可以捕获、忽略大部分信号;仅有两个信号无法捕获:KILL、STOP。

查看系统全部信号

1
kill -l

常用信号清单

信号 作用
TERM kill不带参数默认信号;优雅终止,程序做清理工作
HUP 挂断;守护进程收到,通常重读配置文件
KILL 强制杀死,终极手段,无法捕获,跳过清理逻辑
STOP 暂停进程,无法捕获;CONT恢复被暂停进程
USR1 / USR2 用户自定义信号,程序自己定义行为

最佳实践关闭失控进程:kill -HUP PID → kill -TERM PID → 万不得已才用 kill -KILL PID

按进程名操作

  • pgrep 进程名:按名字查找PID
  • pkill 信号 进程名:按名字发信号;风险:进程名不唯一,容易误杀别的进程

示例

1
2
pgrep netscape
pkill -TERM netscape

shell trap:脚本捕获信号

语法:trap '要执行的命令' 信号名

特殊伪信号

  1. EXIT:脚本退出时一定执行,最常用,用来清理临时文件
  2. DEBUG:每条命令执行前后触发,不同shell行为不一致,不要写可移植脚本用它
  3. ERR:命令非0退出触发;命令替换失败不会触发,兼容性差

示例脚本 looper

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
#! /bin/sh -
trap 'echo 收到HUP信号' HUP
trap 'echo 收到USR1,脚本退出; exit 1' USR1
trap 'echo 脚本结束,执行清理' EXIT

while true
do
    sleep 2
    date > /dev/null
done

后台运行测试

1
2
3
./looper &
kill -HUP  进程号
kill -USR1 进程号

注意:优先使用信号名字,不要用数字编号,可移植性更好。

13.4 系统调用追踪

作用

不依赖源代码,跟踪程序每一条内核系统调用(open/fork/execve/unlink等);排查:程序找哪些配置文件、卡死原因、残留锁文件。

各平台工具

系统 追踪工具
Linux strace
Solaris truss
BSD/macOS ktrace + kdump

示例 Linux strace

1
2
3
4
5
6
7
8
#跟踪命令
strace -e trace=process /bin/sh

#跟踪已经在运行的进程PID
strace -p 1234

#只跟踪文件相关系统调用
strace -e trace=file ./app

典型使用场景:查找程序残留lock锁文件

1
2
truss -f -o log.log mozilla
grep -i lock log.log

13.5 进程记账 process accounting

  • 默认关闭;内核记录每一个进程结束后的资源消耗(CPU、运行时间)
  • 二进制日志文件,需要专用工具解析
  • 工具:acctcom、lastcomm、sa

示例

1
acctcom -a | cut -d' ' -f1 | sort | uniq -c | sort -k1nr | head -n10

13.6 延迟进程调度

1.sleep:休眠一段时间

休眠进程几乎不消耗CPU;脚本循环延时、等待场景大量使用。

1
sleep 5      #休眠5秒

2.at:一次性未来某个时刻执行任务

命令 说明
at 21:00 < cmdfile 晚上21点执行文件内命令
at now + 10 minutes < cmdfile 10分钟后执行
atq 查看at任务队列
atrm 任务编号 删除at任务

示例:准备命令文件 cmdfile,新建文件 cmdfile,里面写要跑的命令:

1
2
3
4
# cmdfile 内容(普通文本,不需要#!/bin/sh也可以,at默认用/bin/sh执行)
echo "任务执行了 $(date)" >> /tmp/at_log.txt
cat /etc/hostname >> /tmp/at_log.txt
echo "done" >> /tmp/at_log.txt

权限文件:

  • /etc/at.allow:写在这里的用户,才允许用 at
  • /etc/at.deny:写在这里的用户禁止使用 at
  • 如果两个文件都不存在,只有 root 可以使用 at。

3.batch:系统空闲时执行任务

系统负载低的时候才跑任务,读取标准输入的命令

1
batch < cmdfile

batch 与 at 的对比

命令 触发条件 适合场景
at 21:00 < file 固定时刻执行一次 明确知道几点跑,比如今晚 21 点备份
batch < file 系统负载低于阈值才执行 非紧急、耗 CPU 的任务;希望不打扰用户交互;不知道什么时候有空

4.crontab:周期性定时任务

守护进程 cron,循环执行定时任务。

操作命令

  • crontab -l:列出当前用户定时任务
  • crontab -e:编辑定时任务
  • crontab -r:全部删除定时任务(不可恢复,建议先备份)

crontab格式

1
2
#分 时 日 月 星期    命令
#0‑59 0‑23 1‑31 1‑12 0‑6(0代表周日)

示例

1
2
3
15 * * * * /home/test/run.sh     #每小时15分执行
0 2 1 * *  /home/test/run.sh     #每月1号凌晨2点执行
0 6 * * 1  /home/test/run.sh     #每周一早上6点

工程建议:crontab只调用shell脚本,业务逻辑写在脚本内部,方便维护。
权限控制文件:cron.allow / cron.deny

备份crontab

1
crontab -l > $HOME/.crontab.`hostname`

13.7 /proc 虚拟文件系统(Linux为主)

不是真实磁盘文件;读取时内核实时生成数据,文件大小显示为0。

目录结构

  • /proc/PID:每个进程一个目录,PID为目录名
    • /proc/PID/cmdline:进程启动命令
    • /proc/PID/fd:进程打开的所有文件描述符
    • /proc/PID/status:进程状态信息
  • /proc/meminfo:内存信息
  • /proc/cpuinfo:CPU硬件信息

示例

1
2
3
ls /proc/1234
cat /proc/1234/cmdline
cat /proc/meminfo

注意:不要直接scp/tar拷贝/proc下文件,会异常。

13.8 本章小结

  1. 进程拥有独立私有地址空间;shell外部命令依靠fork/exec创建子进程。
  2. 终止进程优先优雅信号TERM,KILL是最后手段,会跳过程序清理,容易残留锁文件。
  3. shell脚本使用 trap EXIT,脚本退出自动做资源清理,提升健壮性。KILL、STOP无法被捕获。
  4. 排查程序行为,系统调用追踪工具 strace/truss非常实用,不需要源代码。
  5. 任务调度
    • sleep:短暂延时
    • at:一次性未来时刻
    • crontab:周期重复执行
  6. /proc虚拟文件系统,以文件接口直接读取内核的进程、硬件信息。