Linux 是一个以稳定性和灵活性著称的开源操作系统,其高效的进程管理至关重要。对于刚接触 Linux 的初学者来说,理解进程并掌握相关命令对于有效的系统管理和故障排除至关重要。本指南将全面探讨 Linux 进程、它们的管理方式,以及如 ps、top、jobs
和 bg
等关键命令,专为 Linux 生态系统的新手量身定制。
一)什么是 Linux 进程?
在 Linux 操作系统中,进程(Process) 被定义为正在执行的程序。它表示一个正在运行的程序实例,包括程序代码和当前的活动状态。Linux 为每个进程分配一个唯一的 进程 ID(PID),使操作系统能够有效地管理和跟踪进程。在 Linux 中,进程(Process) 是正在运行的程序实例,包括程序代码、数据、资源等。
Linux 是一个多任务操作系统,可以同时运行多个进程,并通过进程调度和管理保证系统的稳定运行。
1.1) 进程的组成
一个进程由以下几个部分组成:
- 代码段(Text Section):存放可执行代码。
- 数据段(Data Section):存放全局变量和静态变量。
- 堆(Heap):动态分配的内存区域(
malloc
/new
)。 - 栈(Stack):存放函数调用信息、局部变量等。
1.2) 理解进程状态
- 运行(Running):进程当前正在 CPU 上执行。
- 睡眠(Sleeping):进程正在等待某个事件发生,例如 I/O 操作完成。
- 停止(Stopped):进程已被暂停,通常是由于接收到某个信号。
- 僵尸(Zombie):进程已执行完成,但其父进程尚未读取其退出状态。
理解这些状态对于高效管理进程和排查系统问题至关重要。
1.3) 调度信息(Scheduling Information)
调度程序需要这些信息,以便公平地决定系统中哪个进程最应该运行。
1.4) 标识符(Identifiers)
- 系统中的每个进程都有一个唯一的 进程标识符(PID)。
- 进程标识符 不是 任务向量(task vector)中的索引,而仅仅是一个编号。
- 每个进程还具有 用户标识符(User ID, UID) 和 组标识符(Group ID, GID),它们用于控制进程对系统中文件和设备的访问权限。
1.5) 时间和定时器(Times and Timers)
- 内核会跟踪 进程的创建时间以及其整个生命周期内消耗的 CPU 时间。
- 每个 时钟滴答(clock tick),内核会更新进程在 系统模式(system mode) 和 用户模式(user mode) 下消耗的时间(以 jiffies 计)。
- Linux 还支持进程特定的间隔定时器(Interval Timers),进程可以使用 系统调用(system calls) 来设置定时器,以便在到期时向自身发送信号。
- 这些定时器可以是一次性(single-shot) 也可以是周期性(periodic) 的。
1.6) 文件系统(File System)
- 进程可以随意打开和关闭文件,每个进程的
task_struct
结构体都包含指向以下内容的指针:- 已打开文件的文件描述符(File Descriptors)。
- 两个 VFS(虚拟文件系统)inode 指针:
- 根目录(Root Directory):即进程的主目录。
- 当前工作目录(Current Working Directory,pwd):来源于 Unix 命令
pwd
(打印工作目录)。
- VFS inode 唯一标识文件系统中的一个文件或目录,并提供与底层文件系统的统一接口。
- 这些 VFS inode 的 引用计数(count 字段) 会增加,表示它们被一个或多个进程引用:
1.7) 虚拟内存(Virtual Memory)
1.8) 处理器特定上下文(Processor Specific Context)
- 进程可以被看作是当前系统状态的总和。
- 当进程运行时,它会使用 CPU 寄存器、栈(stack)等资源,这些构成了进程的上下文(context)。
- 当进程被 挂起(suspended) 时,所有 CPU 相关的上下文都会被保存在
task_struct
结构体中。 - 当进程被 调度器(scheduler) 重新启动时,它的上下文会从
task_struct
中恢复,以确保进程能够继续执行。
1.9)守护进程(Daemons)
这些是特殊类型的后台进程,在系统启动时自动运行,并作为服务持续运行,不会终止。它们作为系统任务(以服务的形式)自发启动。然而,用户可以通过 init 进程对其进行控制。
守护进程的主要特点:
-
后台运行:
- 守护进程在后台运行,不需要用户直接交互。
-
随系统启动:
- 这些进程通常在系统启动时自动加载并运行。
-
服务型进程:
- 它们提供关键的系统功能,例如日志记录、网络管理、任务调度和设备管理。
-
无控制终端:
-
由 Init/Systemd 管理:
常见的守护进程示例:
守护进程名称 | 功能 |
---|---|
cron |
计划和执行自动化任务(定时任务)。 |
sshd |
处理 SSH 远程登录会话。 |
httpd / nginx |
Web 服务器守护进程。 |
mysqld |
管理 MySQL 数据库服务。 |
systemd-journald |
处理系统日志记录。 |
在 Systemd(现代 Linux 系统)中管理守护进程
大多数现代 Linux 发行版使用 systemd 来管理守护进程,可以使用以下命令控制它们:
- 启动守护进程:
sudo systemctl start <daemon_name>
- 停止守护进程:
sudo systemctl stop <daemon_name>
- 设置开机自启:
sudo systemctl enable <daemon_name>
- 检查守护进程状态:
sudo systemctl status <daemon_name>
守护进程在 Linux 系统的正常运行中起着至关重要的作用。它们在后台静默运行,负责网络、调度、日志等关键服务,确保系统稳定高效地运作。
二)Linux 进程的类型
三)Linux 进程的生命周期

