Linux进程

冯诺依曼体系结构

计算机如笔记本、服务器等,大部分都遵守冯诺依曼体系结构。计算机都是由一个个的硬件组成的。

在这里插入图片描述

输入设备:包括键盘,鼠标,扫描仪等。

中央处理器(CPU):含有运算器和控制器等。

输出设备:显示器,打印机等。

这里的存储器指的是内存。不考虑缓存情况,这里的CPU能且只能对内存进行读写,不能访问外设(输入或输出设备)(数据层面)。外设要输入或者输出数据,也只能写入内存或者从内存中读取。即所有设备都只能直接和内存打交道

操作系统

任何计算机系统都包含一个基本的程序集合,称为操作系统(Operator System 即OS)。

操作系统包括内核(进程管理,内存管理,文件管理,驱动管理等),其他程序(函数库,shell程序等)。

在这里插入图片描述

设计OS的目的,对下,与硬件交互,管理所有软硬件资源;对上,为应用程序提供一个良好的执行环境。

在这里插入图片描述

在整个计算机软硬件架构中,操作系统的定位是:一款纯正的“搞管理”的软件。

总体思路是先描述再组织。计算机先把数据信息封装成结构体,再用高效数据结构把这些结构体对象组织起来,从而管理硬件。

在开发角度,操作系统对外会表现为一个整体,但是会暴露自己的部分接口,供上层开发使用,这部分由操作系统提供的接口,叫做系统调用

系统调用在使用上,功能比较基础,对用户的要求相对也比较高,所以,有心的开发者可以对部分系统调用进行适度封装,从而形成。有了库,就很有利于更上层用户或者开发者进行二次开发。

进程

概念

课本概念:程序的一个执行实例,正在执行的程序等。内核观点:担当分配系统资源(CPU时间,内存)的实体。

可执行程序先在磁盘中,执行时加载到内存,内存中对应有一个对象记录可执行程序各种属性,这些对象由数据结构管理。进程 = 内核数据结构 + 可执行程序的代码和数据

在这里插入图片描述

描述进程PCB与task_struct

进程信息被放在⼀个叫做进程控制块的数据结构中,可以理解为进程属性的集合,即PCB(process control block)。Linux下的PCB是task_struct

在 Linux 中描述进程的结构体叫做 task_struct 。task_struct 是 Linux 内核的⼀种数据结构类型,它会被装载到RAM(内存)里并且包含着进程的信息。

task_struct包含的内容如下:

  1. 标示符:描述本进程的唯一标示符,用来区别其他进程。
  2. 状态:任务状态,退出代码,退出信号等。
  3. 优先级:相对于其他进程的优先级
  4. 程序计数器:程序中即将被执行的下一条指令的地址。
  5. 内存指针:包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针。
  6. 上下文数据:进程执行时处理器的寄存器中的数据。
  7. I/O状态信息:包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。
  8. 记账信息:可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。
  9. 其他信息……

所有运行在系统里的进程都以 task_struct 双链表的形式存在于内核。

查看进程

man man 查看手册。1号手册查看可执行程序和命令,2号手册查看系统调用,3号手册查看库函数。

在这里插入图片描述

man getpid 查看getpid的手册,getpid是系统调用。进程的pid是递增的。

在这里插入图片描述

哪个进程调用getpid,就可以得到哪个进程的标示符。

我们写的程序运行起来也是进程,为了验证,准备如下代码和Makefile。

在这里插入图片描述

运行后便能看到进程标示符。

在这里插入图片描述

ps axj 查看当前系统里进程有哪些。执行myprocess,再打开一台机器,ps axj | grep myprocess 可以查看myprocess的进程。

在这里插入图片描述

Linux中想同时执行两个命令,可以用 ;&& 链接两个命令。如查看某个进程时带上第一行的表头 ps axj | head -1;ps axj | grep myprocessps axj | head -1 && ps axj | grep myprocess

在这里插入图片描述

杀掉进程可以用 Ctrl +C ,也可以用 kill -9 进程pid

在这里插入图片描述

进程的信息可以通过 /proc 系统文件夹查看。

在这里插入图片描述

名称为数字的目录保存进程的信息。

