在计算机系统中,进程是执行程序的基本单位。它不仅仅是代码的集合,更是操作系统管理和分配资源的核心对象。每当我们运行一个应用程序,操作系统就会为其创建一个进程,使得程序能够在计算机中独立执行并进行资源管理。理解进程的概念对于深入学习操作系统和高效利用计算机资源至关重要。 接下来的篇章将带领大家深入探讨进程管理。
💬 欢迎讨论:如果你在学习过程中有任何问题或想法,欢迎在评论区留言,我们一起交流学习。你的支持是我继续创作的动力! 👍 点赞、收藏与分享:觉得这篇文章对你有帮助吗?别忘了点赞、收藏并分享给更多的小伙伴哦!你们的支持是我不断进步的动力! 🚀 分享给更多人:如果你觉得这篇文章对你有帮助,欢迎分享给更多对Linux OS感兴趣的朋友,让我们一起进步!
冯·诺依曼体系结构是现代计算机的基础设计模型,由约翰·冯·诺依曼于1945年提出。该结构的核心思想是将程序和数据存储在同一个内存中,计算机通过中央处理单元(CPU)按顺序执行指令。再此向大佬致敬。👏
存储器(内存)是CPU进行读、获取的必要手段,CPU执行程序,必须先加载,将代码及数据加载到内存,CPU执行代码,访问数据。
结构由五个基本组成部分构成:输入设备、输出设备、存储器、运算器和控制单元。
它的意义在于简化了计算机设计,提高了计算机的可编程性,使得不同的程序可以通过修改存储器中的指令和数据来实现多种功能。冯·诺依曼体系结构成为现代计算机系统的标准架构,推动了计算机技术的飞速发展。
进程是程序的一次执行实例,是操作系统进行资源分配和调度的基本单位。它拥有独立的内存空间、系统资源(如文件句柄、网络端口)和运行状态。
本人理解:进程 = 内核数据结构对象 + 代码和数据 (进程 = PCB(进程控制块) + 代码和数据)
进程的所有属性全保存在PCB中,Linux操作系统下的PCB为 task_struct, 该结构体为Linux内核中的一种数据结构,它会被加载到内存(RAM)中。
task_struct具有的部分信息,另一部分的信息将在后续的博客讲解
标⽰符: 描述本进程的唯⼀标⽰符,⽤来区别其他进程。 • 状态: 任务状态,退出代码,退出信号等。 • 优先级: 相对于其他进程的优先级。 • 程序计数器: 程序中即将被执⾏的下⼀条指令的地址。 • 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针 • 上下⽂数据: 进程执⾏时处理器的寄存器中的数据[休学例⼦,要加图CPU,寄存器]。 • I∕O状态信息: 包括显⽰的I/O请求,分配给进程的I∕O设备和被进程使⽤的⽂件列表。 • 记账信息: 可能包括处理器时间总和,使⽤的时钟数总和,时间限制,记账号等。
OS如何对各种进程进行的管理的?先描述,在组织。 所有运行的进程都以 task_struct 链表的形式存在内核中。
命令格式:
ls /proc/ [pid]
查看指定进程的进程信息。不加pid则查看所有进程的信息
命令格式:
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main()
{
printf("pid: %d\n", getpid());
printf("ppid: %d\n", getppid());
return 0;
}
示例代码:
#include<stdio.h>
#include<unistd.h>
#include <sys/types.h>
int gval = 100;
int main()
{
printf("父进程开始运行 ,pid: %d\n",getpid());
pid_t id = fork();
if(id < 0)
{
perror("id");
return 1;
}
else if(id == 0)
{
printf("我是一个子进程 !, 我的pid:%d, 我的父进程id:%d, gval: %d\n",getpid(),getppid(),gval);
sleep(5);
//child
while(1)
{
sleep(1);
printf("子进程修改变量 : %d -> %d", gval, gval+10);
gval+=10;
printf("我是一个子进程 !, 我的pid:%d, 我的父进程id:%d\n",getpid(),getppid());
}
}
else
{
//father
while(1)
{
sleep(1);
printf("我是一个父进程 !, 我的pid:%d, 我的父进程id:%d, gval: %d\n",getpid(),getppid(),gval);
}
}
printf("进程开始运行 ,pid: %d\n",getpid());
return 0;
}
子进程会共享父进程的代码和数据,如果父子任何一方对数据进行修改,OS系统会在底层第数据进行拷贝,让目标进程修改这个拷贝。
问题1:为什么fork给父子返回各自不同的返回值?
fork() 在父进程中返回 子进程的 PID(进程 ID)。这个 PID 是一个正整数,唯一标识子进程。父进程可以使用该 PID 来跟踪子进程,执行如等待子进程结束、获取子进程的状态等操作。
在子进程中,fork() 返回 0。子进程通过这个返回值可以判断自己是否是子进程,父进程通过返回值判断是否是父进程。
问题2:为什么一个函数会返回两次?
返回值是子进程的 PID(进程ID),是一个正整数。
父进程用这个 PID 来识别和管理子进程。父进程可以使用这个 PID 来执行如 wait()、waitpid() 等系统调用,等待子进程终止或获取子进程的退出状态。
返回值是 0。
子进程用返回值 0 来判断自己是子进程,以便执行不同于父进程的代码。子进程可能会通过这个返回值执行某些特定的初始化工作或处理。
结论:fork() 会返回两次是因为它在父进程和子进程中分别执行,父进程获得的是子进程的 PID,子进程获得的是 0。通过这个返回值,父进程和子进程可以执行不同的代码,保证了进程的正确管理和并行执行。
问题3:fork两个返回值各种给⽗⼦如何返回?
请看Linux内核关于进程状态的描述:
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 */ };
使用ps aux 或 ps ajx 命令可以查看进程的详细状态。 命令选项含义:
僵尸进程危害 进程的退出状态必须被维持下去,因为他要告诉关⼼它的进程(⽗进程),你交给我的任务,我办的怎么样了。可⽗进程如果⼀直不读取,那⼦进程就⼀直处于Z状态。 • 维护退出状态本⾝就是要⽤数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中, 换句话说,Z状态⼀直不退出,PCB⼀直都要维护?是的! • 那⼀个⽗进程创建了很多⼦进程,就是不回收,是不是就会造成内存资源的浪费。因为数据结构对象本⾝就要占⽤内存,想想C中定义⼀个结构体变量(对象),是要在内存的某个位置进⾏开辟空间! • 内存泄漏
孤儿进程
⽗进程如果提前退出,那么⼦进程后退出,进⼊Z之后,那该如何处理呢? • ⽗进程先退出,⼦进程就称之为“孤⼉进程” • 孤⼉进程被1号init进程领养,当然要有init进程回收
如何避免孤儿进程
父进程可以通过适当的进程管理确保它会在子进程结束时正确地回收子进程的资源,避免进程成为孤儿进程。常用的技术包括:
wait() 或 waitpid():父进程可以使用 wait() 或 waitpid() 来等待子进程结束,并获取子进程的退出状态,从而清理和回收子进程的资源。这样,即使父进程在结束之前退出,它也能确保子进程的资源得到回收。
如果父进程结束时没有来得及清理子进程的资源,可以通过捕获特定信号(如 SIGCHLD)来及时回收子进程的资源。通过设置 SIGCHLD 信号处理函数,父进程可以在子进程结束时自动清理。
父进程应该在子进程完成后再退出,例如通过使用 wait() 等系统调用等待子进程完成后再退出。如果父进程在子进程结束前退出,可能导致子进程变成孤儿进程。
定期检查子进程状态,使用 waitpid() 等方法主动回收子进程的资源。
本文主要介绍了操作系统中的进程管理,包括冯诺依曼体系结构、进程概念、进程控制块(PCB)、进程状态及如何查看和管理进程。 冯诺依曼体系结构是现代计算机设计的基础,它将程序和数据存储在同一内存中,提升了计算机的可编程性。进程则是程序的执行实例,操作系统通过进程控制块(PCB)来管理进程的资源和状态。进程通过 fork() 创建子进程,父子进程通过返回值区分身份。进程有不同的状态,如运行、睡眠、停止等,父进程可以通过 wait() 等系统调用回收子进程资源,避免孤儿进程的产生。如果父进程提前退出,子进程可能成为孤儿进程,操作系统会由 init 进程接管。为了避免孤儿进程,父进程应在子进程结束后再退出,并通过信号捕获机制及时清理资源。
路虽远,行则将至;事虽难,做则必成