Linux内核之内存管理分段机制原理与实现(从无到有的过程)

news2024/7/4 23:48:37

1. 分段机制概述

对于分段机制,要从Intel的微处理器的8086开始说起,刚开始内存空间比较小,内存寻址采用的是直接访问物理地址的方式。由于技术的发展,计算机做的事情越来越多,程序也越来越大,为了更大的内存空间,把地址总线扩展到20位。但是,对于内存设计,一个很尴尬的问题产生了,之前的设计CPU的ALU宽度只有16位,也就是说,ALU不能访问20位的地址空间,那时就设计了段机制来处理这种情况。为了坚持这种兼容性,386依然运用段机制,直至现在的64位处理器已经看不到段机制的身影。

1.1 分段机制产生的原因

为了保持兼容,分段机制的被引入,我们来实际的理解分段机制解决了什么实质性的问题呢?在分段机制还没有出现的时候,程序运行是需要从内存分配出足够多的连续内存,然后整个程序装载进去。例如:

某个程序大小是100M,然后我们就需要有连续的100M内存空间才能把这个程序装载到内存里面。如果无法找到连续的100M内存空间,就无法把这个程序装载进内存空间,程序就无法得到运行。

假设我们的内存可以提供连续的区域来使得程序运行,那么我们来看一下还会存在有什么问题呢?

地址空间不隔离(安全性):

如果现在有两个程序A和B在运行,程序A在内存的地址假设为0x0->0x100,而程序B在内存中的地址假设为0x100->0x199。那么假设程序员A本来想存在属于A的地址0x50,而不小心访问到属于B的地址0x150,那么不好的事情就将发生了,A和B程序都异常了。对于程序员B来说,是飞来横祸,同时也很难定位到问题,这种情况会导致程序能访问所有的内存空间,恶意修改数据可能造成安全问题。

程序运行时地址不确定(动态链接):

程序每次要运行的时候,都是需要装载到内存中的。假设你在程序中写死了要操作某个地址的内存,例如你写的地址是0x150。这时问题就来了,你能够保证你操作的地址0x150真的就是你原来想操作的那个位置吗?很可能程序第一次装载进内存的位置是0x100->0x199,而程序第二次运行的时候,这个程序装载进内存的位置变成了0x0->0x100,而你操作的0x150地址压根就不是属于这个程序所占有的内存。

内存使用率低下(内存共享):

现在假设我们写了3个程序,其中程序A大小为10M,程序B为70M,程序C的大小为30M。而你的计算机的内存总共有100M,这三个程序加起来有110M,显然这三个程序是无法同时存在于内存中的,并且最多只能够同时运行两个程序。比如现在是这个样子:程序A占有的内存空间是0x00000000~0x00000009,程序B占有的内存空间是0x00000010~0x00000079。

假设这个时候程序C要运行该怎么做?我们可以把其中的一个程序换出到磁盘上,然后再把程序C装载到内存中。

假设是把程序A换出,那么程序C还是无法装载进内存中,因为内存中空闲的连续区域有两块,一块是原来程序A占有的那10M,还有就是从0x00000080~0x00000099这20M,所以,30M的程序C无法装载进内存中。

那么,唯一的办法就是把程序B换出,保留程序A,但是,最后的结果会有60M的内存无法利用起来。

为了解决这一些问题,分段的概念应运而生。在计算机科学领域,任何的问题都可以通过增加一个间接的中间层来解决问题,那么为了实现分段的这个技术,就需要引入虚拟地址空间的概念。

我们来了解下,虚拟地址空间和物理地址空间的概念,简单的说来,对于可以寻址的一片空间,如果这个空间是虚拟的,我们就叫做虚拟的地址空间;如果这个空间是真实存在的,我们就叫做物理地址空间。虚拟地址空间是虚拟的,所有就决定了他可以是任意的大,而物理地址空间必须是真实存在的,是由实际的硬件决定的。

1.2 硬件分段机制

分段是一种隔离不同的代码、数据、栈模块的机制,能够保证不同进程或任务不会互相干扰。我们可以为一个进程分配属于它的段集合,CPU 的硬件机制会保证其代码不会越权访问段,也不会访问到段外的地址。

