当前位置: 首页 > news >正文

做效果图的外包网站徐州低价seo

做效果图的外包网站,徐州低价seo,网站改版的宣传词,地推的方法和技巧前言#xff1a;本节内容包含进程管理操作的各种基础概念精讲#xff0c;同时部分板块包含Linux操作系统与一般操作系统的概念对比。不仅包含“书面概念”#xff0c;还包含详细操作以及通俗讲解。 目录 一、进程概念引入 二、进程的描述与组织#xff1a;进程控制块本节内容包含进程管理操作的各种基础概念精讲同时部分板块包含Linux操作系统与一般操作系统的概念对比。不仅包含“书面概念”还包含详细操作以及通俗讲解。 目录 一、进程概念引入 二、进程的描述与组织进程控制块PCB与进程标识符PID) 三、fork函数创建子进程 四、进程的地址空间虚拟内存与写时复制 五、进程查看 六、进程状态 七、父子进程、孤儿进程、僵尸进程 孤儿进程 僵尸进程 八、进程的优先级 九、环境变量 【选学】 十、Linux操作系统进程的调度 1、活动队列详解 2、过期队列详解 3、O(1)调度算法  一、进程概念引入 在Linux系统中当触发一个事件时系统都会将它定义为一个进程并且给予这个进程一个ID称为PID同时根据触发这个进程的用户与相关属性关系给予这个PID一组有效的权限设置。自此这个PID能够在系统上执行的操作就与这个PID的权限有关。 可见一个进程的产生离不开触发事件。那我们如何才能在系统中触发一个事件呢 其实很简单执行一个程序或者命令就可以触发一个事件进而产生一个进程。我们所说的“程序与命令”在操作系统中本质上就是一个二进制可执行文件。我们知道系统只认识二进制文件所以我们要让系统工作的时候当然是启动一个二进制可执行文件这个二进制文件就是程序它们通常放置在存储媒介中如硬盘、光盘、软盘、磁带等以物理文件的形式存在。 我们再来了解下什么是PID PID是进程标识符Process IDentifier的缩写是Linux和其他类Unix操作系统中用来唯一标识一个正在运行的进程的数字。每个进程都有一个唯一的PID该PID是由操作系统分配的并且在系统范围内保持唯一性。 PID的主要作用包括 唯一标识进程PID能够在系统范围内唯一标识一个进程即使在多个用户空间或不同的终端中。 进程管理通过PID系统管理员可以轻松地查找、监视、控制和终止特定的进程。 进程通信某些进程间通信IPC机制可能需要使用PID来标识目标进程以便发送消息或执行其他操作。 错误排查在排查系统问题时PID可以帮助定位特定进程可能引发的错误或异常情况。 总之PID是Linux和类Unix系统中用于唯一标识正在运行的进程的数字标识符是进程管理和通信的重要组成部分。 在本节内容中我们仅需了解前两个概念即可。我们知道PID是由操作系统在创建一个进程时为该进程分配的一个具有唯一性的进程标识符通常为一个整数。通过PID我们不仅可以便于对进程进行管理同时操作系统还可以通过这个PID来判断该进程是否具有执行权限。 有了上面基础概念的铺垫后我们正式来进入进程概念的理解 程序一般是放在物理磁盘中通过用户的执行来触发。触发后程序会加载到内存中成为一个个体这就是进程。通俗来讲进程可以为被理解为程序的一个执行实例 / 正在执行的程序。 为了让操作系统管理这个进程操作系统(内核)会将此程序的执行者的权限与属性、程序的代码和所需的属性都会被加载到内存中同时为程序分配可能需要使用系统资源如内存、CPU时间、文件描述符等并在执行过程中进行管理和调度。操作系统还可能需要对程序进行一些初始化操作如设置程序的运行环境变量、加载动态链接库等。 进程概念详解进程角度与内核角度  进程是计算机中正在执行的程序的实例。从进程本身和内核的角度来看我们可以分别讨论进程的概念 从进程本身的角度 程序执行的实例进程是程序在执行过程中的一个实例。当程序被加载到内存中并开始执行时它就成为一个进程。 拥有独立的地址空间每个进程都有自己独立的地址空间包括代码段、数据段、堆和栈等。这意味着每个进程可以在自己的地址空间中执行并且不会直接访问其他进程的内存空间。 具有状态进程可以处于不同的状态例如运行、就绪、等待等。这些状态反映了进程当前的活动和可用性。 拥有标识符每个进程都有一个唯一的标识符称为进程标识符PID。PID用于在系统中唯一标识和管理进程。 可以创建其他进程进程可以创建其他进程从而形成父子进程关系。子进程会继承父进程的一些属性并且可以拥有自己的独立执行流。 从内核的角度 任务调度单位内核将进程视为调度的基本单位。内核通过调度算法决定哪些进程可以获得CPU的使用权并且负责在不同进程之间进行上下文切换。 资源分配内核负责分配系统资源给进程包括CPU时间、内存、文件描述符等。它会根据进程的需求和系统的状况来进行资源管理以保证系统的正常运行和性能优化。 提供系统调用内核提供了一系列系统调用用于进程的创建、销毁、同步和通信等操作。这些系统调用允许进程与内核进行交互并且访问系统提供的服务和功能。 提供进程间通信机制内核提供了多种进程间通信的机制如管道、消息队列、共享内存等以便进程之间进行数据交换和同步操作。 提供进程管理功能内核负责管理系统中所有进程的状态和资源使用情况包括进程的创建、销毁、状态转换等。 总之从进程本身和内核的角度来看进程是程序执行的实例同时也是内核调度和管理的基本单位它具有独立的地址空间、状态、标识符等特征并且可以通过内核提供的接口与系统进行交互和管理。 二、进程的描述与组织进程控制块PCB与进程标识符PID) 一般操作系统中进程的描述与组织方法 进程Process是计算机中的一个基本概念用于描述正在执行的程序实例。进程是操作系统分配资源和执行任务的基本单位。以下是关于进程如何组织与描述的一些基本概念 进程控制块Process Control BlockPCBPCB是操作系统内核中用于描述和管理进程的数据结构。每个进程都有一个唯一的PCB其中包含了进程的状态信息、程序计数器记录下一条要执行的指令位置、寄存器的内容、内存分配情况、进程优先级等等。 进程状态进程可以处于不同的状态通常包括就绪态Ready、运行态Running、阻塞态Blocked等。PCB 中包含了进程当前的状态信息。 进程队列操作系统会维护多个进程队列如就绪队列、阻塞队列等。就绪队列存放可以运行但还未获得CPU时间的进程而阻塞队列存放由于等待某些事件发生而暂时无法运行的进程。 进程调度进程调度是操作系统决定哪个进程获得CPU时间的过程。调度算法可以根据不同的策略进行选择如先来先服务First Come First ServedFCFS、短作业优先Shortest Job FirstSJF、轮转调度Round Robin等。 进程间通信IPC进程之间可能需要进行通信和数据共享。操作系统提供了多种IPC机制如管道、消息队列、共享内存等用于实现进程间的信息交换。 进程的创建和销毁进程可以通过调用系统调用如fork()或CreateProcess()来创建新的进程。进程结束时它会释放占用的资源并从系统中删除。操作系统负责管理进程的创建和销毁过程。 通过上述组织与描述操作系统可以有效地管理系统中的多个并发进程实现资源的合理分配和任务的高效执行。 Linux操作系统中进程组织与描述的方法 在Linux中进程的描述与组织方式与一般的操作系统相似但也有一些特定的特点和工具 进程控制块Process Control BlockPCB在Linux中PCB被称为任务结构Task Structure是用来描述和管理进程的数据结构。Linux中的任务结构包含了进程的状态信息、程序计数器、寄存器的内容、进程的内存分配情况、进程优先级等等与其他操作系统的PCB类似。 进程状态Linux中的进程状态通常包括运行态、就绪态、睡眠态等。可以使用命令 ps 或者 top 来查看系统中运行的进程及其状态。 进程队列Linux内核维护了多个进程队列如就绪队列、等待队列等。就绪队列存放等待运行的进程而等待队列存放因为等待某些事件而被阻塞的进程。 进程调度Linux内核采用不同的调度算法来决定哪个进程获得CPU时间。常见的调度算法包括CFSCompletely Fair Scheduler和O(1)调度器等。 进程间通信IPCLinux提供了多种IPC机制如管道、消息队列、信号量、共享内存等用于实现进程间的通信和数据共享。 进程的创建和销毁在Linux中可以使用fork()系统调用来创建新的进程也可以使用exec()系列函数来加载新的程序替换当前进程的内存映像。进程的销毁通常是通过exit()系统调用来实现。 总的来说在Linux中进程的描述与组织遵循着与其他操作系统类似的原则但也有一些特定的实现方式和工具以适应Linux系统的特点和需求。 通过上述概念我们了解到进程信息被放在一个叫做进程控制块的数据结构中可以理解为进程属性的集合而在Linux中描述进程的结构体叫做task_struct它是PCB的一种。 下面我们来详细介绍一下Linux系统中是如何管理进程的。 这时候就要请出我们的六字真言先描述再组织操作系统管理进程也是一样的操作系统作为管理者是不需要直接和被管理者进程直接进行沟通的当一个进程出现时操作系统就立马对其进行描述之后对该进程的管理实际上就是对其描述信息的管理。 我们在学习数据结构时对于一个个体我们通常是使用一个struct结构体来对一个对象的属性进行描述进而将所有的个体组织起来同一进行管理。例如我们写学生管理系统时我们首先要对一个学生进行描述如学号、班级、年龄、成绩等信息构成的结构体。接着我们将一个个描述学生的结构体通过单链表/双链表的形式组织起来由此我们通过对这一数据结构的管理就可以代替我们对学生的管理。如我们需要管理一名学生时我们可以直接在链表上进行增删查改操作。大大降低了我们管理的复杂度和效率实现高效的管理操作。 类似的Linux系统对待进程的管理亦是如此 在Linux操作系统中在Linux中描述进程的结构体叫做task_structtask_struct是Linux内核的一种数据结构它会被装载到内存中并包含进程的信息。 task_ struct内容分类 标示符: 描述本进程的唯一标示符用来区别其他进程。 状态: 任务状态退出代码退出信号等。 优先级: 相对于其他进程的优先级。 程序计数器: 程序中即将被执行的下一条指令的地址。 内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。 IO状态信息: 包括显示的I/O请求,分配给进程的IO设备和被进程使用的文件列表。 记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。 其他信息 进程控制块在操作系统中的组织形式 所有运行在系统里的进程都以task_struct链表的形式存在内核里 我们了解到进程在创建时操作系统会为其分配唯一的进程标识符PID那么我们如何得到进程的标识符呢接下来让我们认识两个系统调用函数getpid() 和 getppid() getpid()是一个系统调用函数用于获取当前进程的标识符PIDProcess ID它的基本用法如下 #include unistd.hpid_t getpid(void);getpid()函数不需要任何参数。调用getpid()函数时操作系统会返回当前进程的PID。返回值类型为pid_t即一个整数类型用于表示进程的PID。 getppid()是一个系统调用函数用于获取当前进程的父进程的标识符PPIDParent Process ID它的基本用法如下 #include unistd.hpid_t getppid(void);getppid()函数不需要任何参数。调用getppid()函数时操作系统会返回当前进程的父进程的PID。返回值类型为pid_t即一个整数类型用于表示进程的PID。 下面是它们的基本用法示例 #include stdio.h #include unistd.hint main() {pid_t pid, ppid;// 获取当前进程的PIDpid getpid();printf(当前进程的PID为%d\n, pid);// 获取当前进程的父进程的PIDppid getppid();printf(当前进程的父进程的PID为%d\n, ppid);return 0; }在上面的示例中getpid()和getppid()函数分别用于获取当前进程的PID和其父进程的PID并将它们打印输出。 三、fork函数创建子进程 我们在命令行中输入man fork 即可得到fork函数的函数接口的函数的使用方法。 我们可以看到fork函数位于man手册的第2部分由于第2部分通常是用于描述系统调用和库函数所以我们可以了解到fork函数实际是一个系统调用函数。 接下来我们先了解一下什么是系统调用函数 系统调用函数是操作系统提供给用户程序或应用程序的一组接口通过这些接口用户程序可以请求操作系统执行特定的操作如文件操作、进程管理、网络通信等。系统调用函数允许用户程序访问操作系统的底层功能以完成对硬件资源的管理和控制。 系统调用函数与一般的函数调用有所不同。一般的函数调用是在用户程序内部进行的而系统调用函数是用户程序与操作系统之间的通信方式。当用户程序调用系统调用函数时会触发一个特殊的处理机制将控制权转移给操作系统内核执行相应的操作然后将结果返回给用户程序。 系统调用函数通常是由操作系统提供的库函数封装的以便用户程序更方便地调用。这些函数通常包含在标准库中例如在 C 语言中可以通过 unistd.h 头文件来访问系统调用函数。 常见的系统调用函数包括 fork()、exec()、open()、read()、write() 等它们提供了对文件系统、进程管理、内存管理、网络通信等底层功能的访问。系统调用函数是编写操作系统相关程序和系统编程的重要工具也是操作系统与用户程序之间的桥梁。 如果不理解我们先记住加粗蓝字描述的部分。  在操作系统中用户程序处于用户态用户层而操作系统内核处于内核态核心层。用户程序不能直接访问系统的硬件资源或执行特权指令而是通过系统调用接口来请求操作系统执行特定的任务包括对硬件资源的管理和控制。 通过系统调用接口用户程序可以向操作系统发出请求比如读写文件、创建进程、进行网络通信等。操作系统会根据请求执行相应的操作然后将结果返回给用户程序。这样的设计有效地保护了系统的稳定性和安全性同时也提供了一种方便而有效的方式让用户程序与系统进行交互。 fork函数详解 fork函数从已存在进程中创建一个新进程。新进程为子进程而原进程为父进程。 接口 #include unistd.h pid_t fork(void);作用 fork() 函数用于创建一个新的进程该进程是调用进程的副本。子进程与父进程几乎完全相同包括代码段、数据段、堆栈等。在子进程中fork() 返回 0而在父进程中它返回新创建子进程的 PID进程标识符。 返回值 在父进程中fork() 返回新创建子进程的 PID。在子进程中fork() 返回 0。如果 fork() 失败返回值为 -1表示创建子进程失败。 进程的执行 子进程从 fork() 返回的地方开始执行而父进程则继续执行它的代码。这意味着在 fork() 调用之后父进程和子进程会并行执行。 错误处理 如果 fork() 失败返回值为 -1。失败的原因可能是系统资源不足或者进程数达到了限制。 注意事项 在 fork() 后父子进程共享文件描述符这意味着在一个进程中打开的文件在另一个进程中也是打开的。如果不适当地处理可能会导致意想不到的结果。子进程通常需要调用 exec 系列函数来加载新的程序以便替换掉自己的内存映像。否则子进程将继承父进程的内存映像可能会导致一些意外的行为。 接下来我们来看一段程序 #include stdio.h #include unistd.h #include sys/types.h #include stdlib.h int main() {printf(父进程开始运行\n);pid_t id fork();if(id 0){printf(我是子进程\n);sleep(1);}else if(id 0){printf(我是父进程\n);sleep(1);}else {perror(进程创建失败\n);}return 0; } 这段代码的结果是这样的 接下来我们来详细聊一下fork函数。相信大家都有这样的疑问 1、为什么fork()函数可以有两个返回值也就是函数会返回两次这和我们平时见到的函数不同。 当进程调用 fork() 函数时控制会转移到操作系统内核中执行 fork() 函数的代码。在内核中fork() 函数主要完成以下操作 创建新的进程控制块Process Control BlockPCB内核会为新的子进程分配一个唯一的进程标识符PID并在内存中为其创建一个新的进程控制块PCB。这个 PCB 将包含子进程的运行状态、程序计数器、堆栈指针、文件描述符等信息。 复制父进程的地址空间以创建自己的地址空间在大多数情况下fork() 函数会创建子进程的完整副本包括代码段、数据段、堆栈等。这意味着子进程将会获得与父进程几乎完全相同的内存映像。这一步通常通过 Copy-On-Write写时复制技术来实现即在子进程需要修改内存时才会进行实际的复制操作。 将子进程的状态设置为就绪一旦子进程的地址空间准备好内核将其状态设置为就绪态以便在合适的时机可以被调度执行。 返回不同的值在内核中fork() 函数会返回两次一次是在父进程的上下文中返回子进程的 PID另一次是在子进程的上下文中返回 0。这样父进程和子进程可以根据返回值来执行不同的代码路径。 2、为什么父进程接收子进程的PID而子进程返回0或-1 父进程接收子进程的PID父进程在调用fork()函数后会得到子进程的PID作为返回值。通过这个PID父进程可以对子进程进行跟踪、管理和通信。例如父进程可能会使用子进程的PID来等待子进程的结束状态通过waitpid()函数或者向子进程发送信号通过kill()函数等。 子进程返回0或-1子进程在fork()函数返回时需要确定自己是父进程还是子进程。因此子进程通常会检查fork()的返回值来确定自己的身份。具体来说 如果fork()返回0则表示当前进程是子进程。子进程可以通过这个返回值来区别自己和父进程并且通常会在这个基础上执行特定的任务或代码段。如果fork()返回-1则表示进程创建失败。通常这种情况会发生在系统资源不足或者其他错误发生时。子进程在这种情况下会立即退出或者采取相应的错误处理措施。 由于fork()函数具有以上两个特性我们可以采取 if---else 语句对父子进程进行分流这样就可以让父子进程去做不同的事情这也是我们后续进行进程替换的基础。 3、父子进程哪个先运行  在一般情况下无法确定父进程和子进程哪一个先运行。这取决于操作系统的调度策略以及各种竞争条件的发生情况。 通过上面的知识我们了解到在fork函数执行完之后父子进程共享fork函数之后的代码。 在fork函数内部在执行 return pid 之前子进程就已经创建完成所以 return pid 实际也是父子进程的共享代码部分所以父进程会执行一次返回子进程的pid而子进程也会执行一次 return pid 返回进程是否创建完成的信息。 好的在我们学完以上知识后会不会有这样的疑问既然子进程延用父进程代码、数据等是父进程的一个副本那在内存中子进程的内存是不是也是完全拷贝父进程的内存进而形成自己的内存空间呢我们来看一下下图判断一下下图的正确性 这时候可能有同学会发出这样的疑问难道子进程不是直接按照父进程的规格在内存中直接进行一次拷贝吗为什么上图是错误的我们需要了解到子进程虽然创建的与父进程完全一样的副本但这只是在操作系统层面的但其本质还是与父进程其实是同处于一块物理内存当中。 上面的解释仍比较模糊想真正理解这时候就需要引出我们下一个重点虚拟内存、写时复制和进程的地址空间。 四、进程的地址空间虚拟内存与写时复制 我们先来看一段代码 #include stdio.h #include unistd.h #include sys/types.h #include stdlib.hint g_value 2024; int main() {printf(父进程开始运行\n);pid_t id fork();if(id 0){g_value 2025;printf(我是子进程,g_value:%d, %p\n, g_value, g_value);sleep(1);}else if(id 0){sleep(3);//让父进程延迟3秒运行即让子进程先运行printf(我是父进程, g_value:%d, %p\n,g_value, g_value);sleep(1);}else {perror(进程创建失败\n);}return 0; } 相信大部分同学在看到程序运行后的结果会比较诧异。按理说子进程共享父进程的代码、数据和饥内存那么在子进程修改全局变量g_value后父进程打印出来的结果不应该和子进程的结果一样吗为什么同一个内存地址可以储存不同的变量这明显”不符合逻辑“呀。 变量内容不一样所以父子进程输出的变量绝对不是同一个变量。但地址值是一样的说明该地址绝对不是物理地址 难道子进程与父进程不处于同一块物理空间但是打印出来的地址是相同的呀那到底是因为什么才会出现这种情况呢接下来让我们来深入理解Linux操作系统中真正的进程地址空间。 在Linux地址下这种地址叫做 虚拟地址 。我们在用C/C语言所看到的地址全部都是虚拟地址物理地址用户一概看不到由操作系统统一管理 虚拟内存 进程地址空间本质上是内存中的一种内核数据结构在Linux当中进程地址空间具体由结构体mm_struct实现。 进程地址空间就类似于一把尺子尺子的刻度由0x00000000到0xffffffff尺子按照刻度被划分为各个区域例如代码区、堆区、栈区等。而在结构体mm_struct当中便记录了各个边界刻度例如代码区的开始刻度与结束刻度。这也就是我们口中所说的在操作系统层面的虚拟内存。 通过这些信息内核可以有效地管理进程的地址空间包括分配内存、释放内存以及进行内存保护等操作。这种基于结构体的设计使得 Linux 内核能够灵活地管理进程的内存空间确保各个进程之间的内存隔离和安全性。   那么问题来了虚拟内存是如何与物理内存联系起来的呢我们学过一种数据结构哈希表。而虚拟内存与物理内存之间的联系就相当于哈希映射即键值对的映射。 虚拟内存与物理内存之间的联系确实可以类比于哈希表的键值对映射。在虚拟内存系统中虚拟地址就像是哈希表的键而物理地址则是对应的值。操作系统通过页表这样的数据结构来实现这种映射关系。 当一个进程访问其虚拟地址时操作系统首先会将这个虚拟地址作为键进行哈希运算得到对应的哈希值。这个哈希值通常对应着页表中的一个索引位置。然后操作系统在页表中查找这个索引位置以确定该虚拟地址对应的物理地址。 如果在页表中找到了对应的物理地址那么就可以直接访问物理内存中的数据。如果没有找到则可能会触发缺页异常这时操作系统会根据某种页替换算法将一些不常用的页面换出到磁盘上然后将需要访问的页面从磁盘加载到物理内存中并更新页表的映射关系。 什么是页表呢让我们再深入了解一下页表 页表Page Table是操作系统中用于管理虚拟内存和物理内存映射关系的数据结构。在现代计算机系统中虚拟内存是指程序所见到的内存空间而物理内存是真正的计算机内存。 当程序在运行时它所使用的内存地址是虚拟地址而不是实际的物理地址。虚拟地址需要通过页表转换为物理地址才能在物理内存中找到相应的数据。 页表的主要作用包括 地址转换将程序的虚拟地址映射到物理内存中的实际地址。通过页表操作系统可以根据程序提供的虚拟地址找到相应的物理地址。 内存保护通过设置页表中的权限位例如读、写、执行权限可以对内存进行保护防止未经授权的访问。 内存管理页表可以跟踪每个页的使用情况以便进行页面置换和内存回收等管理操作。 内存分配当程序需要更多内存时操作系统可以根据页表信息动态分配新的物理页。 页表通常是一个由操作系统维护的数据结构存储在内存中。在进行地址转换时CPU会根据页表中的信息将虚拟地址转换为物理地址。由于页表可能非常庞大对于大型程序来说页表的管理和维护可能会成为性能瓶颈之一。因此现代操作系统通常会采用一些优化技术如多级页表、反向页表等来提高页表的访问效率。 有了上面的知识铺垫我们来引入Linux操作系统中真正的进程地址空间 每个进程被创建时其对应的进程控制块task_struct和进程地址空间mm_struct也会随之被创建。由于task_struct当中有一个结构体指针存储的是mm_struct的地址所以操作系统可以通过进程的task_struct找到其mm_struct。 例如父进程有自己的task_struct和mm_struct该父进程创建的子进程也有属于其自己的task_struct和mm_struct父子进程的进程地址空间当中的各个虚拟地址分别通过页表映射到物理内存的某个位置如下图 那么如何解释之前的问题同一个地址空间中可以打印出不同的变量值呢这时需要引出另一个概念写时复制 写时复制Copy-on-WriteCOW是一种延迟复制技术通常用于共享内存的场景以节省内存和时间成本。它的核心思想是在需要修改共享内存时才进行复制而不是在创建共享内存时立即复制。 以下是写时复制的详细解释 共享内存的创建当父进程创建子进程时子进程会继承父进程的内存空间包括代码段、数据段、堆栈等。这些内存区域在物理内存中的页表映射关系与父进程相同。 共享内存的修改当父进程和子进程共享相同的内存页时它们在虚拟内存中的地址映射到相同的物理内存页上。当其中一个进程尝试修改共享内存时写时复制机制就会触发。 触发写时复制当子进程尝试修改共享内存时操作系统会检测到这一修改操作。为了保证进程间的独立性操作系统不会直接修改原始的物理内存页而是会执行写时复制操作。 复制并更新页表操作系统会创建一个新的物理内存页将修改后的数据复制到新页中。然后操作系统会更新子进程的页表使得子进程的虚拟地址指向这个新的物理页而不是指向原始的共享内存页。 进程间的独立性通过写时复制父进程和子进程各自拥有了共享内存的独立副本彼此之间的修改不会相互影响。这样可以保证进程间的独立性和隔离性。 写时复制技术通过延迟复制操作避免了不必要的内存复制开销同时确保了进程间的独立性和隔离性。 我们知道子进程创建时会获得与父进程相同的内存镜像即相同的代码段、数据段、堆栈、页表等。而我们使用C/C或其他语言获取内存地址时得到的实际是页表中的虚拟内存的地址。当我们在子进程中对父子共用的变量g_value进行更改时由于操作系统需要保证进程间的独立性和进程互不影响这时操作系统就会控制子进程进行数据的写时复制——即当父进程或子进程需对共享的数据或代码等进行更改时操作系统会控制进程在物理内存中重新开辟一块与原变量相同大小的空间并将改变的值放入新开辟的物理内存中同时更改子进程页表中对应的物理内存的地址值而虚拟内存中的地址并不进行改变仅仅是改变了虚拟内存地址和物理内存地址的键值映射。 那么操作系统为什么这么设计呢接下来我们思考这么几个问题 1、进程空间存在的意义 进程地址空间是指每个运行中的进程所拥有的虚拟内存空间包含了该进程运行所需的代码、数据以及堆栈等信息。进程地址空间的意义主要体现在以下几个方面 隔离性每个进程都拥有独立的地址空间使得各个进程之间的内存相互隔离互不干扰。这种隔离性可以防止进程之间的数据共享和相互干扰提高了系统的稳定性和安全性。 保护性进程地址空间可以通过设置权限位和访问控制来保护其中的数据防止未经授权的进程访问和修改。这种保护性可以有效地保护进程的私有数据和系统关键信息提高了系统的安全性。 共享性虽然进程地址空间是相互隔离的但系统可以通过内存映射等机制实现进程间的内存共享。这种共享性可以提高系统资源的利用效率加快进程间通信的速度促进进程间的协作与交互。 动态性进程地址空间的大小可以根据进程的需要动态调整使得进程能够灵活地管理和利用内存资源。这种动态性可以使系统更加高效地分配和利用内存提高了系统的性能和响应速度。 2、为什么子进程一开始不直接创建自己的物理内存空间而是进行写时复制呢  当一个子进程被创建时通常会通过复制父进程的地址空间来创建自己的地址空间。如果直接复制父进程的物理内存空间那么可能会浪费大量的内存资源特别是当子进程立即执行exec()系统调用来加载新的程序时因为此时父进程的内存内容对于子进程来说是无用的。 因此为了避免不必要的内存复制和浪费操作系统采用了写时复制技术。写时复制允许子进程与父进程共享相同的物理内存空间只有在子进程或父进程尝试修改内存中的数据时才会执行实际的内存复制操作将要修改的数据复制到子进程的独立内存空间中。这样可以节省内存空间并且减少了不必要的内存复制操作提高了系统的性能和效率。 总的来说写时复制技术使得子进程能够延迟对父进程内存空间的复制只在需要修改时才进行复制从而节省内存资源并提高系统的性能。 总的来说进程地址空间的意义在于提供了一个独立、隔离、保护、共享和动态的内存空间为进程的正常运行和系统的稳定性、安全性提供了重要的基础。 五、进程查看 1、通过系统目录/proc来查看进程 ls /proc 这些目录名为数字的文件是进程的PID如果我们想详细查看某一个进程可以直接查看其目录例如查看1号进程的详细信息 2、通过ps命令来查看进程 ps 命令的选项用于指定输出的格式和显示的内容。下面是一些常用的 ps 命令选项及其含义 -e显示所有进程等同于 -A。-f显示详细的进程信息包括进程的 UID、PID、PPID、C、STIME、TTY、TIME 和 CMD。-u以用户格式显示进程信息。-a显示终端上的所有进程包括其他用户的进程。-x显示没有控制终端的进程。j 以用户友好的格式显示进程信息包括进程的 PID进程 ID、PPID父进程 ID、PGID进程组 ID、SID会话 ID、UID用户 ID、STIME启动时间、TTY控制终端、TIMECPU 时间、CMD命令等。-ww使用最宽的输出格式。aux同时列出所有的进程包括其他用户的进程并且显示详细信息。ajx 会列出所有进程的详细信息并以用户友好的格式显示。 由于ps命令选项过多我们现阶段实际使用ps aux 或 ps ajx 与 grep和管道符“ | ”搭配使用即可。 3、通过top命令查看进程 top命令是一个非常有用的Linux系统监视工具可以显示系统中正在运行的进程以及相关的系统资源使用情况。以下是关于top命令的详细解释 启动 top 命令 在终端中输入 top 并按下回车键即可启动 top 命令。默认情况下top 将会以交互方式显示当前系统的运行状况。 交互式界面top 命令以一个交互式界面展示系统资源的使用情况。在默认模式下它会按照 CPU 使用率排序显示进程列表。 实时更新top 命令会持续更新显示系统资源使用情况和进程信息。默认情况下它每隔 3 秒钟刷新一次。 显示信息top 命令默认会显示如下信息 系统整体信息包括系统时间、运行时间、登录用户数量、系统负载等。进程信息包括进程 ID、用户、优先级、CPU 占用率、内存占用等。CPU 使用情况包括用户态、系统态、空闲等。内存使用情况包括总内存、已用内存、空闲内存等。交换空间使用情况包括交换总量、已用交换、空闲交换等。 交互命令top 命令支持一些交互命令可以在其运行时进行操作。例如 h显示帮助信息列出可用的交互命令。k结束一个进程。q退出 top 命令。r改变进程的优先级。Space切换 CPU 时间的显示单位。1显示多核 CPU 每个核的详细信息。 参数设置 你可以使用一些参数来定制 top 命令的行为。例如top -n 5 将只显示前 5 次更新的信息。 六、进程状态 操作系统中的进程状态 操作系统中进程通常会处于以下几种状态之一 创建New新创建的进程正在等待分配资源或初始化。 就绪Ready进程已经准备好运行但由于CPU资源有限或者其他进程正在执行因此暂时无法执行。进程在就绪队列中等待CPU时间片。 运行Running进程正在CPU上执行指令。 阻塞Blocked进程因为某些原因如等待I/O完成、等待消息等暂时无法继续执行进入阻塞状态。在等待期间进程不占用CPU资源。 终止Terminated进程执行结束释放了占用的资源并等待被操作系统回收。 这些状态通常由操作系统的调度程序和内核来管理和维护。进程在不同状态之间转换的过程由操作系统的调度算法控制以实现对进程的合理调度和资源管理。 Linux操作系统中的进程状态 在Linux系统中进程状态通常包括以下几种 运行RunningR进程正在CPU上执行指令。 浅度睡眠SleepS进程已经准备好运行但由于CPU资源有限或者其他进程正在执行因此暂时无法执行。进程在就绪队列中等待CPU时间片进程处于可中断睡眠状态可被信号唤醒。 深度睡眠Disk SleepingD进程由于等待某个事件的发生如I/O操作完成、信号等而被挂起暂时无法执行。进程处于不可中断睡眠状态不可被信号唤醒。 停止StoppedT进程被暂停执行通常是由于接收到了SIGSTOP、SIGTSTP、SIGTTIN或SIGTTOU信号而被挂起。可以通过发送SIGCONT信号来恢复进程执行。 僵尸ZombieZ进程已经终止但其父进程还没有调用wait()或waitpid()函数来获取其终止状态因此进程的退出状态信息还保留在系统中成为僵尸进程。僵尸进程会占用系统资源需要被及时清理。 死亡DeadX这个状态只是一个返回状态你不会在任务列表里看到这个状态。 这些状态在Linux系统中由进程控制块 task_strcut 中的状态字段来表示和管理。操作系统通过调度算法和内核来管理进程状态的转换和调度以实现对系统资源的合理分配和进程的高效管理。 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 */ }; kill命令及常用信号介绍在命令行输入kill -l 即可查看所有信号  kill 命令是用于向指定进程发送信号的工具。除了发送终止信号之外还可以发送其他一些信号来控制进程的行为其中包括暂停信号、重新运行信号、强制终止信号和终止信号。以下是关于这些信号的介绍 暂停信号SIGSTOP 信号编号19作用暂停挂起目标进程的执行使其停止运行。例子可以使用 kill -SIGSTOP 进程ID 或 kill -19 进程ID 命令来发送暂停信号。 重新运行信号SIGCONT 信号编号18作用恢复被暂停的进程的执行使其继续运行。例子可以使用 kill -SIGCONT 进程ID 或 kill -18 进程ID 命令来发送重新运行信号。 强制终止信号SIGKILL 信号编号9作用强制终止目标进程的执行立即结束进程的运行不给进程执行清理操作的机会。例子可以使用 kill -SIGKILL 进程ID 或 kill -9 进程ID 命令来发送强制终止信号。 终止信号SIGTERM 信号编号15作用通知目标进程正常退出允许进程执行清理操作后退出。例子可以使用 kill -SIGTERM 进程ID 或 kill -15 进程ID 命令来发送终止信号。 对于以上命令与信号的使用实际操作并不难大家可以自行实验我们不再过多进行赘述。 七、父子进程、孤儿进程、僵尸进程 前面我们已经对父子进程进行了概述接下来我们再次深入了解一下父子进程的关系 父进程在Linux中父进程是生成其他进程的进程。通常情况下init进程PID为1是所有其他进程的祖先它负责系统的初始化和进程的管理。 子进程在Linux中子进程是由父进程生成的进程。当一个进程调用fork()系统调用时操作系统会创建一个新的进程子进程并将父进程的所有资源复制给子进程。子进程会继承父进程的文件描述符、信号处理方式等属性并与父进程共享代码段、数据段和堆栈但拥有独立的地址空间。 现在来详细解释一下0号、1号、2号进程及其父子关系 0号进程通常情况下0号进程指的是内核线程或系统启动时的第一个进程。在Linux中0号进程可能是内核线程如kthreadd或者是用于特定任务的内核进程。 1号进程在Linux中1号进程通常指的是init进程它是所有其他进程的祖先负责系统的初始化和进程的管理。 2号进程在Linux中2号进程通常指的是kthreadd进程它是内核线程创建进程负责创建和管理内核线程。 在创建子进程后父进程和子进程会继续并发执行它们之间的执行顺序取决于调度器的调度策略。 我们综合使用fork、getpid、getppid函数来观察一下夫子进程的关系 #include stdio.h #include unistd.h #include sys/types.h #include stdlib.hint main() {pid_t id fork();if(id 0){ printf(我是子进程,my_pid:%d; ppid:%d\n, getpid(),getppid());sleep(1);}else if(id 0){printf(我是父进程,my_pid:%d; ppid:%d\n, getpid(),getppid());sleep(1);}else {perror(进程创建失败\n);}return 0; }从结果中我们可以验证新生成的进程确实是父进程的子进程那父进程的父进程又是谁呢 在这里我们需要使用ps aux 或 ps ajx 命令进行进程信息的查看。 在命令行中输入如下命令即可查看父进程的父进程信息 ps ajx | head -1 ps ajx | grep 24885 | grep -v grep 可以看到父进程的父进程实际是bash shell进程即启动该进程的命令行进程。 孤儿进程 孤儿进程是指其父进程已经退出或者被终止但是该进程还在继续执行的情况下产生的进程。在Linux系统中孤儿进程会被init 进程即1号进程接管。init 进程会成为孤儿进程的新父进程并负责对其进行收养和管理确保进程能够正常执行。 孤儿进程的产生常见于以下情况 父进程意外终止但子进程仍在运行。父进程在子进程之前退出导致子进程成为孤儿进程。父进程忽略或者未能正确处理子进程的退出信号。 我们用代码来模拟一下孤儿进程的产生 #include stdio.h #include unistd.h #include sys/types.h #include stdlib.hint main() {pid_t id fork();if(id 0){ while(1)//让子进程一直运行父进程推出后成为孤儿进程之后被1号进程领养{printf(我是子进程,my_pid:%d; ppid:%d\n, getpid(),getppid());sleep(1);}}else if(id 0){int cnt 3;while(cnt--)//让父进程先退出{printf(我是父进程,my_pid:%d; ppid:%d\n, getpid(),getppid());sleep(1);}printf(父进程已退出\n);}else {perror(进程创建失败\n);}return 0; } 通过监视我们确实可以看到父进程退出后子进程依然在运行。但由于父进程已退出无法对子进程进程回收所以子进程由1号进程进行领养子进程仍然可以继续执行并在必要时被1号进程正确回收。 僵尸进程 在Linux系统中僵尸进程是指已经完成执行任务的子进程但其父进程尚未对其进行善后处理导致子进程处于僵死状态的情况。 如果父进程没有及时对已经结束执行的子进程进行善后处理这些已经完成任务但尚未被回收的进程就会成为僵尸进程。僵尸进程不再执行任何任务也不再占用系统资源但它们的进程ID和部分进程信息仍然保留在系统中可能导致进程表膨胀从而影响系统的正常运行。 解决僵尸进程问题的一种常见方法是确保父进程及时对其子进程进行回收。这可以通过在父进程中捕获 SIGCHLD 信号并在信号处理函数中调用 wait() 或 waitpid() 来实现。此外Linux系统的init进程也会定期扫描并回收僵尸进程以确保系统的稳定性和性能。 我们用代码来模拟一下僵尸进程的产生 #include stdio.h #include unistd.h #include sys/types.h #include stdlib.hint main() {pid_t id fork();if(id 0){ int cnt 3;while(cnt--)//让子进程提前退出{printf(我是子进程,my_pid:%d; ppid:%d\n, getpid(),getppid());sleep(1);}printf(子进程已退出\n);}else if(id 0){while(1)//让父进程一直运行{printf(我是父进程,my_pid:%d; ppid:%d\n, getpid(),getppid());sleep(1);}}else {perror(进程创建失败\n);}return 0; } 通过监视我们可以看到子进程先退出后由于父进程还在一直运行无法对子进程进行回收这就导致子进程进入“僵死状态”即Z状态。虽然子进程已经退出但其进程ID和部分进程信息仍然保留在系统中可能导致进程表膨胀从而影响系统的正常运行。虽然僵尸进程本身不再消耗 CPU 资源或执行任何任务但其 PCB 仍然占用系统内存空间并需要操作系统来管理和维护。但其不再消耗 CPU 资源或执行任何任务当僵尸进程被回收时该进程的PCB及相关资源才会被彻底清理。 僵尸进程的危害 内存资源浪费与泄露若是一个父进程创建了很多子进程但都不进行回收那么就会造成资源浪费因为task_struct(PCB)本身就要占用内存。僵尸进程申请的资源如果不进行回收那么僵尸进程越多实际可用的资源就越少也就是说僵尸进程可能会导致内存泄漏。 影响系统性能 当进程表中存在大量僵尸进程时操作系统需要在管理这些进程上花费额外的时间和资源。这可能导致系统调度器效率降低从而影响系统的整体性能。 进程管理混乱 大量僵尸进程的存在可能会导致进程管理变得混乱使得管理员或开发人员难以准确监控和诊断系统中的活动进程。 可能引发资源耗尽 在极端情况下如果系统中存在大量僵尸进程并且它们的父进程没有及时处理可能导致系统资源如进程表项耗尽从而影响系统的稳定性。 八、进程的优先级 在linux或者unix系统中用ps – l 命令则会类似输出以下几个内容 我们很容易注意到其中的几个重要信息 UID : 代表执行者的身份PID : 代表这个进程的代号PPID 代表这个进程是由哪个进程发展衍生而来的亦即父进程的代号PRI 代表这个进程可被执行的优先级其值越小越早被执行NI 代表这个进程的nice值 我们知道UID是执行者的身份PID和PPID我们也已经介绍过。那么PRI和NI是什么呢它们是如何对进程的优先级造成影响的呢我们先来了解一下两者的概念 在Linux中PRI代表进程的静态优先级Static PriorityNI代表进程的调度优先级Nice Value。这两个值是用来确定进程调度顺序的重要参数。 静态优先级PRI取值范围一般是-20到19数值越小表示优先级越高。在进程的调度中静态优先级决定了进程在就绪队列中的顺序。进程的静态优先级可以通过nice命令调整。 调度优先级NI也称为Nice值它是用来调整进程的静态优先级的偏移量。Nice值的范围一般是-20到19数值越大表示优先级越低即进程更“nice”占用更少的CPU资源。可以通过nice和renice命令来调整进程的调度优先级。 这两个值共同决定了进程在CPU上执行的优先级。较高的PRI值和较低的NI值将导致进程更频繁地被调度执行而较低的PRI值和较高的NI值则会导致进程较少地被调度执行。 总的来说通过调整PRI和NI的值可以对进程的调度行为进行影响以满足不同的性能需求和系统资源分配策略。 我们了解到PRI是是由操作系统分配给进程的初始优先级并且通常情况下是由系统管理员或具有特定权限的用户通过一些工具或命令来修改的。可见作为普通用户我们并没有权限直接对程序的PRI进行直接修改。 那么当我们需要更改进程优先级时又该如何做呢这时就不得不提出NI值了。 NI值就是我们所要说的nice值了其表示进程可被执行的优先级的修正数值PRI值越小越快被执行那么加入nice值后将会使得PRI变为PRI(new)PRI(old)nice当nice值为负值的时候那么该程序将会优先级值将变小即其优先级会变高则其越快被执行所以调整进程优先级在Linux下就是调整进程nice值NI 代表这个进程的nice值 作为普通用户通常没有权限直接修改进程的静态优先级PRI。但是可以通过调整进程的Nice值NI来影响其动态优先级从而改变其在系统中的调度行为。通常情况下Nice值可以由普通用户通过一些特定的命令或工具来修改而无需特殊权限。 在Linux和类Unix系统中可以使用像nice和renice这样的命令来调整进程的Nice值。nice命令用于启动新进程并指定其Nice值而renice命令用于修改已经运行的进程的Nice值。 例如要启动一个新的进程并将其Nice值设置为10可以使用以下命令 nice -n 10 ./my_process要修改已经运行的进程的Nice值可以使用以下命令 renice -n 5 -p PID其中-n选项指定要设置的Nice值-p选项指定要修改的进程的PID进程ID。 通过调整Nice值可以让普通用户间接地影响进程的调度优先级从而满足特定的性能需求或调度策略。 我们通过实际操作来观察一下 我们可以通过ps -al 命令来实时观察进程的信息 可以看到初始时由可执行文件app本身以及所衍生出来的子进程的初始PRI值都为80当我们使用nice命令将app的NI值设置为10后父子进程的PRI值均发生了改变。 当然我们也可以通过renice命令分别在进程运行时对父子进程做出不同的改变。 其实除了nice和renice命令在Linux系统中还有一个top命令可以帮助我们进行NI值的修改接下来我们看一下如何使用Linux系统中的任务管理器—top命令对进程的优先级进行更改 具体操作进入top后按“r”–输入进程PID–输入nice值 1、命令行输入top 后回车 按 r  2、输入需要更改的进程的pid并按回车 3、输入需要更改的NI值并按回车 4、完成以上操作后按“q”退出top命令 5、我们使用ps -al 命令查看pid为7643的进程优先级是否已经被修改 可以看到由于我们输入的nice值为10所以该进程的PRI也由初始的80变为了90。 由于top相当于我们Windows下的任务管理器是实时的所以我们可以在进程运行时对其优先级进行更改。 【注意】普通用户无法将NI值设置为负数即无法提升进程的优先级。如果仍要将NI值设置为负数我们可以进行sudo提权使用root的身份对NI值进行更改为负数。 sudo top sudo nice -n -10 command sudo renice -n -10 pid 可以看到我们使用sudo提权后我们成功将NI值设置为了负数成功提高了进程的优先级。 关于进程优先级的其他概念 竞争性: 系统进程数目众多而CPU资源只有少量甚至1个所以进程之间是具有竞争属性的。为了高效完成任务更合理竞争相关资源便具有了优先级独立性: 多进程运行需要独享各种资源多进程运行期间互不干扰 并行: 多个进程在多个CPU下分别同时进行运行这称之为并行 并发: 多个进程在一个CPU下采用进程切换的方式在一段时间之内让多个进程都得以推进称之为 并发。  九、环境变量 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数我们在编写C/C代码的时候在链接的时候从来不知道我们的所链接的动态静态库在哪里但 是照样可以链接成功生成可执行程序原因就是有相关环境变量帮助编译器进行查找。 环境变量通常具有某些特殊用途还有在系统当中通常具有全局特性 基本概念包括 系统环境变量这些变量在整个系统范围内可用并影响所有用户和进程。例如PATH 变量定义了系统在哪些目录中查找可执行文件。 用户环境变量这些变量是针对特定用户的并且只影响该用户的会话。例如HOME 变量指定了用户的主目录路径。 临时环境变量这些变量通常由 shell 临时设置并在当前 shell 会话中有效。它们通常用于在特定操作中使用而不是永久性地影响系统或用户的配置。 永久环境变量这些变量在系统启动时由配置文件设置并且在整个系统的生命周期内有效。例如在 .bashrc 或 /etc/profile 中定义的变量。 设置环境变量可以使用 export 命令在 shell 中设置环境变量例如 export MY_VARvalue查看环境变量可以使用 echo 命令或 env 命令查看当前环境中的变量例如 echo $MY_VAR //查看具体的环境变量的信息 env //查看系统中所有的环境变量清除环境变量可以使用unset命令清除环境变量 unset 环境变量名 显示本地定义的shell变量和环境变量可以使用set命令 set永久性配置要永久性地配置环境变量可以将设置添加到用户的配置文件中例如 .bashrc 或 .bash_profile。 以下是几个常见的环境变量 环境变量的组织方式 那我们如何在代码中获取环境变量呢通常有以下方式 1、通过命令行参数获取 #include stdio.hint main(int argc, char *argv[], char *env[]) {int i 0;for(; env[i]; i){printf(%s\n, env[i]);}return 0; } main函数参数介绍 int main(int argc, char *argv[], char *env[]) 是C/C语言中 main 函数的标准声明形式它的参数含义如下 argc表示命令行参数的数量argument count即程序运行时传递给程序的参数的个数。这个参数至少为1因为程序名本身也算一个参数。 argv是一个指向字符串数组的指针argument vector其中每个字符串都是一个命令行参数。argv[0] 存储的是程序的名称而 argv[1] 到 argv[argc-1] 存储的是传递给程序的命令行参数。 env是一个指向字符串数组的指针environment其中每个字符串都是一个环境变量的定义。每个环境变量都以形如 “NAMEvalue” 的格式存储。数组的最后一个元素通常是一个空指针用于指示环境变量列表的结束。 通过这些参数程序可以获取到命令行传递的参数和环境变量的值从而进行相应的处理。 2、通过第三方变量environ获取 #include stdio.h int main(int argc, char *argv[]) { // environ是一个外部声明它声明了一个指向环境变量的指针数组的全局变量 environ。 // environ 指针数组就是指向这些储存环境变量字符串数组的指针数组extern char **environ;int i 0;for(; environ[i]; i){printf(%s\n, environ[i]);}return 0; } libc中定义的全局变量environ指向环境变量表,environ没有包含在任何头文件中,所以在使用时 要用extern声明。 3、通过系统调用函数获取环境变量 getenv 函数是一个C标准库函数用于获取指定名称的环境变量的值。其原型通常在 stdlib.h 头文件中声明 char *getenv(const char *name);该函数接受一个参数 name表示要获取的环境变量的名称返回一个指向该环境变量值的指针。如果指定名称的环境变量存在则返回该环境变量的值如果不存在则返回空指针NULL。 以下是一个简单的示例演示了如何使用 getenv 函数来获取指定环境变量的值 #include stdio.h #include stdlib.hint main() {// 获取名为 PATH 的环境变量的值char *path_value getenv(PATH);if (path_value ! NULL) {printf(PATH环境变量的值%s\n, path_value);} else {printf(未找到PATH环境变量\n);}return 0; }在这个示例中程序首先调用 getenv(PATH) 来获取名为 “PATH” 的环境变量的值并将其存储在 path_value 变量中。然后程序检查 path_value 是否为NULL如果不是NULL则输出该环境变量的值如果是NULL则输出未找到该环境变量的消息。 【注意】环境变量具有全局性子进程会继承父进程的环境变量。 【选学】 十、Linux操作系统进程的调度 相信同学们学完进程优先级后一定会好奇在Linux系统中进程如此繁多操作系统到底是如何对进程进行调度的呢Linux系统下的调度方式与一般的操作系统并不相同。 接下来我们先引出三个概念活动队列、过期队列和O(1)调度器 Linux活动队列 活动队列是Linux内核中用于存储正在运行和等待运行的进程的队列。活动队列中的进程是具有时间片的它们正在等待CPU执行。内核通过活动队列来决定下一个要执行的进程。 过期队列 过期队列是Linux内核中的一个数据结构用于存储已经用完时间片的进程。当进程的时间片用尽时它会被移动到过期队列中等待重新调度。在过期队列中的进程需要等待一个新的时间片以便重新执行。 O(1)调度器 O(1)调度器是Linux内核中一种优化的进程调度算法。它的设计目标是在常数时间内即O(1)时间复杂度完成进程调度而不受进程数量的影响。O(1)调度器使用了活动队列和过期队列以及一些其他数据结构以便快速地选择下一个要执行的进程。这种调度器的设计旨在提高系统的响应速度和性能。 总的来说Linux活动队列和过期队列是用于管理进程调度的数据结构而O(1)调度器是一种基于这些数据结构设计的高效调度算法它能够在常数时间内完成进程调度从而提高系统的性能和响应速度。 1、活动队列详解 时间片还没有结束的所有进程都按照优先级放在该队列 nr_active: 总共有多少个运行状态的进程 queue[140]: 一个元素就是一个进程队列相同优先级的进程按照FIFO规则进行排队调度,所以数组下标就是优先级 从该结构中选择一个最合适的进程过程是怎么的呢 从0下表开始遍历queue[140] 找到第一个非空队列该队列必定为优先级最高的队列 拿到选中队列的第一个进程开始运行调度完成 遍历queue[140]时间复杂度是常数但还是太低效了 bitmap[5]:一共140个优先级一共140个进程队列为了提高查找非空队列的效率就可以用5*32个比特位表示队列是否为空这样便可以大大提高查找效率。 2、过期队列详解 过期队列和活动队列结构一模一样。 过期队列上放置的进程都是时间片耗尽的进程。 当活动队列上的进程都被处理完毕之后对过期队列的进程进行时间片重新计算。 3、O(1)调度算法  在这里我们对上述bitmap[5]数组和active指针、expired指针的作用进行简要介绍 unsigned long bitmap[5] 【个人见解】为什么数据类型是long而不是int呢因为int无论在32位还是64位操作系统下都是4个字节而long在32位系统下是4字节在64位系统下则是8字节。使用unsigned long类型能使位图在不同的操作系统下具有更强的适应能力这确保了位图在不同的操作系统下仍能存储足够多的位数。  bitmap[5]是一个无符号整型数组用于表示进程队列中的进程是否在运行。数组的每个元素都是一个32位的整数这些整数的每一位对应着进程队列中的一个进程状态即是否在运行或已结束。 假设我们有140个进程需要表示而一个32位整数可以表示32个进程状态。因此我们需要至少5个32位整数共160个比特位来表示这些进程。这就是为什么使用bitmap[5]数组。 在这个数组中每个比特位表示一个进程的状态通常用1来表示进程在运行队列中用0表示进程已经结束。这种方式可以有效地节省内存空间并且能够快速地进行进程状态的检查和修改。 使用位运算可以方便地对bitmap进行操作比如设置某个进程的状态、检查某个进程的状态等。这便是O(1)调度算法因为该算法是很小的常数级算法高效率且所需内存空间小因此它在操作系统中被广泛应用。 active指针和expired指针 在操作系统中特别是在调度算法中active指针和expired指针是两个重要的指针用于管理活动队列active queue和过期队列expired queue。 active指针 active指针通常指向活动队列中当前正在运行或等待运行的进程。当操作系统需要选择下一个要执行的进程时它会从活动队列中选择一个进程来执行。因此active指针指向的是被选中的进程或者下一个要执行的进程。 expired指针 expired指针通常指向过期队列中的进程即已经用完时间片的进程。当一个进程的时间片用尽时它会被移动到过期队列中等待重新调度。因此expired指针指向的是等待重新调度的进程集合。 这两个指针在调度算法中起着关键作用操作系统通过这些指针来管理进程的调度和执行顺序。通常情况下active指针用于选择下一个要执行的进程而expired指针用于管理已经用完时间片的进程确保它们能够及时重新获得执行机会。  注意  active指针永远指向活动队列 expired指针永远指向过期队列 可是活动队列上的进程会越来越少过期队列上的进程会越来越多因为进程时间片到期时一直都存在的。 但是这些都不是问题在合适的时候调度器能够交换active指针和expired指针的内容—即改变指针指向就相当于有具有了一批新的活动进程 本节到此结束博主用了3天的空闲时间整理出本篇文章进程管理的基本概念的各种细节基本都包含在内。本节主要包含进程管理相关概念的精讲在学习如何控制进程前牢靠的基础知识才是我们掌握进程控制的关键。为防止篇幅过长影响大家的阅读。关于进程控制的具体操作我们下节博客见。
http://www.zqtcl.cn/news/620609/

