Linux—进程学习—04(进程地址空间学习)

目录

  • Linux—进程学习—4
    • 1.程序地址空间
      • 1.1虚拟地址空间的现象
      • 1.2虚拟地址空间的理解(感性)
    • 2.进程地址空间
      • 2.0 mm_struct结构体
      • 2.1 mm_struct结构体的源代码
      • 2.2分页&虚拟地址空间
        • 解释前面的实验现象
      • 2.3进程地址空间存在的原因
        • 2.3.1第一个原因
        • 2.3.2第二个原因
        • 2.3.3第三个原因(小难)
      • 2.4关于虚拟地址、线性地址、物理地址、逻辑地址
    • 3.总结
    • 4.内核进程调度队列(拓展)

Linux—进程学习—4

学习了环境变量和进程的一些基础概念之后,现在要来学习一个难点,就是进程地址空间

1.程序地址空间

之前在学习c/c++的时候,有提到一个c/c++程序的内存分布这样一个知识,忘记了可以看这个C&C++内存管理复习

当时提到了数据在内存中的分布情况,也就是数据可能会在栈区,堆区,代码段,数据段。

但是当时这只是一个便于理解代码的浅学习,这个叫做程序地址空间,现在来看看到底是怎么一回事。

下图是一个程序地址空间的一个图:

image-20241124214951774

其实之前对其程序地址空间的理解是不到位的,比如我之前认为这个程序地址空间是内存,其实并不是。这个东西是虚拟地址空间!

怎么理解呢?可以通过下面一个实验来理解虚拟地址空间的存在

1.1虚拟地址空间的现象

一个有趣的小tip:

如果想在vim中批量化替换一个名字,可以使用%s/将被替换的/替换的/g

如下图所示:

image-20241124234054995

可以发现实现批量化替换了,将mycode批量化替换成mytest【这里的c99模式编译记得取消掉,不然无法编译后面的程序】

下面做个实验来感受一下虚拟地址空间。

代码如下:

image-20241125003154619

执行结果如下:

image-20241125004332847

注意:父进程和子进程谁先执行是不知道的,完全由系统的调度器决定

我们分析程序的执行结果会发现:

有个现象很奇怪:父子进程读取同一个地址的变量,出现了不一样的值!

在子进程修改了VAL的值后,明明父子进程的VAL地址指向的还是同一个地址,但是VAL的值却不一样,这不是很奇怪吗?和以前学习的知识不一样。

值不一样我们还能理解,之前说过父子进程之间应该是有独立性的,值不一样可以理解,但是VAL的地址确实一样的,这就很奇怪了。

因此这里的地址——绝对不是物理地址!

即是之前在c/c++所指的地址(指针),并不是物理地址!而是虚拟地址(线性地址)【在Linux中也会叫逻辑地址】

总结:

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

现在已经知道了虚拟地址空间的存在了,也感受了它的存在,看到了现象,现在需要的是理解它,要理解它就得知道它是怎么来的

1.2虚拟地址空间的理解(感性)

进程会认为它自己是独占了系统的资源,但是实际上并不是。

下面是一个小故事来便于理解:

一个富豪有三个私生子,而私生子是不知道其他私生子的存在的。这三个私生子各自都有各自的生活。这三个私生子一个是工厂老板,一个是老师,一个是还在读博。

这个富豪答应3个私生子,死后的家产就属于它们。

而三个私生子自然会认为富豪的家产已经属于了自己,但是每次向富豪要钱的时候,只能向富豪要一些小钱来完成自己的工作。如果要家产的话,富豪就会拒绝。

在这个故事中:

  • 富豪就是操作系统,在它看来,家产是不可能只给一个私生子的,家产只是给私生子的一个大饼。
  • 私生子就是进程,在它看来,它认为富豪的家产已经被自己独占了,它可以通过向富豪索取家产来完成自己的工作
  • 而家产就是虚拟地址空间,富豪给私生子画的大饼,而虚拟地址空间,就是操作系统给进程画的大饼

