引言

在计算机科学的世界中,进程是一个核心概念。无论您是一名系统管理员、一名开发人员,还是只是对计算机操作系统感兴趣的普通用户,都需要理解进程是如何工作的。Linux作为一种广泛使用的操作系统,以其卓越的性能和可定制性而闻名,而Linux内核中的进程管理是其内核力量的一部分。

进程是计算机上运行的程序的实例,它们可以并行执行,执行各种任务,从简单的文本编辑器到复杂的服务器应用。然而,这些进程的状态却是多变的,而深入了解这些状态可以帮助我们更好地理解和优化Linux系统。

在本博客中,我们将深入探讨Linux内核进程状态的世界,解释每个状态的含义、如何监视它们以及在不同状态下如何进行适当的处理。无论您是要解决性能问题、进行系统调优还是简单地了解操作系统的运行方式,这些知识都将对您有所帮助。

运行状态僵尸状态,我们将带您穿越Linux进程状态的全貌,让您更深入地了解Linux操作系统的内部机制。

Linux进程状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
/*
* The task state array is a strange "bitmap" of
* reasons to sleep. Thus "running" is zero, and
* you can test for combinations of others with
* simple bit tests.
*/
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)", /* 32 */
};

这段代码定义了一个名为task_state_array的数组,该数组用于表示Linux内核中任务(或进程)的不同状态。这个数组的目的是将每种任务状态映射到一个位掩码,以便在内核中进行位操作来检查和管理任务的状态。

位掩码

位掩码是一种在编程中用来对某些特定位进行操作的技术。它通常涉及到使用位运算符(如位与、位或、位异或等)来设置、清除或测试特定位的值。位掩码的主要目的是允许对一个整数或一组标志进行高效的单独位操作。

让我们更详细地解释位掩码的概念:

  1. 位运算符

    • 位与(&):将两个二进制数的对应位进行与操作,只有当两个位都为1时,结果位才为1。
    • 位或(|):将两个二进制数的对应位进行或操作,只要其中一个位为1,结果位就为1。
    • 位异或(^):将两个二进制数的对应位进行异或操作,只有当两个位不同(一个为0,一个为1)时,结果位才为1。
    • 位取反(~):将一个二进制数的每个位取反,0变为1,1变为0。
  2. 位掩码的作用:位掩码通常用于以下几种情况:

    • 标志操作:使用位掩码来设置、清除或测试一组标志位,以便在一个整数中表示多个状态或属性。
    • 权限和访问控制:将权限信息编码为位掩码,以便进行权限检查和授权。
    • 硬件寄存器操作:在底层硬件编程中,位掩码用于与硬件寄存器的特定位进行交互,以配置硬件设备或读取状态信息。
  3. 位掩码的示例:假设你希望管理文件的读取、写入和执行权限。你可以为每个权限分配一个唯一的位掩码值:

    • 读取权限:0001
    • 写入权限:0010
    • 执行权限:0100

    现在,你可以将这些权限按位组合,以表示文件的实际权限。例如,要为文件分配读取和写入权限:

    1
    int filePermissions = READ_PERMISSION | WRITE_PERMISSION; // 文件具有读取和写入权限
  4. 检查权限:使用位与运算符可以轻松检查文件是否具有特定权限。例如,要检查文件是否具有执行权限:

    1
    2
    3
    if (filePermissions & EXECUTE_PERMISSION) {
    // 文件具有执行权限
    }
  5. 添加和删除权限:使用位或和位与运算符,你可以添加或删除权限而不影响其他权限。例如,要向文件添加执行权限:

    1
    filePermissions |= EXECUTE_PERMISSION; // 添加执行权限

    要从文件中删除写入权限:

    1
    filePermissions &= ~WRITE_PERMISSION; // 删除写入权限
  6. 位掩码的性能优势:位掩码通常比使用单独的布尔变量或枚举更加高效,因为它们允许在单个整数上进行多个状态的快速操作,而不需要使用条件语句。这在处理大量状态或标志时特别有用。

总之,位掩码是一种强大的技术,用于管理和操作二进制数据中的特定位,通常用于表示多个状态或标志。它在编程中经常用于系统编程、嵌入式编程和许多其他领域,以实现高效的位级操作。

位掩码进程状态应用

当在操作系统内核中管理进程状态时,使用位掩码是一种有效且高效的方法,它允许你表示和操作多个状态,而无需使用大量的布尔变量或复杂的条件语句。

定义状态位掩码:首先,你需要为每个可能的进程状态定义一个唯一的位掩码值。这些位掩码值通常是2的幂次方值,以确保每个状态都有一个不同的位表示。在你的示例中,状态位掩码可以如下定义:

#define RUNNING (1 << 0) // 位掩码值为 1
#define SLEEPING (1 << 1) // 位掩码值为 2
#define DISK_SLEEP (1 << 2) // 位掩码值为 4
#define STOPPED (1 << 3) // 位掩码值为 8
#define TRACING (1 << 4) // 位掩码值为 16
#define DEAD (1 << 5) // 位掩码值为 32
#define ZOMBIE (1 << 6) // 位掩码值为 64

1
2
3
4
5
6
7
   
这里,每个状态都有一个唯一的位掩码值,它们是2的幂次方值,确保它们可以单独表示和组合。