运行myprocess,ls /proc/进程pid -dl 可以找到对应目录,得到对应进程的信息。

在这里插入图片描述
在这里插入图片描述

把进程杀掉后就找不到对应目录了。

在这里插入图片描述

ls /proc/进程pid -l 查看进程信息。其中重点是exe和cwd。

在这里插入图片描述

exe记录了这个进程对应可执行文件的绝对路径。运行myprocess,在另一台Linux机器中删除myprocess,进程仍在跑。

在这里插入图片描述

但是此时会有警告。

在这里插入图片描述

这说明我们删除的是磁盘上的myprocess,进程启动时myprocess的拷贝已经在内存了,所以程序仍在运行。

cwd即current work dir ,记录当前工作目录。如果在myprocess.c加一行代码 fopen("hello.txt","a"),进程就能在当前路径下创建该文件。

更改进程所处当前路径,查手册man chdir

在这里插入图片描述

chdir也是系统调用。输入如下代码,更改进程路径。

在这里插入图片描述

可以看到,进程的当前工作路径被更改,那么文件hello.txt也被创建在/home/dx 目录下。

在这里插入图片描述

与getpid()类似,getppid()能取得进程的父进程的pid。

在这里插入图片描述

可以看到父进程的pid是不变的。

在这里插入图片描述

这个进程的父进程是bash,OS会给每一个登录用户分配一个bash。bash就是命令行解释器,其本质也是一个进程。像命令ls、top、pwd、mkdir、touch等,都是bash的子进程。

通过系统调用创建进程——fork初步

man fork 查看手册,fork用于创建子进程,是一个系统调用。

在这里插入图片描述

输入如下代码。因为fork创建了子进程,父进程和子进程两个执行流从fork()这一行向下执行,所以执行完fork后,原本的进程继续执行第二个printf,创建的子进程也会执行第二个printf。

在这里插入图片描述

fork()创建子进程,就要创建子进程的PCB,子进程的PCB拷贝自父进程PCB,二者大部分进程属性相同,子进程也会指向父进程指向的数据和代码。

fork()的返回值如下。如果创建子进程成功,子进程的pid返回给父进程,0返回给子进程。

在这里插入图片描述

在这里插入图片描述

为什么fork()返回给父子进程不同的返回值?因为父进程有多个子进程,子进程只有一个父进程,类似多叉树,父进程为了管理子进程所以要接收子进程的id,所以给父进程返回子进程的pid,给子进程返回0。

为什么一个函数会返回两次?调用fork(),fork内部完成子进程的创建,两个进程各自独立执行后续代码,拿到不同返回值,分别完成一次函数返回。

在这里插入图片描述

进程具有独立性。比如父进程挂掉不会影响子进程。代码只能读,不怕被修改。但数据父子进程共享,父子进程任何一方修改数据,OS会在底层内存中把被修改的数据拷贝一份,然后让进程去修改这个拷贝,即写时拷贝

在这里插入图片描述

上述代码定义全局变量gval,在子进程修改gval,然后父子进程分别打印gval。可以看到,对于同一个全局变量gval,子进程的gval会变化,而父进程的gval不变,这就是发生了写时拷贝。

在这里插入图片描述

进程状态

状态在kernel源代码里的定义:

/*
*The task state array is a strange "bitmap" of
*reasons to sleep. Thus "running" is zero, and
*you can test for combinations of others with
*simple bit tests.
*/
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 */
};

R运行状态(running):并不意味着进程⼀定在运行中,它表明进程要么是在运行中,要么在运行队列里

S睡眠状态(sleeping):即阻塞状态。意味着进程在等待事件完成(这里的睡眠有时候也叫做可中断睡眠 interruptible sleep)。S状态的进程可以被OS杀掉。

D磁盘休眠状态(Disk sleep):也是一种阻塞状态,有时候也叫不可中断睡眠状态(uninterruptible sleep),在这个状态的进程通常会等待IO的结束。OS不能杀掉D状态的进程。

T停止状态(stopped):可以通过发送 SIGSTOP 信号给进程来停止T进程。这个被暂停的进程可以通过发送 SIGCONT 信号让进程继续运行。

