第41集:进程概念与状态

章节标题

进程概念与状态

核心知识点讲解

进程的基本概念

进程是操作系统中正在运行的程序的实例。它包含了程序代码、数据、堆栈、寄存器状态以及其他资源。在Linux系统中,每个进程都有自己独立的地址空间和系统资源。

进程与程序的区别

特性 程序 进程
定义 静态的可执行文件 动态的执行过程
存在形式 存储在磁盘上的文件 存在于内存中
生命周期 永久存在 有创建、执行、终止的生命周期
资源占用 不占用系统资源 占用CPU、内存等系统资源
独立性 独立的地址空间和资源

进程标识符

进程ID (PID)

每个进程都有一个唯一的进程ID (Process ID),用于在系统中标识和管理进程。PID是一个非负整数,通常从1开始递增。

# 查看当前进程的PID
echo $$

# 查看特定进程的PID
pgrep 进程名

父进程ID (PPID)

每个进程都有一个父进程,父进程ID (Parent Process ID) 标识了创建当前进程的进程。

# 查看当前进程的PPID
echo $PPID

进程组ID (PGID)

进程组是一个或多个进程的集合,它们共享同一个进程组ID (Process Group ID)。进程组通常与作业控制相关联。

# 查看当前进程的PGID
echo $PGID

会话ID (SID)

会话是一个或多个进程组的集合,它们共享同一个会话ID (Session ID)。会话通常与终端相关联。

# 查看当前进程的SID
ps -p $$ -o sid=

进程状态

Linux系统中的进程有以下几种基本状态:

运行状态 (R - Running/Runnable)

进程正在CPU上运行,或者在就绪队列中等待CPU时间片。

睡眠状态

可中断睡眠 (S - Interruptible Sleep)

进程等待某个事件的发生(如IO操作完成),可以被信号中断。

不可中断睡眠 (D - Uninterruptible Sleep)

进程等待某个事件的发生,不能被信号中断,通常与磁盘IO相关。

停止状态 (T - Stopped)

进程被暂停,通常是由于收到SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信号。

僵尸状态 (Z - Zombie)

进程已经终止,但父进程还没有回收其资源(如进程描述符)。

死亡状态 (X - Dead)

进程已经完全终止,即将被系统移除。

进程状态转换

进程状态之间的转换是一个动态的过程,主要包括以下几种转换:

  1. 运行 → 可中断睡眠:进程需要等待某个事件的发生,主动放弃CPU。
  2. 可中断睡眠 → 运行:进程等待的事件发生,被唤醒。
  3. 运行 → 不可中断睡眠:进程进行某些特殊的IO操作,需要等待完成。
  4. 不可中断睡眠 → 运行:特殊IO操作完成,进程被唤醒。
  5. 运行 → 停止:进程收到停止信号(如SIGSTOP)。
  6. 停止 → 运行:进程收到继续信号(如SIGCONT)。
  7. 运行 → 僵尸:进程执行完毕,终止运行。
  8. 僵尸 → 死亡:父进程回收子进程资源。

进程的生命周期

进程的生命周期包括以下几个阶段:

  1. 创建:通过fork()vfork()clone()系统调用创建新进程。
  2. 初始化:分配资源、设置环境、加载程序代码。
  3. 执行:在CPU上运行,可能在不同状态之间转换。
  4. 等待:等待子进程结束(父进程)。
  5. 终止:完成执行或被信号终止。
  6. 回收:父进程回收子进程资源。

进程的创建方式

fork()系统调用

fork()系统调用创建一个与父进程几乎完全相同的子进程。

#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid;
    
    pid = fork();
    
    if (pid < 0) {
        // fork失败
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("Child process: PID = %d, PPID = %d\n", getpid(), getppid());
    } else {
        // 父进程
        printf("Parent process: PID = %d, Child PID = %d\n", getpid(), pid);
        wait(NULL); // 等待子进程结束
    }
    
    return 0;
}

exec()系列系统调用

exec()系列系统调用用于在当前进程中加载并执行新的程序,替换当前进程的代码和数据。

