参考:《ppp2e》
p258
1.前言
书中有专门的一章来讲嵌入式开发,因为我本身不做嵌入式开发,故此篇笔记不会讲的太复杂。书中有提到 TEA 算法,还有一些个概念,我个人认为这对理解硬件与 C++关系有帮助,这一部分概念我在 C 语言中也学过,这里再复习一下还可以加深记忆。
本文主要涉及内容:
- 位域
- 二进制 IO 操作
- 页管理程序
- 位运算
2.位域
在C++中,位域可以通过结构体或者类中的成员来定义。位域允许程序员精确地控制每个成员的位数,并将多个成员组合到一个字节或更大的单元中,以便节省内存空间或者进行位操作。
位域的定义形式如下:
struct BitField {
type memberName : width;
};
其中:
type
表示成员的数据类型,可以是基本数据类型(如int
、char
等)或者自定义类型。memberName
表示成员的名称。width
表示成员的位数。
下面是一个简单的例子,演示了如何在结构体中定义位域:
#include <iostream>
struct Status {
unsigned int power : 1; // 1位用于表示电源状态
unsigned int temperature : 7; // 7位用于表示温度
};
int main() {
Status deviceStatus;
deviceStatus.power = 1; // 设置电源状态为开启
deviceStatus.temperature = 50; // 设置温度为50
std::cout << "Power: " << deviceStatus.power << std::endl;
std::cout << "Temperature: " << deviceStatus.temperature << std::endl;
return 0;
}
在这个例子中,Status
结构体包含了两个位域成员:power
和temperature
。其中,power
占据了1位,用于表示电源状态(开启或关闭),temperature
占据了7位,用于表示温度值。
3.二进制 IO 操作
C++中的二进制IO操作是指以二进制形式读写文件数据,而不是以文本形式。这允许程序直接操作文件的原始数据,而不会对数据进行任何格式化或解释。
在C++中,可以使用std::ofstream和std::ifstream类来进行二进制写和读操作。这些类提供了成员函数write()和read(),用于写入和读取二进制数据块。
#include <iostream>
#include <fstream>
int main() {
// outFile对象将数据写入文件,std::ios::binary表示以二进制模式打开文件
std::ofstream outFile("data.bin", std::ios::binary);
if (outFile.is_open()) {
int data[] = {1, 2, 3, 4, 5};
// 将整数数组转换为字符指针:char*,write参数需要
outFile.write(reinterpret_cast<char*>(data), sizeof(data));
outFile.close();
} else {
std::cerr << "Failed to open file for writing." << std::endl;
return 1;
}
// inFile对象从文件中读取数据
std::ifstream inFile("data.bin", std::ios::binary);
if (inFile.is_open()) {
int data[5];
inFile.read(reinterpret_cast<char*>(data), sizeof(data));
inFile.close();
for (const auto& num : data) {
std::cout << num << std::endl;
}
// for (int i = 0; i < 5; ++i) {
// std::cout << data[i] << std::endl;
// }
} else {
std::cerr << "Failed to open file for reading." << std::endl;
return 1;
}
return 0;
}
可以看看下面的视频演示,很直观:
4.页管理程序
p297
页管理程序是一个新概念,在今天之前从来没听过。因为,最近在学习微机原理,书中使用的是 8086 处理器,刚查了下,8086 处理器是一个16位的处理器,它使用的是实模式内存管理,不支持虚拟内存和分页管理,而引入是在 80386 处理器中。虚拟内存是在后续的x86架构中引入的,特别是在Intel 80386处理器(简称386)以及后续的x86架构中开始支持虚拟内存。Intel 80386是Intel推出的第一个32位x86架构处理器,它是第一个支持虚拟内存和分页机制的x86处理器。虚拟内存的引入使得操作系统能够更灵活地管理内存,并提供了更好的内存保护和资源共享机制。
页管理程序书中大概提了一嘴,不知道就学呗。这一部分内容由 ChatGPT 生成,算是拓展知识:
虚拟内存是操作系统提供的一种高级特性,通过硬件支持实现,但具体的管理和实现由操作系统完成。CPU提供了一些硬件机制来支持虚拟内存的实现,但实际的虚拟内存管理工作是由操作系统来完成的。
具体来说,CPU本身提供了一些硬件机制来支持虚拟内存,包括:
- 地址转换:CPU通过地址转换机制将虚拟地址转换为物理地址。在x86架构中,地址转换由段选择器、段描述符和页表等硬件结构完成。
- 分页机制:CPU支持分页机制,即将内存划分为固定大小的页面,并通过页表将虚拟页面映射到物理页面上。
- 访存权限控制:CPU支持访存权限控制,可以通过页表中的权限位来控制对页面的访问权限,如读、写、执行等。
然而,具体的虚拟内存管理工作是由操作系统提供的页管理程序来完成的,包括:
- 页面分配和回收:操作系统负责管理虚拟内存的分配和回收,以满足进程的内存需求。
- 页面映射:操作系统负责建立虚拟地址空间与物理内存之间的映射关系,以便进程能够访问内存中的数据。
- 页面替换:操作系统在物理内存不足时负责进行页面替换,将部分页面换出到磁盘上的交换空间中,以释放物理内存。
总结,上述大致了解即可。
《现代操作系统:原理与实现》陈海波 夏虞斌等著
在腾讯云开发者社区读到这篇文章,结合上图和下图我们读这四个概念有点认识即可。
https://cloud.tencent.com/developer/article/1808858?areaId=106001
物理地址:逻辑上,我们可以把物理内存看成一个大数组,其中每个字节都可以通过与之对应的地址进行访问,这个地址就叫做物理地址
虚拟地址 :应用程序在运行时使用的地址
TLB(转址旁路缓存 Translation Lookaside Buffer):加速地址翻译的过程
MMU(内存管理单元 Memory Management Unit): 负责虚拟地址到物理地址的转换
图片摘自:https://ask.qcloudimg.com/http-save/yehe-4631360/urlzed9ot3.png
图片摘自:https://ask.qcloudimg.com/http-save/yehe-4631360/03aionuj4w.png
https://cloud.tencent.com/developer/article/1808858?areaId=106001
段页式内存管理:
- 将地址空间按照程序自身的逻辑关系分为若干层,将各段分为大小相等的页面
- 将物理内存与虚拟内存划分为大小相等的一个个的内存块,系统以块为单位为进程分配内存
- 逻辑地址/虚拟地址(段号,页号,页内偏移量)
虚拟地址翻译为物理地址的步骤变为:
- 根据逻辑地址取出其中的段号,判断这个段号是否正常
- 如果正常,则找到该段号对应的页表初始地址
- 根据页号是否正常,若正常则根据页号找到物理初始地址,在加上页内偏移量则找到真正的物理地址
现在,我们再回到上文《ppp2e》中提到的页管理程序,书中的小 demo 是用位域完成的。emm,贴这个主要是想了解下页管理程序。
最后,这篇文章写的也不错:
为什么要有虚拟地址空间呢?
先从没有虚拟地址空间的时候说起吧!没有虚拟地址空间的时候,程序都是直接访问和操作的都是物理内存。 但是这样有什么问题呢?
- 用户程序可以访问任意内存,寻址内存的每个字节,这样就很容易(有意或者无意)破坏操作系统,造成操作系统崩溃。
- 想要同时运行多个程序特别困难,比如你想同时运行一个微信和一个 QQ 音乐都不行。为什么呢?举个简单的例子:微信在运行的时候给内存地址 1xxx 赋值后,QQ 音乐也同样给内存地址 1xxx 赋值,那么 QQ 音乐对内存的赋值就会覆盖微信之前所赋的值,这就造成了微信这个程序就会崩溃。
总结来说:如果直接把物理地址暴露出来的话会带来严重问题,比如可能对操作系统造成伤害以及给同时运行多个程序造成困难。
通过虚拟地址访问内存有以下优势:
- 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
- 程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。当物理内存的供应量变小时,内存管理器会将物理内存页(通常大小为 4 KB)保存到磁盘文件。数据或代码页会根据需要在物理内存与磁盘之间移动。
- 不同进程使用的虚拟地址彼此隔离。一个进程中的代码无法更改正在由另一进程或操作系统使用的物理内存。
5.位运算
就直接从 demo 中体会就行,很小的知识点:
#include <iostream>
int main() {
// 位与运算(&):将两个操作数的对应位进行与运算
unsigned int a = 0b101010; // 42
unsigned int b = 0b110011; // 51
unsigned int result_and = a & b; // 0b100010 = 34
std::cout << "Bitwise AND: " << result_and << std::endl;
// 位或运算(|):将两个操作数的对应位进行或运算
unsigned int result_or = a | b; // 0b111011 = 59
std::cout << "Bitwise OR: " << result_or << std::endl;
// 位异或运算(^):将两个操作数的对应位进行异或运算
unsigned int result_xor = a ^ b; // 0b011001 = 25
std::cout << "Bitwise XOR: " << result_xor << std::endl;
// 位取反运算(~):对操作数的每个位进行取反
unsigned int result_not_a = ~a; // 0b11111111111111111111111111110101 = 4294967253
std::cout << "Bitwise NOT (a): " << result_not_a << std::endl;
// 位左移运算(<<):将操作数的所有位向左移动指定的位数
unsigned int result_left_shift = a << 2; // 0b10101000 = 168
std::cout << "Bitwise Left Shift (a << 2): " << result_left_shift << std::endl;
// 位右移运算(>>):将操作数的所有位向右移动指定的位数
unsigned int result_right_shift = b >> 2; // 0b1100 = 12
std::cout << "Bitwise Right Shift (b >> 2): " << result_right_shift << std::endl;
return 0;
}
暂无评论