X死亡状态(dead):这个状态只是一个返回状态,你不会在任务列表里看到这个状态。

名词解释:

  • 阻塞:进程等待 IO 设备、互斥锁、信号等资源就绪,主动放弃 CPU,驻留内存并加入对应等待队列。

  • 挂起:系统物理内存资源紧张时,内核将长时间不活跃的进程完整换出至 swap 交换分区;进程原有状态不变,必须换回内存后才能参与 CPU 调度。

输入如下代码,gcc编译后运行,再另开一台Linux机器,输入命令查看myprocess进程 while :; do ps axj | head -1; ps axj | grep myprocess; sleep 1; done

在这里插入图片描述

从STAT可以看到,myprocess进程的状态为睡眠状态,但我们的明明正在运行程序,这是因为CPU 速度远快于终端输出 IO,绝大多数时间内进程都在等显示器输出,极少时刻短暂处于 R 状态,ps 抓拍几乎只能看到 S。

注释掉printf,即可看到myprocess进程处于R运行状态。

在这里插入图片描述

状态的加号表示进程在前台运行,这种情况下会阻塞命令行输入,./myprocess & 可以让进程在后台运行,此时命令行能正常输入,状态不带加号,杀掉进程需要使用命令 kill -9 pid

在这里插入图片描述

观察S状态,使用如下代码。利用scanf阻塞进程,使进程不被调度,等待键盘输入。S状态可以被OS杀掉。

在这里插入图片描述

t状态是暂停状态,debug模式下调试器在断点处暂停代码运行,本质是进程被暂停了。

在这里插入图片描述

T状态,进程不是被调试器暂停,而是被用户使用键盘Ctrl+Z暂停。

在这里插入图片描述

kill -19 pid 也可以暂停进程,使其为T状态。kill -18 pid 使进程从T状态恢复为运行状态,此时需要 kill -9 pid 杀掉进程。kill l 查看对应的选项。

在这里插入图片描述

Z状态即僵尸状态,是一个比较特殊的状态,当进程退出并且父进程没有读取到子进程退出的返回代码时就会产生僵尸进程。僵尸进程会以终止状态保持在进程表中,并且会一直在等待父进程读取退出状态代码。所以,只要子进程退出,父进程还在运行,但父进程没有读取子进程状态,则子进程进入Z状态。

在这里插入图片描述

可以看到有两个./test,分别是父进程和子进程。当子进程结束,父进程仍在运行,子进程就变为Z状态。

父进程如果一直不读取,那子进程就一直处于Z状态。维护退出状态本身就是要用数据维护,也属于进程基本信息,所以保存在task_struct(PCB)中,即Z状态⼀直不退出,PCB⼀直都要维护。如果子进程一直不被回收,则会导致内存泄漏。

父进程先退出,子进程就称之为孤儿进程。此时子进程的父进程为1号进程,即systemd进程,子进程退出后就由systemd进程回收。子进程变成孤儿进程后就变为了后台进程,需要用 kill -9 pid 才能杀掉。

在这里插入图片描述

top 可以看到systemd进程。

在这里插入图片描述

进程优先级

CPU资源分配的先后顺序,就是指进程的优先级。优先级高的进程有优先执行权利。配置进程优先级对多任务环境的Linux很有用,可以改善系统性能。

优先级是一个数字,是PCB的一个属性,值越低优先级越高。优先级可以变化,但变化幅度不会太大。

在这里插入图片描述

UID:即user id,每个用户都有自己的UID,Linux靠UID识别用户。ls -nl 可以查看到用户UID。

在这里插入图片描述

PRI:进程的优先级,默认为80。范围[60,99]。优先级设置不合理,会使优先级低的进程长时间得不到CPU资源,导致进程饥饿

NI:即nice值,进程优先级的修正数据。

PRI = 80 + NI,一般通过修改NI来修改优先级。

使用 top 命令,输入 r ,表示更改NI,输入进程pid,再输入NI的新值,如下图所示,PRI也就被更改了。按 q 退出。NI更改为负值时,需要 su - 切换成root用户才能操作。

在这里插入图片描述

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

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

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

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

进程切换与调度

