虚拟内存以及进程的虚拟内存分布(第六章)

2023-05-16

在早期的计算机中,程序都是直接运行在物理内存上的,意思是运行时访问的地址都是物理地址,而这要求程序使用的内存空间不超过物理内存的大小

在现代计算机操作系统中,为了提高CPU的利用率计算机同时运行多个程序,为了将计算机上有限的物理内存分配给多个程序使用,并做到隔离各个程序的地址空间和提高内存利用率,操作系统应用虚拟内存机制来管理内存

本文介绍的是一些与虚拟内存相关的概念,包括虚拟内存和物理内存之间的映射,一个进程的虚拟内存空间的划分等。
 

目录

1.物理内存 vs 虚拟内存

2.物理内存空间 和 虚拟内存空间

3.4GB虚拟内存

cpu位宽 vs cpu的地址总线位宽

4.虚拟内存的地址空间划分

1)Windows 系统下— NULL 指针区+用户区+ 64KB 禁入区+内核区

1)NULL指针区和64KB禁入区:略

2)用户区每个进程私有使用的,称为用户地址空间。

3)内核区是所有进程共享的,称为系统地址空间。

2)Linux下和Windows下的差不多——内核空间,用户空间和保留区

1.保留区:

2.可执行文件映像,堆,栈,动态库

3) 详细介绍下栈空间(Stack)——函数调用:

通过例子1看汇编:

例子2:(VC9,i386,Debug模式)

PS1:编译器 生成的函数 的 标准进入和退出指令序列 

PS2:编译器 实现的hook技术

PS3:函数调用之调用惯例

PS4:函数调用之返回值的传递

PS5:函数调用之C++对象

4)堆空间(heap)——动态申请内存:

5.虚拟地址和物理地址的映射

6.物理内存和硬盘之间的置换

7.虚拟内存的重要性

8.进程的虚存空间分布——装载(《程序员的自我修养-链接装载库》第6.4节)

readelf -S链接视图——25个section头

执行视图:7个program头——程序头表记录着程序头

VMA

Stack VMA[stack]

动态链接时的进程堆栈初始化信息

9.windows打开任务管理器

内存项含义

1.工作集(内存)Working Set = 内存(专用工作集)+ 内存(共享工作集)【第2列=第3列+第4列】

2.提交大小 Comitted Memory——进程独占的内存

3.PROCESS_MEMORY_COUNTERS  类 和 GetProcessMemoryInfo 函数

2 其他项目含义

cpu时间是cpu在这个进程用的总时间。

磁盘有关的问题:

分析内存问题:

其他观察进程的exe

10.硬件概念--存储器芯片。


文章比较长,也比较杂。可以分次阅读。【修改记录:2023.5.6修改看了1)】

1)1,2,3,4(排除栈空间函数调用的例子),5,6,7

2)8

3)9,10

3)4中栈空间函数调用的例子

1.物理内存 vs 虚拟内存

物理内存就是内存条,实实在在的内存,即RAM。

虚拟内存是内存管理中的一个概念,是操作系统管理每个进程的内存空间的一个概念。

对于一个进程来说,虚拟内存是进程运行时所有内存空间的总和,包括共享的,非共享的、存在物理内存中,存在分页内存中(分页后面会介绍)、提交的,未提交的。(上面这些不是互斥的概念哈,注意标点符号)

【进程运行起来以后,虚拟内存映射=物理空间(PP)+硬盘空间(DP)+未使用使用映射的。】

硬盘空间(DP):虚拟内存映射的可能有一部分不在物理内存中,在其他介质中,比如硬盘。举个例子,当你的程序需要创建一个1G的数据区,但是此时剩余500M的可用物理内存了,那么此时势必不是所有数据都能一起加载到内存(物理内存)中,势必有一部分数据要放到其他介质中(比如硬盘),待进程需要访问那部分在其他介质中的数据时,再通过调度进入物理内存。】

ps:有些概念不懂,继续看就行,后面都会讲到一些,就慢慢理解了。


2.物理内存空间 和 虚拟内存空间

怎么标识每个内存位置呢,用地址来表示。

物理内存大小组成的地址空间就叫物理内存空间

物理内存空间表示的是实实在在的RAM物理内存,其地址空间可以看成一个由 M 个连续的字节大小的单元组成的数组,每个字节都有一个唯一的物理地址。

(其实,还有一些io设备会映射到物理空间,但是这里就先忽略了)

【存储单元,也就是每个字节都有其地址,cpu进行访问的时候需要知道其地址。M就是RAM的大小】

虚拟内存大小组成的地址空间就叫虚拟内存空间

跟物理内存一样,也是有地址空间的,用地址标识哪个内存位置,也看成一个连续的字节大小的单元组成的数组。

虚拟内存空间实际上并不存在的,需要的只是虚拟内存空间到物理内存空间的映射关系的一种数据结构。当然有时候并不是都映射到物理内存中,前面说过还有其他介质中

上面说的数组的大小,就是地址空间的长度

即地址空间是一个抽象的概念,可以想象成一个很大的数组,每个数组的元素是一个字节(就是一个地址,存一个字节的内容),而这个数组的大小就是由地址空间的地址长度决定。一般画图也是这么画的。

比如16G的物理内存条,具有0~0x3FFFFFFFF(16G)的地址长度的寻址能力。

【另一方面,cpu的地址总线位宽决定了可以直接进行寻址物理内存空间最大值

4G虚拟内存,具有0~0xFFFFFFFF的地址长度的寻址能力。(为什么举例4GB虚拟内存,因为每个进程的虚拟内存空间是4GB,32位操作系统中)

在一个系统中,物理内存空间只有一个,但是虚拟内存空间有很多个(运行着多个进程)每个虚拟内存空间都有必须有一个映射关系对应于物理内存。

【在进程启动的时候会建议一个映射关系,在运行中维护这个关系,后面会讲】

对于一般程序而言,虚拟内存空间中的很大部分的都是未使用的,【虚拟内存是4G的空间】

每个虚拟内存空间往往只能映射到很少一部分物理空间上

每个物理页(将整个物理空间划分成多个大小相等的页)可能只是被映射到一个虚拟地址空间中,也有可能存在一个物理页被映射多个虚拟地址空间中,那么这些地址空间将共享此页面(这时,如果在一个虚拟地址空间改写了此页面的数据,这在其他的虚拟地址空间也可以看到变化)


3.4GB虚拟内存

操作系统(32位)会为每一个新创建的进程分配一个 4GB 大小的虚拟内存,从0到2^32-1。

这里说的分配4GB的虚拟内存并不是分配4GB的空间,而是创建一个映射表。

映射表的设计有一级页表二级页表结构。