相关文章:

  • xp系统中做网站服务器吗网站设计版权
  • 化妆品网站建设经济可行性分析怎么做好网站
  • 软件企业网站建设栏目结构图服务公司有哪些
  • 郑州专业做淘宝网站推广哪些公司需要网站开发工程师
  • 如何为企业做网站单页网站推广
  • 做公众号封面图的网站凡客精选app
  • 张家界做旅游网站网业小说畅读服务
  • 短租网站那家做的好网络设计工作好找吗
  • 企业建网站哪家好网络书签 wordpress
  • 网站策划的工作职责有关网站开发的创意
  • 上国外网站dns如何免费做网站推广
  • wordpress导航站的源码网页设计与制作微课教程第4版李敏
  • 建站的好公司wordpress 小工具 调用
  • 郑州高考网站建设wordpress调用多个底部
  • 在线做爰直播网站dw制作网页步骤
  • 视频网站 php源码深圳高端网站建设招聘
  • 企业网站服务费怎么做记账凭证那个网站上有打码的任务做
  • 沈阳做网站优化的公司长春网络建站模板
  • 秒收网站鞍山58同城
  • 模板网站建设方案wordpress系统在线升级
  • 男女做爰视频网站在线视频seo也成搜索引擎优化
  • 网站优化和网站推广深圳市高端网站建设
  • 宁波网站建设优化企业推荐四川省建设厅新网站
  • 哈尔滨模板自助建站优秀的电子商务网站
  • 有站点网络营销平台wordpress 退出 跳转
  • 网站建设的内容规划国内做网站群平台的公司
  • 浙江省院士专家工作站建设网站网站的请求服务做优先级
  • 建一个国外网站多少钱邵阳建设银行网站是多少
  • h5页面有哪些seo关键词智能排名
  • 电信的网做的网站移动网打不开该找电信还是移动杨和勒流网站建设