- 创建(Create):进程通过
fork()
产生一个子进程。 - 就绪(Ready):进程等待 CPU 调度执行。
- 运行(Running):进程在 CPU 上执行。
- 阻塞(Blocked):进程等待某个事件(如 I/O)。
- 终止(Terminated):进程执行完毕或被终止,释放资源。
四)进程创建
通常,当一个现有进程在内存中创建一个与自身完全相同的副本时,会生成一个新进程。子进程的环境与父进程相同,唯一不同的是进程 ID 号。
- 使用
system()
函数 – 这种方法相对简单,但效率较低,并且存在一定的安全风险。 - 使用
fork()
和exec()
函数 – 这种技术稍微复杂一些,但提供了更大的灵活性、更高的速度以及更好的安全性。
#include <unistd.h> #include <stdio.h> int main() { pid_t pid = fork(); // 创建子进程 if (pid == 0) { printf("子进程:PID=%d, 父进程 PID=%d\n", getpid(), getppid()); } else { printf("父进程:PID=%d, 子进程 PID=%d\n", getpid(), pid); } return 0; }
fork()
之后的情况
vfork()
vfork()
类似fork()
,但不会复制进程地址空间,适用于exec
系列函数。
exec()
exec()
用于执行新程序,会替换进程的代码段,如:execlp("ls", "ls", "-l", NULL);
五) 进程状态
Linux 进程的状态可以通过 ps aux
或 top
查看:
状态 | 说明 |
---|---|
R (Running) | 运行中或可运行 |
S (Sleeping) | 休眠(等待事件) |
D (Uninterruptible Sleep) | 不可中断的睡眠(等待 I/O) |
T (Stopped) | 进程已暂停 |
Z (Zombie) | 僵尸进程,已退出但未清理 |
示例:
ps -aux # 查看所有进程 top # 实时监控进程
六)进程调度
查看进程优先级(Nice 值):
ps -eo pid,ni,comm
调整进程优先级:
nice -n 10 ./my_program # 设置启动时的优先级 renice -n 5 -p 1234 # 修改运行中进程的优先级
七)进程通信
7.1) 进程间通信(Inter-Process Communication, IPC)
- Linux 支持经典的 Unix IPC 机制,包括 信号(Signals)、管道(Pipes)和信号量(Semaphores)。
- 还支持 System V IPC 机制,如 共享内存(Shared Memory)、信号量(Semaphores)和消息队列(Message Queues)。
- Linux 支持的 IPC 机制在 IPC 章节 中有详细描述。
7.2) 进程关联(Links)
- 在 Linux 系统中,没有任何进程是完全独立的,每个进程(除了第一个进程)都有 父进程(Parent Process)。
- 新进程并不是直接创建的,而是从现有进程 复制(Copy) 或 克隆(Clone) 而来。
task_struct
结构体用于表示进程,它包含指向以下进程的指针:- 你可以使用
pstree
命令查看 Linux 系统中运行进程的家族关系:pstree
pstree 此外,系统中的所有进程都存储在一个双向链表(doubly linked list)中,其根节点是
init
进程的task_struct
结构体。
7.3) 进程间通信(IPC)方式:
- 管道(Pipe):
pipe()
- 消息队列(Message Queue):
msgget()
- 共享内存(Shared Memory):
shmget()
- 信号量(Semaphore):
semget()
- Socket 套接字:
socket()
示例:管道通信
#include <unistd.h> #include <stdio.h> int main() { int fd[2]; // 文件描述符 pipe(fd); // 创建管道 if (fork() == 0) { close(fd[0]); // 关闭读端 write(fd[1], "Hello, parent!", 14); close(fd[1]); } else { char buf[20]; close(fd[1]); // 关闭写端 read(fd[0], buf, sizeof(buf)); printf("收到消息: %s\n", buf); close(fd[0]); } return 0; }
八) 进程终止
正常终止
exit()
:进程自行退出return
:main()
返回
异常终止
kill(pid, SIGKILL)
:强制终止abort()
:产生SIGABRT
segmentation fault (SIGSEGV)
:非法内存访问
示例:终止进程
kill -9 1234 # 杀死进程 1234 pkill firefox # 杀死所有 firefox 进程
九) 僵尸进程和孤儿进程
在 Unix/Linux 系统中,正常情况下,子进程是通过父进程创建的,且两者的运行是相互独立的,父进程永远无法预测子进程到底什么时候结束。当一个进程调用 exit 命令结束自己的生命时,其实它并没有真正的被销毁,内核只是释放了该进程的所有资源,包括打开的文件、占用的内存等,但是留下一个称为僵尸进程的数据结构,这个结构保留了一定的信息(包括进程号 the process ID,退出状态,运行时间),这些信息直到父进程通过 wait()/waitpid() 来取时才释放。这样设计的目的主要是保证只要父进程想知道子进程结束时的状态信息,就可以得到
9.1)僵尸进程(Zombie)
一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中,这种进程称之为僵尸进程。
wait(NULL); // 父进程回收子进程
9.2)孤儿进程(Orphan)
一个父进程退出,而它的一个或多个子进程还在运行,那么这些子进程将成为孤儿进程。孤儿进程将被 init 进程(进程号为1)所收养,并由 init 进程对它们完成状态收集工作。
9.3)僵尸进程与孤儿进程的问题危害
僵尸进程虽然不占有任何内存空间,但如果父进程不调用 wait() / waitpid()
的话,那么保留的信息就不会释放,其进程号就会一直被占用,而系统所能使用的进程号是有限的,如果大量的产生僵尸进程,将因为没有可用的进程号而导致系统不能产生新的进程,此即为僵尸进程的危害。
孤儿进程是没有父进程的进程,孤儿进程这个重任就落到了 init 进程身上,init 进程就好像是一个民政局,专门负责处理孤儿进程的善后工作。每当出现一个孤儿进程的时候,内核就把孤儿进程的父进程设置为 init,而 init 进程会循环地 wait()
它的已经退出的子进程。这样,当一个孤儿进程凄凉地结束了其生命周期的时候,init 进程就会出面处理它的一切善后工作。因此孤儿进程并不会有什么危害。
9.4)如何解决僵尸进程造成的问题
(1)方案一:父进程通过 wait
和 waitpid
等函数等待子进程结束,但这会导致父进程挂起,所以这并不是一个好办法,父进程如果不能和子进程并发执行的话,那我们创建子进程的意义就没有。同时一个 wait 只能解决一个子进程,如果有多个子进程就要用到多个 wait
(2)方案二:通过信号机制:
子进程退出时,向父进程发送 SIGCHILD
信号,父进程处理 SIGCHILD
信号,在信号处理函数中调用 wait
进行处理僵尸进程。
(3)方案三:fork
两次:
原理是将进程成为孤儿进程,从而其的父进程变为 init
进程,通过 init 进程处理僵尸进程。具体操作为:父进程一次 fork() 后产生一个子进程随后立即执行 wait(NULL) 来等待子进程结束,然后子进程 fork() 后产生孙子进程随后立即exit(0)。这样子进程顺利终止(父进程仅仅给子进程收尸,并不需要子进程的返回值),然后父进程继续执行。这时的孙子进程由于失去了它的父进程(即是父进程的子进程),将被转交给Init
进程托管。于是父进程与孙子进程无继承关系了,它们的父进程均为Init
,Init
进程在其子进程结束时会自动收尸,这样也就不会产生僵死进程了
(4)方案四:kill
父进程:
严格地来说,僵死进程并不是问题的根源,罪魁祸首是产生出大量僵死进程的那个父进程。因此,当我们寻求如何消灭系统中大量的僵死进程时,答案就是把产生大量僵死进程的那个元凶枪毙掉(也就是通过 kill
发送 SIGTERM
或者 SIGKILL
信号啦)。枪毙了元凶进程之后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被 init 进程接管,init 进程会 wait() 这些孤儿进程,释放它们占用的系统进程表中的资源,这样,这些已经僵死的孤儿进程就能瞑目而去了。
十) 进程管理命令
在 Linux 系统中,进程管理是系统管理员和开发者的核心任务之一。以下是常用的 进程管理命令,帮助你监视、控制、终止和优化进程。执行中的程序称作进程。当程序可以执行文件存放在存储中,并且运行的时候,每个进程会被动态得分配系统资源、内存、安全属性和与之相关的状态。可以有多个进程关联到同一个程序,并同时执行不会互相干扰。操作系统会有效地管理和追踪所有运行着的进程。
为了管理这些进程,用户应该能够:
Linux提供了许多命令来让用户来高效掌控上述的操作。
命令 | 作用 |
---|---|
ps -aux |
查看所有进程 |
top |
实时监控进程 |
htop |
交互式进程管理 |
kill -9 <pid> |
强制终止进程 |
pkill <name> |
根据进程名称杀死进程 |
nice -n <priority> command |
以指定优先级启动进程 |
renice <priority> -p <pid> |
调整进程优先级 |
总结
- Linux 进程是运行程序的实例,有 创建、运行、阻塞、终止 等状态。
fork()
复制进程,exec()
运行新程序。- 进程通信方式包括 管道、共享内存、消息队列 等。
wait()
回收子进程,避免 僵尸进程。nice
和renice
调整进程优先级。kill
和pkill
终止进程。