2. **表示进程状态**:要表示一个进程的状态,你可以使用位掩码的按位或操作将相应的状态位掩码合并在一起。例如,如果一个进程既处于运行状态又处于睡眠状态,可以这样表示:

```c
int processStatus = RUNNING | SLEEPING; // 进程同时处于运行和睡眠状态

这将在processStatus变量中使用位掩码来表示进程的状态。

  1. 检查进程状态:使用位与操作可以轻松地检查进程是否处于特定状态。例如,要检查进程是否处于运行状态:

    1
    2
    3
    if (processStatus & RUNNING) {
    // 进程处于运行状态
    }

    通过类似的方式,你可以测试其他状态,如睡眠状态、磁盘睡眠状态等。

  2. 多状态组合:位掩码允许你高效地测试多个状态的组合。例如,如果你想检查进程是否同时处于运行状态和睡眠状态,只需使用位与操作来测试这两个状态的位掩码是否都被设置:

    1
    2
    3
    if ((processStatus & RUNNING) && (processStatus & SLEEPING)) {
    // 进程同时处于运行和睡眠状态
    }

    这避免了使用多个布尔变量或复杂的条件语句来管理进程状态。

  3. 修改进程状态:使用位操作,你可以轻松地修改进程的状态。例如,如果要将进程从运行状态切换到睡眠状态,可以使用位与操作来清除运行状态位并设置睡眠状态位:

    1
    2
    processStatus &= ~RUNNING;  // 清除运行状态位
    processStatus |= SLEEPING; // 设置睡眠状态位

总之,位掩码是一种非常有效的方式来管理和操作多个状态或标志。在操作系统内核等低级编程中,它可以使代码更加紧凑、高效和易于维护,因为它允许你在单个整数上执行多个状态操作,而无需引入大量的变量和条件逻辑。

进程状态

宏观解析各类进程状态

Linux内核中的进程状态是一个关键的概念,它用于跟踪和管理操作系统中运行的进程。在内核中,这些状态通常用位掩码来表示,位掩码是一个二进制数,每个位都代表一种状态。下面详细解释了Linux内核中使用的进程状态位掩码和对应的文本描述:

1
2
3
4
5
6
7
8
9
static const char * const task_state_array[] = {
"R (running)", /* 0 */
"S (sleeping)", /* 1 */
"D (disk sleep)", /* 2 */
"T (stopped)", /* 4 */
"t (tracing stop)", /* 8 */
"X (dead)", /* 16 */
"Z (zombie)" /* 32 */
};
  1. R (running) - 运行状态 (位掩码值:0)

    • 描述:进程正在执行(在CPU上运行)或者处于就绪状态(在运行队列中等待CPU执行)。
    • 意义:这是进程处于活跃执行状态的标志,它正在占用CPU时间。
    • 进程正在执行(在CPU上运行):当一个进程处于 “R (running)” 状态时,它实际上正在消耗 CPU 时间,执行其指定的任务。这是进程处于最活跃状态的情况,它正在运行中,使用 CPU 资源。
    • 进程在运行队列中等待:另一种情况是,当一个进程需要执行但当前没有分配到CPU时,它会被放置在运行队列中等待CPU的分配。这时进程状态仍然被标记为 “R (running)”,尽管它实际上还没有开始执行。这是因为操作系统将其标记为“准备好执行”,只要有CPU时间可用,它就可以立即开始运行。
  2. S (sleeping) - 睡眠状态 (位掩码值:1)

    • 描述:进程进入睡眠状态,通常因为它在等待某些事件的发生,如等待I/O操作完成或等待信号。
    • 意义:在这个状态下,进程没有活跃执行,它在等待外部事件的发生。
  3. D (disk sleep) - 磁盘睡眠状态 (位掩码值:2)

    • 描述:进程处于磁盘休眠状态,通常是因为它在等待磁盘I/O操作完成。
    • 意义:这是睡眠状态的一种特殊情况,表示进程等待磁盘操作的完成。
  4. T (stopped) - 停止状态 (位掩码值:4)

    • 描述:进程被明确地停止或暂停,通常是由于接收到SIGSTOP信号或类似的停止信号。
    • 意义:在这个状态下,进程不再执行,但它仍然存在,等待继续执行。
  5. t (tracing stop) - 跟踪停止状态 (位掩码值:8)

    • 描述:这是一种特殊的停止状态,表示进程正在被调试器跟踪。
    • 意义:进程停止执行以供调试器检查其状态,通常用于调试目的。
  6. X (dead) - 死亡状态 (位掩码值:16)

    • 描述:表示进程已经终止或被销毁,不再存在。
    • 意义:在这个状态下,进程已经完成了其执行,但其描述信息仍然在内核中存在。
  7. Z (zombie) - 僵尸状态 (位掩码值:32)

    • 描述:这是进程在终止后的中间状态,表示进程已经终止,但其父进程还没有等待获取其终止状态。
    • 意义:一旦父进程等待获取终止状态,僵尸进程将被完全销毁。

这些状态位掩码用于表示和管理进程的状态变化。进程通常会在这些状态之间转换,具体取决于它的活动和操作系统的调度。了解和监视这些状态对于操作系统的正常运行和故障排除非常重要。这个位掩码数组提供了对这些状态的清晰描述,以帮助开发人员和系统管理员更好地理解进程的状态。

S (sleeping)D (disk sleep)

理解"S (sleeping)" 和 “D (disk sleep)” 进程状态的区别确实很重要,因为它们涉及不同的场景和影响。让我们更详细地解释它们的区别:

**S (sleeping) - 睡眠状态 **:

  • 场景

    • 进程通常进入 S 睡眠状态,因为它主动等待某些事件或条件的发生。这些事件可以是等待用户输入、等待网络数据、等待信号、等待资源解锁或等待其他进程完成等。
    • 这种状态是正常的,因为它表示进程正在等待某些操作的完成或等待外部事件的触发。
  • 影响

    • S 睡眠状态的进程通常不会占用 CPU 时间,因为它们正在等待外部事件。这意味着它们不会导致明显的 CPU 负载。
    • 在系统性能方面,S 睡眠状态的进程不会导致太大的开销。它们只有在等待事件期间才会挂起,而一旦事件发生,它们就会被唤醒并继续执行。
睡眠状态实例
  1. 等待用户输入:一个命令行程序等待用户输入命令,当用户没有输入时,进程可能会进入睡眠状态。它会等待用户键入命令并按回车键。

  2. 等待网络数据:一个网络服务器进程等待从客户端接收数据。如果没有数据到达,服务器进程可能会进入睡眠状态,以避免不必要的 CPU 使用。

  3. 等待资源解锁:多个进程尝试获取一个共享资源的锁。如果某个进程无法获得锁,它可能会进入睡眠状态,等待其他进程释放锁。

D (disk sleep) - 磁盘睡眠状态:

当一个进程处于深度睡眠状态(D状态)时,它通常是由于等待某些不可中断的事件或资源完成而被挂起的。深度睡眠是Linux中进程状态的一种,与其他状态(如运行、可中断睡眠等)不同,因为它在很大程度上受限于底层系统和硬件的工作。

  1. 进程状态的演变

    • 运行状态(Running):进程当前正在执行指令。

    • 可中断睡眠状态(Interruptible Sleep):进程等待某些事件的发生,这些事件可以是中断、信号、数据等。进程可以在这个状态下被打断,以响应中断或其他事件。

    • 不可中断睡眠状态(Uninterruptible Sleep):进程等待某些不可中断的事件,通常是底层硬件资源或内核操作,例如等待磁盘I/O完成、等待硬件中断。进程在这个状态下是不可中断的,不能被正常终止。

  2. D状态的特点

    • 不可中断性:D状态的进程是不可中断的,这意味着它们无法通过普通的终止信号(如SIGTERM)来终止。这是为了确保在等待底层资源时不会发生数据损坏或系统不稳定。

    • 等待硬件资源:D状态通常是由于进程等待硬件资源或底层内核操作而导致的。例如,一个进程可能在等待磁盘I/O完成,但由于磁盘操作不可中断,所以它无法继续执行。

    • 系统稳定性:D状态的进程通常不会对系统的整体稳定性产生重大影响,因为它们被设计成不可中断的,这意味着它们不会妨碍其他进程的正常执行。

  3. 常见原因

    • 硬件故障:硬件故障或问题可能导致进程进入D状态,例如磁盘故障。

    • 驱动程序问题:不正确的或过时的硬件驱动程序可能导致进程陷入深度睡眠状态。

    • 文件系统问题:文件系统损坏或其他文件系统问题可能导致进程进入D状态。

  4. 处理D状态进程

    • 故障排除:确定导致进程进入D状态的根本原因非常重要。这通常需要详细的系统故障排除,包括检查硬件健康状况、更新驱动程序、修复文件系统等。

    • 系统重启:在某些情况下,如果无法快速解决问题,可能需要重启系统,以便清除D状态进程并恢复系统正常运行。

    • 升级或修复:在一些情况下,可能需要升级操作系统、修复硬件或更换受损的组件,以解决D状态问题。

深度睡眠状态是Linux系统中的一个特殊状态,通常需要仔细的故障排除和维护来解决潜在的问题。要处理D状态进程,通常需要深入了解系统的硬件和操作系统配置,并在必要时采取适当的措施来修复问题。这通常需要系统管理员或高级用户的介入。

磁盘睡眠状态实例
  1. 文件读取:一个进程正在从磁盘上的大文件中读取数据。由于磁盘读取速度较慢,进程可能会进入磁盘睡眠状态,等待数据从磁盘加载到内存。

  2. 文件写入:一个进程正在将大量数据写入磁盘。由于写入操作可能需要较长时间,进程可能会进入磁盘睡眠状态,等待数据成功写入磁盘。

  3. 磁盘故障:如果硬盘遇到故障或出现问题,相关的磁盘 I/O 操作可能会进入 D 磁盘睡眠状态,因为硬盘无法响应读取或写入请求。

需要注意的是,这些实际例子只是说明了可能导致进程进入睡眠状态的情况。在实际系统中,有许多其他因素可能会影响进程状态,包括操作系统调度、多任务处理、I/O 子系统性能等。监视工具和系统日志可以帮助您更好地理解和识别进程状态,并采取必要的措施来调整系统性能或处理问题。

僵尸状态

**僵尸进程(Zombie Process)**是指已经终止但其终止状态尚未被其父进程收集的进程。在Unix-like操作系统(包括Linux)中,当一个子进程终止时,其终止状态信息仍然需要由其父进程收集,以便操作系统可以释放子进程占用的资源。如果父进程没有正确处理这些终止状态,子进程就会变成僵尸进程。

概念解释

  1. 进程生命周期:进程通常经历创建、运行和终止三个阶段。当进程终止时,它变成一个僵尸进程,等待其父进程调用wait()waitpid()系统调用来收集其终止状态。
  2. 终止状态:终止状态包含有关子进程终止的信息,如退出代码、终止原因等。这些信息对于父进程了解子进程的终止情况非常重要。
  3. 父进程的责任:父进程负有责任及时处理其子进程的终止状态,以避免子进程成为僵尸进程。通常,父进程应该在子进程终止后调用wait()waitpid()来等待终止状态,并在获取状态后释放子进程的资源。

僵尸进程实例

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main() {
pid_t child_pid;

// 创建子进程
child_pid = fork();

if (child_pid < 0) {
perror("Fork failed");
exit(1);
}

if (child_pid == 0) {
// 子进程
printf("Child process is running.\n");
// 子进程退出,成为僵尸进程
exit(0);
} else {
// 父进程
printf("Parent process is running.\n");
sleep(20);
// 父进程不等待子进程,没有处理终止状态
// 这将导致子进程成为僵尸进程
}

return 0;
}

在这个示例中,父进程创建一个子进程,但父进程没有等待子进程的终止状态,也没有处理它。因此,当子进程终止后,它会变成一个僵尸进程,因为父进程没有收集其终止状态。

要测试这个示例,可以将其编译为可执行文件,然后运行。在父进程和子进程都运行后,可以使用ps命令查看进程状态:

1
2
3
4
5
$ gcc zombie_example.c -o zombie_example
$ ./zombie_example
Parent process is running.
Child process is running.

接下来,打开另一个终端窗口并运行以下命令,以查看僵尸进程:

1
$ ps aux | grep zombie_example

你将看到输出中存在一个僵尸进程(状态为Z),这是因为父进程没有处理子进程的终止状态。要解决僵尸进程问题,父进程应该在fork之后等待子进程终止,并使用waitwaitpid等函数来处理子进程的终止状态。

等待子进程实例

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
pid_t child_pid;

// 创建子进程
child_pid = fork();

if (child_pid < 0) {
perror("Fork failed");
exit(1);
}

if (child_pid == 0) {
// 子进程
printf("Child process is running.\n");
exit(0);
} else {
// 父进程
printf("Parent process is waiting for the child to terminate.\n");
// 父进程等待子进程终止状态
wait(NULL);
printf("Parent process collected child's termination status.\n");
}

return 0;
}

在这个示例中,父进程创建了一个子进程,然后通过wait(NULL)等待子进程的终止状态。一旦子进程终止,父进程将收集子进程的终止状态。这确保了子进程不会变成僵尸进程。

要测试这个示例,可以将其编译为可执行文件,然后运行。当子进程终止时,父进程会收集其终止状态,然后打印相应的消息。

1
2
$ gcc zombie_example.c -o zombie_example
$ ./zombie_example

通过这个示例,可以清楚地看到父进程如何等待并处理子进程的终止状态,从而避免创建僵尸进程。

孤儿进程

产生情景

当一个父进程创建一个子进程,并且在子进程创建后,父进程意外终止(例如,父进程崩溃或被终止)而没有等待子进程完成时,这个子进程就会变成孤儿进程。这是因为父进程不再存在,没有能够等待子进程的机会。

进程特点

  • 孤儿进程的父进程ID(PPID)会被设置为1,这是init进程的进程ID。
  • 孤儿进程继续在系统中运行,它们并不会受到父进程的终止影响。
  • Init进程会成为孤儿进程的新父进程,它负责收养这些孤儿进程。
  • Init进程会定期检查是否有子进程终止,如果有,它会调用wait()waitpid()系统调用来回收子进程的资源和状态信息,防止子进程变成僵尸进程。

孤儿进程与僵尸进程的关系

  • 孤儿进程不同于僵尸进程。孤儿进程是一个活跃的正在运行的子进程,而僵尸进程是已经终止但尚未被其父进程回收资源的子进程。
  • 当一个孤儿进程终止并被init进程接管时,它最终会在其终止后被回收,不会成为僵尸进程。
  • 僵尸进程通常是由于父进程没有正确回收子进程的状态信息而产生的,而孤儿进程则是因为父进程提前终止而没有机会回收子进程。

总之,孤儿进程是一种在父进程提前终止的情况下保证子进程继续运行并在适当时机被回收的机制。这有助于维护系统的稳定性和资源管理,避免了僵尸进程的问题。要注意的是,编写多进程的应用程序时,必须仔细处理子进程的创建和回收,以确保不会导致僵尸进程或其他问题。

进程实例

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
#include <iostream>
#include <sys/types.h>
#include <unistd.h>

int main() {
pid_t child_pid = fork(); // 创建一个子进程

if (child_pid == -1) {
// 创建子进程失败
perror("fork");
return 1;
}

if (child_pid == 0) {
// 子进程代码
std::cout << "Child process (PID " << getpid() << ") is running." << std::endl;
sleep(2); // 子进程休眠一会儿
std::cout << "Child process (PID " << getpid() << ") is done." << std::endl;
} else {
// 父进程代码
std::cout << "Parent process (PID " << getpid() << ") created a child process (PID " << child_pid << ")." << std::endl;
// 不等待子进程完成
std::cout << "Parent process (PID " << getpid() << ") is done." << std::endl;
}

return 0;
}
  1. <sys/types.h>
    • 功能:这个头文件包含了一些系统数据类型的定义,通常用于系统编程和与操作系统交互。
    • 在函数中的使用:在程序中,pid_t数据类型的定义位于这个头文件中。pid_t用于表示进程ID(PID),在fork()函数的声明中使用了pid_t来表示子进程的PID。
  2. <unistd.h>
    • 功能:这个头文件包含了一些与Unix标准接口有关的常量和函数声明,通常用于系统编程和进程控制。
    • 在函数中的使用:在程序中,fork()getpid()等系统调用的声明位于这个头文件中。具体来说,fork()函数用于创建子进程,getpid()函数用于获取当前进程的PID。所以,#include <unistd.h> 允许你在程序中使用这些系统调用。

总之,这段C++代码演示了通过fork()系统调用创建一个孤儿进程的过程。父进程创建子进程后,不等待子进程完成,因此子进程会在父进程终止后继续执行。这样,子进程就成为了孤儿进程,最终被init进程接管并在完成任务后终止。父进程则立即完成,不会等待子进程。

进程优先级

优先级概念

在Linux系统中,进程的优先级概念是关于进程调度和资源分配的重要概念。进程的优先级决定了它在系统中获得CPU时间的相对权重。在Linux中,优先级通常通过Nice值(也称为niceness)来表示,Nice值越低的进程拥有更高的优先级。

深入剖析

Linux中的进程优先级是通过Nice值来控制的。Nice值是一个整数,通常范围从-20到19,其中-20表示最高优先级,而19表示最低优先级。较低的Nice值意味着更高的优先级,进程将更容易获得CPU时间片。

以下是关于Linux进程优先级的详细信息:

  1. Nice值的含义:

    • 较低的Nice值: 具有较低Nice值(通常为负数)的进程将被视为更重要,操作系统会更频繁地将CPU时间片分配给它们。这使得这些进程能够更快地执行任务。

    • 较高的Nice值: 具有较高Nice值(通常为正数)的进程被认为不太重要,操作系统会相对较少地将CPU时间片分配给它们。这意味着这些进程可能需要更长时间来完成其任务。

  2. Nice值的设置和修改:

    • 设置初始Nice值: 在启动进程时,可以使用nice命令来设置其初始Nice值。例如,nice -n 10 ./my_program会启动一个进程并将其Nice值设置为10。

    • 修改Nice值: 在进程运行时,可以使用renice命令来修改进程的Nice值。**管理员或进程所有者可以使用此命令来调整进程的优先级。**例如,renice -n 5 -p 12345将进程ID为12345的进程的Nice值修改为5。

  3. Nice值对系统性能的影响:

    • 较低Nice值的进程更容易获得CPU时间,因此它们通常能够更快地响应用户请求或完成计算任务。

    • 较高Nice值的进程通常会获得较少的CPU时间,这可能会导致它们的响应时间变长。这对于一些后台任务或低优先级任务是合适的。

  4. CFS调度算法:

  • 在Linux中,进程调度通常由Completely Fair Scheduler(CFS)算法管理。CFS的目标是公平地分配CPU时间,确保所有进程都有机会获得执行。

  • CFS使用Nice值来影响进程的调度优先级。较低Nice值的进程在调度时会被更频繁地选择执行,以确保它们获得更多的CPU时间。

总之,Nice值是Linux中控制进程优先级的关键概念。通过设置和调整Nice值,可以影响进程在系统中的相对优先级,从而优化系统性能和资源分配。在CFS调度算法下,Nice值对进程的调度优先级起着关键作用,确保公平而有效的资源分配。

实操解析

1
2
3
4
5
[root@VM-16-4-centos ~]# ps -al
F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD
4 S 0 7663 7521 0 80 0 - 48519 do_wai pts/0 00:00:00 su
4 S 0 7689 7663 0 80 0 - 29217 do_wai pts/0 00:00:00 bash
0 R 0 7743 7689 0 80 0 - 38332 - pts/0 00:00:00 ps
  1. F(Flags): 进程状态标志,通常表示进程的状态。在您的示例中,大多数进程的状态标志是"4 S",这表示这些进程是处于睡眠(Sleep)状态。

  2. S(State): 进程的状态,通常表示进程当前在何种状态下执行。在您的示例中,状态通常是"S",表示进程正在运行。

  3. UID(User ID): 进程的用户ID,标识进程所属的用户。在您的示例中,所有进程的用户ID都是0,表示root用户。

  4. PID(Process ID): 进程的唯一标识符,用于在系统中识别和管理进程。

  5. PPID(Parent Process ID): 父进程的PID,表示创建当前进程的父进程。在您的示例中,每个进程的PPID是其父进程的PID。

  6. C(CPU utilization): 进程的CPU利用率,表示进程正在使用CPU的百分比。在您的示例中,CPU利用率都为0,表示这些进程当前没有使用CPU。

  7. PRI(Priority): 进程的优先级,通常在0到139的范围内,其中较低的数值表示更高的优先级。在您的示例中,优先级都为80。

  8. NI(Nice Value): 进程的Nice值,它是影响进程优先级的因素之一。较低的Nice值表示较高的优先级。在您的示例中,Nice值都为0,表示这些进程的Nice值未被显式设置。

  9. ADDR(Address): 进程的内存地址。

  10. SZ(Size): 进程的内存占用大小,以字节为单位。

  11. WCHAN(Wait Channel): 进程正在等待的内核等待通道。在您的示例中,大多数进程的等待通道是"do_wai",表示它们正在等待某些条件。

  12. TTY(Terminal Type): 进程所关联的终端类型,如果没有关联终端,则显示"?"。

  13. TIME: 进程已经消耗的CPU时间。

  14. CMD(Command): 正在运行的进程的命令行。

根据示例,列出了一些正在运行的进程,它们的优先级(PRI)都为80,Nice值(NI)都为0,表示它们在调度时具有默认优先级。这些进程的CPU利用率(C)都为0,表示它们当前没有在使用CPU。进程的状态(S)为"Sleep"或"Running",并且它们的终端类型(TTY)显示为"pts/0",表示它们与一个终端会话关联。

pst/0

“pts/0” 表示伪终端(pseudo-terminal slave)的名称,它表示一个终端会话的名称。在Linux系统中,终端会话是用户与系统交互的一种方式,允许他们在终端上运行命令和程序。

  • pts(pseudo-terminal slave): “pts” 表示伪终端从属端口,它通常用于远程登录或在图形用户界面(GUI)中打开终端窗口。每当用户在终端窗口中登录或打开一个新的终端会话时,系统会分配一个唯一的 “pts” 名称。

  • 0: “0” 表示终端会话的序号,通常从0开始递增,以标识不同的终端会话。如果系统上有多个终端会话,每个会话都会有一个唯一的 “pts” 名称和序号。

因此,“pts/0” 表示第一个伪终端从属端口,它与用户的一个终端会话相关联。这是一个用户在命令行终端中运行命令的地方,用户可以在其中输入命令并查看输出。在多用户系统上,每个用户登录时都会分配一个独立的终端会话(通常对应一个 “pts” 名称),使他们可以并行执行命令和任务。

bashpst/0

Bash(Bourne Again Shell)是一种Unix shell,用于在终端或终端仿真器中与操作系统进行交互。伪终端(pseudo-terminal,通常缩写为pts)则是一种用于在Unix系统中提供终端会话的机制。

Bash 和伪终端之间的关系是,Bash 是用户在终端或伪终端中输入命令并与操作系统进行交互的主要方式之一。以下是它们之间的关系:

  1. Bash 在终端中运行: 当您在终端中打开一个新的终端会话时,例如通过SSH登录远程服务器或在本地计算机的终端窗口中运行,通常会创建一个伪终端。然后,系统将Bash shell启动在这个伪终端中,用户可以使用Bash shell与系统进行交互。

  2. Bash 处理用户输入和命令执行: 在Bash shell中,用户可以输入各种命令、执行程序、导航文件系统等等。Bash负责解释和执行这些命令,同时还允许用户与操作系统进行通信。

  3. 伪终端提供终端会话: 伪终端是一种机制,用于模拟物理终端,允许用户通过终端会话与操作系统进行交互。当Bash在终端中运行时,它实际上是在伪终端中运行,用户的输入和输出都经过这个伪终端传递。

  4. Bash 和伪终端的关系: **Bash shell和伪终端之间是一种客户端-服务器关系。**Bash充当终端会话的客户端,而伪终端充当服务器,负责处理输入和输出。当用户在终端中键入命令时,Bash将命令发送到伪终端,伪终端执行命令并将结果返回给Bash,然后Bash将结果显示在终端上,以便用户查看。

综上所述,Bash是用户与系统进行交互的shell,而伪终端是在Unix系统中提供终端会话的机制。Bash运行在伪终端中,用户通过Bash来执行命令并与操作系统互动。因此,Bash和伪终端密切相关,共同实现了在终端窗口中运行命令的功能。

调整优先级

nice 命令:

  • nice 命令用于启动新进程并设置其初始优先级。语法如下:

    1
    nice -n <优先级> <命令>
    • <优先级> 是一个整数,通常范围从-20(最高优先级)到19(最低优先级),较低的值表示较高的优先级。
    • <命令> 是要运行的命令。例如,要以较高优先级运行一个命令,可以使用以下命令:
    1
    nice -n -10 ./my_command

renice 命令:

  • renice 命令用于修改正在运行的进程的优先级。语法如下:

    1
    renice -n <新的优先级> -p <进程ID>
    • <新的优先级> 是要设置的新优先级。
    • <进程ID> 是要调整优先级的进程的进程ID。例如,要将进程ID为12345的进程的优先级提高到较高的水平,可以使用以下命令:
    1
    renice -n -10 -p 12345

taskset 命令:

  • taskset 命令用于将进程绑定到特定的 CPU 核心,并可以在命令中设置优先级。语法如下:

    1
    taskset -c <CPU核心列表> nice -n <优先级> <命令>
    • <CPU核心列表> 是要绑定进程到的 CPU 核心列表。
    • <优先级> 是要设置的新优先级。
    • <命令> 是要运行的命令。

    例如,要将一个命令运行在 CPU 核心0上并设置其优先级,可以使用以下命令:

    1
    taskset -c 0 nice -n -10 ./my_command

top命令:

top 中,不能直接修改进程的优先级(Nice值),但可以使用 top 提供的快捷键来调整进程的优先级。

以下是如何在 top 中改变进程优先级的步骤:

  1. 打开终端窗口并运行 top 命令:

    1
    top
  2. top 窗口中,将看到系统上运行的进程列表以及系统性能的实时统计信息。

  3. 使用上下箭头键浏览进程列表,找到想要调整优先级的进程。注意进程的 PID(进程ID)。

  4. 选中要调整优先级的进程。为了选中进程,请按下"r",输入进程PID。

  5. 您将看到一个弹出窗口,提示您输入新的 Nice 值。较低的 Nice 值表示较高的优先级。

  6. 输入您希望设置的新 Nice 值。

  7. 按下 Enter 键,确认更改。

  8. top 会将新的 Nice 值应用于选中的进程,并在进程列表中更新。

请注意,这个操作需要足够的权限来改变进程的优先级。通常,只有超级用户(root)或具有适当权限的用户可以执行这个操作。如果您没有足够的权限,您可能需要使用 sudo 命令以超级用户权限运行 top

1
sudo top

这样,就可以在 top 中调整进程的优先级。确保在调整进程优先级时谨慎操作,以免影响系统的正常运行。

优先级计算

在Unix/Linux操作系统中,进程的优先级通常通过Nice Value(NI)来表示,以及调度优先级也可以通过PRI(Priority)来表示。以下是有关这两个概念的详细信息:

  1. Nice Value (NI) - 优先级调整

    • NI(Nice Value)是一个整数值,通常在-20到19之间。
    • 一个更高的NI值表示进程更**“友好”**,更愿意让出CPU时间片给其他进程,因此它降低了进程的优先级。
    • 一个较低的NI值表示进程更**“不友好”**,更愿意占用CPU时间片,因此它提高了进程的优先级。
    • 可以使用nice命令来调整进程的NI值,只有root用户可以将NI值设置为负数,即提高进程的优先级。
  2. 优先级计算

    • 优先级计算的具体方法可以因不同的Unix/Linux变种而异,但一般而言,进程的优先级可以通过以下方式计算:

      1
      进程优先级 = 基本优先级 (PRI) + NI (Nice Value)
    • 基本优先级(PRI)通常在不同的系统中有不同的范围,但一般来说,它是一个整数值,表示进程的基本调度优先级。

    • NI值可以为正数或负数,正数表示较低的优先级,负数表示较高的优先级。通常,系统将0作为默认NI值。

总之,要计算一个进程的最终优先级,只需将其基本优先级(PRI)和Nice Value(NI)相加即可。如果NI值为正数,那么将降低进程的优先级,如果NI值为负数,那么将提高进程的优先级。但请注意,优先级的具体计算方法可能因不同的Unix/Linux系统而异,可以查阅相关文档以了解系统的详细规则。

小问杂谈

程序调用sleep是什么状态

当一个程序调用 sleep 函数时,它的进程状态会经历以下状态变化:

  1. 运行态 (Running):在调用 sleep 函数之前,进程通常处于运行状态。这意味着它正在占用CPU执行任务。
  2. 系统调用 (System Call):当程序调用 sleep 函数时,它会发起一个系统调用,请求操作系统将其置于睡眠状态并设置一个定时器,以便在一定时间后唤醒它。
  3. 睡眠态 (Sleeping):一旦系统调用生效,进程状态会从 “运行态” 转变为 “睡眠态”。这表示进程现在处于休眠状态,不再占用CPU时间。同时,操作系统会设置一个定时器,以计算进程需要休眠多长时间。
  4. 等待时间:在 “睡眠态” 期间,进程会等待指定的时间(即 sleep 函数参数中指定的时间)。这个时间可以是秒数、毫秒数或其他时间单位,取决于系统和 sleep 函数的具体实现。
  5. 唤醒 (Wake-up):当休眠时间到期后,定时器会触发,操作系统将进程从睡眠状态唤醒,并将其置于 “就绪态 (Ready)”。此时,进程已经休眠了指定的时间,并准备好继续执行。
  6. 就绪态 (Ready):一旦进程被唤醒,它进入 “就绪态”,等待操作系统的调度器将其分配到CPU上执行。从 “就绪态” 到 “运行态” 的转换取决于操作系统的调度算法和系统负载情况。

总之,调用 sleep 函数会导致进程从 “运行态” 转变为 “睡眠态”,然后在指定的时间后重新进入 “就绪态”,以便继续执行。这种机制允许程序在需要休眠一段时间而不占用 CPU 时间的情况下继续执行其他任务。

需要注意的是,虽然进程在 sleep 期间处于睡眠状态,但这并不意味着它会阻塞整个系统。其他进程仍然可以继续执行,因为操作系统会根据进程的状态和调度策略来分配CPU时间片。当 sleep 时间到期后,进程将被重新置于就绪状态,然后根据调度算法等待获取CPU时间,再次执行其任务。

进程处于暂停状态位于什么队列

在Linux中,当一个进程处于暂停状态(Stopped),它通常会进入内核中的一个特殊队列,称为停止队列(Stopped Queue),也可以称为终止队列(Terminated Queue)。这个队列用于管理已经被明确停止或暂停的进程,以便稍后可以对它们进行操作或管理。

下面是停止队列的详细解释:

  1. 停止状态(Stopped):当一个进程被明确停止,例如通过发送SIGSTOP信号或执行类似的操作,它的状态从 “运行态 (Running)” 或其他状态转变为 “停止态 (Stopped)”。

  2. 进入停止队列:一旦进程被停止,它会从运行队列中移除,并被放入停止队列中。在停止队列中,操作系统会维护进程的状态信息和其他相关信息。

  3. 等待操作或管理:在停止队列中,进程会等待进一步的操作或管理。通常情况下,这些操作由进程的父进程或其他管理机制来执行。例如,父进程可以选择继续执行已停止的子进程,终止子进程或执行其他管理任务。

  4. 重新启动或终止:根据管理操作,进程可以被重新启动,继续执行其任务,或者被终止,彻底结束其生命周期。这取决于父进程或管理者的决策。

停止队列的存在使操作系统能够有效地管理已停止的进程,以便稍后执行必要的操作。这对于进程的控制和管理非常有用,例如在调试和监控方面,允许用户或系统管理员对进程进行适当的操作,而不会丢失进程的状态信息。

Linux中没有挂起状态吗

在Linux中,没有一个专门称为"挂起状态"(Suspend State)的进程状态,与其他一些操作系统(如Windows)不同,Linux将进程状态分为了不同的状态,包括运行状态、可中断睡眠状态、不可中断睡眠状态等,而没有单独的"挂起状态"。

以下是Linux中常见的进程状态:

  1. 运行状态(Running):表示进程当前正在执行指令,并且占用CPU时间。

  2. 可中断睡眠状态(Interruptible Sleep):表示进程正在等待某些事件的发生,例如等待用户输入或等待网络数据到达。这种状态下,进程可以被中断,以响应中断或其他事件。

  3. 不可中断睡眠状态(Uninterruptible Sleep):表示进程正在等待某些不可中断的事件或资源,通常是底层硬件资源或内核操作,例如等待磁盘I/O完成。这种状态下,进程是不可中断的,不能被正常终止。

  4. 僵尸状态(Zombie):表示进程已经终止,但其父进程还没有读取到其终止状态。这种状态通常是由于父进程没有正确地等待或处理其子进程的终止状态而导致的。

  5. 终止状态(Terminated):表示进程已经终止并且不再运行。

“挂起状态"通常在某些桌面操作系统中用于指代进程暂停执行,以便稍后继续执行。在Linux中,进程的暂停和继续通常通过SIGSTOPSIGCONT信号来实现,而不会有一个单独的状态来表示"挂起”。SIGSTOP信号用于暂停进程,SIGCONT信号用于继续进程的执行。

要将进程挂起,可以使用如下命令:

1
kill -STOP <进程ID>

要继续挂起的进程,可以使用如下命令:

1
kill -CONT <进程ID>

总之,虽然Linux中没有一个单独的"挂起状态",但可以通过SIGSTOPSIGCONT信号来实现进程的挂起和继续操作。进程状态在Linux中通常包括运行状态、可中断睡眠状态、不可中断睡眠状态等,以反映进程的当前情况。

何时会把进程数据交换到外存

Linux会在以下情况下将进程的数据交换到外部存储(通常是硬盘):

  1. 内存不足: 当系统内存不足以容纳当前正在运行的进程所需的数据时,Linux会将一些进程的数据交换到硬盘上的交换分区(swap partition)或交换文件(swap file)中,以释放物理内存供其他进程使用。这是Linux内存管理的一部分,用于避免内存耗尽的情况。

  2. 页面置换: Linux使用页面置换算法来决定哪些页面(内存中的数据块)将被交换到外部存储器中。常见的页面置换算法包括最近未使用(LRU)和最不常用(LFU)等。当需要更多内存来运行新进程或满足当前活动进程的内存需求时,系统会选择一些页面进行交换。

  3. 进程休眠: 当进程处于休眠状态(可中断或不可中断睡眠状态)时,它的数据可能会被交换出内存,以释放内存资源供其他活跃进程使用。一旦进程再次被唤醒,它的数据将从交换空间还原到内存中。

  4. 内存压力: 当系统面临内存压力(内存用尽或接近用尽)时,Linux会主动开始将部分进程的数据交换到外部存储,以维持系统的稳定性。这有助于防止系统因内存不足而崩溃或变得不响应。

  5. **交换分区的设置:**系统管理员可以配置交换分区的大小,以影响系统交换的频率和程度。合适的交换空间大小取决于系统的需求和硬件资源。通常,建议交换分区的大小至少等于系统内存的1.5倍,但这也取决于具体情况。

虽然交换可以避免内存耗尽问题,但频繁的交换操作会对系统性能产生负面影响,因为硬盘访问速度比内存慢得多。这可能导致进程响应时间变长,系统变得不够流畅。因此,为了避免过多的交换操作,建议在物理内存上提供足够的内存资源。