针对于嵌入式软件杂乱的知识点总结起来,提供给读者学习复习对下述内容的强化。
目录
1.在1G内存的计算机能否malloc(1.2G)?为什么?
2.malloc能申请多大的空间?
3.内存管理有哪几种方式?
4.什么虚拟内存?
5.解释下内存碎片,内碎片,外碎片?
6.解释下虚拟地址,逻辑地址,线性地址,物理地址?
7.虚拟内存置换方法是怎么样的?
8.给你一个类,里面有static,virtual之类的,来说一说这个类的内存分布?
9.假设临界区资源释放,如何保证只让一个线程获得临界区资源而不是都获得?
10.操作系统中的缺页中断是什么?
11.OS缺页置换算法如何实现的?
12.系统调用是什么,你用过哪些系统调用,和库函数有什么区别?
13.为什么要有page cache,操作系统怎么设计的page cache?
1.在1G内存的计算机能否malloc(1.2G)?为什么?
在一台拥有1GB内存的计算机上,调用 malloc(1.2G)
是不可能成功的,因为操作系统会检查可用的内存空间。在这种情况下,系统只能分配最多1GB的内存,超出了这个限制,malloc
会返回 NULL
,表示分配失败。
这里的原因有几个方面:
物理内存限制:计算机的物理内存只有1GB,操作系统无法为单一进程分配超过这个值的内存。虽然现代操作系统(如Linux、Windows)支持虚拟内存,但虚拟内存并不是无限的。
操作系统的内存管理:即使系统使用虚拟内存,操作系统通常会设置一定的内存限制来避免进程超出可用内存范围,尤其是对于单个进程的内存分配。所以即使你有足够的交换空间(swap space),系统仍会限制进程分配过多内存。
虚拟内存:如果系统启用了虚拟内存,并且交换空间足够大,操作系统可能会允许你申请超过物理内存的内存量。但是,这会导致频繁的磁盘交换,严重影响性能,且并不意味着所有的内存都会被实际分配给进程。
malloc能够申请的空间大小与物理内存的大小没有直接关系,仅与程序的虚拟地址空间相关。
程序运行时,堆空间只是程序向操作系统申请划出来的一大块虚拟地址空间。应用程序通过malloc申请空间,得到的是在虚拟地址空间中的地址,之后程序运行所提供的物理内存是由操作系统完成的。
本题要申请空间的大小为 1.2G=230x1.2 Byte ,1.2G=(1024*1024*1024)*1.2Byte转换为十六进制约为 4CCCCCCC,这个数值已经超过了 int 类型的表示范围,但还在 unsigned 的表示范围。幸运的是malloc 函数要求的参数为 unsigned 。见下面的示例代码。
#include <stdio.h>
#include <stdlib.h>
int main() {
char *p;
const unsigned k = 1024 * 1024 * 1024 * 1.2; // 计算内存大小,1.2GB
printf("%u\n", k); // 输出计算的内存大小
p = (char *)malloc(k); // 申请内存
if (p != NULL)
printf("ok\n");
else
printf("error\n");
free(p); // 释放内存
return 0;
}
该程序计算 1.2GB 的内存大小并尝试进行内存分配。
在1GB内存的计算机上,通常因为系统的物理内存限制(除非系统配置了非常大的交换空间),malloc
很可能会失败,返回 NULL
,然后打印 error
。
如果系统有足够的内存(或者有足够的交换空间),程序将成功分配内存,并打印 ok
。
现代操作系统使用虚拟内存机制,允许程序使用比物理内存更大的地址空间。
虚拟内存通过将部分数据存储在磁盘(如交换分区或页面文件)上来扩展可用内存。
因此,即使物理内存不足,malloc(1.2G)
也可能成功,因为操作系统会将部分数据交换到磁盘。
2.malloc能申请多大的空间?
malloc
可以申请多大的空间,取决于多个因素,包括操作系统的内存管理、硬件架构、操作系统的限制和系统配置。下面是一些影响 malloc
可分配内存大小的关键因素:
32位系统:在32位操作系统中,单个进程的虚拟地址空间通常限制为4GB(理论上是 2^32 字节)。在这种系统上,malloc
申请的最大内存会受到该限制的影响。实际上,由于操作系统和硬件的配置,通常只有2GB或者更少的地址空间可以供进程使用(剩余的内存地址用于操作系统自身),并且受限于内存碎片和内存管理策略。
64位系统:在64位操作系统中,虚拟地址空间大大增加,理论上支持的内存可以高达2^64 字节,极其庞大。实际上,操作系统和硬件会有一些实际限制(如硬件支持的最大内存、操作系统的配置等)。在64位系统中,malloc
能够申请的空间大大增加,可能达到数TB甚至更多。
Linux:Linux 操作系统中的 malloc
申请内存的最大限制通常受到物理内存、交换空间以及进程最大虚拟内存空间的限制。在64位 Linux 系统上,malloc
的最大内存限制可以非常大(通常由操作系统内核和硬件支持来决定),但也取决于系统配置(如 ulimit
设置、内存映射、虚拟内存设置等)。malloc
的实际限制也可能受到内存碎片化和系统资源的影响。
Windows:Windows 操作系统也支持大内存分配,但其最大分配空间受到物理内存、虚拟内存(例如页面文件的大小)以及系统架构的影响。在32位 Windows 上,单个进程的最大地址空间限制通常为2GB(对于某些配置为3GB),而在64位 Windows 上,理论上支持极大的虚拟内存空间,但仍受硬件和操作系统配置限制。
3.内存管理有哪几种方式?
1. 连续内存分配(Contiguous Allocation)
概念:所有的进程都在物理内存中占据一个连续的内存区域。
特点:简单高效,但容易造成内存碎片。
类型:
单一连续分配:一个进程只占用一个连续的内存区域,适用于短小的程序。
固定分区分配:内存被分为若干个固定大小的区块,进程被分配到适合大小的区块中。
动态分区分配:内存区块大小根据需要动态分配,不固定,但会随着时间推移出现碎片。
优缺点:
优点:容易实现,访问效率高。
缺点:随着时间的推移,内存碎片问题严重,可能导致无法找到足够的连续内存。
2. 非连续内存分配(Non-contiguous Allocation)
概念:进程在内存中的分配不要求是连续的,而是通过地址映射和分页技术来管理。
实现方式:
分页(Paging):将内存和进程的地址空间都分为固定大小的块(页),进程的每个页可以在物理内存中的任何位置,不需要连续。
分段(Segmentation):将进程划分为多个逻辑段(如代码段、数据段、堆栈段等),每个段在内存中可以独立分配,段的大小不固定。
优缺点:
优点:消除内存碎片问题,灵活性更高。
缺点:需要复杂的内存映射和地址转换,可能引入管理开销。
3. 虚拟内存管理(Virtual Memory Management)
概念:虚拟内存通过将硬盘的一部分作为扩展内存(交换空间或页面文件),允许程序使用比物理内存更多的内存。
实现方式:
页面交换(Paging):将内存分为固定大小的页面,操作系统根据需要将内存页交换到硬盘中,进程运行时只需加载当前需要的页面。
分段交换:将整个段(如代码段)交换到硬盘中,适合较大块的内存管理。
优缺点:
优点:程序能够使用比物理内存更多的内存,支持多任务操作。
缺点:访问硬盘时速度较慢,可能引发“交换抖动”(Thrashing),即频繁从硬盘交换数据,导致性能下降。
4. 内存池管理(Memory Pooling)
概念:内存池将内存预先分配好并组织为一个内存池,进程或线程从池中申请内存。这种方式常用于需要频繁分配和释放内存的场景。
特点:避免频繁的内存分配和释放,提升效率。
优缺点:
优点:提高内存分配效率,避免碎片化。
缺点:可能造成内存的浪费,尤其是当内存池的大小过大或过小时。
5. 伙伴系统(Buddy System)
概念:内存被划分为大小为2的幂次方的块,每个块有一个伙伴,合并和分裂操作通过伙伴来管理,保证内存分配和回收时没有碎片。
特点:内存分配时每次分配2的幂次方大小的内存块,减少内存碎片。
实现:当一个内存块释放时,操作系统会查找其“伙伴”是否空闲,若空闲,则合并成一个更大的内存块。
4.什么虚拟内存?
虚拟内存是一种让程序认为它拥有连续的大内存的技术,实际上程序使用的内存可能分布在不同的物理位置,甚至不完全在内存中。
简单来说,虚拟内存让程序可以使用比实际物理内存更多的内存。操作系统通过将不常用的数据暂时存储到硬盘上的交换空间(swap space)来实现,只有当需要时再将它们加载回内存。
- 让程序可以使用更多内存,比实际物理内存大。
- 增强多任务处理能力,让多个程序能并行运行。
5.解释下内存碎片,内碎片,外碎片?
内碎片是分配的内存比需要的内存多,造成空间浪费。
外碎片是内存中有很多小块空闲空间,但这些小块不连续,无法被大内存块使用。
6.解释下虚拟地址,逻辑地址,线性地址,物理地址?
虚拟地址:程序看到的地址,由操作系统管理,跟物理内存无关。
逻辑地址:程序生成的地址(有时和虚拟地址一样),在分段管理中使用。
线性地址:在分页系统中,经过段映射后的地址,还需要分页才能得到物理地址。
物理地址:实际的内存地址,硬件操作的地址,直接对应内存中的位置。
7.虚拟内存置换方法是怎么样的?
虚拟内存的置换方法主要是通过选择合适的页面来腾出内存空间,常见的置换算法有:
FIFO:先来先服务,简单,但可能不够高效。
LRU:选取最久未使用的页面,效果较好。
LFU:选取最少使用的页面,适合访问频率较低的场景。
OPT:理论上最佳,但不可实现。
Random:随机置换,简单但不高效。
实际操作系统会根据不同情况选择适合的置换算法,以平衡性能和效率。
8.给你一个类,里面有static,virtual之类的,来说一说这个类的内存分布?
实例数据:存储在堆或栈中,每个对象有一份副本。
静态成员:类的所有实例共享,存储在数据段中。
虚函数表(Vtable):存储在只读数据区,类中每个对象有一个指向虚函数表的指针(Vptr)。
9.假设临界区资源释放,如何保证只让一个线程获得临界区资源而不是都获得?
要保证 临界区 资源的 互斥访问,通常需要使用同步机制,确保在同一时间只有一个线程能访问临界区。最常见的方法是使用 互斥锁(mutex) 或 信号量(semaphore):
互斥锁(Mutex):在进入临界区时,线程会先尝试获取锁,如果锁已经被其他线程占用,当前线程将被阻塞,直到锁被释放。这样,多个线程就不会同时进入临界区。
信号量(Semaphore):信号量控制临界区的资源数量,确保不超过一定数量的线程同时进入临界区。二值信号量通常用于实现互斥锁。
10.操作系统中的缺页中断是什么?
缺页中断是虚拟内存管理中的一个重要概念。当程序访问一个当前不在物理内存中的页面时,会触发缺页中断。操作系统通过捕获这个中断并进行处理,加载所需的页面到内存中,从而让程序继续执行。
触发条件:程序访问的虚拟地址在物理内存中没有对应的页面。
处理过程:
- 触发缺页中断。
- 操作系统暂停程序,查看页面是否在磁盘中的交换空间(Swap Space)或其他地方。
- 如果页面在硬盘中,将其加载到内存中。
- 更新页表,将虚拟地址映射到新的物理地址。
- 继续执行程序。
11.OS缺页置换算法如何实现的?
缺页置换算法是在缺页中断发生时,操作系统决定哪些页面需要被换出,并选择一个合适的页面进行替换。常见的缺页置换算法有:
LRU(Least Recently Used):选择最久未使用的页面进行置换。操作系统可以通过维护一个队列或时间戳来跟踪页面的使用情况,选择最近最少使用的页面换出。
FIFO(First In, First Out):最早加载到内存的页面优先被置换。操作系统维护一个队列,新的页面在队列尾部,最老的页面在队列头部,队列头部的页面会被置换。
OPT(Optimal):理论上最优的算法,选择未来最长时间不会被使用的页面进行置换。缺点是不可实现,因为操作系统无法预测未来的访问模式。
随机置换:操作系统随机选择一个页面进行置换,简单但不一定高效。
12.系统调用是什么,你用过哪些系统调用,和库函数有什么区别?
系统调用(System Call):系统调用是应用程序与操作系统内核之间的接口。当应用程序需要操作系统提供的服务(如文件操作、进程管理、内存分配等)时,会通过系统调用与操作系统交互。常见的系统调用包括 read()
, write()
, fork()
, exec()
等。
库函数(Library Function):库函数是由程序库提供的预定义函数,它们封装了常见的操作(如数学计算、字符串处理等)。库函数通常是用户空间的代码,直接调用库函数不会进入操作系统内核。
13.为什么要有page cache,操作系统怎么设计的page cache?
Page Cache 是操作系统用来提高磁盘I/O性能的一种机制。它将磁盘文件内容缓存到内存中,避免每次访问文件时都需要从磁盘读取,提高了文件访问的速度。
原因:
磁盘访问速度远远低于内存,频繁的磁盘I/O会极大影响性能。
文件内容往往会被多次访问,缓存可以减少重复读取磁盘的开销。
设计:
操作系统会在内存中为文件数据保留一个缓存区,这个区域叫做页面缓存(Page Cache)。
当程序访问文件时,操作系统首先检查是否文件的数据已存在于缓存中。如果存在,直接从内存返回数据;如果不存在,操作系统会从磁盘读取数据并缓存到内存中。
采用 LRU 或其他替换算法来管理缓存中的数据,保证高效地使用有限的内存空间。