跳转至

进程管理

程序(Program),通常为二进制可执行文件,一般存储在物理磁盘中,被用户触发执行后,它就从磁盘上的二进制文件,变成了计算机内存中的数据、寄存器里的值、堆栈中的指令、被打开的文件,以及各种设备的状态信息的一个集合,即进程(Process),为了管理这个进程,操作系统会根据触发这个进程的用户与相关属性关系,给予这个进程一组有效的权限,以及进程所需要的脚本或数据,最后再给予一个PID,操作系统通过PID来判断该进程是否有执行权限。

  • 查看进程(动态)
top [-d n]  # 每n秒更新一次,输入?号查看top中可以输入的按键
  • 查看进程(静态)
# 只查看自己bash的进程
ps -l
# 查看所有进程,包含systemd
ps -lA  # 方式1,显示字段与ps -l一致
ps aux  # 方式2,显示格式与ps -l不一致

20210830231304

  • 查看进程树
pstree [-up]  # -u显示所属用户,-p显示PID

20210830230858

  • 其他命令
# 一台 Linux 机器上的进程总数目是有限制的。如果超过这个最大值,那么系统就无法创建出新的进程了
# 查看最大进程数
/proc/sys/kernel/pid_max  # 4194304kb

# 进程的优先级(PRI)由系统内核动态调整,用户可以通过nice值来影响它:PRI(new) = PRI(old) + nice(-20~19)
# PRI越小优先级越高,非root用户nice可控范围在0~19,即只有调低优先级的权限
nice [-n num] command  # 进程运行前设置
renice [num] PID  # 进程运行中设置

free [-m]  # 查看内存使用情况,-m结果单位为MB,默认KB
  • init 进程

一个 Linux 操作系统,在系统打开电源,执行 BIOS/boot-loader 之后,就会由 boot-loader 负责加载 Linux 内核

Linux 内核执行文件一般会放在 /boot 目录下,文件名类似 vmlinuz*

在内核完成了操作系统的各种初始化之后,这个程序需要执行的第一个用户态程就是 init 进程,也叫1号进程,于是系统从内核态切换到用户态,然后创建其他进程。

内核代码启动 1 号进程的时候,在没有外面参数指定程序路径的情况下,一般会从几个缺省路径尝试执行 1 号进程的代码

Systemd 是目前最流行的 Linux init 进程(在它之前还有 SysVinit、UpStart 等),无论是哪种 Linux init 进程,它最基本的功能都是创建出 Linux 系统中其他所有的进程,并且管理这些进程。

目前主流的 Linux 发行版都会把/sbin/init 作为符号链接指向 Systemd

ls -l /sbin/init
"""
lrwxrwxrwx 1 root root 20 Jul 21 19:00 /sbin/init -> /lib/systemd/systemd
"""

任务

无论进程还是线程,在 Linux 内核里其实都是用 task_struct{}这个结构来表示的。它其实就是任务(task),也就是 Linux 里基本的调度单位

20210828204948

运行态(R stat)

  • 运行中(即获得了CPU资源)
  • 运行队列中

睡眠态(等待队列)

进程需要等待某个资源(可以是一个信号量(Semaphore), 或者是磁盘I/O)

  • TASK_INTERRUPTIBLE 可中断(S stat)
  • TASK_UNINTERRUPTIBLE 不可中断(D stat)

退出态

  • EXIT_ZOMBIE 进程退出前的状态(僵尸进程)
  • EXIT_DEAD 进程结束退出时的状态

每一个 Linux 进程在退出的时候都会进入一个僵尸状态(EXIT_ZOMBIE),实际上它只是一个等待被父进程删除掉的条目,而它不是一个真正的进程,所以无法被杀死,而是需要父进程调用 wait() 或者 waitpid() 系统调用来清理,这也是容器中 init 进程必须具备的一个功能

wait() 系统调用是一个阻塞的调用,也就是说,如果没有子进程是僵尸进程的话,这个调用就一直不会返回,那么整个进程就会被阻塞住,而不能去做别的事了
waitpid() 如果在调用的时候没有僵尸进程,那么函数就马上返回

单一僵尸进程虽然无害,但如果有过多的僵尸进程,就会消耗系统中的进程数资源,最坏的情况是导致新的进程无法启动。

# 给僵尸进程的父进程发送消息,让其清理掉子进程
kill -s SIGCHLD ppid
# 如果以上命令无效,则需要杀死父进程,使其变为孤儿进程,继而被init进程回收
# 注意,如果僵尸进程的父进程是1,请谨慎删除,否则将会重启
sudo kill -9 ppid
# 将任务丢到后台执行,在命令的最后加上&
# 但如果有stdout和stderr依旧会输出到前台,导致前台被影响,可以利用数据重定向将输出传送到文件中
cp file1 file2 > /tmp/log.txt 2>&1 &