#include <stdio.h>
#include <unistd.h>

int main() {
    printf("Before exec\n");
    
    // 执行ls命令
    execl("/bin/ls", "ls", "-la", NULL);
    
    // 如果exec成功,下面的代码不会执行
    printf("After exec (should not be printed)\n");
    
    return 0;
}

system()函数

system()函数在一个新的子进程中执行指定的命令。

#include <stdio.h>
#include <stdlib.h>

int main() {
    printf("Executing ls command\n");
    system("ls -la");
    printf("Command executed\n");
    return 0;
}

实用案例分析

案例1:查看进程状态

# 查看所有进程的状态
ps aux

# 查看特定进程的状态
ps -p PID -o pid,ppid,state,command

# 查看进程树(包含状态)
ps axjf

案例2:进程状态转换示例

# 1. 创建一个后台进程(运行状态)
sleep 60 &

# 2. 查看进程状态(应该是S状态,可中断睡眠)
ps -p $! -o pid,state,command

# 3. 向进程发送停止信号(变为T状态,停止)
kill -STOP $!
ps -p $! -o pid,state,command

# 4. 向进程发送继续信号(变为S状态,可中断睡眠)
kill -CONT $!
ps -p $! -o pid,state,command

# 5. 等待进程结束(变为Z状态,僵尸),然后被父进程回收
wait $!
ps -p $! -o pid,state,command  # 应该找不到进程

案例3:僵尸进程的产生与处理

# 1. 创建一个会产生僵尸进程的脚本
cat > zombie.sh << 'EOF'
#!/bin/bash

# 创建子进程
/bin/sleep 1 &

# 父进程睡眠10秒,期间子进程会结束并变为僵尸
sleep 10

# 父进程结束后,僵尸进程会被init进程回收
EOF

chmod +x zombie.sh

# 2. 运行脚本
./zombie.sh &

# 3. 在脚本运行期间查看僵尸进程
ps aux | grep Z

代码示例

示例1:进程创建与状态查看

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main() {
    pid_t pid;
    int status;
    
    printf("Parent process started: PID = %d\n", getpid());
    
    // 创建子进程
    pid = fork();
    
    if (pid < 0) {
        // fork失败
        fprintf(stderr, "Fork failed\n");
        return 1;
    } else if (pid == 0) {
        // 子进程
        printf("Child process created: PID = %d, PPID = %d\n", getpid(), getppid());
        
        // 子进程睡眠5秒
        printf("Child process sleeping for 5 seconds...\n");
        sleep(5);
        
        printf("Child process exiting\n");
        return 0;
    } else {
        // 父进程
        printf("Parent process: Child PID = %d\n", pid);
        
        // 父进程睡眠2秒,然后查看子进程状态
        sleep(2);
        printf("Parent process: Checking child process status...\n");
        
        // 查看子进程状态
        char command[100];
        sprintf(command, "ps -p %d -o pid,ppid,state,command", pid);
        system(command);
        
        // 等待子进程结束
        printf("Parent process: Waiting for child to exit...\n");
        wait(&status);
        
        printf("Parent process: Child exited with status %d\n", WEXITSTATUS(status));
        printf("Parent process exiting\n");
    }
    
    return 0;
}

示例2:进程状态监控脚本

#!/bin/bash

# 进程状态监控脚本

# 显示帮助信息
show_help() {
    echo "进程状态监控脚本"
    echo "用法: $0 [选项]"
    echo "选项:"
    echo "  -p, --pid <pid>         监控指定PID的进程"
    echo "  -n, --name <name>       监控指定名称的进程"
    echo "  -i, --interval <sec>    监控间隔(秒),默认1秒"
    echo "  -c, --count <num>       监控次数,默认无限"
    echo "  -h, --help              显示帮助信息"
}

# 解析命令行参数
PID=""
NAME=""
INTERVAL=1
COUNT=-1