CPU上下文切换:其实际含义是任务切换,或者CPU寄存器切换。当多任务内核决定运行另外的任务时,它会保存正在运行任务的当前状态,即CPU寄存器中的全部内容。这些内容被保存在任务自己的堆栈中,入栈工作完成后就把下一个将要运行的任务的当前状况从该任务的栈中重新装入CPU寄存器,并开始下一个任务的运行。

在这里插入图片描述

时间片分时操作系统中,CPU 分配给单个任务连续运行的固定时间长度(本质是计数器),是时间片轮转调度的核心。CPU 轮流给就绪队列里每个进程分配一段时间,时间耗尽就触发上下文切换,换下一个进程运行,实现宏观上多任务同时运行。时间片长短可配置,片长越大,上下文切换次数越少、系统开销越小,响应速度变慢。

Linux2.6内核进程O(1)调度队列:

调度的目的是挑队列,挑进程。

每个CPU有一个runqueue,多核则需做负载均衡。

active指针永远指向活动队列,expired指针永远指向过期队列。

活动队列:时间片还没有结束的所有进程都按照优先级放在该队列。nr_active表示总共有多少个运行状态的进程。queue[140]中一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,数组下标就是优先级。bitmap[5]为位图,一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32=160个bit位表示队列是否为空,这样,便可以大大提高查找效率。

过期队列:与活动队列结构一样,过期队列上放置的进程都是时间片耗尽的进程,当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算。

在这里插入图片描述

调度流程:从 active 队列按 bitmap 取最高优先级进程运行(队头进程),时间片耗尽后将进程移入 expired 队列;active 队列上的进程会越来越少,expired 队列上的进程会越来越多,active 无进程时,互换 active/expired 指针,继续调度。

在系统中,通过位图查找一个最合适调度的进程的时间复杂度是一个常数,不随着进程增多而导致时间成本增加,我们称之为进程调度O(1)算法。

命令行参数和环境变量

环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数。例如我们在编写C/C++代码的时候,在链接时,从来不知道我们的所链接的动态静态库在哪里,但是照样可以链接成功,生成可执行程序,原因就是有相关环境变量帮助编译器进行查找。环境变量通常具有某些特殊用途,还有在系统当中通常具有全局特性。

在编写C/C++代码时,main函数也是有参数的,int main(int argc,char* argv[]) 。argv是一个字符指针数组,argc为数组长度。准备如下代码和Makefile,可以观察到argv的用途。

在这里插入图片描述

可以看到,命令行被空格分割成字符串,argv的指针指向这些字符串。argv[0]为程序名,argv的元素都是命令行参数。命令行参数的用途是让一个程序可以通过不同的选项实现不同的子功能,指令选项也就是以此实现的。

在这里插入图片描述

当我们使用Linux指令时,进程会有argc表来支持选项功能。

要执行一个程序,必须先找到它。系统中存在环境变量来帮助系统找到二进制文件。系统不能从默认路径找到我们的code,所以我们要./code才能运行程序。系统一般会从 /usr/bin 下查找指令,如果把code拷贝到该目录下,运行时输入 code 即可。

在这里插入图片描述

Linux中环境变量PATH记录了默认指令搜索路径。env 查看所有环境变量。

在这里插入图片描述

echo $环境变量 可以根据环境变量名查看一个环境变量

在这里插入图片描述

其中冒号是分隔符,分隔出路径,当输入一个指令时,系统先从第一个路径下搜索,没找到则去下一个路径搜索。

所以,如果把code的路径添加到PATH中,使用时也可以直接用 code

直接赋值 PATH=code所在路径 会覆盖环境变量的内容,导致其他指令无法正常使用。

在这里插入图片描述

PATH是在内存上的,退出后重新登录,PATH就会恢复。

PATH=$PATH:code路径 则不会覆盖,在后面接上code路径。

在这里插入图片描述

bash有两张表,命令行参数表和环境变量表,环境变量表是字符指针数组,指向环境变量的字符串如 "PATH=/usr/local/bin:/usr/bin:/usr/local/sbin:/usr/sbin:/home/dx/.local/bin:/home/dx/bin" ,输入指令 ls -l bash先根据命令行参数表解析,再去环境变量表找到环境变量,按照路径搜索指令。