# 将任务暂并停放到后台
# 比如正在编辑一个文件或者执行的命令正在运行中时使用
Ctrl+z

# 列出所有任务
jobs [-l/r/s]  # 不加选项默认只显示任务编号(job number),-l显示任务对应的PID,-r只列出放在后台运行的,-s只列出放在后台暂停的

# 将后台(background)任务拿到前台(foreground)来执行
fg %job_number  # %可加可不加,建议加(为了与PID做区分)

# 让暂停到后台的任务在后台继续运行
bg %job_number

# 如果想要脱机后依旧运行任务则需要使用nohup
nohup command  # 前台运行
nohup command &  # 后台运行

管理任务

管理任务就是调用kill向进程发送一个信号

kill -l  # 查看所有信号的编号和名字
kill -<信号编号或名称> <%job_number或PID>  # 发送信号
killall -<信号编号或名称> <命令名称>

20210826205955

  • 1 SIGHUP 启动被终止的进程/任务,重新读取一次配置文件
  • 2 SIGINT 中断进程/任务,执行Ctrl+c时即发送的就是这个信号
  • 9 SIGKILL 立刻强制中断/删除一个进程/任务(通常用来删除不正常的任务)
  • 15 SIGTERM 以正常的进程方式结束一个任务(默认)
  • 19 SIGSTOP 暂停一个进程,执行Ctrl+z时即发送的就是这个信号
kill %job_number  # 默认发送编号为15的SIGTERM信号
kill -9 %job_number  # 指定发送编号为9的信号
kill -SIGKILL %job_number  # 指定发送名称为SIGKILL的信号

20210826212807

当我们运行 kill 1 这个命令的时候,希望把 SIGTERM 这个信号发送给 1 号进程,于是调用了kill()这个内核的调用接口(即系统调用),从而进入到了内核函数 sys_kill(),而内核在决定把信号发送给 1 号进程的时候,会调用 sig_task_ignored() 这个函数来做个判断,有以下三种情况:

  1. 缺省(Default),每个信号都有一个缺省行为,可使用man 7 signal查看
  2. 捕获(Catch),即进程可以针对某个信号注册相应的handler,用户进程如果不注册,则使用SIG_DFL这个缺省的 handler
  3. 忽略(Ignore),即不做任何处理

有两个特权信号:SIGKILLSIGSTOP,它们是Linux 为 kernel 和超级用户去删除任意进程所保留的,不能被忽略,也不可以被捕获。比如进程一旦收到SIGKILL信号就要退出。

另外,Linux 内核针对每个 Nnamespace 里的 init 进程,把只有 default handler 的信号都给忽略了。所以容器的 init 进程是永远不能被 SIGKILL 所杀(因为特权信号不能被捕获,进程默认使用default handler),即kill -9 1在容器中是不工作的(宿主机也一样),但是在可以被 SIGTERM 杀死(前提是注册了handler)。

进程对信号的处理其实就包括两个问题:

  1. 进程如何发送信号:发送信号的系统调用是 kill()
  2. 进程收到信号后如何处理:signal() 它可以给信号注册 handler

内核中对不同的信号有不同的缺省行为,一般会采用退出(terminate),暂停(stop),忽略(ignore)这三种行为中的一种。

对于 SIGTERM 这个信号来说,它的缺省行为就是进程退出

捕获指的就是我们在代码中为某个信号,调用 signal() 注册自己的 handler。这样进程在运行的时候,一旦接收到信号,就不会再去执行内核中的缺省代码,而是会执行通过 signal() 注册的 handler。

当 Linux 进程收到 SIGTERM 信号并且使进程退出,这时 Linux 内核对处理进程退出的入口点就是 do_exit() 函数,do_exit() 函数中会释放进程的相关资源,比如内存,文件句柄,信号量等等。

在做完这些工作之后,它会调用一个 exit_notify() 函数,用来通知和这个进程相关的父子进程等。

20210829153533

服务

当我们登陆并执行bash中执行命令时,其实就是在bash这个父进程(PPID)中运行的子进程,只不过这些命令基本都是运行完就结束了

但也有些进程执行完并不会立刻结束,而是常驻在内存中,这些进程通常都是负责一些系统所提供的功能以服务用户的各项任务,称之为:服务(daemon)

daemon一般会在文件名后加上d,比如:httpd

daemon主要分为

  • 本地服务
  • 网络服务,网络服务会提供一个端口(port),供外部客户端请求连接

最后更新: 2022-05-01