【Linux系统编程】—— 进程与进程管理在Linux中的基本概念
文章目录
- 进程基本概念与操作
- 进程的创建与管理
- 进程状态与调度
- 进程的状态
- 查看进程状态
- 僵尸进程(Zombie)
- 僵尸进程的危害
- 如何避免僵尸进程:
- 孤儿进程
进程基本概念与操作
进程定义:
- 课本概念:程序的一个执行实例,是正在执行的程序。
- 内核观点:在操作系统中,进程被视为负责分配系统资源(如CPU时间、内存等)的实体。
进程控制块 (PCB):
- PCB (Process Control Block):进程的信息存储在一个叫做进程控制块的结构中,它可以理解为进程的属性集合。Linux中使用 task_struct 结构来描述进程。
- task_struct:是Linux内核的数据结构,存储了进程的详细信息,并且驻留在内存中。
task_struct 结构的主要内容:
- 标识符:描述进程的唯一标识符,用于区分不同的进程。
- 状态:描述进程的状态,如进程是否正在运行、是否退出等。
- 优先级:描述进程相对于其他进程的优先级。
- 程序计数器:即将执行的下一条指令的地址。
- 内存指针:包括程序代码、进程相关数据的指针,以及共享内存块的指针。
- 上下文数据:包括进程执行时处理器中的寄存器数据。
- I/O状态信息:包括I/O请求和分配给进程的I/O设备、文件列表。
- 记账信息:包括处理器时间、时钟数、时间限制等信息。
组织进程
可以在内核源代码⾥找到它。所有运⾏在系统⾥的进程都以task_struct链表的形式存在内核⾥。
进程的创建与管理
查看进程信息
- 进程的详细信息可以通过 /proc 文件系统查看。例如,要查看PID为1的进程信息,可以访问 /proc/1 目录。
- 常见的进程查看命令包括 ps 和 top。
获取进程标识符
- 使用 getpid() 系统调用可以获取当前进程的PID。
- 使用 getppid() 系统调用可以获取当前进程的父进程PID。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()6
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
创建进程:fork() 系统调用
使用 fork() 系统调用可以创建子进程。fork() 会返回两个值:
- 父进程返回子进程的PID。
- 子进程返回0。
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
int ret = fork();
if(ret < 0)
{
perror("fork");
return 1;
}
else if(ret == 0)
{
//child
printf("I am child : %d!, ret: %d\n", getpid(), ret);
}else
{
//father
printf("I am father : %d!, ret: %d\n", getpid(), ret);
}
sleep(1);
return 0;
}
fork为什么会有两个返回值?
fork() 系统调用会有两个返回值,这是因为它用于创建一个新的进程。具体来说:
- 父进程(Parent Process):
fork() 在父进程中返回子进程的 PID(进程标识符),也就是新创建的子进程的进程ID。这让父进程知道它创建了一个新的进程,并可以跟踪和管理这个子进程。 - 子进程(Child Process):
fork() 在子进程中返回 0。子进程通过返回值得知它自己是子进程,并且执行子进程的逻辑。
原因:fork() 的这种行为是为了区分父子进程,便于父进程与子进程执行不同的代码。通过返回值,父进程可以判断自己是父进程,执行一部分逻辑;而子进程可以判断自己是子进程,执行不同的代码逻辑。
两个返回值各自给⽗⼦如何返回?
父进程返回值:
- 在父进程中,fork() 返回的是 子进程的 PID(进程标识符),即刚刚创建的子进程的进程ID。
- 这允许父进程可以知道它创建了哪个子进程,并且可以与子进程进行进一步的交互(例如等待子进程结束、获取子进程状态等)。
子进程返回值:
- 在子进程中,fork() 返回的是 0。这个返回值告诉子进程它是由父进程创建的,且在其逻辑中可以知道自己是子进程,因此通常会执行与父进程不同的代码。
原因:fork() 函数的目的是创建一个新的进程(子进程),并让父进程和子进程能够独立地运行。通过将 fork() 的返回值区分为父进程返回子进程的 PID 和子进程返回0,可以让父进程与子进程在代码中做不同的事情。例如:
- 父进程可以继续执行它的任务或等待子进程结束。
- 子进程可以执行自己特定的任务。
这种设计简化了父子进程之间的分流控制,使得它们能够根据 fork() 返回的值决定各自的行为。
进程状态与调度
进程的状态
Linux内核中,进程的状态在 task_struct 结构中定义,包括以下几种常见的状态:
R (Running):运行状态,表示进程正在运行或准备运行。
S (Sleeping):睡眠状态,进程等待某些事件的完成(可中断睡眠)。
D (Disk Sleep):磁盘休眠状态,进程通常在等待I/O操作完成(不可中断睡眠)。
T (Stopped):停止状态,进程被暂停。
t (Tracing stop):是一个特殊的状态,通常用于调试场景,表示进程由于被调试工具跟踪而停止。
X (Dead):死亡状态,进程已终止。
Z (Zombie):僵尸进程,子进程已结束,但父进程尚未读取其退出状态。
下面是关于Linux内核的源码
查看进程状态
使用 ps aux 或 ps axj 命令可以查看进程的详细状态。命令选项的含义如下:
a:显示所有用户的进程。
x:显示没有控制终端的进程。
u:以用户为中心格式显示进程信息。
j:显示进程的进程组ID、会话ID、父进程ID等。
僵尸进程(Zombie)
僵尸进程(Zombie Process)是指已经结束执行的子进程,但其父进程尚未通过 wait() 或类似的系统调用来读取其退出状态。这些进程的状态是 Z (Zombie),它们在进程表中仍然占据一个位置,但不再占用CPU资源。
来⼀个创建维持30秒的僵死进程例⼦:
#include <stdio.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id > 0)
{
//parent
printf("parent[%d] is sleeping...\n", getpid());
sleep(30);
}
else
{
printf("child[%d] is begin Z...\n", getpid());
sleep(5);
exit(EXIT_SUCCESS);
}
return 0;
}
编译并在另⼀个终端下启动监控
开始测试
看到结果
僵尸进程的危害
- 占用系统资源(进程表条目)
每个进程在内核中都有一个对应的 task_struct 结构,它包含了进程的状态、优先级等信息。僵尸进程仍然占据这部分结构。
由于操作系统的进程表的大小是有限的,如果系统中积累了大量僵尸进程,可能会耗尽进程表条目,导致系统无法创建新进程,造成资源浪费。 - 内存泄漏
尽管僵尸进程不再执行,但其在内核中占用的内存(如 task_struct)并没有被回收,直到其父进程通过 wait() 系统调用清理它。
如果父进程一直没有回收子进程的退出状态,僵尸进程就会一直存在,导致系统内存的浪费。 - 导致系统性能下降
尽管僵尸进程本身不再占用CPU时间,但它们依然会占用系统的一些内核资源,如内存和进程表项。大量的僵尸进程会增加内核管理这些资源的负担,进而可能影响系统的整体性能。 - 影响父进程的管理
当父进程没有及时回收子进程的状态时,父进程可能无法正常获取子进程的退出码,影响父进程后续的执行逻辑。
此外,如果父进程创建了大量的子进程,而没有及时清理,它可能会变得越来越难以管理这些子进程,导致资源泄漏和其他管理问题。 - 内存消耗和系统资源耗尽
进程表条目和 task_struct 结构体是有限的,系统中的每个僵尸进程都会占用这些有限的资源。当僵尸进程过多时,系统可能无法分配新的进程,导致无法创建新的进程,从而影响整个系统的运行。 - 对系统维护带来困扰
在一些长时间运行的系统中,如果出现大量僵尸进程,它们可能会掩盖系统本应正常运行的进程,增加系统维护的难度。
系统管理员可能需要手动清理僵尸进程,这增加了管理的复杂性和额外的工作量。
如何避免僵尸进程:
父进程回收子进程状态:父进程在子进程退出后,应该及时调用 wait() 或 waitpid() 系统调用来回收子进程的退出状态,避免子进程成为僵尸进程。
使用 signal 处理:父进程可以通过设置信号处理函数来在子进程终止时自动回收其状态。
孤儿进程的处理:如果父进程退出,子进程会成为孤儿进程,并由 init 进程(PID为1)收养并清理其状态,避免僵尸进程的积累。
孤儿进程
当父进程结束时,子进程会变为孤儿进程,且由 init 进程(PID为1)领养并回收。
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
int main()
{
pid_t id = fork();
if(id < 0){
perror("fork");
return 1;
}
else if(id == 0)
{
//child
printf("I am child, pid : %d\n", getpid());
sleep(10);
}
else
{
//parent
printf("I am parent, pid: %d\n", getpid());
sleep(3);
exit(0);
}
return 0;
}