这个映射表肯定加载在物理内存中,理想上,这个映射表是设计得当然是越小越好了,因为每个进程都需要这么一个映射表

之前一直说是4G的虚拟地址空间,那么为什么是分配一个4GB的虚拟地址空间,因为在32位操作系统一个32位的程序的一个指针长度是 4 字节,

(指针即为地址,指针里面存储的数值被解释成为内存里的一个地址。64位程序指针是64位,寻址能力2^64-1)

那么4 字节指针(地址)的寻址能力是从 0x00000000~0xFFFFFFFF ,最大值 0xFFFFFFFF 表示的即为 4GB 大小的容量。

那么这个虚拟空间地址大小 当然也是和 实际物理内存RAM大小没有关系的。

一个进程的虚拟内存的大小是由操作系统的位宽程序的位数(是32位还是64位)决定的。

64位的cpu可以支持32位的操作系统,也可以支持64位的操作系统。

扯一个别的:cpu位宽cpu地址总线位宽

cpu位宽 vs cpu的地址总线位宽

cpu位宽不等于cpu的地址总线位宽

16位cpu(cpu位宽是16位)地址总线位宽可以是20位

32位cpu地址总线位宽可以是36位

64位cpu的地址总线位宽可以是36位或者40位(cpu能够寻址的物理地址空间为64GB或者1T)。

1.我们说的16位cpu,32位cpu,64位cpu,指的都是cpu的位宽,表示的是一次能够处理的数据宽度,即一个时钟周期内cpu能处理的2进制位数,即分别是16bit,32bit和64bit。不是地址总线的数目。

(那么是谁决定了cpu可以处理的数据宽度呢?通用寄存器的宽度决定了cpu可以直接表示的数据范围。我们说的16位cpu,32位cpu,64位cpu,指的就是通用寄存器的位数(宽度)。见 汇编语言 那篇文章)

2.cpu的地址总线位宽决定了可以直接进行寻址物理内存空间

所以如果cpu的址总线位宽是32位的,也就是它可以寻址能力就是0~0xFFFFFFFF(4G)的物理内存空间。(36位或者40位,它们寻址的物理地址空间为64GB或者1T

虽然如果你的计算机上只装了512M的内存条,物理地址的有效部分只有0x00000000~0x1FFFFFFF,其他部分都是无效的物理地址

(这里无视一些外部的I/O设备映射到物理空间。)

cpu访问任何存储单元必须知道其物理地址,所以在一定程度上,cpu的地址总线宽度影响了最大支持的物理内存RAM大小。

32位系统最大只能支持4GB内存之由来 - Matrix海子 - 博客园


4.虚拟内存的地址空间划分

1)Windows 系统下— NULL 指针区+用户区+ 64KB 禁入区+内核区

为了高效的调用和执行操作系统的各种服务,Windows会把操作系统的内核数据和代码(内核提供了各种服务供应用程序使用)映射到所有进程的进程空间中。即内核态,也称为系统空间,也可以叫做系统空间。

所以内核态只有一个,会映射到所有进程的虚拟内存空间中。从这个角度来看,用户空间是每个进程独立的,内核空间是共享的。

下面这两个图可以看的比较清楚。来自《软件调试 卷2 windows平台调试》,如果没记错的话。

1)NULL指针区64KB禁入区:略

2)用户区每个进程私有使用的,称为用户地址空间。

用户区存放的是程序代码和数据, 堆, 共享库, 栈

默认的32位windows配置下是2GB+2GB,称为2GB模式。可以修改windows配置,可以设置3GB用户地址空间+1GB的系统地址空间称为3GB模式

(但是现在64位操作系统,是一个更大的数字了,具体不造) ,

如上图:(以下都是用户态的,用户态的,用户态的,ntdll.dll是用户态的,而且都是共享,每个进程都是同一个虚拟地址的。一些固定的地址。)

0x80000000附近的:

系统库

ntdll.dll:

windows操作系统用户层的最底层的dll,(从上上个图中也可以看出来)

负责windows子系统与windows内核之间接口,比如堆管理器的核心接口,HeapCreate、HeapAlloc、HeapFree和HeapDestroy,向操作系统申请内存的时候WindowsAPI——VirtualAlloc进行申请。

ntdll.dll是NT内核派驻到用户空间的领事馆

ntdll.dll是用户态内核态沟通的桥梁。

用户空间的代码通过这个dll,来调用内核空间的系统服务

操作系统会在启动阶段将这个加载到内存中,并把他映射到进程空间的同一个虚拟地址

0x10000000:如运行时库(msvcr90d.dll,microsoft的c运行时库,运行时库这这篇文章介绍过

0x00400000:可执行程序映像exe,即程序代码和数据(为什么叫映像,在这个文章介绍过

stack栈的位置:在0x00300000exe后面都有分布。因为有多少个线程就有多少个栈,线程默认栈大小1MB,也可以启动线程的时候自行设定大小。(所以栈,是相对于线程来说的,这在调试的时候可能看出来)

heap堆的位置:在剩下的空间中进行分配。

3)内核区是所有进程共享的,称为系统地址空间

内核区保存的是系统线程调度内存管理设备驱动等数据,这部分数据供所有的进程共享以及操作系统的使用——程序在运行的时候处于操作系统的监管下,监管进程的虚拟空间,当进程进行非法访问时强制结束程序。

(进程只能使用操作系统分配给进程的虚拟空间。错误提示“进程因非法操作需要关闭”就是访问了未经允许的地址。)

2)Linux下和Windows下的差不多——内核空间,用户空间和保留区

1.保留区

对内存中收到保护而禁止访问的内存区域的总称。在大多数操作系统中,极小的地址都是不允许访问的,如果访问了就会抛出错误。如NULL。 通常C语言将无效指针赋值为0,因为0地址上正常情况下不可能有有效的可访问的数据的。

2.可执行文件映像,堆,栈,动态库

1)可执行文件映像:装载的时候涉及到。可执行文件的装载,进程和线程,运行时库的入口函数(第六章)_u012138730的专栏-CSDN博客_运行时动态装载链接至少需要用到以下哪些函数

2):应用程序动态分配的 通常在栈的下方。地址从低到高分配。

3)动态链接库(共享库)映射区。动态链接的时候介绍Linux之前是默认从0x4000000开始装载的,但是在linux内核2.6中,共享库的装载地址已经挪到了靠近栈的位置,即0xbfxxxxxx附近。。

4):用于维护函数调用的上下文。有函数调用就要用到栈。地址从高到低分配。

3) 详细介绍下栈空间(Stack)——函数调用:

虚拟内存空间的一段连续的地址。里面的内容是 调用函数的活动记录,记录目前为止函数调用之前的那些函数的维护信息

函数调用,维护信息,堆栈帧,活动记录