其实严格来说,这个虚拟地址空间(家产),这个大饼,叫做进程地址空间!

站在进程的角度上,它是认为它独占了所有的系统资源(进程地址空间)的,

站在操作系统角度上,进程地址空间是操作系统给每个进程”画的一个大饼“

一个进程实际上占用了操作系统的部分资源,进程也可以向操作系统申请一定的资源,但是进程如果要全部的资源(地址空间),操作系统只会拒绝

那这个饼(进程地址空间)是怎么画的呢?

  • 画饼对于人来说:就是在人的脑海中构建一个蓝图,让人记住

  • 画饼对于计算机来说:一个数据结构对象,让每个进程都指向一个数据结构的对象。

image-20241125191742419

每个进程都需要给其画饼(地址空间),因此每个进程都指向属于自己的一个饼(地址空间)、而每个进程都需要被管理(PCB)。

自然,给每个进程的饼(地址空间)也需要被管理,这个饼(地址空间)也是结构体对象,也得通过先描述后组织的方式管理。

地址空间的本质: 内核的一种数据结构【在Linux内核中是 mm_struct

2.进程地址空间

之前叫做程序地址空间,是不太准确的,严格来说实际上应该叫做进程地址空间。

image-20241126120041151

由于这个进程地址空间,是完成的连续的地址,很多教材和官方都管它不叫虚拟地址,管它叫做线性地址

2.0 mm_struct结构体

PCB里面有一个struct mm_struct* mm指针,这个mm指向的这个结构体划分了进程地址空间的区域分配情况

那这个mm_struct结构体内部会有什么成员呢?

image-20241125235656000

我们现在已经知道了,对于地址空间,操作系统也是需要管理的

在地址空间上是有各种区域的【堆,栈,等区域】,这个不同的区域要怎么理解呢?

其实就是区域划分,在32位的环境下,从低地址到高地址一共有2^32次方个地址,每个地址1字节。而区域的划分就是在这个地址上划出一个个范围,规定一个个范围分别属于谁的。

而地址一共有2^32次方个,在mm_struct中是用unsigned long存储的

因此,mm_struct结构体 肯定会有很多跟区域划分有关的成员

image-20241126010329090

而一个mm_struct结构体对象mm,就会开始具体的区域划分。

image-20241126011002383

每个区域的起始地址和结束地址当中就会有很多个地址,每个地址都代表着一个位置,比如代码段区域有N个地址,4个地址(字节)存在一个int类型的对象。每个区域中间的这N个地址就可以拿来给用户存放数据。

而这每个区域中的地址,就叫做虚拟地址!也就是这2^32个地址都是虚拟地址!

之前说了,操作系统会给每个进程画大饼,这个大饼就是mm_struct结构体对象(进程地址空间),每个进程都会指向一个mm_struct结构体对象,这个指针存在于进程的PCB中。这意味着每个进程都可以拥有2^32个地址空间吗?不是的,只是一个大饼罢了。

既然有了区域划分,就会有区域调整,比如一个区域的范围要扩大,就叫区域扩大,区域要缩小,就叫区域缩小。

我们知道,栈区和堆区这两个区域的范围不是固定的,有些时候会扩大和缩小。这就涉及到了区域调整。而区域调整的本质:就是调整mm_struct结构体对应区域的start和end

比如之前在C/C++中,使用new/malloc申请空间 ,其实就是在扩大堆区,而定义很多局部变量,就是在扩大栈区。当申请的空间被free/delete掉之后,扩大的堆区就会缩小回原来的大小,当栈帧销毁的时候,因局部变量扩大的栈区也会缩小回原来的大小

2.1 mm_struct结构体的源代码

下图是Linux内核中,mm_struct结构体的源代码:

image-20241126105937890

image-20241126110122821

image-20241126110147257

这个有很多看不懂很正常,只需要看下面这个就行