while [[ $# -gt 0 ]]; do
    case $1 in
        -p|--pid)
            PID="$2"
            shift 2
            ;;
        -n|--name)
            NAME="$2"
            shift 2
            ;;
        -i|--interval)
            INTERVAL="$2"
            shift 2
            ;;
        -c|--count)
            COUNT="$2"
            shift 2
            ;;
        -h|--help)
            show_help
            exit 0
            ;;
        *)
            echo "未知选项: $1"
            show_help
            exit 1
            ;;
    esac
done

# 检查参数
if [[ -z "$PID" && -z "$NAME" ]]; then
    echo "错误: 必须指定PID或进程名称"
    show_help
    exit 1
fi

# 如果指定了进程名称,获取其PID
if [[ -n "$NAME" ]]; then
    PID=$(pgrep -f "$NAME" | head -1)
    if [[ -z "$PID" ]]; then
        echo "错误: 找不到名称为 '$NAME' 的进程"
        exit 1
    fi
    echo "找到进程 '$NAME',PID: $PID"
fi

# 监控进程状态
iteration=0
while true; do
    # 检查进程是否存在
    if ! ps -p $PID > /dev/null 2>&1; then
        echo "进程 $PID 不存在或已结束"
        break
    fi
    
    # 显示进程状态
    echo "[$(date '+%Y-%m-%d %H:%M:%S')] 进程 $PID 状态:"
    ps -p $PID -o pid,ppid,pgid,sid,state,command
    echo ""
    
    # 增加迭代计数
    iteration=$((iteration + 1))
    
    # 检查是否达到指定的监控次数
    if [[ $COUNT -gt 0 && $iteration -ge $COUNT ]]; then
        break
    fi
    
    # 等待指定的间隔
    sleep $INTERVAL
done

echo "监控结束"

示例3:进程生命周期演示

#!/bin/bash

# 进程生命周期演示脚本

echo "=== 进程生命周期演示 ==="
echo "当前脚本PID: $$"
echo "父进程PID: $PPID"
echo ""

# 阶段1: 创建子进程
echo "阶段1: 创建子进程"
child_pid=$(bash -c 'echo $$; sleep 5') &
sleep 1  # 等待子进程启动
echo "创建的子进程PID: $!"
echo ""

# 阶段2: 查看进程状态
echo "阶段2: 查看进程状态"
ps -p $$ -o pid,state,command
ps -p $! -o pid,state,command
echo ""

# 阶段3: 向子进程发送信号
echo "阶段3: 向子进程发送信号"
echo "向子进程发送停止信号..."
kill -STOP $!
sleep 1
ps -p $! -o pid,state,command

echo "向子进程发送继续信号..."
kill -CONT $!
sleep 1
ps -p $! -o pid,state,command
echo ""

# 阶段4: 等待子进程结束
echo "阶段4: 等待子进程结束"
echo "等待子进程结束..."
wait $!
echo "子进程已结束"
echo ""

# 阶段5: 查看子进程状态(应该不存在)
echo "阶段5: 查看子进程状态"
if ps -p $! > /dev/null 2>&1; then
    ps -p $! -o pid,state,command
else
    echo "子进程已不存在,资源已被回收"
fi
echo ""

echo "=== 演示结束 ==="

总结

本集介绍了 Linux 中的进程概念与状态,包括:

  1. 进程的基本概念和与程序的区别
  2. 进程标识符,包括PID、PPID、PGID和SID
  3. 进程的各种状态,如运行(R)、可中断睡眠(S)、不可中断睡眠(D)、停止(T)和僵尸(Z)状态
  4. 进程状态之间的转换关系
  5. 进程的生命周期,包括创建、初始化、执行、等待、终止和回收
  6. 进程的创建方式,如fork()、exec()系列系统调用和system()函数
  7. 实用案例分析,包括查看进程状态、进程状态转换示例和僵尸进程的产生与处理
  8. 代码示例,展示了进程创建与状态查看、进程状态监控脚本和进程生命周期演示

通过理解进程的概念、状态和生命周期,用户可以更好地管理和监控系统中的进程,提高系统的稳定性和性能。在后续的章节中,我们将学习如何查看、监控和控制进程,以及如何管理进程的优先级和资源限制。

« 上一篇 Shell 历史记录 下一篇 » 进程查看命令