一个函数的活动记录ebp 和 esp 划定范围,如下图所示:

(其中一个函数的参数函数返回地址用ebp+x 可以得到):

ebp和esp都是寄存器,详情看汇编指令和寄存器_u012138730的专栏-CSDN博客。

以上就是一个堆栈帧

bp指向当前的堆栈帧底部

sp指向当前的栈帧顶部

随着函数调用的进行以及返回,bp和sp也就是随着改变,指向新的堆栈帧或者旧的堆栈帧。

如上图,一条活动记录包括一下几个方面:

  • 1)函数的返回地址(地址 ebp + 4)和函数的参数(地址 ebp + 8,ebp + 12等等)、

如果对应到汇编指令中,

函数的返回地址是在call指令中push 的

函数的输入实参是在call之前的指令压入的

  • 2)调用该函数之前的ebp的值(旧ebp值,上一个堆栈帧的底部的地址),目的是为了:这个函数返回的时候ebp的值可以恢复到上一个位置,回到上一个堆栈帧现场。
  • 3)【可选】需要保持不变的寄存器的值(地址 ebp - 4,ebp - 8等等),也就是当进入函数时,会push进去一些需要保存的寄存器的值,等函数退出之前再pop出来。
  • 4)临时数据,局部变量,调试信息——扩大的栈空间中。

通过例子1看汇编:

SimpleSection.c中的func1

看func1函数的反汇编的实现(这里是.AT&T的汇编格式,看文章的最后):——其实就是创建了一个堆栈帧的过程,从ebp开始,不包括输入实参和返回地址。

前面几句的含义写到了图片上,接下去几句:

  • movl $0x0,(%esp) 的含义:

在esp指针寄存器指向的位置存入0x0(其实代表的是第一个参数“%d\n”,那为什是0呢,因为要重定位。这条指令就是printf函数的第一个实参,上一条指令就是printf的第二个参数参数i。相当于从右到左压入实参

(所以printf的两个输入参数就分别存在当前堆栈帧的esp+4 和 esp 中,下面一条语句就是call了再次进入函数调用了,所以这就是之前说的,输入实参是在call之前压入的。)

可以看到.text段(代码段)offset0x10的地方正好是指令movl $0x0,(%esp) 0x0的地址。(上上个图中数数,第16个字节)

需要重定位的符号是.rodata,可以看到.rodata里存的正好就是%d

【.rodata, .data, .bss应该也是链接的时候重定位,跟普通的符号printf和func1一样,因为链接完了以后这些段的VMA已经确定了,就可以填上正确的值了。这里看的是目标文件.o,还没有重定位过

  • call 15<func1+0x15>的含义:

15<func1+0x15>,就是跳转到func1+0x15 这里的的内存的地址进行执行,call指令做了两件事情:

1)把当前指令的下一条指令的地址压入栈中,即函数的返回地址