在这里插入图片描述

环境变量最开始是从配置文件中来的。配置文件在家目录下 .bash_profile.bashrc

在这里插入图片描述

cd ~ ,打开这两个配置文件。

在这里插入图片描述

把code所在路径放到配置文件中,退出后重新登录,就能永久地输入 code 直接运行code。

在这里插入图片描述

常见的环境变量HOME : 指定用户的主工作目录,即用户登陆到Linux系统中时,默认的目录。

在这里插入图片描述

环境变量 USER 记录当前用户,LOGNAME 记录登录用户。HISTSIZE 最多记录历史命令条数,如上图为3000,即最多记录历史命令3000条。HOSTNAME 当前主机名。SSH_TTY 当前是哪个设备。PWD 记录当前Shell所在路径。OLDPWD 记录上一次Shell所在路径,所以 cd - 能回到上一次所在路径。

export 设置一个新的环境变量unset 环境变量 删除环境变量

在这里插入图片描述

main函数最多有3个参数,即 main(int argc,char* argv[],char* env[]) 。当我们写的代码编译为可执行程序并运行后,它的父进程bash会传过来命令行参数表和环境变量表,即argv[],env[],这两个指针数组的最后一个元素都为空指针。

使用如下代码,便可以看到环境变量。

在这里插入图片描述

export 添加环境变量,重新运行code,能显示出我们添加的环境变量,进一步说明我们添加的环境变量能被子进程拿到。环境变量可以被子进程继承

在这里插入图片描述

函数 getenv 获取环境变量,找到环境变量则返回其地址,否则返回空。

在这里插入图片描述

我们可以写一个只有自己能执行的文件,即使是root也不能执行。

在这里插入图片描述

environ 是一个全局字符指针数组,用来存放当前进程全部环境变量

在这里插入图片描述

bash会记录两套变量,环境变量和本地变量,本地变量不会被子进程继承,只会在bash内部使用。

在这里插入图片描述

set 显示环境变量和本地变量unset 删除本地变量。

在这里插入图片描述

此时用 export i 也可以把 i 添加到环境变量。

export 是内建命令,使用时不需要创建子进程,bash自己调用函数或系统调用完成。

程序地址空间

在这里插入图片描述

这是我们在学习C/C++时接触到的空间分布图。

准备如下代码,可以看到,对于全局变量gval,父子进程输出的地址一样,但变量内容不一样。

在这里插入图片描述

变量内容不一样,所以父子进程输出的变量绝对不是同一个变量。但地址值是一样的,说明该地址绝对不是物理地址。在Linux地址下,这种地址叫做虚拟地址。我们在用C/C++所看到的地址,全部都是虚拟地址。物理地址,用户一概看不到,由OS统一管理。

一个进程有一个虚拟地址空间和一套页表,页表用做虚拟地址和物理地址(物理内存)的映射。虚拟地址空间的宽度是1字节,32位机器下有 232 个地址,即4G,64位下有 264 个地址。32位下,0x0 ~ 0xFFFFFFFF(4G)为虚拟地址空间,0x0 ~ 0xBFFFFFFF(3G用户空间)为进程地址空间。

描述Linux下进程的地址空间的所有的信息的结构体是 mm_struct (内存描述符)。每个进程只有一个mm_struct结构,在每个进程的 task_struct 结构中,有一个指向该进程的mm_struct结构体指针。

在这里插入图片描述

创建全局变量g_val,其在父进程的地址为0x111111。调用fork()创建子进程,子进程创建它的task_struct,子进程的mm_struct发生浅拷贝,与父进程指向同一个mm_struct对象。

父子各自拥有一套独立页表,但两张页表中虚拟地址0x111111都映射到同一块物理内存 0x112233,页面被标记为只读,实现地址空间资源共享。当父子进程的任意一方修改g_val的值时,内核分配一块全新物理内存0x223344,把原物理页上g_val的数据完整拷贝到新物理页。之后把修改g_val的进程的页表的映射关系更改,内核为子进程创建专属、独立的 mm_struct,父子不再共享地址空间管理结构。