分段机制就是把虚拟地址空间中的虚拟内存组织成一些长度可变的的段的内存单元,80386虚拟地址空间中的逻辑地址由一个段部分和一个段内偏移部分构成,段是虚拟地址空间到线性地址转换的基础。每个段都有3个参数定义:

  • 段基地址:指定段在线性地址空间中的开始地址,基地址是线性地址对应于段中偏移0处
  • 段限长:是虚拟地址空间中段内最大可用偏移地址,定义了段的长度
  • 段属性:指定段的特性,如该段是否可读,可写或可执行,段的特权级等

当需要访问处理器地址空间的某个字节时,段选择符指定了该字节所在的段,偏移量指定了该字节在段中相对于段基址的位置,处理器把逻辑地址转化成一个线性地址的过程如下:

  • 1.使用段选择符中的偏移值(在GDT(全局描述符表) 或 LDT(局部描述符表)中定位相应的段描述符
  • 2.利用段描述符校验段的访问权限和范围,以确保该段是可以访问的并且偏移量位于段界限内
  • 3.利用段描述符中取得的段基地址加上偏移量,形成一个线性地址

1.2.1 段选择符

段选择符(或称段选择子)是段的一个十六位标志符,如下图所示。段选择符并不直接指向段,而是指向段描述符表中定义段的段描述符。

段选择符包括 3 个字段的内容:

  • 请求特权级RPL([0:1])
  • 表指引标志TI([2])TI = 0 ,表示描述符在GDT中,TI = 1,表示描述符在LDT中
  • 索引值,给出了描述符在GDT或LDT表中的索引项号

下面是一些段选择符的示例:

1.2.2 段描述符

段描述符表是段描述符的一个数组,如下图所示。描述符表的长度可变,最多可以包含8192个 8 byte 描述符。有两个描述符表: 全局描述符表GDT (Global descriptor table); 局部描述符表 LDT (Local descriptor table),由段选择符的bit[2]会选择到对应的GDT表还是LDT表去拿到对应的段基址。

而对于段描述符,每个段描述符长度是 8 字节,含有三个主要字段:段基地址、段限长和段属性。段描述符通常由编译器。链接器、加载器或者操作系统来创建,绝不可能由应用程序来创建。

段描述符通用格式如下:

了解了这个过程,我们来总体的梳理下,如果使用分段机制,那么怎么使虚拟地址空间转到对应的物理地址空间呢?转换过程如下图所示:

1.取出虚拟地址空间中的段选择符,根据TI位判断段描述符是存储在GDT还是LDT中

2.段选择符中的index*8,也就是左移3位,就是段描述符在GDT中的位置,在加上GDT的基地址,就是段描述符的地址,从而去除段描述符

3.段描述符中保存了该段的基地址,加上虚拟地址中的偏移量就是对应到的物理地址空间。

2. Linux中分段的实现原理

上一节讨论了80x86如何从硬件上提供分段机制的支持,而本节讨论下linux如何使用分段机制。最开始的时候,操作系统不支持分段,内存的换入换出都是以整个进程的内存空间为单位,导致系统非常的耗时,同时利用率也不高,当内存不足时,很容易导致内存交换失败。后来有了分段技术,把内存空间分成多个模块:代码段、数据段,或者是一个大的数据块。段成了内存交换的单位,在一定程度上增加了内存利用率。那时候还没有分页技术,虚拟地址(线性地址)是直接映射到物理空间的。

引入分页机制后,目前linux很少使用分段,分段和分页在某些方面是冗余的,因为他们都可以把物理地址空间分割成不同部分:分段给每个进程分配不同的逻辑地址空间,而分页可以把相同的逻辑地址空间映射到不同的物理地址上。因此,Linux优先采用了分页(分页操作系统),基于以下原因:

内存管理更简单:所有进程使用相同段寄存器值,也就是相同的线性地址集

出于兼容大部分硬件架构的考虑,RISC架构对分段支持的不是很好

所以自从x86-64起,除了在“传统模式”下,分段机制已被认为是过时的且不再被支持。虽然在x86-64的本机模式下仍然有分段机制的某些痕迹,但大多只是为了兼容,且它们不再具起到同样的作用,也不再提供真正的分段。

那么linux内核是怎么支持分段机制呢?我们来看上节的分段机制的原理图如下:

比如,我们将虚拟地址空间分成4个段,用0-3来编号,每个段在段表中有一个项,在物理空间中,段的排列如下图所示:

如果要访问段2中偏移量为600的虚拟地址,我们可以计算出物理地址为段基地址+偏量=2000+600=2600

3. Linux分段机制的软件实现

Linux对段机制的应用效果是等价于几乎绕过了段基址。在Linux中仅有4个段,用户代码段、数据段和内核代码段、数据段。

这些段相应的选择器分别由以下宏定义:_USER_CS, __USER_DS, __KERNEL_CS, 和__KERNEL_DS。举例来说,如果要定位内核代码段,内核只需要加载__KERNEK_CS宏的值到cs寄存器中。

接下来我们看一下linux代码吧,进入保护模式的函数go_to_protected_mode:

void go_to_protected_mode(void)
    {
        /* Hook before leaving real mode, also disables interrupts */
        realmode_switch_hook();

        /* Enable the A20 gate */
        if (enable_a20()) {
            puts("A20 gate not responding, unable to boot...\n");
            die();
        }

        /* Reset coprocessor (IGNNE#) */
        reset_coprocessor();

        /* Mask all interrupts in the PIC */
        mask_all_interrupts();

        /* Actual transition to protected mode... */
        setup_idt();
        setup_gdt();
        protected_mode_jump(boot_params.hdr.code32_start,
                 (u32)&boot_params + (ds() << 4));
    }

里面的函数略带一下吧,realmode_switch_hook()根据注释和函数命名可以知道这是在实模式切换前的钩子函数调用的地方;enable_a20()这个太熟悉了,就开启A20;reset_coprocessor()是把协处理器重置一下mask_all_interrupts()则是把中断关了,避免切换过程中出现状况。其中setup_idt()和setup_gdt()是本节的重点,函数名字告诉我们这是设置idt和gdt的,看一下两者具体代码吧:

static void setup_idt(void)
{
	static const struct gdt_ptr null_idt = {0, 0};
    asm volatile("lidtl %0" : : "m" (null_idt));
}

根据setup_idt()的实现,可以明显看到这没做什么,纯粹置一下idt为空的描述符表。

static void setup_gdt(void)
    {
        /* There are machines which are known to not boot with the GDT
         being 8-byte unaligned. Intel recommends 16 byte alignment. */
        static const u64 boot_gdt[] __attribute__((aligned(16))) = {
            /* CS: code, read/execute, 4 GB, base 0 */
            [GDT_ENTRY_BOOT_CS] = GDT_ENTRY(0xc09b, 0, 0xfffff),
            /* DS: data, read/write, 4 GB, base 0 */
            [GDT_ENTRY_BOOT_DS] = GDT_ENTRY(0xc093, 0, 0xfffff),
            /* TSS: 32-bit tss, 104 bytes, base 4096 */
            /* We only have a TSS here to keep Intel VT happy;
             we don't actually use it for anything. */
            [GDT_ENTRY_BOOT_TSS] = GDT_ENTRY(0x0089, 4096, 103),
        };
        /* Xen HVM incorrectly stores a pointer to the gdt_ptr, instead
         of the gdt_ptr contents. Thus, make it static so it will
         stay in memory, at least long enough that we switch to the
         proper kernel GDT. */
        static struct gdt_ptr gdt;

        gdt.len = sizeof(boot_gdt)-1;
        gdt.ptr = (u32)&boot_gdt + (ds() << 4);

        asm volatile("lgdtl %0" : : "m" (gdt));
    }

首先,我们看看之前的GDT entry的结构图如下:

GDT_ENTRY的定义如下:

/* Constructor for a conventional segment GDT (or LDT) entry */
    /* This is a macro so it can be used in initializers */
    #define GDT_ENTRY(flags, base, limit)            \
        ((((base) & 0xff000000ULL) << (56-24)) |    \
         (((flags) & 0x0000f0ffULL) << 40) |        \
         (((limit) & 0x000f0000ULL) << (48-16)) |    \
         (((base) & 0x00ffffffULL) << 16) |        \
         (((limit) & 0x0000ffffULL)))

可以清楚得看到,base, limit和flag通过位移和或组成了GDT_ENTRY。其中flags代表了40-47位的access byte和52-55位的flags。

CS和DS的flags为0xc0,所以G=1,意味着4K为一个页面,B/D为1,1-32位段;

CS的Access Byte=0x9b,意味着P=1(合法的Entry Pr必须为1),DPL=0,S=1,这里该段只能在Ring 0下访问,该段是代码段

DS的Access Byte=0x93,意味着P=1(合法的Entry Pr必须为1),DPL=0,S=1,这里该段只能在Ring 0下访问,该段是数据段

linux中逻辑地址等于线性地址。为什么这么说呢?因为Linux所有的段(用户代码段、用户数据段、内核代码段、内核数据段)的线性地址都是从 0x00000000 开始,长度4G,这样 线性地址=逻辑地址+ 0x00000000,也就是说逻辑地址等于线性地址了。通过分析,我们发现,所有的段的起始地址都是一样的,都是 0。这算哪门子分段嘛!所以,在 Linux 操作系统中,并没有使用到全部的分段功能。那分段是不是完全没有用处呢?分段可以做权限审核,例如用户态 DPL 是 3,内核态 DPL 是 0。当用户态试图访问内核态的时候,会因为权限不足而报错。

还是以 mov 0x80495b0, %eax 中的地址为例分析一下转换过程:

  • 1.首先段选择符中的TI为0,表明段描述符在GDT表中,使用段选择符中的偏移值定位到相应的段描述符,找到15这个位置
  • 2.从15号位置的段描述符,找到对应的访问权限,访问基地址(0)和访问范围(0xffff)
  • 3.利用段描述符中去得到的段基址0x0000000,加上逻辑地址偏移0x80495b0,形成线性地址0x80495b0。

所以Linux没有采用严格的分段机制,已经慢慢的弱化分段机制,而使用分页机制来替换分段机制。

4. 分段机制的优缺点

现在大致了解了分段的基本原理,系统运行时,地址空间中不同段被重定位到物理内存中,与之前的整个物理地址空间中只有一个基地址+偏移量的方式相比,大量的节省了物理内存。同时分段管理就是将一个程序按照逻辑单元分成多个程序段,每一个段使用自己单独的虚拟地址空间。例如,对于编译器来说,我们可以给其5个段,占用5个虚拟地址空间,如下图所示

如此,一个段占用一个虚拟地址空间,不会发生空间增长时碰撞到另一个段的问题,从而避免因空间不够而造成编译失败的情况。如果某个数据结构对空间的需求超过整个虚拟之地所能够提供的空间,则编译仍将失败,开编提到的问题1好像得到了完美解决。

正是因为这种映射,使得程序无需关注物理地址是多少,只要虚拟地址没有改变,那么程序就不会操作地址不当,问题2也好像可以很好的解决。

但是问题3,是换入换出的问题,这个问题的关键是能不能在换出一个完整程序之后,把另外一个程序换进来,而这种分段机制,就存在一个很严重的问题。

物理内存很快充满了许多空闲空间的小洞,因而很难分配给新的段,或扩大已有的段。这种问题被称为外部碎片(external fragmentation)。 该问题的一种解决方案是紧凑(compact)物理内存,重新安排原有的段。

分段机制采用的是分段,这就导致一个问题,已分配的段有大有小,未使用的段也有大有小,将要分配的段也有大有小,各方需求不一定,理想的情况,但系统中的程序比较少,内存没有完全使用的情况下会如紧凑型分配。但是在程序运行过程中,有些程序运行完后,要释放新已分配的内存空间,当使用一段时间后,可能会出现非紧凑的情况,在这个例子中,一个进程需要分配一个20K的段,当前有24K的空闲,却不连续,因此操作系统无法满足这20K的请求。这也就是外部碎片,其特征如下:

外部碎片是指还没有被分配出去(不属于任何进程),但是由于太小了,无法分配给申请内存空间的新进程的内存空闲区域。

虽然这些存储块的总和可以满足当前申请的长度的要求,但是由于他们的地址不连续或者其他原因,使得系统无法满足当前的申请。

5. 分段机制的改进之路

紧凑物理内存,重新安排原有的段,例如,操作系统先终止运行的进程,将他们的数据复制到连续的内存区域中去,改变他们的段寄存器中的值,指向新的物理地址,从而得到足够大的连续空闲空间。这样做,大大提高了成本,系统开销也很大,会占用大量的处理器时间。

软件优化的算法,一种更简单的做法是利用空闲列表管理算法,保留大的内存块用于分配。相关的算法很多,例如传统的最优匹配(从空闲链表中找到最接近需要分配空间的空闲块返回)、最坏匹配、首次匹配以及伙伴算法等。但遗憾的是,无论算法多么精妙,都无法完全的消除外部碎片。

无论如何,分段机制解决了上面两个问题,算是一个很大的进步。但是对于内存效率问题仍然无能为力,同时也产生了内存的外部碎片。为了解决分段机制存在的问题,更为合理的分页机制就应运而生,后面的章节我们会接着讨论。

6. 总结

分段机制解决了一些问题,帮助我们实现了更高效的虚拟内存。不只是动态重定位,通过避免地址空间的逻辑段之间的大量潜在的内存浪费,分段机制更好的支持了虚拟地址空间。分段机制有好处,也有它的局限性,我们就需要更好的解决方案,以后章节会慢慢总结。

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

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

相关文章

重生奇迹MU游戏开店技巧

重生奇迹游戏开店攻略 在游戏之中有很多炫酷的玩法&#xff0c;而且还有存在很多奇怪的现象&#xff0c;很多玩家喜欢在游戏之中充当商人&#xff0c;然后在游戏之中开店卖一些东西&#xff0c;或者是倒卖一些物品。 从买卖物品之中赚取一定差价&#xff0c;的确这样可以为玩…

Jetsonnano B01 笔记8:屏幕分辨率的调整

最近我的jetson 被学弟借用学习了一阵子&#xff0c;他在一顿捯饬后&#xff0c;成功将我的屏幕分辨率改成了一个阴间的水平&#xff0c;十分影响操作学习。 终端命令更改分辨率&#xff1a; 我们可以打开终端&#xff0c;输入以下命令来更改分辨率&#xff0c;但这样做有一个…

6个基于DSPM组件的使用案例

在这个信息技术时代&#xff0c;企业必须应对日益增长的生成和保护大量数据的需求。这就是为什么制定广泛而有效的策略来处理和保护这些数据比以往任何时候都更加重要的原因。数据安全状况管理 &#xff08;DSPM&#xff09; 是一种解决方案&#xff0c;可帮助现代组织实施全面…

Unity 2021.x及以下全版本Crack

前言 最近Unity那档子事不出来了吗&#xff0c;搞得所有人都挺烦的&#xff0c;顺便在公司内网需要我完成一个游戏的项目&#xff0c;就研究了一下如何将Unity给Crack掉。 注意所有操作应有连接外网的权限 以我选择的版本为例&#xff0c;我使用的是Unity 2021.3.5f1与Unity…

【HarmonyOS】解决API6 WebView跳转外部浏览器问题、本地模拟器启动黑屏

【问题描述1】 HarmonyOS API6 Java开发中使用WebView组件&#xff0c;如果网页中有跳转链接&#xff0c;点击会跳转到手机系统浏览器。 【解决方案】 解决这个问题的方法就是给WebView这种自定义的WebAgent对象。具体代码如下&#xff1a; WebConfig webConfigthis.webView…

甲骨文创新中心与正初为职教集团达成人才培养合作,探索数实结合产教融合模式

2023年9月20日&#xff0c;甲骨文&#xff08;南京&#xff09;人工智能创新中心&#xff08;以下简称“甲骨文创新中心”&#xff09;与正初为职教集团在南京举行了战略合作签约仪式。甲骨文创新中心正式宣布和正初为职教集团达成职业教育数实结合产教融合合作协议&#xff0c…

数据集笔记:T-drive 北京出租车轨迹数据

数据地址&#xff1a;T-Drive trajectory data sample - Microsoft Research 1 数据描述 此数据集包含了2008年2月2日至2月8日在北京期间10,357辆出租车的GPS轨迹。此数据集中的总点数约为1500万&#xff0c;轨迹的总距离达到了900万公里。图1显示了两个连续点之间的时间间隔和…

java后端笔记

写在前面 节选自&#xff1a; 黑马程序员&#xff1a;新版Java面试专题视频教程&#xff1a;https://www.bilibili.com/video/BV1yT411H7YK?p1 javaguide&#xff1a;https://javaguide.cn/home.html 集合 ConcurrentHashMap JDK1.7&#xff1a;分段数组链表&#xff0c;用…

TCP 和 UDP哪个更好

传输控制协议 &#xff08;TCP&#xff09; 和用户数据报协议 &#xff08;UDP&#xff09; 是互联网的基础支柱&#xff0c;支持从网络源到目的地的不同类型的数据传输。TCP更可靠&#xff0c;而UDP优先考虑速度和效率。本文解释了两种协议的工作原理&#xff0c;并详细讨论了…

分享53个Python源码源代码总有一个是你想要的

分享53个Python源码源代码总有一个是你想要的 链接&#xff1a;https://pan.baidu.com/s/1ew3w2_DXlSBrK7Mybx3Ttg?pwd8888 提取码&#xff1a;8888 项目名称 100-Python ControlXiaomiDevices DRF-ADMIN 后台管理系统 FishC-Python3小甲鱼 Flask框架的api项目脚手架 …

LLM-TAP随笔——语言模型训练数据【深度学习】【PyTorch】【LLM】

文章目录 3、语言模型训练数据3.1、词元切分3.2、词元分析算法 3、语言模型训练数据 数据质量对模型影响非常大。 典型数据处理&#xff1a;质量过滤、冗余去除、隐私消除、词元切分等。 训练数据的构建时间、噪音或有害信息情况、数据重复率等因素都对模型性能有较大影响。训…

JavaScript系列从入门到精通系列第九篇:JavaScript中赋值运算符和关系运算符以及Unicode编码介绍

一&#xff1a;赋值运算符 1&#xff1a; 右侧的值可以赋值给左侧的变量。 var a 123; console.log(a);//123 2&#xff1a; var a 10; a a 5; a 5; 上边这两个写法是一样的。 3&#xff1a;- var a 10; a a-5; a - 5; 上边这两个写法是一样的。 4&#xff1a;* …

Cesium 展示——label 从十进制小数转度分秒

文章目录 需求分析 需求 Cesium 显示 坐标点label 从十进制小数转度分秒 分析 写一个转换方法 function decimalToDMS(decimal) {const degree Math.floor(decimal);const minute Math.floor((decimal - degree) * 60);const second ((decimal - degree) * 60 - minute) *…

ChunJun: 自定义插件

序言 Chunjun的版本兼容可能会有问题,在我们了解了自定义插件后,在修改源码以应对不同的场景就会得心应手了,针对Chunjun1.12.Release版本说明cuiyaonan2000163.com 自定义插件整体流程 从数据流的角度来看ChunJun&#xff0c;可以理解为不同数据源的数据流通过对应的ChunJu…

一文了解数据治理全知识体系!

在业界&#xff0c;大家都为如何做好数据治理而感到困惑。数据治理工作一定要先摸清楚数据的家底&#xff0c;规划好路线图&#xff0c;再进行决策。 本文从数据治理的误区、元数据管理、数据质量管理、数据资产管理等4个方面整理出数据治理的一套经验总结&#xff0c;给予数据…

python算法部署(通信篇)

1.dockerflask方式 # YOLOv5 &#x1f680; by Ultralytics, AGPL-3.0 license """ Run a Flask REST API exposing one or more YOLOv5s models """import argparse import io import jsonimport torch from flask import Flask, jsonify, req…

公司新来的实习生问我SpringBoot多个环境的配置方式

这是一篇写给新手的文章&#xff0c;老手可以绕行了。 起因是一个同学让我帮他看个问题&#xff0c;他说有两个环境&#xff0c;一个环境有问题&#xff0c;另一个环境没问题&#xff0c;但是一直找不到原因&#xff0c;假设一个环境是 dev&#xff0c;另一个环境是 test。 于…

2023 版 QQ 机器人运行部署文档

文章目录 1.前置说明2.机器人框架的下载与运行2.1 下载机器人框架2.2 下载可操作框架的静态页面2.3 运行机器人框架 3.登录QQ机器人3.1 前提说明3.2 扫码登录3.3 注意事项 4.后端程序处理消息4.1 下载 stater4.2 stater 基本说明4.3 运行后端程序4.4 测试消息处理4.5 关于扩展消…

ISE_ChipScope Pro的使用

1.ChipScope Pro Core Inserter 使用流程 在之前以及编译好的流水灯实验上进行学习 ChipScope的使用。 一、新建一个ChipScope 核 点击Next,然后在下一个框中选择 Finish&#xff0c;你就会在项目菜单中看到有XX.cdc核文件。 二、对核文件进行设置 右键“Synthesize – XST” …

计算机丢失ac1st16.dll怎么解决?教你简单的修复ac1st16.dl文件

在使用Windows操作系统时&#xff0c;有时会遇到一些DLL文件丢失的问题&#xff0c;其中之一就是AC1ST16.DLL。AC1ST16.DLL是用于AutoCAD相关软件的动态链接库文件&#xff0c;当该文件丢失时&#xff0c;可能会导致软件无法正常运行。本文将详细介绍多种解决AC1ST16.DLL丢失问…