探索进程世界:初学者必备的操作系统概念 前言
在计算机系统中,进程是程序执行的基本单元。它不仅是代码的集合,还是操作系统管理和分配资源的核心对象。每当我们启动一个应用程序,操作系统便会为其创建一个进程,使程序能够独立运行并进行资源管理。理解进程的概念对深入学习操作系统和高效利用计算机资源至关重要。接下来,我们将深入探讨进程管理的各个方面。
一. 冯·诺依曼体系结构1.1 背景与历史冯·诺依曼体系结构是现代计算机的基础设计模型,由约翰·冯·诺依曼在1945年提出。其核心思想是将程序和数据存储在同一个内存中,计算机通过中央处理单元(CPU)按顺序执行指令。在此向这位伟大的人物致敬。?
1.2 组成部分中央处理器(CPU)由运算器和控制器组成。存储器(内存)是CPU进行读取和获取数据的必要工具,CPU执行程序时,必须先将代码及数据加载到内存中,然后执行代码并访问数据。
1.3 意义其意义在于简化了计算机设计,提升了计算机的可编程性,使得通过修改存储器中的指令和数据可以实现多种功能。冯·诺依曼体系结构成为现代计算机系统的标准架构,推动了计算机技术的快速发展。
二. 进程2.1 进程概念进程是程序的一次执行实例,是操作系统进行资源分配和调度的基本单位。它拥有独立的内存空间、系统资源(如文件句柄、网络端口)以及运行状态。
2.1.1 PCB(进程控制块)task_struct包含部分信息,另一部分信息将在后续博客中详细讲解。
操作系统如何管理各种进程?首先是描述,然后是组织。所有运行的进程都以task_struct链表的形式存在于内核中。
2.2 查看进程2.2.1 使用系统文件查看命令格式:
功能:查看指定进程的进程信息。不指定pid则查看所有进程的信息。
2.2.2 使用top和ps等用户级工具获取命令格式:
ps ajx | grep myprocess | grep -v grep2.2.3 通过系统调用获取进程标识符代码语言:JavaScript代码运行次数:0运行复制“`javascript
include #include #include int main(){printf(“pid: %dn”, getpid());printf(“ppid: %dn”, getppid());return 0;}
getpid()系统调用,用于获取当前进程的PID(进程ID),在子进程中调用getpid()返回的是子进程自身的PID。getppid()系统调用,用于获取当前进程的父进程PID。2.3 创建子进程示例代码: <p>代码语言:javascript代码运行次数:0<svg fill="none" height="16" viewbox="0 0 16 16" width="16" xmlns="<a href="https://www.php.cn/link/c9041cfd2a40932691855abd98fd219a">http://www.w3.org/2000/svg"><path</a> d="M6.66666 10.9999L10.6667 7.99992L6.66666 4.99992V10.9999ZM7.99999 1.33325C4.31999 1.33325 1.33333 4.31992 1.33333 7.99992C1.33333 11.6799 4.31999 14.6666 7.99999 14.6666C11.68 14.6666 14.6667 11.6799 14.6667 7.99992C14.6667 4.31992 11.68 1.33325 7.99999 1.33325ZM7.99999 13.3333C5.05999 13.3333 2.66666 10.9399 2.66666 7.99992C2.66666 5.05992 5.05999 2.66659 7.99999 2.66659C10.94 2.66659 13.3333 5.05992 13.3333 7.99992C13.3333 10.9399 10.94 13.3333 7.99999 13.3333Z" fill="currentcolor"></path></svg>运行<svg fill="none" height="16" viewbox="0 0 16 16" width="16" xmlns="<a href="https://www.php.cn/link/c9041cfd2a40932691855abd98fd219a">http://www.w3.org/2000/svg"><path</a> clip-rule="evenodd" d="M4.5 15.5V3.5H14.5V15.5H4.5ZM12.5 5.5H6.5V13.5H12.5V5.5ZM9.5 2.5H3.5V12.5H1.5V0.5H11.5V2.5H9.5Z" fill="currentcolor" fill-rule="evenodd"></path></svg>复制```javascript</p><h1>include<stdio.h>#include<unistd.h>#include <sys>int gval = 100;int main(){printf("父进程开始运行 ,pid: %dn",getpid());pid_t id = fork();if(id %d", gval, gval+10);gval+=10;printf("我是一个子进程 !, 我的pid:%d, 我的父进程id:%dn",getpid(),getppid());}}else{//fatherwhile(1){sleep(1);printf("我是一个父进程 !, 我的pid:%d, 我的父进程id:%d, gval: %dn",getpid(),getppid(),gval);}}printf("进程开始运行 ,pid: %dn",getpid());return 0;}</sys></unistd.h></stdio.h>
子进程会共享父进程的代码和数据,如果父子任何一方对数据进行修改,操作系统会在底层对数据进行拷贝,让目标进程修改这个拷贝。
问题1:为什么fork给父子返回各自不同的返回值?
父进程中的返回值:fork() 在父进程中返回子进程的PID(进程ID)。这个PID是一个正整数,唯一标识子进程。父进程可以使用该PID来跟踪子进程,执行如等待子进程结束、获取子进程的状态等操作。
子进程中的返回值:在子进程中,fork()返回0。子进程通过这个返回值可以判断自己是否是子进程,父进程通过返回值判断是否是父进程。
问题2:为什么一个函数会返回两次?
父进程的情况:返回值是子进程的PID(进程ID),是一个正整数。
父进程用这个PID来识别和管理子进程。父进程可以使用这个PID来执行如wait()、waitpid()等系统调用,等待子进程终止或获取子进程的退出状态。
子进程的情况:返回值是0。
子进程用返回值0来判断自己是子进程,以便执行不同于父进程的代码。子进程可能会通过这个返回值执行某些特定的初始化工作或处理。
问题3:fork两个返回值如何分别给父子进程返回?
父进程:父进程使用返回子进程的PID,来管理或等待子进程。子进程:子进程返回0,子进程用这个值来判断自己是子进程,以执行不同于父进程的代码逻辑(比如初始化、执行任务等)。fork返回负值,表示fork调用失败(资源不足等),它会返回-1,并且没有子进程创建。操作系统会设置errno来表示具体的错误原因。2.4 进程状态请看linux内核关于进程状态的描述:
R(running): 运行状态,表示某进程正在运行或准备运行。S(sleep): 睡眠状态,进程在等待某些事件完成(也称为浅度睡眠,可中断睡眠)。D(disk sleep): 磁盘休眠状态,进程通常在等待I/O操作完成(也称为深度睡眠,不可中断睡眠)。T(stopped): 停止状态,可以通过发送信号让该进程继续进行。t(Tracing stop): 通常是在进行调试或进程跟踪时出现的状态。X(dead): 死亡状态,不会出现在进程列表中。Z(Zombie): 僵尸状态,子进程已结束,父进程为获取子进程的退出信息。2.4.1 查看进程状态使用ps aux 或 ps ajx 命令可以查看进程的详细状态。 命令选项含义:
a:显示一个终端所有的进程,包括其他用户的进程。x:显示没有控制终端的进程,例如后台运行的守护进程。j:显示进程归属的进程组ID、会话ID、父进程ID,以及与作业控制相关的信息。u:以用户为中心的格式显示进程信息,提供进程的详细信息,如用户、CPU和内存使用情况等。僵尸进程危害 进程的退出状态必须被维持下去,因为它要告诉关心它的进程(父进程),你交给我的任务,我办得怎么样了。如果父进程一直不读取,那子进程就一直处于Z状态。 • 维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中, 换句话说,Z状态一直不退出,PCB一直都要维护?是的! • 一个父进程创建了很多子进程,就是不回收,是不是就会造成内存资源的浪费。因为数据结构对象本身就要占用内存,想想C中定义一个结构体变量(对象),是要在内存的某个位置进行开辟空间! • 内存泄漏
如果父进程先退出,那么子进程后退出,进入Z状态后,该如何处理呢? • 父进程先退出,子进程就称之为“孤儿进程” • 孤儿进程被1号init进程领养,当然要有init进程回收
父进程等待子进程退出(避免子进程成为孤儿进程)父进程可以通过适当的进程管理确保它会在子进程结束时正确地回收子进程的资源,避免进程成为孤儿进程。常用的技术包括:
wait() 或 waitpid():父进程可以使用wait() 或 waitpid() 来等待子进程结束,并获取子进程的退出状态,从而清理和回收子进程的资源。这样,即使父进程在结束之前退出,它也能确保子进程的资源得到回收。
使用 signal() 或 sigaction() 捕获终止信号如果父进程结束时没有来得及清理子进程的资源,可以通过捕获特定信号(如SIGCHLD)来及时回收子进程的资源。通过设置SIGCHLD信号处理函数,父进程可以在子进程结束时自动清理。
避免父进程直接退出父进程应该在子进程完成后再退出,例如通过使用wait()等系统调用等待子进程完成后再退出。如果父进程在子进程结束前退出,可能导致子进程变成孤儿进程。
定期检查子进程状态,使用waitpid()等方法主动回收子进程的资源。
三. 最后