image-20241126110714166

除了对应的代码段,栈、堆等区域的划分,甚至连命令行参数都有划分区域

2.2分页&虚拟地址空间

现在已经知道了,每个进程的PCB中都有一个指针指向进程地址空间(mm_struct结构体对象),每个进程都拥有了自己的进程地址空间了,但是这个是虚拟的地址空间。

此时有个问题,那进程要如何找到加载到物理内存中的代码呢?

image-20241126120742991

此时就需要一个新的东西——页表

image-20241126121302576

注意:(下面这个只是了解,不详细学习,等到后面学文件系统的时候会详细学习)

内存在和磁盘这种外设做数据的输入输出的时候,叫做IO。而IO一般来说一次是4KB,也就是4096个字节。而每一次加载到内存,一般都直接加载成一个page单位,一个page大小为4KB,占据内存4KB的空间,其实可以认为内存会把自己看做一个个4KB大小的page。【在32位环境下,一共会有4GB/4KB个page】

继续回到页表,虚拟地址空间是通过页表来实现与物理内存的联系的。如何联系呢?——通过映射,看下图:

image-20241126122627502

可以举个实例:

我们在C语言中写了一个代码 int a = 10,如果此时取地址,前面也实验过,取的其实是虚拟地址上的地址,但是如果是修改其值,就会通过页表来找到虚拟地址所对应的物理地址,也就是真正存储a的物理地址,然后访问这个地址并修改所存储的值

注意:这里页表看似很简单,实际上页表是一个复杂的东西,它是一个类似于map的树状数据结构。涉及到多级页表【并且页表的功能也不仅仅只有映射】

解释前面的实验现象

知道了进程地址空间和物理内存是如何联系起来的之后,就可以解释上面实验的现象了**【父子进程取同样的地址,但是值不一样】**

由于子进程会直接按照父进程为模版,进行拷贝【这里子进程的PCB和进程地址空间拷贝的都是父进程的】,因此父子进程的关于VAL变量的虚拟地址是相同的。并且通过页表的映射关系找到的存储VAL的物理地址也是一个地址

但是在子进程运行过程中,修改了VAL这个共享变量,如果两个页表的映射仍然指向一个地址,那么父子进程的独立性就会被破坏、

因此Linux为了保持父子进程的独立性。在VAL这个共享变量被任何一方修改的时候,都会单独在内存中找一个一样大的地方,拷贝VAL到新地址,并且修改对应进程的页表的映射关系。如果是子进程修改那就修改子进程的页表映射关系,父进程就修改父进程的**【这种操作叫做写时拷贝】**

这样处理的话,尽管在进程地址空间上,父子进程的VAL的地址是一样的。但是通过各自的页表所找到的物理地址是不同的,不同的物理地址存放着父子进程各自的VAL变量

下图是关于这个现象的解释:

image-20241125231937980

这上面的所有操作和功能,都是OS帮我们做的、

2.3进程地址空间存在的原因

2.3.1第一个原因
  • 首先就是直接访问物理内存这种方式,对于在内存的数据来说非常不安全
  1. 自己写的程序,如果越界访问了,会直接修改到其他地址的数据
  2. 如果有恶意进程可以扫描物理内存,那就可以恶意修改数据或者获得关键数据并上传。

但是只能说直接访问物理内存不行,并没有说进程地址空间就行。

  • 访问进程地址空间配上页表就安全非常多

访问进程地址空间,由于是虚拟的,最终数据的存取仍然要通过访问物理内存实现,但是页表可以判断本次操作是否合法。如果合法就通过页表来映射物理内存,从而访问物理内存。如果不合法那就拒绝访问物理内存。

因此哪怕进程的执行内容会有野指针和越界以及恶意程序的情况,影响的也只是这个进程对应的进程地址空间,不会影响到物理内存。

2.3.2第二个原因

进程地址空间的存在——可以更方便的进行不同进程之间数据和代码的解耦,保证了进程的独立性。

