「独立的浪漫」:进程与操作系统的优雅平衡

文章目录

冯诺依曼体系结构

冯诺依曼体系结构(Von Neumann Architecture)是计算机的基本设计理念之一,由美国数学家约翰·冯·诺依曼于1945年提出,也被称为"冯诺依曼模型"或"冯诺依曼计算机体系结构"。它的核心思想是将程序和数据存储在计算机的内存中,并通过中央处理单元(CPU)执行程序。冯诺依曼体系结构至今仍然是大多数计算机的基础架构。

  • 中央处理器(CPU

    • 控制单元(CU):负责指挥计算机各部分的工作。

    • 算术逻辑单元(ALU):进行算术和逻辑运算。

    • 寄存器:用于暂时存储数据和指令。

  • 内存(RAM

    • 存储程序和数据。冯诺依曼结构中的程序和数据都存储在同一内存中。
  • 输入设备:用于向计算机输入数据,例如键盘、鼠标等。

  • 输出设备:用于输出处理结果,例如显示器、打印机等。

  • 总线:用于在各个组件之间传输数据和指令的通道。

注意:

  1. 上面的存储器指的就是内存

  2. 不考虑缓存的情况下这里的CPU只能对内存中的数据进行操作,不能从外设 (输入和输出设备)中获取数据

  3. 外设(输入或输出设备)要输入或输出数据,只能从内存中获取

  4. 总的来说,所有设备都只能与内存打交道

为什么体系结构中要存在内存?

CPU处理速度非常快,但是输入数据的速度相较于CPU的速度是非常慢的,这就导致了很多时候CPU都在等待数据的输入,严重浪费了CPU的性能,所以增加内存,让CPU直接跟内存交换数据,充分发挥CPU的性能。(内存输入输出的数据的速度是非常快的)

计算机存储金字塔:

冯诺依曼瓶颈:

冯诺依曼架构存在一个著名的问题,即"冯诺依曼瓶颈"(Von Neumann Bottleneck)。这是由于程序和数据共享同一个内存系统,CPU在执行指令时需要频繁地从内存读取指令和数据,导致内存的读写速度成为限制计算机性能的瓶颈。随着计算机硬件的不断发展,解决冯诺依曼瓶颈的问题成为计算机体系结构研究的一个重要方向。

总的来说,冯诺依曼体系结构让计算机保持一定处理速度的同时,降低了计算机的成本,使得计算机能够进入各家各户,为之后互联网的发展奠定了基础。

操作系统

操作系统(Operating System,简称OS)是管理计算机硬件与软件资源的系统软件,它为应用程序提供了一个运行环境,并为用户提供与计算机硬件交互的接口。

操作系统包括:

  1. 内核(进程管理,内存管理,文件管理,驱动管理)

  2. 其他程序(例如函数库,shell程序等等)

一般而言,操作系统指的是内核

设计目的:

  1. 操作系统对下与硬件交互,进行软硬资源的管理(手段)

  2. 操作系统对上为用户程序(应用程序)提供⼀个良好的执行环境(目的)

  • 软硬件体系结构是层状结构

  • 访问操作系统,其实就是系统调用(系统提供的函数)

  • 只要程序运行访问了硬件,那么必须贯穿整个软硬件体系结构

  • 函数库在底层封装了系统调用

系统调用与库函数:

操作系统会暴露部分接口供上层开发者使用,这部分接口就是系统调用。

系统调用的功能比较基础,对使用者要求较高,所以一部分开发者将系统调用的接口进行封装,从而形成了库,有利于开发者进行二次开发。

进程

进程概念

进程 (Process)是计算机中正在执行的程序的一个实例。它是操作系统资源分配和调度的基本单位,是操作系统管理计算机硬件和软件资源时的核心概念之一。

程序与进程的区别

  • 程序:是一组静态的指令集合(二进制文件),它是一个静态的实体,不会占用计算机的系统资源。

  • 进程:是程序的执行实例,它是动态的,并且在执行过程中会占用系统资源(如CPU时间、内存等)。

内存在同一时间会有成百上千的被加载进来的程序,操作系统是需要对其进行管理的。

操作系统会给每个代码和数据块建立一个struct结构体(进程控制块),结构体存的是对代码和数据块的信息,也有相应的指针指向对应的代码和数据。许多这样的结构体组成双链表,也就是进程列表

进程控制块PCB→Process Control Block

Linux系统中PCBtask_struct

PCB相关内容: https://www.cnblogs.com/tongyan2/p/5544887.html

总结:进程 = PCB(task_struct) + 对应的代码和数据
进程信息被放在进程控制块中,可以理解为进程属性的集合。操作系统要对进程进行管理,其实就是对描述进程的task_struct形成的数据结构进行增删查改。

注:我们在Linux执行的指令、工具、程序,运行起来都是进程


查看进程的方法

c 复制代码
getpid()	//获取进程pid
getppid()	//获取父进程pid
  • pid 就是进程的标识符(编号);
  • 这两个函数都是系统调用

我们可以使用 man 2 getpid 来查看相关信息。

其返回值就是一个整型变量,成功调用返回一个大于0的整数,失败就返回-1。


bash 复制代码
ps axj | head -1 ; ps axj | grep mypro	//查看mypro进程相关信息

由于grep本来也是个进程,查找mypro进程,grep进程就会带上mypro的关键字,那么查出来的进程也会有grep进程,要屏蔽的话就使用grep -v反向匹配

bash 复制代码
ps axj | head -1 ; ps axj | grep mypro | grep -v grep

bash 复制代码
ls /proc

ls /proc 以文件的方式查看进程,proc目录记录进程信息,每个数字目录代表一个正在运行的进程,进程结束后,对应的目录文件就会删除。

我们先运行一个pid70816的进程。

再使用ls /proc查看进程信息,可以看到有70816的文件夹。

让我们直接查看70816的文件夹。

  1. exe 代表我们的可执行文件,如果将其删除不会对正在运行的进程造成影响,因为删除的是磁盘上的程序,可执行文件已经被加载在内存里了。

  2. cmd (current work directory)是进程所在的当前文件路径,我们的程序在创建文件时默认是在当前路径下创建的,而当前路径就是通过cmd获取的。

我们可以使用chdir改变当前进程的文件路径,以下为相关信息。

结束进程:ctrl+ckill -9 pid


父进程pid不变现象

多次启动进程时,会发现其父进程的pid不变,这是因为该父进程其实就是bash进程 ,我们执行的程序或者指令大多都是bash的子进程

当子进程出问题,不会影响bash进行,因为进程具有独立性;当我们启动xshell时,系统自动生成bash进程。

创建进程

创建进程需要使用系统调用fork函数。

cpp 复制代码
#include <unistd.h>
pid_t fork();
  1. fork 系统调用,没有参数,有两个返回值

  2. fork创建进程成功时,给父进程返回子进程的pid,给子进程返回0,失败时返回-1给父进程(没有子进程创建)。这样做的原因是因为父进程与子进程的关系是一对多的关系,将子进程的pid返回给父进程让其可以区分不同的子进程。

  3. 父子进程代码共享,数据各自开辟空间,私有一份(写时拷贝)

fork的使用示例:

C 复制代码
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

int main()
{
    pid_t id = fork();
    if(id < 0)
    {
        perror("fork");
        return 1;
    }
    else if(id == 0)
    {
        //子进程
        while(1)
        {
            sleep(1);
            printf("I am a child process, my pid: %d\n", getpid());
            printf("\n");
        }
    }
    else
    {
        //父进程
        while(1)
        {
            sleep(1);
            printf("I am a fathermak process, my pid: %d\n", getpid());
        }
    }
    return 0;
}

从上面的现象我们可以看出,id == 0 和 id > 0 同时成立,这是因为当我们创建完子进程后,其实已经存在了两个进程,会共用fork之后的代码,对于父进程 id > 0,对于子进程 id == 0 ,在同一时间内两个进程执行不同的代码,就会产生上图的结果。

为什么fork会返回两个值?

在fork函数内部,执行到最后的return语句时,子进程已经创建好了,两个进程就会同时执行return语句,fork就能返回两个值,我们以前认知的一个函数只能有一个返回值是在同一个进程的条件下才成立的。

为什么 id == 0id > 0 同时成立?

进程具有独立性。父子进程不同的PCB、代码虽然共享,但是只读的,不会影响独立性

父进程和子进程共用代码和数据,当任意进程尝试修改数据,操作系统在数据被修改前拷贝一份,让目标进程修改这份数据的拷贝。通过这种写时拷贝技术让不同进程的数据独立,从而保证进程的独立性。

进程状态

进程状态说明了进程当前的行为

进程状态就是task_struct中的一个整数变量

各种进程状态转换图

一个进程会有如下的很多状态

运行 && 阻塞 && 挂起

CPU能执行的进程只有一个,但是进程会有很多个,所以这些进程就需要排队等待执行,而这个队列就是调度队列。一个CPU只能有一个调度队列。

以下为基本调度结构:

注:task_struct既可以属于全局中的PCB双链表,也可以属于调度队列中的队列,跟以前一个节点只属于一个数据结构稍有不同。

调度算法之一:FIFO ,先进先出。CPU依次选择一个task_struct来执行。


进程的运行阻塞挂起状态

运行 :但是只要一个进程在调度队列中,它就是running 状态。换句话说,进程是运行状态,要么它持有CPU,要么就是完全准备好随时被调度。

阻塞 :进程等待某种设备或者资源就绪。如:C语言的scanf C++的cin,这些设备可以是键盘、显示器、网卡、磁盘、摄像头、话筒等

运行和阻塞的切换

操作系统中不仅有调度队列,也有其他的队列,比如设备队列。我们举scnaf例子来说明进程运行与阻塞状态的改变。

一个进程要scanf, 操作系统要去等待键盘输入,进程无法执行,将该进程从调度队列中拿掉,链接到其他队列(特定设备的等待队列 ),该进程不会被调度,此时进程就处于阻塞状态,当键盘输入数据完毕, 进程又会链接到调度队列,进程状态变为运行状态

进程状态的变化、表现之一,就是在不同的队列中进行流动,本质都是数据结构的增删查改。


挂起状态

  1. 阻塞挂起 :当内存资源不足时,操作系统就会将阻塞进程对应的代码和数据送到磁盘的swap分区以腾出内存空间资源,此时进程变为阻塞挂起状态

  2. 运行挂起 :如果内存资源严重不足,操作系统就会将调度队列末端的PCB对应的代码和数据交换到磁盘的swap分区,此时进程变为运行挂起状态

对于进程列表(PCB双链表)的理解

一个PCB是如何做到属于进程列表(全局双链表)又属于调度队列或者其他队列?

双链表一般定义,结构体里定义当前结构体的指针。

C 复制代码
struct Node
{
	int data;
	struct Node* next;
	struct Node* prev;
}

task_struct中,将next指针和prev指针再封装成list_head的结构体作为task_struct的成员变量。

C 复制代码
struct list_head
{
	struct list_head* next;
	struct list_head* prev;
}

struct task_struct
{
	......
	list_head links;
}

list_head通过偏移量去找到task_struct的其他成员,或者说去构建一个task_struct的指针。

C 复制代码
&((struct task_stuct*)0→links)		//links在`ask_struct的统一偏移量
(struct task_struct*)(next/list - 偏移量) 	//构建出一个`task_struct`的指针

我们可以通过构建出来的指针去访问task_struct的其他成员。而一个task_struct里会有多个list_head类型的成员,通过各个list_head类型的成员相互连接task_struct,这样就做到了一个task_struct既属于进程列表又属于调度队列或者其他队列

Linux中具体的进程状态

在Linux内核源码中,task_state_array指针数组用于存放进程的各种状态。

C 复制代码
static const char* const task_state_array[] = {
	"R(running)",
	"S(sleeping)",
	"D(disk sleeping)",
	"T(stopped)",
	"t(tracing stop)",
	"x(dead)",
	"Z(zombie)"
}
  • R:运行状态

R+ 代表进程是运行状态,+代表是前台运行,没有就是后台运行

  • S:阻塞状态(Linux叫休眠),浅度休眠,可中断休眠,可以直接kill 进程

  • D :也属于阻塞状态,深度休眠(不可中断休眠)disk是磁盘,跟磁盘有关。当进程涉及磁盘进行深度I/O时,进程状态为D深度休眠状态,不能被OS杀掉,防止出现数据丢失的问题(OS在极端情况下(资源严重不足)会杀掉一部分进程,防止OS崩溃)

  • T、t :暂停状态,Linux独有,对于T,OS怀疑进程有问题,进程做了非法操作,OS就将进程暂停(止损操作) ,让用户处理,或者直接对一个不断循环的程序输入ctrl+c ,此时进程也是T暂停;t是追踪暂停,debug代码时,程序在断点停下,进程是t追踪暂停状态

  • X:死亡状态,进程完全结束了(释放对应的代码和PCB)就是死亡状态。

  • Z:僵尸进程

    一个子进程结束后了,不能立刻释放所有的资源,其对应的代码可以被释放掉,但是PCB不能被释放,因为父进程要获取子进程的PCB信息,或者说父进程需要子进程是否完成父进程交代的任务的结果。 在子进程结束后,父进程获取子进程的PCB信息之前,子进程是僵尸状态。

内存泄漏问题

  1. 进程结束了,或者说程序结束了,内存泄漏问题就不在了,因为OS会回收动态分配的空间

  2. 僵尸进程的内存泄漏问题是很严重的(系统界别)

  3. 常驻内存的进程是非常惧怕内存泄漏问题的。

孤儿进程

如果在父子进程的关系中,父进程先于子进程结束,子进程就会变成孤儿 进程。孤儿进程会被1号进程(操作系统)领养。

孤儿进程如果不被领养则会导致内存泄漏的问题。

进程变成孤儿 进程会变成后台进程,一般的ctrl+c不能结束孤儿 进程,结束孤儿 kill -9 pid指令。

1号进程

进程优先级

进程优先级指的是进程获取CPU资源的先后顺序,就好比学生在食堂排队打饭。优先级高的进程有优先占用CPU执行的权利。

  • 进程优先级与权限 :优先级决定的是获取资源的顺序,权限决定的是能不能获取资源。

进程优先级的存在可以让CPU有序的执行各个进程,发挥CPU的性能优势。

进程优先级是一种数字,其值越低,优先级越高。

注:现在大多数的操作系统是基于时间片的操作系统,考虑公平性,优先级可能会变化,但是幅度不会太大。

UID(users id),用于标识用户的特定数字,让操作系统可以区分拥有者、所属组和Other

  • 衡量进程优先级的具体标准进程优先级PRI,默认值为80;进程优先级的修正数据NI,默认值为0;

  • PRI(new) = PRI(default) + nice

  • NI的范围是[-20, 19],由此推出进程优先级的范围[60, 99]。

进程级先级的设立要合理,不然可能会导致低优先级的进程得不到CPU的使用权,导致进程饥饿。

进程优先级的设置

进程优先级可以随意改低,但是如果要提高进程优先级,也就是将NI值改小,需要sudo提权。

  1. top指令

    • 终端上输入top进入任务管理器
    • 输入r,然后输入你要修改进程的pid和修改的NI
  2. nice指令,修改未运行的进程优先级

    • nice -n <nice_value> <command>
    • nice_value是你希望的NI
    • command是你要执行的指令或程序
  3. renice指令,既可以修改未运行的进程的优先级,也可以修改已经运行的进程的优先级

    • renice NI [[-p] pid ...] [[-g] pgrp ...] [[-u] user ...]
    • NI :修改后的NI值
    • -p pid :需要修改优先级的目标进程pid
    • -g pgrp :需要修改优先级的目标进程组的id
    • -u user:指定进程拥有者为user的进程来修改优先级

竞争、独立、并行、并发

  1. 竞争性:系统进程数⽬众多,而CPU资源只有少量,甚至1个,所以进程之间是具有竞争属性的。为 了高效完成任务,更合理竞争相关资源,便具有了优先级

  2. 独立性:多进程运行,需要独享各种资源,多进程运行期间互不干扰

  3. 并行:多个进程在多个CPU下分别,同时运行,这称之为并行

  4. 并发:多个进程在⼀个CPU下采用进程切换的⽅式,在⼀段时间之内,让多个进程都得以推进,称之为并发。

进程切换

死循环程序的运行

进程占有CPU,不会把代码执行完,操作系统会分配时间片,只让该进程执行特定的时间,CPU就会切换到另一个进程执行,同样执行特定的时间,经过排队重新执行死循环进程。

死循环进程不会一直占有CPU,与其他进程轮流执行的。

CPU&&寄存器

CPU(中央处理单元)是计算机的核心部件,负责执行程序中的指令并进行数据处理。寄存器是CPU内部的一个小型高速存储单元,用于存储临时数据、指令和操作数。

进程在使用CPU时,寄存器保留的是进程的上下文数据。

具体的进程切换

进程在切换时,首先被进程从CPU上剥离下来,然后将进程的上下文数据,既CPU寄存器的内容保存在task_structTSS任务状态段,如果重新执行进程,需要恢复进程的上下文数据。

进程切换的核心就是保留和恢复当前进程的硬件上下文数据,既CPU内寄存器的内容。

进程的调度算法

参考:玩转Linux内核进程调度,这一篇就够(所有的知识点)

Linux如何调度进程?大学老师不讲的,看完动画秒懂!

Linux2.6内核中进程队列的数据结构

  1. queue[140]是一个队列数组,0-99号队列是实时进程,这类进程优先被调度,不在讨论范围内。(队列数组本质是开散列的哈希表)

  2. 当进程被建时,通过其优先级和哈希函数得到该进程在活跃队列数组的位置,将其头插在对应队列。

  3. 活跃队列数组和过期队列数组的存在,可以防止进程饥饿的出现,如果没有过期队列,优先级较高的进程可能会一直占用CPU资源,导致优先级较低的进程无法执行。

  4. 当活跃队列数组的进程执行完一个时间片的操作,该进程就会被转移到过期队列数组中,当所有活跃队列数组的所有进程都执行完一个时间片的操作,操作系统就会将active指针和expired指针交换。

  5. 很多操作系统支持内核抢占,优先级较高的新进程就会插入活跃队列,类似于插队行为。

  6. 位图的存在是为了快速找出优先级高的进程,避免遍历哈希表,从而达成近似于O(1)的查找进程。

总的来说,这样的进程队列数据结构实现O(1)的调度算法,可以更大幅度地利用CPU资源,提高整体效率。

环境变量

基本概念

环境变量(environmentvariables)⼀般是指在操作系统中用来指定操作系统运行环境的⼀些参数

如:我们在编写C/C++代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,⽣成可执⾏程序,原因就是有相关环境变量帮助编译器进行查找。

环境变量通常具有某些特殊⽤途,还有在系统当中通常具有全局特性

main 的命令行参数是程序实现不同功能的方法,是指令选项的实现原理。

C 复制代码
int main(int argc, char* argv[])
{
    for(int i = 0; i < argc; ++i)
        printf("argv[%d]: %s\n", i, argv[i]);
    return 0;
}

我们在终端输入的指令和选项时,bash会以空格为标识符来划分各个字符串,从而存入main参数的指针数组,在main内部根据不同的选项来实现不同的功能。

进程拥有一张argv表,用于实现选项功能。

二进制程序与指令

我们所写的二进制程序与系统的指令没有本质区别。而执行自定义程序需要指明路径,执行指令却不需要,原因在于系统存在环境变量保留有默认路径。

Linux存在环境变量PATH,用于标识系统指令的默认搜索路径。

Shell 复制代码
env    #用于查询环境变量 
echo $PATH    #查询PATH环境变量

环境变量是内存级变量,bash启动时会读取环境变量形成一张环境变量表

我们输入指令时,bash将其构建成命令行参数表 , 将其解析后在环境变量表搜索路径,在路径后拼上你的指令名,然后创建子进程执行指令。

环境变量是从系统的配置文件来的,bash在启动时就会读取配置文件形成环境变量表。

更多环境变量

HOME:当前用户的家目录路径

USER: 当前用户

LOGNAME:登录名

su - 就会切换当前用户和登录名,相当于重新登录。

HISTSIZE:记录最近输入指令的条数

PWD:当前工作目录

OLDPWD:上一次工作目录

cd -指令可以在新旧工作目录来回切换

获取环境变量

Shell 复制代码
export MYENV=xxxx    # 导入环境变量
unset MYENV          # 取消环境变量

main的参数最多有三个,是父进程bash传递的

C 复制代码
int main(int argc, char* argv[], char* env[])

方法一:获取父进程(bash)的环境变量

C 复制代码
#include <stdio.h>

int main(int argc, char* argv[], char* env[])
{
    for(int i = 0; env[i]; ++i)
        printf("env[%d]-> %s\n", i, env[i]);
    return 0;
}

环境变量是可以被子进程继承的;环境变量具有全局特性。

方法二:

C 复制代码
getenv("xxx")    //获取指定环境变量

方法三:

在 Linux , environ 是一个环境变量的数组,包含了当前进程的环境变量。

bash 复制代码
environ - user environment

具体信息

使用案例

C 复制代码
#include <stdio.h>
#include <unistd.h>

extern char** environ; 

int main(int argc, char* argv[], char* env[])
{
    for (size_t i = 0; environ[i]; i++)
        printf("environ[%ld]-> %s\n", i, environ[i]);
   
    return 0;
}

环境变量的特性

环境变量最重要的一个特性就是全局性,可以被被各个进程使用。

补充知识:

  1. 在命令行也可以创建本地变量,本地变量只属于当前进程,不具有全局性


  1. 环境变量存在于bash进程。

export 命令是内建命令(built-in command),bash不创建子进程而由bash亲自来执行。export的作用是导入环境变量,如果是创建子进程来执行,由于进程的独立性,子进程是不能向父进程传输数据的,export就无法导入环境变量

程序地址空间

同一地址空间下的不同变量

程序地址空间,严格来说应该是进程地址空间(虚拟地址空间),一个系统级别的概念,不是语言层的概念。它不是实际物理内存。

  1. 共享区:一段镂空的空间

  2. 静态区:static变量其实就是全局变量,只不过受到了main的限制,只能在main里面访问。

  3. 只读区:存放代码、常量字符串

  4. 栈区:用于临时存储函数调用信息、局部变量和控制信息的一个区域

  5. 堆区:用于动态分配的内存区域

证明内存地址是虚拟地址。

C 复制代码
#include <stdio.h>
#include <unistd.h>

int gval = 100;

int main()
{
    __pid_t id = fork();
    if(id == 0)
    {
        while(1)
        {
            printf("子进程: gval: %d, &gval: %p, pid: %d\n", gval, &gval, getpid());
            gval++;
            sleep(1);
        }
    }
    else
    {
        while(1)
        {
            printf("父进程: gval: %d, &gval: %p\n", gval, &gval);
            sleep(1);
        }
    }

    return 0;
}

同一地址空间下的变量不可能同时是两个值,只能说明内存地址是虚拟地址

原因:

  1. 一个进程对应一个虚拟地址空间,32位机器 -> 2^32个字节 -> 4GB

  2. 一个进程,一套页表,页表是用于虚拟地址和物理地址做相对映射的。

  3. 页表相当于一个哈希表,存储的是虚拟地址-物理地址。子进程会继承父进程的页表,所以父子进程共用数据和代码,当任意一方进程尝试修改变量时,操作系统会将变量复制到另外的物理地址空间上,其页表的物理地址做出相应修改,然后让目标进程修改变量。所以会出现同一地址空间下的不同变量(虚拟地址是一样的,物理地址不一样)

注:用户无法查看实际的物理地址。

虚拟地址空间与进程地址空间

操作系统管理进程地址空间采取画饼时管理,让每一个进程都认为自己独占所有物理内存。

虚拟地址空间也是一个结构体,在Linux上是mm_struct

如何在mm_struct划分栈和堆等空间?

记录区域的开始和结束,调整区域就修改其开始和结束。

区域划分需要确定区域的开始和结束。

c 复制代码
stuct mm_struct
{
	long code_start, code_end;
	long init_start, init_end;
	//...
}

由于每个不同进程的虚拟内存区域功能和内部机制都不同,因此⼀个进程使⽤多个vm_area_struct结构来分别表示不同类型的虚拟内存区域(栈、堆、静态区等)。

有关虚拟地址空间结构体更深的认识:

虚拟地址是一个结构体,在开辟空间初始化时,其变量的值从内存加载的代码和数据的大小来,加载程序到内存时会申请物理空间,同时在虚拟地址空间也申请相应大小的空间(调整区域划分)。

为什么会存在虚拟地址空间?

  1. 有序: 将地址从"无序"变为"有序",从磁盘加载的数据和代码可以在物理内存的任何位置,通过页表的映射可以将这些分离的地址空间变得相对统一,在进程视⻆所有的内存分布都可以是有序的。

  2. 保护:页表中不仅有虚拟地址-物理地址,还有权限标志位,在对地址进行操作时,会检查其是否合法(是否权限越界),操作系统会停滞或者结束不合法的地址操作(比如访问野指针,对常量字符串进行修改)的进程,保护了物理内存中的所有的合法数据。

  3. 低耦合: 虚拟地址空间的存在可以让进程管理内存管理 进行一定的解耦合,有助于后续的维护操作。


一般而言,在创建进程时先要有内核数据结构,再加载代码和数据,但是也可以不加载代码,只有task_struct、mm_struct;

进程挂起更深刻的认识:

  • 进程挂起:将页表的物理地址清空,将内存里的代码和数据换到磁盘的swap分区。

Have a good day😏

See you next time, guys!😁✨🎞

相关推荐
MasterNeverDown4 分钟前
maven发包踩坑
java·maven
Mike铭6 分钟前
Golang Gin 实现无本地文件下载功能
开发语言·golang·gin
m0_7493175222 分钟前
蓝桥杯训练
java·学习·职场和发展·蓝桥杯
Flash.kkl22 分钟前
C++红黑树封装map和set
开发语言·c++
练小杰22 分钟前
Linux 文件的特殊权限—ACL项目练习
android·linux·运维·服务器·经验分享·学习
sukalot23 分钟前
windows C#-泛型类型参数的约束详解(二)
开发语言·c#
AI青年志24 分钟前
【服务器】ubuntu20.04安装cuda12.01(使用runfile安装)
linux·运维·服务器
HelloZheQ36 分钟前
Java 项目中引入阿里云 OSS SDK
java·数据库·阿里云
叶 落38 分钟前
Centos 修改 yum 源为阿里云
linux·阿里云·centos·yum
玖石书38 分钟前
[c++]Linux平台下的动态库加载技术详解
linux·c++·算法