2)进入新的函数调用了(新的函数开头都是,重复开头的ebp入栈ebp=esp等等,像进入func1一样的——进入printf的例行操作)

  • leave 的含义:(相当于执行了出栈的操作,把栈恢复到函数调用以前的样子

等价于下面两条指令:

movl %ebp %esp  恢复esp=ebp的值,即此时esp指向的是ebp的位置,就一开始的时候那样。

popl %ebp 从栈中弹出值,其实就是旧的ebp的值,赋值给ebp寄存器,即ebp = 旧ebp。弹出旧ebp以后此时esp指向的就是函数的返回地址那个位置了。对应上面活动记录的图。

  • ret 的含义:

等效于以下汇编指令

popl %eip  从栈中取出函数的返回地址,并跳转到该位置执行

这个例子中没有push寄存器的值,退出的时候也就不需要pop回来。

例子2:(VC9,i386,Debug模式)

int foo()
{
       return 123;
}

下面是汇编:(第四步,在debug模式下会加入调试信息,把栈空间上的内容都初始化为CC,两个连续排列的CC就是中文“烫”

例子1是AT&T汇编格式,例子2是Intel汇编格式。

比例子1多了第3,4,6步

下的ps可以不用看,跟本章内容无关只是做个记录。                              

PS1:编译器 生成的函数 的 标准进入和退出指令序列 

不知道不懂蓝色划线的原因。

PS2:编译器 实现的hook技术

钩子技术:

下面是可被替换的函数(FUNCTION)进入指令序列

nop指令占用一个字节,一共5个字节的nop

比如一替换函数(REPLACEMENT_FUNCTION)如下:

替换以后如下:

进入到Function以后,先执行了jmp LABEL(这个jump是近跳指令只占用两个字节,覆盖原来的mov edi,edi)

LABEL下是jmp 到一个新的标签替换函数这个jmp占5个字节,,覆盖原来nop的五个字节

这样就实现了函数的替换了。

说实话,没有很懂,具体实际应用中的流程。

PS3:函数调用之调用惯例

函数声明的时候可以用关键字声明调用惯例默认是cdecl(C语言中):

可以看到调用惯例规定了 :

  • 出栈方:出栈可以是调用方,也可是函数本身(将函数实参压入栈的肯定是调用方)。上面例子中的leave就是调用方执行的。
  • 参数传递:即调用方将实参压入栈序 需要和函数本身 有一致的规定,这样才能取到正确的值。
  • 名字修饰:不同的调用惯例有不同的名字修饰规则,所以,如果不一致的话,就会找不到符号了

不同的编译器同一种调用惯例的实现也不尽相同,比如gcc和vc对于C++的thiscall(p298)

调用惯例调用方被调用方不一致的例子:p299 ,要在动态链接中说明如何链接成功,这里先略了。

例子:

main函数:1 2-调用方对函数参数进行压栈,从右到左,4-调用方对函数参数进行出栈。

f函数也是:栈在调用完以后都栈都恢复到原来的调用之前

vs中设置默认调用惯例:

 

cdecl调用惯例的用途——p337变长参数。

常用的调用约定类型有__cdecl、stdcall、PASCAL、fastcall。除了fastcall可以支持以寄存器的方式来传递函数参数外,其他的都是通过堆栈的方式来传递函数参数的。(这是网上看到的,不是书里面写的)

PS4:函数调用之返回值的传递

上面的——例子2:(VC9,i386,Debug模式)——中可以看到返回值是由寄存器eax来传递的。但是如果返回值大于4个字节,不能存放在eax寄存器中应该怎么办呢——解决办法是:eax指向一个地址,返回值就存放在这个这个地址中。下面是返回是类的例子,其中 big_thing 大小为 128个字节。

typedef struct big_thing
{
	char buf[128];
}big_thing;

big_thing return_test()
{
	big_thing b;
	b.buf[0] = 0;
	return b;
}

int main() {

	big_thing n = return_test();
	return 0;
}

main函数的汇编:

int main() {
01041720  push        ebp  
01041721  mov         ebp,esp  
01041723  sub         esp,258h   ---》开辟258h的栈空间
01041729  push        ebx  
0104172A  push        esi  
0104172B  push        edi  
0104172C  lea         edi,[ebp-258h]  
01041732  mov         ecx,96h  
01041737  mov         eax,0CCCCCCCCh  
0104173C  rep stos    dword ptr es:[edi] 
 ----》把栈空间都初始化为cch,即汉字烫烫烫。。。 96h*4=258h。就是栈空间的大小。

    big_thing n = return_test();
0104173E  lea         eax,[ebp-254h]  ---》lea指令看文章汇编指令和寄存器_u012138730的专栏-CSDN博客
01041744  push        eax   ----》eax值是栈空间的最后一个地址,把eax压入栈,紧接着下面就调用call,说明把这个当作了输入参数 return_test(ebp-254h)
01041745  call        return_test (010410E1h)  
0104174A  add         esp,4  --》函数调用方负责压栈参数还原
0104174D  mov         ecx,20h  
01041752  mov         esi,eax  
01041754  lea         edi,[ebp-1CCh]  
0104175A  rep movs    dword ptr es:[edi],dword ptr [esi] 
---》把eax的内容复制到ebp-1CCh中,ebp-1CCh是栈上的一个临时的地址。20h*4=80h 就是正好128字节就是big_thing的大小。rep movs指令汇编指令和寄存器_u012138730的专栏-CSDN博客
0104175C  mov         ecx,20h  
01041761  lea         esi,[ebp-1CCh]  
01041767  lea         edi,[n]  
0104176D  rep movs    dword ptr es:[edi],dword ptr [esi]  ---》再把临时的地址的内容复制到真正的n中。
    return 0;
0104176F  xor         eax,eax  
}

return_test的汇编

big_thing return_test()
{
01041690  push        ebp  
01041691  mov         ebp,esp  
01041693  sub         esp,148h  ---》开辟148h的栈空间
01041699  push        ebx  
0104169A  push        esi  
0104169B  push        edi  
0104169C  lea         edi,[ebp-148h]  
010416A2  mov         ecx,52h  
010416A7  mov         eax,0CCCCCCCCh  
010416AC  rep stos    dword ptr es:[edi]   ---》初始化148h的栈空间  rep stos  指令见文章 汇编指令和寄存器_u012138730的专栏-CSDN博客
    big_thing b;
    b.buf[0] = 0;
010416AE  mov         eax,1  
010416B3  imul        ecx,eax,0  
010416B6  mov         byte ptr b[ecx],0  ---》假汇编 b其实是栈空间的一个地址 
    return b;
010416BE  mov         ecx,20h  
010416C3  lea         esi,[b]  
010416C9  mov         edi,dword ptr [ebp+8]  
---》ebp+8就是之前main函数调用return_test时,压入了一个作为隐形参数出入到return_test中的,在main函数的栈的地址。 (数据应该是存入 [旧的ebp-254h]的内存地址中了 
010416CC  rep movs    dword ptr es:[edi],dword ptr [esi]  ---》把b的内容复制到ebp+8中
010416CE  mov         eax,dword ptr [ebp+8]  ---》把epb+8中存的地址复制给eax,也就是main函数的中的栈空间的某个地址,也就是返回值。
}

但是如果return_test返回类型太大main中的栈空间也无法满足要求,那么就是会使用一个临时的栈上的内存作为中转,返回值对象就会被拷贝2次。

即如果是函数的返回值大于4字节,调用的时候相当于多传入一个输入参数——一个指针,函数里面的返回值指向传入的这个指针。这个指针赋值给eax。返回以后,调用方通过这个指针获取到真正的返回值来进行使用。

PS5:函数调用之C++对象

#include <iostream>
using namespace std;

struct cpp_obj
{
	cpp_obj()
	{
		cout << "ctor\n";
	}
	~cpp_obj()
	{
		cout << "dtor\n";
	}
	cpp_obj(const cpp_obj& )
	{
		cout << "copy ctor\n";
	}
	cpp_obj& operator=(const cpp_obj& rhs)
	{
		cout << "operator=\n";
		return *this;
	}
};

cpp_obj return_test()
{
	cpp_obj b;
	cout << "before return\n";
	return b;
}

int main() {

	cpp_obj n = return_test();
	return 0;
}

 输出:(断点下在main中的return那里,并且不启用任何优化

把函数return_test()换成(用了返回值优化技术Return Value Optimization RVO优化将对象的拷贝减少一次):

cpp_obj return_test()
{
	return cpp_obj();
}

 输出: 

总结:如果是c++对象的话,有临时对象需要调用构造和析构函数

函数传递大尺寸的返回值,在不同编译器 ,不同平台,不同的编译参数,下都不相同,上述是win10,vs2015下的输出。

4)堆空间(heap)——动态申请内存:

占据虚拟内存空间的绝大部分,在程序运行过程中进行动态的申请,在主动放弃申请的空间之前一直有效。(栈上的数据出了函数就无效了,全局变量放的数据区。数据区属于可执行文件映像里面的,有各种段,数据段,代码段)

linux系统下堆分配,有两个系统调用(系统调用就是操作系统提供的api的调用

1)brk——扩大或缩小数据段(data段和bss段的合称)

int brk(void* end_data_segment);

2)mmap——申请虚拟地址空间,可以申请的虚拟内存空间映射到文件,也可以不映射到文件(不映射到文件的叫匿名空间

void* mmap(void* start,size_t length,int prot,int flags,int fd,off_t offset);

windows系统下堆分配,的系统调用

VirtualAlloc——空间大小必须是页的整数倍x86系统必须是4096字节

LPVOID VirtualAlloc{

LPVOID lpAddress, // 要分配的内存区域的地址

DWORD dwSize, // 分配的大小

DWORD flAllocationType, // 分配的类型

DWORD flProtect // 该内存的初始保护属性

};

运行时库malloc函数的具体实现:

第一次肯定也是先通过系统调用(申请虚拟地址空间:linux调用mmap申请,windows调用VirtualAlloc申请申请一块大的虚拟地址空间,后续的话再在这个虚拟地址空间中根据空闲进行分配。不够再重新向系统申请。

分配算法有:空闲链表法,位图法,对象池法

具体的分配算法就看运行时库的实现了,glibc的malloc

小于64字节,采用类似于对象池的方法;

大于64,小于512字节,采用上述方法中的最佳折中策略;

大于512字节,采用最近适配算法;

大于128kb的申请,直接用mmap向操作系统申请内存。

=============忽略下面这段话。

msvc的入口函数使用了alloca(链接到入口函数的实现)。因为一开始的时候堆还没有初始化alloca是唯一可以不使用堆的动态分配机制,是在栈上分配任意空间,在函数返回时候释放,跟局部变量一样


5.虚拟地址和物理地址的映射

一个程序要想执行(指令cpu执行,cpu访问内存),光有虚拟内存是不行的,必须运行在真实的内存上,所以必须在虚拟地址物理地址间建立一种映射关系

虚拟地址与物理地址间的映射关系由操作系统建立,建立好的了以后当程序访问虚拟地址空间上的某个地址值时,就相当于访问了物理地址空间中的另一个值

这种映射机制需要硬件的支持——cpu中的内存管理单元

cpu拿到一个需要虚拟地址(virtual address,VA),经过内存管理单元(Memory Management Unit, MMU)利用存放在物理内存中的映射表(页表,由操作系统管理该表的内容)动态翻译,转换成物理内存地址,进而访问物理内存。

这也叫做cpu虚拟寻址方式。【对比文章开头说的,在早期的计算机中,程序都是直接运行在物理内存上的,运行时访问的地址都是物理地址

整个的映射过程是由软件(操作系统)来设置,而实际的地址转换是由硬件(MMU)来完成。

应用程序和许多系统进程都是使用虚拟寻址

只有操作系统内核的核心部分会使用cpu物理寻址,即直接使用实际的物理内存地址

虚具体的映射方案,映射表后面介绍。段机制(段描述符)和页机制(内存分页)_u012138730的专栏-CSDN博客


6.物理内存和硬盘之间的置换

物理内存RAM不够使用的时候,操作系统会根据一些置换算法将物理内存中的数据置换到磁盘上的交换空间(swap),腾出空闲的物理内存页来存储需要在内存中运行的程序和相关数据。

前面文章说到任务管理器中的提交大小包含两部分

一部分是独占的物理内存(即专用工作集内存),

另一部分是在分页文件(交换空间)中的独占内存映射

windows下分页文件叫,pagefile.sys,一般在硬盘的操作系统所在的分区中。

那么如何设置可以写入硬盘的内存大小呢——我的电脑 右键 选择【属性】,左侧栏里选择【高级系统设置】,然后点击如下图所示:正如解释的是,操作系统把这个分页文件当作RAM使用,即硬盘中的虚拟内存的概念。(70526/1024=68G)

ps: windows下有两种虚拟内存文件

1)专用的页面文件,位于磁盘根目录,即上面说的pagefile.sys 。

2)使用文件映射机制加载过的磁盘文件,比如加载了的dll和exe,即成了映像文件(虚拟内存的映像文件)。一旦被加载到内存中,这个dll和exe文件就不能删除了(这是内存管理器和文件系统之间的重要约定)。所以很多有时候删除不了某文件,一般都是正在用着呢。


7.虚拟内存的重要性

1.每个进程使用的是一个一致的地址空间(从0到2^32-1),降低了程序员对内存管理的复杂性。让操作系统来完成虚拟地址空间物理地址空间的转换。

(对于程序来说,不需要关心物理地址的变化,最后被分配到哪对程序来说是透明的)

2.每个进程有自己独立的虚拟地址空间,只能访问自己的地址空间,有效地做到进程之间的隔离,保证进程的地址空间不会被其他进程破坏。

(从进程角度来看,独占cpu,独占内存有单一的地址空间。)

3.提高物理内存的利用率。


8.进程的虚存空间分布——装载(《程序员的自我修养-链接装载库》第6.4节)

在进程创建的时候操作系统会根据可执行文件的头部信息(记录着这个可执行文件有哪些段可执行文件中的段虚拟空间之间进行映射,这个过程就是装载最重要的一步。

可执行文件从链接角度看,是按Section分段的,一般都有十几个Section,二十几个,三十几个——链接视图

可执行文件从装载角度看,是按Segment分段的,一般就是五六七八个——执行视图

我们可以用readelf命令看同一个执行文件(elf格式的)两个视图

readelf -S 命令看链接视图(也就是看段表的命令)

readelf -l 命令看执行视图

下面的例子不是书中的,是我自己电脑上之前编的一个可执行程序,angular的。

然后是64位的不是32位的,书本中的例子是32位的。所以我这边看地址都是8个字节的。

readelf -S链接视图——25个section头

执行视图:7个program头——程序头表记录着程序头

程序头的类Elf32_Phdr,其数据成员对应下面打印出来的8列:(ELF的目标文件不需要被装载,所以他没有程序头表,而ELF的可执行文件共享库文件(linux下的so文件)都有。)

      只有LOAD类型的Segment是真正需要被映射虚拟空间的。其他类型的都是在装载的时候起到辅助作用。

      可以看到LOAD类型的Segment是按权限划分的:

  • RE代码段
  • R只读数据段
  • RE数据段和BSS段(因为有BSS段,所以FileSiz 和 MemSiz不一样的大小)

      可以看到其实权限相同的Section可执行文件中的位置都是放在一起的。

       对于权限相同的Section把他们的合并到一起称为一个Segment进行映射,因为从装载的角度操作系统不关心各个段的实际内容,最关心的是权限

       ELF可执行文件被映射到虚拟空间的时候,是以系统的页长度(4K)为单位的,所以每个段在映射虚拟空间的时候都会按页的整数倍的,多余部分占一个页。按Segment作为段进行映射明显(比按section进行映射)减少了内存的浪费。

VMA

       Linux中将进程虚拟空间的一个段叫做虚拟内存区域VMA,windows叫虚拟段VirtualSection。

      上述例子中,可执行文件映射到虚拟空间的就有三个VMA。除了有实际文件映射的VMA,还有匿名虚拟空间区域AVMA

      使用命令行看进程的虚拟空间的分布情况,就看上面例子中的main(9个VMA):

第1列VMA的地址范围 。这个例子中跟执行视图中的Virtual地址范围一样,有的例子会略有不同(6.4.4节中的段地址对齐)。

第2列VMA的权限 最后一个p是私有,s是共享。

第3列VMA中的Segment在映像文件中偏移。(详情看6.4.4节中描述道VMA中的Segment可执行文件中的Segment其实不是完全对应的)

第4列:映像文件所在设备的主设备号:次设备号

第5列:映像文件的节点号

第6列:映像文件的路径

可以看到除了前3个的 最后几列都是没有的,说明最后的都是没有映射到文件中,这种VMA也叫做匿名虚拟内存区域AVMA(怎么没有 [heap])

Stack VMA[stack]

操作系统进程启动前系统环境变量进程的运行参数提前保存到虚拟空间的栈中。

我们熟知的main()函数中的argc和argv就是从这里获取的。

动态链接时的进程堆栈初始化信息

详情看动态联接 可执行文件的装载,进程和线程,运行时库的入口函数(第六章)_u012138730的专栏-CSDN博客_运行时动态装载链接至少需要用到以下哪些函数   C/C++的编译和链接过程_u012138730的专栏-CSDN博客

进程堆栈初始化信息中包含了动态链接器所需要的一些信息

可执行文件的段(程序头表),

可执行文件的入口地址等等。

这些辅助信息位于环境指针变量的后面,用一个辅助信息数组表示。

辅助信息结构

辅助信息结构中的类型和值含义:

写一个小程序打印出这些数据:


9.windows打开任务管理器

内存项含义

打开任务管理--详细信息---右键 选择列,选择下面这4个。

1.工作集(内存)Working Set = 内存(专用工作集)+ 内存(共享工作集)【第2列=第3列+第4列】

工作集(内存)——进程当前正在使用的物理内存量——表示进程此时所占用的总物理内存(即占用RAM内存)。

这个值是由两部分组成专用工作集 和 共享工作集

专用工作集内存——由该进程正在使用,而其他进程无法使用的物理内存量——是此进程独占的物理内存

共享工作集内存——由该进程正在使用,且可与其他进程共享的物理内存量——是指这个进程与其它进程共享的物理内存,比如加载了某一个dll所占用的内存。

2.提交大小 Comitted Memory——进程独占的内存

提交大小是操作系统为该进程保留的虚拟内存量

Committed Memory is the number of bytes that have been allocated by processes, and to which the operating system has committed a RAM page frame or a page slot in the page file (or both).

Windows allocates memory for processes in two stages. In the first stage, a series of memory addresses is reserved for a process. The process may reserve more memory than it actually needs or uses at one time, just to maintain ownership of a contiguous block of addresses. At any one time, the reserved memory addresses do not necessarily represent real space in either the physical memory (RAM) or on disk. In fact, a process can reserve more memory than is available on the system.

Before a memory address can be used by a process, the address must have a corresponding data storage location in actual memory (RAM or disk). Commit memory is memory that has been associated with a reserved address, and is therefore generally unavailable to other processes. Because it may be either in RAM or on disk (in the swap file), committed memory can exceed the RAM that is installed on the system

是进程独占的内存。这个值也是包含两部分,一部分是独占的理内存(即专用工作集内存),另一部分是分页文件中的独占内存映射。分页文件是硬盘中的虚拟内存,当RAM物理内存资源紧张,或者有数据长时间未使用时,操作系统通常会将数据占用的物理内存先映射到页面文件(pagefile.sys)中,并拷贝数据到硬盘中,然后将本来占用的RAM空间释放。这个也就是虚拟内存技术。

下面是一个Windows API,功能是向操作系统发送请求, 将此进程的不常用的内容从物理内存中换出到分页文件中保存

EmptyWorkingSet

提交大小这部分内存在虚拟内存的线性地址中是连续的,不过在物理内存或者分页内存中,不一定是连续的。提交但未使用的内存一般都在分页内存里面,只有去使用的时候,才会换到物理内存里面。

提交大小 大于 专用工作集

3.PROCESS_MEMORY_COUNTERS  类 和 GetProcessMemoryInfo 函数

MEMORYSTATUSEX 类 和 GlobalMemoryStatusEx 函数 可以获得还可用的虚拟内存。

PROCESS_MEMORY_COUNTERS  类 和 GetProcessMemoryInfo 函数可以获得当前进程使用的内容,如下:

  MEMORYSTATUSEX  MemoryInfo;
  memset( &MemoryInfo,0,sizeof(MEMORYSTATUSEX) );
  MemoryInfo.dwLength = sizeof(MEMORYSTATUSEX);
  GlobalMemoryStatusEx( &MemoryInfo );
  PROCESS_MEMORY_COUNTERS pmc;
  UINT uMemSwapUsed = 0;
  UINT uMemPhyUsed =0;
  if ( GetProcessMemoryInfo( GetCurrentProcess(), &pmc, sizeof(pmc)) )
  {
   uMemSwapUsed = pmc.PagefileUsage/1024/1024;  // 即提交大小,进程独占内存(物理+交换)
   uMemPhyUsed = pmc.WorkingSetSize/1024/1024;  // 即工作集WorkingSet的大小,总占的物理内存(独占+共享)
  }
  CString szInfo;
  szInfo.Format( "%s  SVM:%u  PM:%u  AVM:%llu",GetTitle(),
   uMemSwapUsed,
   uMemPhyUsed,
   MemoryInfo.ullAvailVirtual/1024/1024
   );
  SetConsoleTitle( szInfo );


 

    PERFORMANCE_INFORMATION PerformanceInfo = {};
    GetPerformanceInfo(&PerformanceInfo, sizeof PerformanceInfo);
 
    MEMORYSTATUSEX MemoryInfo = {};
    MemoryInfo.dwLength = sizeof MemoryInfo;
    GlobalMemoryStatusEx(&MemoryInfo);
     


    MemoryInfo.dwMemoryLoad, 系统已使用的物理内存百分比:%d
    MemoryInfo.ullTotalPhys, 系统实际物理内存(字节):%lld,
= static_cast<unsigned long long>(PerformanceInfo.PhysicalTotal) * PerformanceInfo.PageSize;

    MemoryInfo.ullAvailPhys, 系统实际可用内存(字节):%lld, 
= static_cast<unsigned long long>(PerformanceInfo.PhysicalAvailable) * PerformanceInfo.PageSize;

    MemoryInfo.ullTotalPageFile, 系统页文件 大小(字节):%lld,
= static_cast<unsigned long long>(PerformanceInfo.CommitLimit) * PerformanceInfo.PageSize;

    MemoryInfo.ullAvailPageFile, 系统可用页文件 大小(字节):%lld,
= static_cast<unsigned long long>(PerformanceInfo.CommitLimit - PerformanceInfo.CommitTotal) * PerformanceInfo.PageSize;

    MemoryInfo.ullTotalVirtual, 当前进程用户模式分区大小(字节):0x%llX 。(/LARGEADDRESSAWARE:是 YES 还是 NO
    (是不是如果是系统开启了3GB模式 这里就是显示3GB以上了。
    如果是64位系统的话 32位进程的话 估计是不用开始3GB模式的,直接如果是大地址模式就可以使用,测试一下?)
    MemoryInfo.ullAvailVirtual  当前进程虚拟地址空间中未保留和未提交的量(字节):%lld
    );


typedef struct _PERFORMANCE_INFORMATION {
    DWORD cb;                   //The size of this structure, in bytes.
    SIZE_T CommitTotal;         //The number of pages currently committed by the system.
    SIZE_T CommitLimit;         //The current maximum number of pages that can be committed by the system without extending the paging file(s)
    SIZE_T CommitPeak;          //The maximum number of pages that were simultaneously in the committed state since the last system reboot.
    SIZE_T PhysicalTotal;       //The amount of actual physical memory, in pages.
    SIZE_T PhysicalAvailable;   //The amount of physical memory currently available, in pages.
    SIZE_T SystemCache;         //The amount of system cache memory, in pages. This is the size of the standby list plus the system working set.
    SIZE_T KernelTotal;         //核心内存总数(单位:页面)
    SIZE_T KernelPaged;         //分页核心内存数(单位:页面)
    SIZE_T KernelNonpaged;      //非分页核心内存数(单位:页面)       核心内存数情况在任务管理器 性能 左下角有显示
    SIZE_T PageSize;            //页面大小
    DWORD HandleCount;          //系统当前句柄数
    DWORD ProcessCount;         //系统当前进程数
    DWORD ThreadCount;          //系统当前线程数
} PERFORMANCE_INFORMATION, *PPERFORMANCE_INFORMATION, PERFORMACE_INFORMATION, *PPERFORMACE_INFORMATION;


typedef struct _PROCESS_MEMORY_COUNTERS {
    DWORD cb;                           //The size of the structure, in bytes.
    DWORD PageFaultCount;               //The number of page faults.
    SIZE_T PeakWorkingSetSize;          //The peak working set size, in bytes.      (对应任务管理器中的 峰值工作设置(内存))
    SIZE_T WorkingSetSize;              //The current working set size, in bytes.   (对应任务管理器中的 工作设置(内存))
    SIZE_T QuotaPeakPagedPoolUsage;     //The peak paged pool usage, in bytes.     
    SIZE_T QuotaPagedPoolUsage;         //The current paged pool usage, in bytes.   (对应任务管理器中的 分页池)
    SIZE_T QuotaPeakNonPagedPoolUsage;  //The peak nonpaged pool usage, in bytes.
    SIZE_T QuotaNonPagedPoolUsage;      //The current nonpaged pool usage, in bytes.(对应任务管理器中的 非页面缓存池)
    SIZE_T PagefileUsage;               /*The Commit Charge value in bytes for this process.    (对应任务管理器中的 提交大小)
                                        Commit Charge is the total amount of memory that the memory manager has committed for a running process.*/
    SIZE_T PeakPagefileUsage;           //The peak value in bytes of the Commit Charge during the lifetime of this process.
} PROCESS_MEMORY_COUNTERS;
typedef PROCESS_MEMORY_COUNTERS *PPROCESS_MEMORY_COUNTERS;
 
//例子:
PROCESS_MEMORY_COUNTERS ProcessMemoryInfo = {};
GetProcessMemoryInfo(GetCurrentProcess(), &ProcessMemoryInfo, sizeof ProcessMemoryInfo);


工作设置(内存):
专用(私有)工作集(当前进程独占)中的物理内存数量与进程正在使用且可以和其他进程共享的物理内存数量的总和。
因此可以这么理解,该值就是该进程所占用的总的物理内存

峰值工作设置(内存):
进程的工作设置(内存)的最大值,可以这么理解,因为工作设置(内存)是波动的,这个项专门记录最大的那个值。

内存(专用工作集):
工作集的子集,它专门描述某个进程正在使用且无法与其他进程共享的物理内存值。这个值对于一个进程来说也是最重要的,它代表了一个进程到底独占了多少物理内存。

内存(共享工作集):
进程和可以和别的进程共享的物理内存值(注意:是可以共享的,不一定共享了)。
比较常见的,譬如,加载系统的一些DLL所占用的物理内存,文件共享内存(文件映射),命名共享内存等等。

提交大小:
给当前进程使用而保留的私有虚拟内存的数量,如果要查内存泄漏,可以关注这个值。new malloc VirtualAlloc 分配的内存大小。

分页池:
由内核或驱动程序代表进程分配的可分页内核内存的数量。
可分页内存是可以写入其他存储媒介(例如硬盘)的内存。
此页面的增长通常是由于打开的句柄没有关闭造成的

非分页缓冲池:
由内核或驱动程序代表进程分配的不可分页的内核内存的数量。
不可分页的内存是不能写入其他存储媒介的内存。

2 其他项目含义

cpu时间是cpu在这个进程用的总时间。

当系统没有任务执行是,cpu就会执行空闲进程。空闲进程的pid总是0,他的线程数就是系统中总的cpu数目。他的运行时间cpu时间占用了绝大部分的时间。如果一个进程占用了小时以上,就算是重度的进程了。

cpu这一列指的是1s的cpu占用百分比。如果这一列的某个进程一直同一个值,可能某个线程陷入了死循环。

磁盘有关的问题:

IO读取和IO写入 次数

IO读取字节和IO写入字节 字节数

分析内存问题:

上文

其他观察进程的exe

比如process explorer


 [转载]windows任务管理器中的工作设置内存,内存专用工作集,提交大小详解

10.硬件概念--存储器芯片。

存储器芯片读写属性分类可以分为:

    1)RAM 随机存储器(可读可写),必须带电存储

        A》主随机存储器(主存)

            装在主板上的RAM(主板上插槽,组装电脑的时候)

            装在扩展插槽上的RAM(可以扩展到多少内存,自己买内存条)

        B》接口卡上的RAM

           接口卡(cpu通过地址总线与扩展插槽上的接口卡相连,从而与对应外设相通信)有的需要对大批量的输入输出数据进行暂存,其上需要装RAM,最典型的为显示器的接口卡,显卡,上的RAM,称之为显存。 

    2)ROM 只读存储器,关机后其内容不丢失。

        存储BIOS的ROM。BIOS是软件系统,由主板和各类接口卡厂商提供。通过BIOS,对对应硬件设备进行最基本的输入输出。

            主板上的ROM存储系统的BIOS

            显卡上的ROM存储显卡的BIOS

            网卡上的ROM存储网卡的BIOS

划重点:内存中存储数据和指令用于与cpu沟通。内存是仅次于cpu的部件,性能再好的cpu,没有内存也不能工作。就像再聪明的大脑,没有了记忆也无法进行思考(摘自《汇编语言》)。

那存储器是怎么存储的呢?存储器的容量怎么表示的呢?

存储器被划分为多个存储单元,并从零开始编号。比如一个存储器有128个存储单元,编号即为0~127。

为什么要编号呢,因为编号了就相当于有地址了,cpu可以进行寻址了(通过与之相连的地址总线)。

微型机一个存储单元存1个字节(8bit,8个二进制位),所以,存储器的容量就可以表示了,128个存储单元即128字节容量。

cpu将系统中各类存储器看作一个逻辑存储器。 

内存地址空间的概念。内存地址空间总是大于物理内存(物理存储器,真正的存储器芯片所提供的存储单元)的。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

虚拟内存以及进程的虚拟内存分布(第六章) 的相关文章

随机推荐

  • 【Rust日报】2023-05-09 Stackoverflow 开发者调研开始了

    Stackoverflow 开发者调研开始了 又到了一年一度的时候 xff0c 我们再次向社区宣布 xff1a 又到了一年一度的时候 xff01 xff08 这个递归玩笑是给那些使用 LISP 的 1 31 的用户的 xff09 我们的 2
  • 【Rust日报】 2019-07-06:使用Rust與GTK 快速開發桌面應用

    Analysis of Rust Crate Sizes on crates io 有人寫了一個程式可以分析crates io 裡面庫的大小 read more tiny keccak 1 5 0 Keccak是一種多功能的加密函數 tin
  • 使用python PIL库实现简单验证码的去噪

    首先要感谢一下字符型图片验证码识别完整过程及Python实现的博主 xff0c 我的大部分知识点都是从他那里学来的 想要识别验证码 xff0c 收集足够多的样本后 xff0c 首先要做的就是对验证码原始图片进行处理 xff0c 对验证码识别
  • 将一个长度最多为30位数字的十进制非负整数转换为二进制数输出 c语言 大数处理

    Time Limit 1000 ms Memory Limit 32768 mb 将一个长度最多为30位数字的十进制非负整数转换为二进制数输出 输入描述 多组数据 xff0c 每行为一个长度不超过30位的十进制非负整数 xff08 注意是1
  • Nginx文件上传报413错误

    目录 一 问题描述二 解决方法 一 问题描述 Nginx文件上传报413错误 二 解决方法 nginx 默认对上传文件的大小做了限制 xff0c 默认大小为 1m 超过默认大小就会报 413 xff08 too large xff09 错误
  • Redis报错MISCONF Redis is configured to save RDB snapshots

    目录 一 问题描述二 解决方法1 命令行修改2 配置文件修改 三 其它问题 一 问题描述 Redis 之前一直使用正常 xff0c 某一天突然报错 xff1a MISCONF Redis is configured to save RDB
  • 内核的同步(synchronization)以及锁机制

    1 前言 为什么要考虑内核同步临界区例子 2 加锁3 死锁4 扩展性 1 前言 为什么要考虑内核同步 在单一CPU 的情况下 xff0c 中断或者内核代码明确调度时 xff0c 多个执行线程并发访问共享数据 目前多处理器以及抢占式内核的存在
  • 内核中链表(Linked List)的实现

    前言比较简单的链表 单向链表双向链表 linux 内核中双向链表的实现 存放数据的结构体存放链表指针的结构体container of 宏 内核中链表的使用自己手写的链表实验 前言 内核中链表是比较简单的数据结构 xff0c 通过两个指针元素
  • LUKS 磁盘加密

    前言LUKS 的使用步骤开机自动识别加密磁盘 根分区自动识别 忘记密码怎么办 xff1f 前言 LUKS xff08 Linux Unified Key Setup xff09 是 Linux 硬盘加密的标准 通过提供标准的磁盘格式 xff
  • linux 分区和文件系统结构

    前言磁盘分区 1 分区结构 MBR 分区GPT 分区 文件系统 1 文件系统结构2 inode 如何找到文件 3 File descriptor 彩蛋 xff1a df 是怎么计算出来的 前言 本篇文章总结一下磁盘分区以及文件系统的结构 x
  • Linux 内存分析——进程和物理结构角度

    文章目录 前言进程如何使用内存进程地址空间虚拟内存在内核中的实现系统上查看进程内存进程用来申请内存的函数共享内存实现进程间通讯64位系统地址空间 从物理内存角度看内存内存页及虚拟内存到物理内存的映射物理内存的 ZONE伙伴系统buddy 和
  • Ubuntu18.04 禁用自动挂起,禁止自动休眠

    戴尔的7920工作站新安装了ubuntu18 04LTS xff0c 结果登录远程桌面差不多半个小时之后就断开连接了 xff0c 查看发现机器自动挂起了 修改了power设置中中blank screen 为never xff0c 保存之后过
  • 手把手教你,搭建内网穿透服务

    我的 GitHub 仓库 xff1a 手把手教你搭建内网穿透服务 xff0c 基于 lanproxy 穿透服务 xff0c 为你深度定制了开箱即用的服务端和客户端 Docker 镜像 在很多场景下内网穿透都是我们常常遇到的需求 xff0c
  • 支持alpha通道的视频编码格式以及容器类型汇总

    支持alpha通道的视频编码格式以及容器类型汇总 1 png图像序列 xff0c mov mkv等格式 2 qtrle编码 xff0c mov格式 3 Apple ProRes 4444 rgba 4个通道 xff0c 其容器格式尚未了解
  • word如何给论文加引用文献

    给论文加引用文献其实差不多就是加了个链接 xff0c 通过点击链接跳转到文末最后展示引用文献额作者 xff0c 论文名等等信息 xff0c 给论文加引用文献只要有一下几步 xff1a 一 设置参考文献标号字体格式 对于论文中的文献 xff0
  • unix中c语言典型的存储空间布局

    此文转载于 xff1a https www cnblogs com LUO77 p 5853534 html 一个程序本质上都是由 BSS 段 data段 text段三个组成的 可以看到一个可执行程序在存储 xff08 没有调入内存 xff
  • ffmpeg命令分析-b:v

    本系列 以 ffmpeg4 2 源码为准 xff0c 下载地址 xff1a 链接 xff1a 百度网盘 提取码 xff1a g3k8 本系列主要分析各种 ffmpeg 命令参数 在代码里是如何实现的 a mp4下载链接 xff1a 百度网盘
  • 魔百和M401A刷入Armbian系统EMMC

    魔百和M401A刷入Armbian系统 准备工具 span class token number 1 span 电视盒子 U盘 键盘 显示器 HDMI线 span class token number 2 span armbian系统镜像包
  • SonarQube9社区版环境配置

    由于种种原因 xff0c 需要配置SonarQuber9社区版配置中心 xff0c 记录下配置搭建过程 本次部署环境 win10 43 SonatQube9 9 43 opjdk17 1 软件下载 SonarQube9 9社区版 https
  • 虚拟内存以及进程的虚拟内存分布(第六章)

    在早期的计算机中 xff0c 程序都是直接运行在物理内存上的 xff0c 意思是运行时访问的地址都是物理地址 xff0c 而这要求程序使用的内存空间不超过物理内存的大小 在现代计算机操作系统中 xff0c 为了提高CPU的利用率计算机同时运