具体的案例分析在上面解释实验现象【父子进程取同样的地址,但是值不一样】

2.3.3第三个原因(小难)
  • 让进程以统一的视角来看待进程对应的代码和数据等各个区域(也就是每个进程都有代码区和数据区等区域),方便使用【可以做到10个进程的main函数地址都是一样的】
  • 让编译器以统一的视角来编译代码【编译完即可直接使用】

这两个原因其实有点难理解,可以看下面的分析来理解、

首先要知道一个点——不要认为虚拟地址空间只有在OS中才会用到,编译器也会遵守对应的规则

我们自己写的程序,在加载到物理内存之前,在编译的时候,每句代码和数据就已经存在地址了.【程序需要经过编译和链接才能生成可执行文件!】

什么意思呢?可以看下面这个图:

这个编址是编译器在编译的时候编址的,可以说是按照虚拟地址空间一样的方式去编址的,一样会有代码段,数据段等区域的划分。【栈区和堆区在编译的时候是不会开辟出这个区域的(这两个区域是程序执行之后动态生成的)】

在磁盘中这个地址说是虚拟地址不太准确,严格来说应该是逻辑地址。但是在Linux中是一回事

然后呢,编译完成之后,就要链接并加载到物理内存。

image-20241127153519624

这个时候需要注意,虽然代码其本身自带逻辑地址,在加载到内存之后,天然的就会被赋予物理地址

image-20241127154149921

此时有两套地址

  1. 物理地址:标识代码和数据在物理内存的位置
  2. 代码的逻辑地址:标识程序内部代码和数据的地址【比如函数跳转时,用的地址就是逻辑地址】

由于此时加载到物理地址,因此OS就可以拿到代码和数据在物理内存的位置,此时就会将这个位置记录下来并记录在页表中

image-20241127154448525

那如果此时有一个进程,要执行这个程序,这个进程该如何找到程序的位置呢?

之前说了每个进程都有其对应的mm_struct进程地址空间,因此这个进程地址空间在初始化的时候,在划分区域的时候,就会拿这个加载到内存的可执行程序的逻辑地址来加载进程地址空间中的虚拟地址。

image-20241127160046648

通过这样的方式,页表就被OS建立好了。进程地址空间可以通过页表找到代码的物理地址。

下面CPU调度该进程并执行代码的过程:

此时如果CPU在调度该进程,并且到了要执行该程序的时候,就会将进场地址空间中的code_start等区域的起始地址交给CPU,然后CPU就会根据该虚拟地址,去找到对应的main函数【OS会将页表的映射关系处理,找到虚拟地址对应的物理地址】,找到之后,开始执行代码。如果中间碰到函数跳转,进程地址空间有对应的虚拟地址,页表也记载了对应的物理地址,CPU可以继续根据虚拟地址来完成代码的执行

要注意:CPU只能接收指令!通过虚拟地址然后经过页表的映射在物理地址找到代码指令,然后执行指令。而这个指令就自带地址,这个地址是虚拟地址!【因为加载到内存了,如果在磁盘上就是逻辑地址】

而**CPU在执行完整个进程的这段时间,是见不到物理地址的!它一直都在用虚拟地址!**中间通过进程地址空间的虚拟地址并通过页表找到物理地址的过程,是OS完成的,CPU并不知道。

在VS2022中为什么说程序要在32位或者64位环境下编译?因为就跟编译器要在编译程序的时候为程序以什么环境编址有关,也就是逻辑地址。

2.4关于虚拟地址、线性地址、物理地址、逻辑地址

由于这个进程地址空间,是完成的连续的地址,很多教材和官方都管它不叫虚拟地址,管它叫做线性地址

在Linux中我们认为虚拟地址和线性地址是一样的

而虚拟地址和物理地址之间的联系是通过页表完成映射关系的

