操作系统真相还原 第八章 内存管理系统
第八章 内存管理系统
位图bitmap及其函数的实现
位图简介
位图:
bitmap,常用的数据结构,广泛用于资源管理,大量资源的管理可以采用位图的方式。redis也有bitmap。
位(bit)是最小的单位,节省空间,可以用位来映射(map)一个被管理的资源单位,0表示此资源单位未使用,1表示使用。所以此种数据结构被称为bitmap。
一个位图结构相当于一组资源的映射,位图中的每一位都与被管理的资源单位一一映射,很适合用于管理大容量的资源。
位图本质是一个二进制串,可以使用字节数组来实现。一个字节有8位,映射8个资源单位。
位图的定义与实现
位图结构
#ifndef __LIB_KERNEL_BITMAP_H
#define __LIB_KERNEL_BITMAP_H
#include "global.h"
#define BITMAP_MASK 1
struct bitmap {
uint32_t btmp_bytes_len;
/* 在遍历位图时,整体上以字节为单位,细节上是以位为单位,所以此处位图的指针必须是单字节 */
uint8_t* bits;
};
void bitmap_init(struct bitmap* btmp);
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx);
int bitmap_scan(struct bitmap* btmp, uint32_t cnt);
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value);
#endif
具体实现
#include "bitmap.h"
#include "stdint.h"
#include "string.h"
#include "print.h"
#include "interrupt.h"
#include "debug.h"
/* 将位图btmp初始化 */
void bitmap_init(struct bitmap* btmp) {
memset(btmp->bits, 0, btmp->btmp_bytes_len);
}
/* 判断bit_idx位是否为1,若为1则返回true,否则返回false */
bool bitmap_scan_test(struct bitmap* btmp, uint32_t bit_idx) {
uint32_t byte_idx = bit_idx / 8; // 向下取整用于索引数组下标
uint32_t bit_odd = bit_idx % 8; // 取余用于索引数组内的位
return (btmp->bits[byte_idx] & (BITMAP_MASK << bit_odd));
}
/* 在位图中申请连续cnt个位,成功则返回其起始位下标,失败返回-1 */
int bitmap_scan(struct bitmap* btmp, uint32_t cnt) {
uint32_t idx_byte = 0; // 用于记录空闲位所在的字节
/* 先逐字节比较,蛮力法 */
while (( 0xff == btmp->bits[idx_byte]) && (idx_byte < btmp->btmp_bytes_len)) {
/* 1表示该位已分配,所以若为0xff,则表示该字节内已无空闲位,向下一字节继续找 */
idx_byte++;
}
ASSERT(idx_byte < btmp->btmp_bytes_len);
if (idx_byte == btmp->btmp_bytes_len) { // 若该内存池找不到可用空间
return -1;
}
/* 若在位图数组范围内的某字节内找到了空闲位,
* 在该字节内逐位比对,返回空闲位的索引。*/
int idx_bit = 0;
/* 和btmp->bits[idx_byte]这个字节逐位对比 */
while ((uint8_t)(BITMAP_MASK << idx_bit) & btmp->bits[idx_byte]) {
idx_bit++;
}
int bit_idx_start = idx_byte * 8 + idx_bit; // 空闲位在位图内的下标
if (cnt == 1) {
return bit_idx_start;
}
uint32_t bit_left = (btmp->btmp_bytes_len * 8 - bit_idx_start); // 记录还有多少位可以判断
uint32_t next_bit = bit_idx_start + 1;
uint32_t count = 1; // 用于记录找到的空闲位的个数
bit_idx_start = -1; // 先将其置为-1,若找不到连续的位就直接返回
while (bit_left-- > 0) {
if (!(bitmap_scan_test(btmp, next_bit))) { // 若next_bit为0
count++;
} else {
count = 0;
}
if (count == cnt) { // 若找到连续的cnt个空位
bit_idx_start = next_bit - cnt + 1;
break;
}
next_bit++;
}
return bit_idx_start;
}
/* 将位图btmp的bit_idx位设置为value */
void bitmap_set(struct bitmap* btmp, uint32_t bit_idx, int8_t value) {
ASSERT((value == 0) || (value == 1));
uint32_t byte_idx = bit_idx / 8; // 向下取整用于索引数组下标
uint32_t bit_odd = bit_idx % 8; // 取余用于索引数组内的位
/* 一般都会用个0x1这样的数对字节中的位操作,
* 将1任意移动后再取反,或者先取反再移位,可用来对位置0操作。*/
if (value) { // 如果value为1
btmp->bits[byte_idx] |= (BITMAP_MASK << bit_odd);
} else { // 若为0
btmp->bits[byte_idx] &= ~(BITMAP_MASK << bit_odd);
}
}
bitmap_init
初始化位图,把位图的每个字节填充为0,即每个位都是0。
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。返回一个指向存储区 str 的指针。
bitmap_scan_test
判断bit_idx位是否为1,即映射的内存是否已被分配
java版
public static void main(String[] args) {
int b = 0b1110_1111;
char[] carr = {(char) b, '0'};
bitmap_scan_test(carr, 4);
}
static int bitmap_scan_test(char[] carr, int bitIdx) {
int byteIdx = bitIdx / 8;
int bitOdd = bitIdx % 8;
int a = carr[byteIdx];
int b = BIT_MASK<< bitOdd;
System.out.println("十进制:" + a + "," + b);
System.out.println("二进制:" + Integer.toBinaryString(a) + "," + Integer.toBinaryString(b));
int result = a & b;
System.out.println("结果:" + result);
return ( result);
}
执行结果
十进制:239,16
二进制:11101111,10000
结果:0
BIT_MASK<< bitOdd后,只剩下要比较的位为1,其他为都是0, return (btmp->bits[byte_idx] & (BITMAP_MASK << bit_odd));
返回值:此位为0时,结果为0;此为为1时,结果不一定为1,只是大于0。
bitmap_scan
在位图中申请连续cnt个位
找到连续的内存,不支持不连续的内存分配。
内存管理系统
内存池规划
分为物理内存池,虚拟内存池。通过页表来做虚拟和物理的映射。内存池中内存单位为页,即4k。
物理内存池分为内核物理内存池,用户内核内存池。
每个用户进程有自己的一个虚拟内存池,用户进程的页表把虚拟内存池的虚拟内存地址映射到用户物理内存地址,不能映射到内核物理内存池。
虚拟内存池和物理内存池:
/* 用于虚拟地址管理 */
struct virtual_addr {
struct bitmap vaddr_bitmap; // 虚拟地址用到的位图结构
uint32_t vaddr_start; // 虚拟地址起始地址
};
/* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */
struct pool {
struct bitmap pool_bitmap; // 本内存池用到的位图结构,用于管理物理内存
uint32_t phy_addr_start; // 本内存池所管理物理内存的起始地址
uint32_t pool_size; // 本内存池字节容量
};
struct pool kernel_pool, user_pool; // 生成内核内存池和用户内存池
struct virtual_addr kernel_vaddr; // 此结构是用来给内核分配虚拟地址
申请内存时,就是使用位图,然后先从虚拟内存池申请虚拟内存地址,然后从物理内存池中申请物理内存地址,然后用页目录表页面映射虚拟地址和物理地址。