• 操作系统


    进程和线程和协程?

    进程 系统分配资源的基本单位, 进程是一个实体,每个进程都有自己地址空间 (进程切换要换页表,所以要更多的资源)

    线程 CPU调度的最小单位, 一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成 (线程切换通过操作系统把所需内容资源加载到寄存器)

    协程 微线程,用户态线程,可以任意跳转切换,如: yield(通过汇编 实现用户层面的切换, **_switch(nty_cpu_ctx new_ctx, nty_cpu_ctx cur_ctx)函数 把当前的上下文保存下来,将新的上下文加载到寄存器中)

    • 线程的切换,如下图所示:

      • CPU有很多的寄存器,这些寄存器保存了当先在处理器上运行线程的信息
      • 例如当前CPU运行的是A线程,那么寄存器保存的都是线程A的信息
      • 当此时需要把线程A切出CPU,来让B线程在CPU上运行,那么就需要把当前寄存器的内容都保存在线程A的栈中,然后把B运行所需要的内容加载到寄存器中,从而使得B线程在CPU上运行起来

    img

    • 协程如何切换?与线程是相同的道理,还是以上面的图片为例:

      • sync_http_commit()函数调用send()之后,在跳转到pos位置之前把当前寄存器的内容保存到一个结构体中(例如命名为store结构体)
      • 跳转到async_http_thread_func()函数指定的pos位置之后,把pos位置的内容信息加载到寄存器中开始执行

    如何实现这些内容的保存与切换

    • 下载代码https://github.com/wangbojing/NtyCo开始解析,首先到代码的Nty_coroutine.c文件中找到_switch()函数,这个函数是实现切换的核心:

      • 参数1:新的上下文
      • 参数2:当前的上下文
      • _switch()函数就是把当前寄存器的内容保存在参数2中,然后加载参数1所指定的内容加载到寄存器中
      • 例如,如果是上面的sync_http_commit()函数,其在"jump-pos"的时候就调用_switch()进行切换,然后async_http_thread_func()函数执行完需要切换回sync_http_commit()函数的时候,会在"jump->back"的地方调用这个函数,只是参数不同而已

    img

    • nty_cpu_ctx结构体就是一些寄存器的指针

    img

    • _switch()函数可以用下面的图来表示

    img

    • 那么如何实现这些寄存器值的保存与交换呢?

      • 以下面为例,自己看图片吧,稍微有点复杂
      • _switch()函数的参数1名为rdi、参数2名为rsi
      • 在左侧,前一半部分:汇编指令把寄存器的内容保存到rsi中,留下次跳转回来使用;后一半部分:把rdi的内容加载到寄存器中开始使用
      • 0、8、16那些是偏移,因为一个指针就是8字节,所以rsp对应的是esp、rbp对应的是ebp......依次类推

    img

    • X86-64有16个64位寄存器,分别是:

      • %rax:作为函数返回值使用
      • %rsp:栈指针寄存器,指向栈顶
      • %rdi,%rsi,%rdx,%rcx,%r8,%r9:用作函数参数,依次对应第1参数,第2参数......依次类推(例如在_switch()函数中,参数1叫做rdi、参数2叫做rsi......)
      • %rbx,%rbp,%r12,%r13,%14,%15:用作数据存储,遵循被调用者使用规则,简单说就是随便 用,调用子函数之前要备份它,以防他被修改 %r10,%r11 用作数据存储,遵循调用者使用规则,简单说就是使用之前要先保存原值

    创建进程的方式

    fork()

    进程通信方式

    内存、管道

    管道需要几次复制

    4次

    用户态缓存 》内核缓存

    内核缓存》内存

    内存》内核缓存

    内核缓存》用户态缓存

    虚拟内存

    虚拟内存是计算机系统内存管理的一种技术。它使得应用程序认为它拥有连续可用的内存(一个连续完整的地址空间),而实际上,它通常是被分隔成多个物理内存碎片,还有部分暂时存储在外部磁盘存储器上,在需要时进行数据交换。 它使程序一开始无需全部加载至内存,需要时在加载(当发生中断时, 通过操作系统的请求调页功能,加载必要数据)。当内存不够是会置换出一些不必要的内存( 通过操作系统的请求置换页功能)

    三种链接方式?

    静态链接 在程序运行之前,先将各目标模块及它们所需的库函数链接成一个完整的可执行文件(装入模块),即得到完整的逻辑地址,之后不再拆开。

    装入时动态链接 运行前边装入边链接的链接方式。

    运行时动态链接 运行时该目标模块时,才对它进行链接,用不到的模块不需要装入内存。其优点是便于修改和更新,便于实现对目标模块的共享。

    三种装入方式

    绝对装入

    编译或汇编时得到绝对地址,即内存物理地址,直接存到对应的物理地址。

    单道处理系统就是直接操作物理地址,因此绝对装入只适用于单道程序环境。

    静态重定位装入

    又称可重定位装入,这里引入逻辑地址,装入时将逻辑地址重定位转化为物理地址,多道批处理系统的使用方式。

    静态重定位的特点是在一个作业装入内存时,必须分配其要求的全部内存空间,如果没有足够的内存,就不能装入该作业。作业一旦进入内存后,在运行期间就不能再移动,也不能再申请内存空间。

    动态重定位装入

    又称动态运行时装入,运行时将逻辑地址重定位转化为物理地址,这种方式需要一个重定位寄存器的支持,当然现代操作系统使用的都是这种。

    逻辑地址都是从0开始的,假设装入的起始物理地址为100,动态重定位装入如下图:

    img

    img

    内存管理-连续分配管理方式

    连续分配:为用户进程分配的必须是一个连续的内存空间。

    单一连续分配方式固定分区分配动态分区分配

    单一连续分配

    在单一连续分配方式中,内存被分为系统区和用户区。系统区通常位于内存的低地址部分,用于存放操作系统相关数据;用户区用于存放用户进程相关数据。内存中只能有一道用户程序,用户程序独占整个用户区空间。

    优点:实现简单;无外部碎片;

    缺点:只能用于单用户、单任务的操作系统中;有内部碎片;存储器利用率极低。

    固定分区分配

    img

    将整个用户空间划分为若干个固定大小的分区,在每个分区中只装入一道作业,这样就形成了最早的、最简单的一种可运行多道程序的内存管理方式。

    操作系统需要建立一个数据结构——分区说明表,来实现各个分区的分配与回收。每个表项对应一个分区,通常按分区大小排列。每个表项包括对应分区的 大小、起始地址、状态(是否已分配),如下图。

    img

    当某用户程序要装入内存时,由操作系统内核程序根据用户程序大小检索该表,从中找到一个能满足大小的、未分配的分区,将之分配给该程序,然后修改状态为“已分配”。

    优点: 实现简单,无外部碎片。

    缺点: 会产生内部碎片,内存利用率低。

    动态分区分配

    动态分区分配又称为可变分区分配。这种分配方式不会预先划分内存分区,而是在进程装入内存时,根据进程的大小动态地建立分区,并使分区的大小正好适合进程的需要。因此系统分区的大小和数目是可变的。

    缺点:动态分区分配没有内部碎片,但是有外部碎片。

    内部碎片:分配给某进程的内存区域中,有些部分没有用上。

    外部碎片:是指内存中的某些空闲分区由于太小而难以利用。

    如果内存中空闲空间的总和本来可以满足某进程的要求, 但由于进程需要的是一整块连续的内存空间,因此这些 “碎片”不能满足进程的需求。 可以通过紧凑(拼凑,Compaction)技术来解决外部碎片。

    img

    产生三个问题:

    • 系统要用什么样的数据结构记录内 存的使用情况?

    img

    • 当很多个空闲分区都能满足需求时, 应该选择哪个分区进行分配?

    img

    1. 首次适应算法: 每次都从低地址开始查找,找到第一个能满足大小的空闲分区。
    2. 最佳适应算法:由于动态分区分配是一种连续分配方式,为各进程分配的空间必须是连续的一整片区域。因此为了保证当“大进程”到来时能有连续的大片空间,可以尽可能多地留下大片的空闲区,即,优先使用更小的空闲区
    3. 最坏适应算法:为了解决最佳适应算法的问题——即留下太多难以利用的小碎片,可以在每次分配时 优先使用最大的连续空闲区,这样分配后剩余的空闲区就不会太小,更方便使用。
    4. 邻近适应算法:首次适应算法每次都从链头开始查找的。这可能会导致低地址部分出现很多小的空闲分区,而每次分配查找时,都要经过这些分区,因此也增加了查找的开销。如果每次都从上次查找结束的位置开始检索,就能解决上述问题。

    img

    • 如何进行分区的分配与回收操作?

    内存管理-非连续分配管理方式

    非连续分配:为用户进程分配的可以是一些分散的内存空间。

    img

    分页-什么是基本分页存储

    将内存空间分为一个个大小相等的分区(比如:每个分区 4KB),每个分区就是一个“页框”(页框=页帧=内存块=物理块=物理页面)。每个页框有一个编号,即“页框号”(页框号=页帧号=内存块号=物理块号=物理页号),页框号从0开始。

    img

    将进程的逻辑地址空间也分为与页框大小相等的一个个部分,每个部分称为一个“页”或“页面” 。每个页面也有一个编号,即“页号”,页号也是从0开始。

    操作系统以页框为单位为各个进程分配内存空间。进程的每个页面分别放入一个页框中。也就是说,进程的页面与内存的页框有一一对应的关系。各个页面不必连续存放,可以放到不相邻的各个页框中

    分页-页表

    为了能知道进程的每个页面在内存中存放的位置,操作系统要为每个进程建立一张页表,页表通常存在PCB(进程控制块)中。

    img

    分页-分页之后的地址转换

    img

    页号 = 逻辑地址 / 页面长度 (取除法的整数部分)

    页内偏移量 = 逻辑地址 % 页面长度(取除法的余数部分)

    img

    基本地址变换:

    基本地址变换机构可以借助进程的页表将逻辑地址转换为物理地址。 通常会在系统中设置一个页表寄存器(PTR),存放页表在内存中的起始地址F 和页表长度M。 进程未执行时,页表的始址 和 页表长度 放在进程控制块(PCB)中,当进程被调度时,操作系 统内核会把它们放到页表寄存器中。

    img

    引入快表地址变换:

    img

    快表,又称联想寄存器(TLB, translation lookaside buffer ),是一种访问速度比内存快很多的高速缓存(TLB不是内存!),用来存放最近访问的页表项的副本,可以加速地址变换的速度。与此对应,内存中的页表常称为慢表。

    注:TLB 和 普通 Cache 的区别——TLB 中只有页表项的副本,而普通 Cache 中可能会有其他各种数据的副本

    img

    快表快多少?

    例:某系统使用基本分页存储管理,并采用了具有快表的地址变换机构。访问一次快表耗时 1us,访问一次内存耗时 100us。若快表的命中率为 90%,那么访问一个逻辑地址的平均耗时是多少?

    (1+100) * 0.9 + (1+100+100) * 0.1 = 111 us 有的系统支持快表和慢表同时查找,如果是这样,平均耗时应该是 (1+100) * 0.9 + (100+100) * 0.1 = 110.9 us

    若未采用快表机制,则访问一个逻辑地址需要 100+100 = 200us 显然,引入快表机制后,访问一个逻辑地址的速度快多了。

    分页-两级页表

    单级页表的问题:

    问题一: 根据页号查询页表的方法:K 号页对应的页表项存放位置 = 页表始址 + K * 4 ,页表必须连续存放,因此当页表很大时,需要占用很多个连续的页框;

    问题二:没有必要让整个页表常驻内存,因为进程在一段时间内可能只需要访问某几个特定的页面。

    解决办法:把页表再分页并离散存储,然后再建立一张页表记录页表各个部分的存放位置,称为页目录表,或称外层页表,或称顶层页表。

    img

    img

    分页-多级页表

    img

    感兴趣的看看多级页表分页方式。

    分段-什么是分段

    进程的地址空间:按照程序自身的逻辑关系划分为若干个段,每个段都有一个段名(在低级语言 中,程序员使用段名来编程),每段从0开始编址。

    内存分配规则: 以段为单位进行分配,每个段在内存中占据连续空间,但各段之间可以不相邻。

    img

    分段-段表

    img

    分段-地址转换

    img

    分段 VS 分页

    1.1 页是信息的物理单位。分页的主要目的是为了实现离散分配,提高内存利用率。分页仅仅是系统管理上的需要,完全是系统行为,对用户是不可见的。

    1.2 段是信息的逻辑单位。分段的主要目的是更好地满足用户需求。一个段通常包含着一组属于一个逻辑模块的信息。

    2.1 分段对用户是可见的,用户编程时需要显式地给出段名。

    2.2 页的大小固定且由系统决定。段的长度却不固定,决定于用户编写的程序。

    3.1 分页的用户进程地址空间是一维的,程序员只需给出一个记忆符即可表示一个地址。

    3.2 分段的用户进程地址空间是二维的,程序员在标识一个地址时,既要给出段名,也要给出段内地址。

    img

    4.1 分段比分页更容易实现信息的共享和保护。 不能被修改的代码称为纯代码或可重入代码(不属于临界资源),这样的代码是可以共享的。可修改的代码是不能共享的(比如,有一个代码段中有很多变量,各进程并发地同时访问可能造成数据不一致)

    img

    分段小结

    与“分页”最大的区别就 是——离散分配时所分配地址空间的基本单位不同。

    img

    段页式

    分段和分页的优缺点:

    img

    段页式-什么是段页式

    img

    每个段对应一个段表项,每个段表项由段号、页表长度、页表存放块号(页表起始 地址)组成。

    每个段表项长度相等,段号是隐含的。

    内存每个页面对应一个页表项,每个页表项由页号、页面存放的内存块号组成。每个页表项长度相等,页号是隐含的。

    段表式页表

    img

    段页式地址转换

    img

    段页式小结

    img

    内存空间的扩充

    很多游戏的大小超过 60GB,按理来说这个游戏程序运行之前需要把 60GB 数据全部放入内存。然而,实际我的电脑内存才 8GB,我还要开着微信浏览器等别的进程,但为什么这个游戏可以顺利运行呢?

    利用虚拟技术(操作系统的虚拟性)

    img

    时间局部性: 如果执行了程序中的某条指令,那么不久后这条指令很有可能再次执行;如果某个数据被访问过,不久之后该数据很可能再次被访问。(因为程序中存在大量的循环);

    空间局部性: 一旦程序访问了某个存储单元,在不久之后,其附近的存储单元也很有可能被访问。 (因为很多数据在内存中都是连续存放的,并且程序的指令也是顺序地在内存中存放的)

    img

    虚拟内存大小是多少?

    虚拟内存的最大容量是由计算机的地址结构(CPU寻址范围)确定的,虚拟内存的实际容量 = min(内存和外存容量之和,CPU寻址范围)

    如:某计算机地址结构为32位,按字节编址,内存大小为512MB,外存大小为2GB。

    则虚拟内存的最大容量为 2^32 B = 4GB;

    虚拟内存的实际容量 = min (232B, 512MB+2GB) = 2GB+512MB;

    虚拟内存的实现

    img

    请求分页管理

    请求分页存储管理与基本分页存储管理的主要区别:
    
    1. 请求调页:在程序执行过程中,当所访问的信息不在内存时,由操作系统负责将所需信息从外存调入内存,然后继续执行程序。
    2. 页面置换:若内存空间不够,由操作系统负责将内存中暂时用不到的信息换出到外存。

    img

    请求分页-缺页中断

    img

    缺页中断是因为当前执行的指令想要访问的目标页面未调入内存而产生的,因此属于内中断一条指令在执行期间,可能产生多次缺页中断。(如:copy A to B,即将逻辑地址A中的数据复制到 逻辑地址B,而A、B属于不同的页面,则有可能产生两次中断)

    img

    请求分页-地址转换

    img

    img

    1. 只有“写指令”才需要修改“修改位”。并且,一般来说只需修改快表中的数据,只有要将快表项删除时才需要写回内存中的慢表。这样可以减少访存次数。
    2. 和普通的中断处理一样,缺页中断处理依然需要保留CPU现场。
    3. 需要用某种“页面置换算法”来决定一个换出页面(下节内容)
    4. 换入/换出页面都需要启动慢速的I/O操作,可见,如果换入/ 换出太频繁,会有很大的开销。
    5. 页面调入内存后,需要修改慢表,同时也需要将表项复制到快表中。

    img

    请求分页-页面置换

    页面的换入、换出需要磁盘 I/O,会有较大的开销,因此好的页面置换算法应该追求更少的缺页率
    
    1. 最佳置换算法(OPT)
    2. 先进先出置换算法(FIFO)
    3. 最近最久未使用置换算法(LRU)
    4. 时钟置换算法(CLOCK)
    5. 改进型的时钟置换算法

    img

    内存管理的职责-地址转换

    为了使编程更方便,程序员写程序时应该只需要关注指令、数据的逻辑地址。而逻辑地址到物理地址的转换(这个过程称为地址重定位)应该由操作系统负责,这样就保证了程序员写程序时不需要关注物理内存的实际情况。

    具体的地址转化方式如上。

    内存管理的职责-存储保护

    操作系统需要提供内存保护功能。保证各进程在各自存储空间内运行,互不干扰。

    img

    img

    img

    注文章内容来自这两篇文章: https://zhuanlan.zhihu.com/p/252761693、https://zhuanlan.zhihu.com/p/359165294

    此时此刻,非我莫属
  • 相关阅读:
    写给自己的2020年总结
    docker镜像与docker容器
    docker安装&docker简介
    windows 安装linux子系统
    typora设置图床
    让Mysql插入中文
    pip 换源
    Unity中如何将一个场景(Scene)的Light Settings复制给另一个场景
    Windows API开发
    【C#】判断字符串中是否包含指定字符串,contains与indexOf方法效率问题
  • 原文地址:https://www.cnblogs.com/taozhengquan/p/15324609.html
Copyright © 2020-2023  润新知