逻辑地址:前面在第三个原因处提到的逻辑地址,这只是逻辑地址的一种形成方案,它多种形成方式

下面是两种典型的逻辑地址的形成方案:

image-20241128125438346

  • 新的是直接按照线性去编址,code区域假如是0~100,那么data区直接从101开始编址,这样的好处是,形成的逻辑地址和进程地址空间的虚拟地址比较相似,加载到内存可以直接当做虚拟内存看待。

上面在第三个原因所讲述的逻辑地址,就是按照新的方式去编址的【它恰好和虚拟地址非常像】

  • 旧的是按照偏移量的方式去编址的,code区域假如是0~100,那么data区域是从起始地址+偏移量100,然后再从0开始编址。这样的坏处是,形成的逻辑地址加载到物理内存后,仍然需要在物理地址去加上偏移量才能得到代码的虚拟地址。

3.总结

经过上述的理解和学习,现在需要知道3个问题的答案

  • 进程地址空间是什么?

进程地址空间是OS为了管理进程的一种虚拟化解决方案,实际上进程地址空间是为进程所画的"大饼",让进程认为自己独占了系统资源(地址空间)、

  • 进程地址空间为什么存在?

仔细说就看上面讲述的三个原因,这里大概总结一下

三个原因:

  1. 防止进程中存在越界访问和野指针等非法操作,保证物理内存的其他数据的安全性。

  2. 保证进程与进程之间数据和代码的解耦,保证了进程与进程之间的独立性

    1. 让进程以统一的视角来看待进程对应的代码和数据等各个区域(也就是每个进程都有代码区和数据区等区域),方便使用

    2. 让编译器以统一的视角来编译代码【编译完的逻辑地址即可直接使用】

  • 进程地址空间如何实现的?

为了管理进程需要有PCB,进场地址空间自然也需要管理,要遵守先描述后组织,因此进程地址空间就是内核的一种数据结构结构体mm_struct,每个进程PCB中都有一个mm指针指向一个mm_struct结构体对象,结构体对象会实现地址空间的区域划分。如果需要到物理内存寻址,OS会通过页表来实现进程地址空间与物理内存之间的联系

现在回过来看这个图,理解就多了一些。

但是需要注意的是,32位环境下为进程准备的进程地址空间,并不是全部空间都是给用户准备的,这里有1/4的空间是给内核准备的,这个内核空间后面会讲到

image-20241124214951774

4.内核进程调度队列(拓展)

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

image-20241128134548647

一个CPU拥有一个runqueue(运行队列)

如果有多个CPU就要考虑进程个数的负载均衡问题

优先级普通优先级:100~139(我们都是普通的优先级,想想nice值的取值范围,可与之对应!)

实时优先级:0~99(不关心)

关于优先级的详细解答:Linux的进程优先级 NI 和 PR - 简书

活动队列

  • 时间片还没有结束的所有进程都按照优先级放在该队列

  • nr_active: 总共有多少个运行状态的进程

  • queue[140]: 一个元素就是一个进程队列,相同优先级的进程按照FIFO规则进行排队调度,所以,数组下标就是优先级!

从该结构中,选择一个最合适的进程,过程是怎么的呢?

  1. 从0下表开始遍历queue[140]

  2. 找到第一个非空队列,该队列必定为优先级最高的队列

  3. 拿到选中队列的第一个进程,开始运行,调度完成!

  4. 遍历queue[140]时间复杂度是常数!但还是太低效了!

  • bitmap[5]:一共140个优先级,一共140个进程队列,为了提高查找非空队列的效率,就可以用5*32个比特位表示队列是否为空,这样,便可以大大提高查找效率!

过期队列

  • 过期队列和活动队列结构一模一样

  • 过期队列上放置的进程,都是时间片耗尽的进程

  • 当活动队列上的进程都被处理完毕之后,对过期队列的进程进行时间片重新计算