mm_struct通过记录起始地址和终止地址来实现对虚拟地址空间的区域划分。如mm_struct中有成员变量 unsigned long start_code 记录代码段的起始地址,unsigned long end_code 记录代码段的终止地址。

由 task_struct 到 mm_struct ,进程地址空间的分布情况:

在这里插入图片描述

struct mm_struct
{
    /*...*/
    struct vm_area_struct *mmap; /* 指向虚拟区间(VMA)链表 */
    struct rb_root mm_rb;    /* 红黑树 */
    unsigned long task_size; /*具有该结构体的进程的虚拟地址空间的⼤⼩*/
    /*...*/
    // 代码段、数据段、堆栈段、参数段及环境段的起始和结束地址。
    unsigned long start_code, end_code, start_data, end_data;
    unsigned long start_brk, brk, start_stack;
    unsigned long arg_start, arg_end, env_start, env_end;
    /*...*/
}

每一个进程都会有自己独立的 mm_struct ,操作系统需要将这么多进程的 mm_struct 组织管理起来。虚拟空间的组织方式有两种:

  1. 当虚拟区间较少时采取链表,由mmap指针指向这个链表。
  2. 当虚拟区间多时采取红黑树进行管理,由mm_rb指向这棵树。

Linux内核使用 vm_area_struct 结构来表示一个独立的虚拟内存区域(VMA),由于每个不同质的虚拟内存区域功能和内部机制都不同,因此一个进程使用多个vm_area_struct结构来分别表示不同类型的虚拟内存区域。上面提到的两种组织方式使用的就是vm_area_struct结构来连接各个VMA,方便进程快速访问。

struct vm_area_struct {
    unsigned long vm_start; //虚拟内存区起始
    unsigned long vm_end;   //虚拟内存区结束
    struct vm_area_struct *vm_next, *vm_prev; //前后指针
    struct rb_node vm_rb;   //红黑树中的位置
    unsigned long rb_subtree_gap;
    struct mm_struct *vm_mm; //所属的 mm_struct
    
    pgprot_t vm_page_prot;
    unsigned long vm_flags; //标志位
    struct {
        struct rb_node rb;
        unsigned long rb_subtree_last;
    } shared;
    struct list_head anon_vma_chain;
    struct anon_vma *anon_vma;
    const struct vm_operations_struct *vm_ops; //vma对应的实际操作
    unsigned long vm_pgoff; //文件映射偏移量
    struct file * vm_file;  //映射的⽂件
    void * vm_private_data; //私有数据
    atomic_long_t swap_readahead_info;
#ifndef CONFIG_MMU
    struct vm_region *vm_region; /* NOMMU mapping region */
#endif
#ifdef CONFIG_NUMA
    struct mempolicy *vm_policy; /* NUMA policy for the VMA */
#endif
    struct vm_userfaultfd_ctx vm_userfaultfd_ctx;
} __randomize_layout;

在这里插入图片描述

页表除了做虚拟地址和物理地址的映射,还有记录对物理地址的读写等权限。例如代码 const char* s = "Hello World"; *s='h' 常量字符串是不可以被修改的,当我们通过页表去访问物理内存打算修改s时,系统会根据页表中对应的权限进行阻止。

在这里插入图片描述

地址空间和页表由OS创建并维护,凡是想使用地址空间和页表进行映射,也⼀定在OS的监管之下进行访问。这样就保护了物理内存中的所有的合法数据,包括各个进程以及内核的相关有效数据。

因为有虚拟地址空间的存在和页表的映射存在,物理内存中可以对未来的数据进行任意位置的加载。物理内存的分配和进程的管理就可以做到脱钩,实现进程管理模块和内存管理模块的解耦合

因为有虚拟地址空间的存在,所以我们在C、C++上new,malloc空间的时候,其实是在虚拟地址空间上申请,物理内存甚至可以一个字节都不分配。当真正进行对物理地址空间访问的时候,才执行内存的相关管理算法,帮助申请内存,构建页表映射关系(延迟分配),这是由操作系统自动完成,用户包括进程完全0感知。

因为页表的映射的存在,程序在物理内存中理论上就可以任意位置加载。它可以将地址空间上的虚拟地址和物理地址进行映射,在进程视角所有的内存分布都可以是有序的。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