active指针和expired指针

  • active指针永远指向活动队列

  • expired指针永远指向过期队列

  • 可是活动队列上的进程会越来越少,过期队列上的进程会越来越多,因为进程时间片到期时一直都存在的。没关系,在合适的时候,只要能够交换active指针和expired指针的内容,就相当于有具有了一批新的活动进程

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/887901.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

图论入门编程

卡码网刷题链接:98. 所有可达路径 一、题目简述 二、编程demo 方法①邻接矩阵 from collections import defaultdict #简历邻接矩阵 def build_graph(): n, m map(int,input().split()) graph [[0 for _ in range(n1)] for _ in range(n1)]for _ in range(m): …

Oracle对比表与表之间的结构

自己首先想到的就是,navicat有提供结构同步 但是有些时候情况不一样,比如我遇到的是连接不同,而且是互相同步,以最多的列的那个表为样 没有说一个固定的源 那么还可以通过导出表结构去另一个库中执行看是否报错,以此来判断结构的不同 但是我感觉有点儿麻烦 最后想到通过sql语…

数据结构与算法——N叉树(自学笔记)

本文参考 N 叉树 - LeetBook - 力扣(LeetCode)全球极客挚爱的技术成长平台 遍历 前序遍历:A->B->C->E->F->D->G后序遍历:B->E->F->C->G->D->A层序遍历:A->B->C->D->…

关于音频 DSP 的接口种类以及其应用场景介绍

在音频系统中,DSP(数字信号处理器)扮演着重要角色,通常会通过不同的接口与音频系统中的其他组件(如功放、扬声器、音频源等)进行连接。以汽车应用场景为例,以下是一些常见的接口类型分类及其介绍…

Linux操作系统学习---初识环境变量

目录 ​编辑 环境变量的概念: 小插曲:main函数的第一、二个参数 获取环境变量信息: 1.main函数的第三个参数 2.查看单个环境变量 3.c语言库函数getenv() 和环境变量相关的操作指令: 1.export---导出环境变量: 2.unse…

多视图几何中向量叉乘的矩阵转换 Matrix Transformation for Cross Product in MVG

Title: 多视图几何中向量叉乘的矩阵转换 Matrix Transformation for Cross Product in MVG 文章目录 I. 向量叉乘的矩阵转换恒等式II. 符号计算 Maxima 程序推导III. 推论和关联公式的说明1. 推论2. 应用于对极几何中基本矩阵推导3. 应用于基本矩阵与单应矩阵关系4. 与刚体运动…

支持多种快充协议的取电芯片,支持最大功率140W

前言 在快节奏的现代生活中,人们对于小家电的依赖日益加深,而随之而来的充电问题也日益凸显。传统的充电方式往往受限于电压、电流的限制,难以满足不同设备对电力的多样化需求。而PD快充协议的诞生,则为这一难题提供了全新的解决…

分页查询功能

EmployeeController /** * 员工分页查询 * * param employeePageQueryDTO * return */ GetMapping("/page") ApiOperation("员工分页查询") public Result<PageResult> page(EmployeePageQueryDTO employeePageQueryDTO) { log.info("…

“移门缓冲支架:为家庭安全加码”

在智能家居日益普及的今天&#xff0c;科技不仅改变了我们的生活方式&#xff0c;也提升了家居的安全。移门缓冲支架作为一项结合了现代技术的小型装置&#xff0c;正逐渐成为提升家庭安全的重要配件。它通过吸收门关闭时的冲击力、减缓关门速度以及减少噪音等多重功能&#xf…

力扣96:不同的二叉搜索树

给你一个整数 n &#xff0c;求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种&#xff1f;返回满足题意的二叉搜索树的种数。 示例 1&#xff1a; 输入&#xff1a;n 3 输出&#xff1a;5示例 2&#xff1a; 输入&#xff1a;n 1 输出&#xff1a;1 卡…

小程序-基于java+SpringBoot+Vue的微信小程序养老院系统设计与实现

项目运行 1.运行环境&#xff1a;最好是java jdk 1.8&#xff0c;我们在这个平台上运行的。其他版本理论上也可以。 2.IDE环境&#xff1a;IDEA&#xff0c;Eclipse,Myeclipse都可以。推荐IDEA; 3.tomcat环境&#xff1a;Tomcat 7.x,8.x,9.x版本均可 4.硬件环境&#xff1a…

Torchtune在AMD GPU上的使用指南:利用多GPU能力进行LLM微调与扩展

Torchtune on AMD GPUs How-To Guide: Fine-tuning and Scaling LLMs with Multi-GPU Power — ROCm Blogs 这篇博客提供了一份详细的使用Torchtune在AMD GPU上微调和扩展大型语言模型&#xff08;LLM&#xff09;的指南。Torchtune 是一个PyTorch库&#xff0c;旨在让您轻松地…

Java使用replaceAll替换时不使用正则表达式

前言 public String replaceAll(String regex, String replacement) {return Pattern.compile(regex).matcher(this).replaceAll(replacement);}在使用String.replaceAll() 方法时&#xff0c;由于入参时regex &#xff0c;而入参刚好是正则表达式的字符该怎么办&#xff1f;我…

【0346】Postgres内核 Startup Process 通过 signal 与 postmaster 交互实现 (5)

1. Startup Process 进程 postmaster 初始化过程中, 在进入 ServerLoop() 函数之前,会先通过调用 StartChildProcess() 函数来开启辅助进程,这些进程的目的主要用来完成数据库的 XLOG 相关处理。 如: 核实 pg_wal 和 pg_wal/archive_status 文件是否存在Postgres先前是否发…

STM32C011开发(3)----Flash操作

STM32C011开发----3.Flash操作 概述硬件准备视频教学样品申请源码下载参考程序生成STM32CUBEMX串口配置堆栈设置串口重定向FLASH数据初始化FLASH 读写演示 概述 STM32C011 系列微控制器内置 Flash 存储器&#xff0c;支持程序存储与数据保存&#xff0c;具备页面擦除、双字写入…

电商项目高级篇06-缓存

电商项目高级篇06-缓存 1、docker下启动redis2、项目整合redis3、redis改造三级分类业务 缓存 流程图&#xff1a; data cache.load(id);//从缓存加载数据 If(data null){ data db.load(id);//从数据库加载数据 cache.put(id,data);//保存到 cache 中 } return data;在我们…

PS的功能学习

背景差色较大&#xff0c;就魔棒 魔棒的连续就是倒水点的跨越问题 魔棒的容差的选择就有点看经验了&#xff0c;看颜色的统一程度选择 Ctrl D 取消当前所有的选区 至于快速选择工具&#xff0c;和对象选择工具也差不多&#xff0c;只不过控制范围变成了一块一块的&#x…

深度学习实验--初步探索数据增强、优化器对模型的影响

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 前言 这次主要是探究&#xff0c;优化器、数据增强对模型训练的影响&#xff1b;基础篇还剩下几个, 后面的难度会逐步提升;越学&#xff0c;越觉得这个东西很…

新型大语言模型的预训练与后训练范式,谷歌的Gemma 2语言模型

前言&#xff1a;大型语言模型&#xff08;LLMs&#xff09;的发展历程可以说是非常长&#xff0c;从早期的GPT模型一路走到了今天这些复杂的、公开权重的大型语言模型。最初&#xff0c;LLM的训练过程只关注预训练&#xff0c;但后来逐步扩展到了包括预训练和后训练在内的完整…

SQL Server管理员sa登录失败原因

文章目录 一、开启混合登录模式二、启用sa三、更改密码四、登录sa一、开启混合登录模式 用Windows身份登录数据库服务。 在连接名上右键→属性。 在安全性选项卡下,选择【SQL Server和Windows身份验证模式】,点击【确定】,提示需要重启服务。 Win+R,输入指令:services.ms…