《操作系统真象还原》第十二章(二) —— 完善堆内存管理

章节任务介绍

任务简介

上一节,我们完成了用户程序和操作系统之间的接口------系统调用,本节我们在此基础上完善堆内存管理------实现malloc和free

之前我们虽然已经实现了内存管理,但之前实现分配的内存都是以 4KB 大小的页框为单位的,当我们仅需要几十字节、几百字节这样的小内存块 时,显然无法满足这样的需求了,为此必须实现一种小内存块的管理,可以满足任意内存大小的分配,这就是我们为实现malloc要做的基础工作。

本节的主要任务有:

  1. 实现sys_malloc

  2. 实现sys_free

  3. 实现malloc

  4. 实现free

内存分配

底层初始化

划分和管理内存块

为了实现对小内存块的管理和分配

  • 我们将一页内存按照块大小平均划分

  • 假如我们规定一块的内存大小是512B,则这页内存可以被分为4k/512B=8个块

  • 为了适应不同块大小的分配,我们分别定义7种类型大小的内存块

接下来我们定义一些必要的数据结构,以下是这些数据结构之间的关系

`/kernel/memory.h`

首先是空闲内存块的定义

cpp 复制代码
/*
内存块描述符个数
对应16、32、64、128、256、512、1024这7种小内存块
*/
#define DESC_CNT 7

/*内存块*/
struct mem_block
{
   // 内存块使用双链表进行管理
   struct list_elem free_elem;
};

为了对内存块进行管理,我们还需要定义内存块的信息管理数据结构

cpp 复制代码
/*内存块描述符*/
struct mem_block_desc
{
   // 每个块的大小
   uint32_t block_size;
   // 块的总数量
   uint32_t blocks_per_arena;
   // 用于管理组织块的双链表结构的头节点
   struct list free_list;
};

同时为了分配内存,我们需要记录空闲内存块的数量和位置

/kernel/memory.c

cpp 复制代码
/*
内存仓库元信息
每个页面有7种类型大小的内存块
一个arena管理一种类型大小的内存块
*/
struct arena
{
	/*管理该种类型大小的内存块数组索引*/
	struct mem_block_desc *desc;
	/*
	标记是分配的页框还是内存块
	如果是页框,则cnt表示页框的数量
	如果是内存块,则cnt表示空闲内存块的数量
	(注意,是空闲块的数量,mem_block_desc中表示的是块的总数)
	*/
	bool large;
	uint32_t cnt;
};
从哪申请内存块

内存块的分配和释放本质上都是从内存池中进行申请和归还的,而我们有两种内存池------内核内存池和用户内存池

其中内核内存池供内核程序使用,用户内存池供用户程序使用

因此针对不同类型的内存池需要各自分别添加内存管理结构

内核内存池

/kernel/memory.c

cpp 复制代码
/*不同类型大小的内存块管理数组*/
struct mem_block_desc k_block_descs[DESC_CNT];
用户内存池

/thread/thread.h

cpp 复制代码
/* 进程或线程的pcb,程序控制块, 此结构体用于存储线程的管理信息*/
struct task_struct
{
   uint32_t *self_kstack; // 用于存储线程的栈顶位置,栈顶放着线程要用到的运行信息
   pid_t pid;             // 定义线程或者进程的pid
   enum task_status status;
   uint8_t priority; // 线程优先级
   char name[16];    // 用于存储自己的线程的名字

   uint8_t ticks;                                // 线程允许上处理器运行还剩下的滴答值,因为priority不能改变,所以要在其之外另行定义一个值来倒计时
   uint32_t elapsed_ticks;                       // 此任务自上cpu运行后至今占用了多少cpu嘀嗒数, 也就是此任务执行了多久*/
   struct list_elem general_tag;                 // general_tag的作用是用于线程在一般的队列(如就绪队列或者等待队列)中的结点
   struct list_elem all_list_tag;                // all_list_tag的作用是用于线程队列thread_all_list(这个队列用于管理所有线程)中的结点
   uint32_t *pgdir;                              // 进程自己页目录表的虚拟地址
   struct virtual_addr userprog_vaddr;           // 每个用户进程自己的虚拟地址池
   struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符
   uint32_t stack_magic;                         // 如果线程的栈无限生长,总会覆盖地pcb的信息,那么需要定义个边界数来检测是否栈已经到了PCB的边界
};

上述主要为PCB数据结构添加内存块管理成员

cpp 复制代码
struct mem_block_desc u_block_desc[DESC_CNT]; // 用户进程内存块描述符

内存块管理初始化

内核内存块管理初始化

/kernel/memory.c

cpp 复制代码
/*
初始化内存块管理数组
分别对应16、32、64、128、256、512、1024这7种大小内存块类型
*/
void block_desc_init(struct mem_block_desc *desc_array)
{
	uint16_t desc_idx, block_size = 16;
	for (desc_idx = 0; desc_idx < DESC_CNT; desc_idx++)
	{
		// 块的大小
		desc_array[desc_idx].block_size = block_size;
		// 块的总数
		desc_array[desc_idx].blocks_per_arena = (PG_SIZE - sizeof(struct arena)) / block_size;
		// 管理组织块的双链表结构
		list_init(&desc_array[desc_idx].free_list);
		block_size *= 2;
	}
}

添加内存块管理结构的初始化入口

cpp 复制代码
/* 内存管理部分初始化入口 */
void mem_init()
{
	put_str("mem_init start\n");
	uint32_t mem_bytes_total = (*(uint32_t *)(0xb00));
	mem_pool_init(mem_bytes_total); // 初始化内存池
	block_desc_init(k_block_descs);
	put_str("mem_init done\n");
}
用户内存块管理初始化

/userprog/process.c

cpp 复制代码
// 用于创建进程,参数是进程要执行的函数与他的名字
void process_execute(void *filename, char *name)
{
   /* pcb内核的数据结构,由内核来维护进程信息,因此要在内核内存池中申请 */
   struct task_struct *thread = get_kernel_pages(1);
   /*初始化pcb*/
   init_thread(thread, name, default_prio);
   // 初始化进程pcb中特有的页目录表
   thread->pgdir = create_page_dir();
   // 初始化进程虚拟内存池,进程有自己的虚拟内存
   create_user_vaddr_bitmap(thread);
   // 初始化进程空闲链表管理数组
   block_desc_init(thread->u_block_desc);

   // 首先初始化线程栈运行环境,执行start_process
   // 然后初始化中断栈,此时的中断栈也是进程的运行栈,接着执行真正的执行函数filename
   thread_create(thread, start_process, filename);

   enum intr_status old_status = intr_disable();
   ASSERT(!elem_find(&thread_ready_list, &thread->general_tag));
   list_append(&thread_ready_list, &thread->general_tag);

   ASSERT(!elem_find(&thread_all_list, &thread->all_list_tag));
   list_append(&thread_all_list, &thread->all_list_tag);
   intr_set_status(old_status);
}

用户内存块的初始化在创建用户进程时进行,而用户进程的创建也是内核负责完成的,因此只需在用户创建函数中调用block_desc_init完成对pcb中内存块管理结构的初始化即可,如下

cpp 复制代码
   // 初始化进程空闲链表管理数组
   block_desc_init(thread->u_block_desc);

实现sys_malloc

/kernel/memory.c

首先是一些必要的辅助函数

cpp 复制代码
/*返回arena中第idx个内存块的地址*/
static struct mem_block *arena2block(struct arena *a, uint32_t idx)
{
	/*跨过arana元信息,然后按照块大小和索引值寻找对应内存块地址*/
	return (struct mem_block *)((uint32_t)a + sizeof(struct arena) + idx * a->desc->block_size);
}

/*返回内存块block所在的arena地址*/
static struct arena *block2arena(struct mem_block *block)
{
	return (struct arena *)((uint32_t)block & 0xfffff000);
}

接下来正式实现sys_malloc

cpp 复制代码
/*在堆中申请size字节的内存*/
void *sys_malloc(uint32_t size)
{
	enum pool_flags PF;
	struct pool *mem_pool;
	uint32_t pool_size;
	struct mem_block_desc *descs;
	struct task_struct *cur_thread = running_thread();

	// 判断从哪个内存池中申请内存
	if (cur_thread->pgdir == NULL)
	{
		// 如果是内存线程,就从内核内存池中申请内存
		PF = PF_KERNEL;
		pool_size = kernel_pool.pool_size;
		mem_pool = &kernel_pool;
		descs = k_block_descs;
	}
	else
	{
		// 从用户内存池中申请内存
		PF = PF_USER;
		pool_size = user_pool.pool_size;
		mem_pool = &user_pool;
		descs = cur_thread->u_block_desc;
	}

	/* 若申请的内存不在内存池容量范围内则直接返回NULL */
	if (!(size > 0 && size < pool_size))
		return NULL;

	struct arena *a;
	struct mem_block *block;
	lock_acquire(&mem_pool->lock);
	/* 超过最大内存块1024, 就分配页框 */
	if (size > 1024)
	{
		// 向上取整需要的页框数
		uint32_t page_cnt = DIV_ROUND_UP(size + sizeof(struct arena), PG_SIZE);
		a = malloc_page(PF, page_cnt);
		if (a != NULL)
		{
			memset(a, 0, page_cnt * PG_SIZE);

			a->desc = NULL;
			a->cnt = page_cnt;
			a->large = true;
			lock_release(&mem_pool->lock);
			// 跨过arena大小,把剩下的内存返回
			return (void *)(a + 1);
		}
		else
		{
			lock_release(&mem_pool->lock);
			return NULL;
		}
	}
	// 若申请的内存小于等于1024,可在各种规格的mem_block_desc中去适配
	else
	{
		uint8_t desc_idx;
		for (desc_idx = 0; desc_idx < DESC_CNT; desc_idx++)
		{
			// 首次适应,从小往大后,找到后退出
			if (size <= descs[desc_idx].block_size)
				break;
		}

		/*找到了可以容纳申请大小的内存块类型,然后寻找空闲块*/
		if (list_empty(&descs[desc_idx].free_list))
		{
			// 如果链表为空,说明此时没有空闲块,就创建新块分配之
			// 分配1页框做为arena
			a = malloc_page(PF, 1);
			if (a == NULL)
			{
				lock_release(&mem_pool->lock);
				return NULL;
			}
			memset(a, 0, PG_SIZE);
			/* 对于分配的小块内存,将desc置为相应内存块描述符,
			 * cnt置为此arena可用的内存块数,large置为false */
			a->desc = &descs[desc_idx];
			a->large = false;
			a->cnt = descs[desc_idx].blocks_per_arena;

			uint32_t block_idx;
			enum intr_status old_status = intr_disable();
			/* 开始将arena拆分成内存块,并添加到内存块描述符的free_list中 */
			for (block_idx = 0; block_idx < descs[desc_idx].blocks_per_arena; block_idx++)
			{
				block = arena2block(a, block_idx);
				ASSERT(!elem_find(&a->desc->free_list, &block->free_elem));
				list_append(&a->desc->free_list, &block->free_elem);
			}
			intr_set_status(old_status);
		}
		/* 开始分配内存块 */
		// block = elem2entry(struct mem_block, free_elem, list_pop(&(descs[desc_idx].free_list)));
		block = list_pop(&(descs[desc_idx].free_list));
		memset(block, 0, descs[desc_idx].block_size);

		a = block2arena(block);
		a->cnt--;
		lock_release(&mem_pool->lock);
		return (void *)block;
	}
}

其代码逻辑为

  1. 判断在哪种类型的内存池中申请内存块------内核内存池 or 用户内存池?

  2. 判断申请的内存大小是否大于最大可分配类型大小的内存1024B

    • 如果申请内存的块大小,大于最大可分配类型大小的内存1024B,则说明无法分配小的内存块,直接分配一页页框给申请者

    • 否则转向步骤3

  3. 遍历各种类型大小的内存块,找到第一个满足申请内存大小的类型内存(首次适配算法)。如申请者申请500B的内存,遍历后发现256B<500B<512B,说明此时可以尝试去找一块空闲的512B的内存块分配给申请者

  4. 查看空闲链表是否为空,即查看是否有没有被分配的512B内存块

    • 如果空闲链表为空,说明512B大小的内存块还没有创建或者已经被分配完了

      • 则此时重新从内核中申请一页内存

      • 然后将该页内存划分成7个(由于有arena的存在,可用的块只有7个)512B大小的内存块

      • 然后将划分好的内存块插入到空闲链表中

    • 如果空闲链表不为空,则转向5

  5. 空闲链表不为空,说明有可用的内存块使用,则从空闲链表中弹出然后分配给申请者

测试

/kernel/main.c

cpp 复制代码
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall-init.h"
#include "syscall.h"
#include "stdio.h"
#include "memory.h"

void k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);

int main(void)
{
   put_str("I am kernel\n");
   init_all();
   intr_enable();
   thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
   thread_start("k_thread_b", 31, k_thread_b, "I am thread_b ");
   while (1)
      ;
   return 0;
}

/* 在线程中运行的函数 */
void k_thread_a(void *arg)
{
   char *para = arg;
   void *addr = sys_malloc(33);
   console_put_str(" I am thread_a, sys_malloc(33), addr is 0x");
   console_put_int((int)addr);
   console_put_char('\n');
   while (1)
      ;
}

/* 在线程中运行的函数 */
void k_thread_b(void *arg)
{
   char *para = arg;
   void *addr = sys_malloc(63);
   console_put_str(" I am thread_b, sys_malloc(63), addr is 0x");
   console_put_int((int)addr);
   console_put_char('\n');
   while (1)
      ;
}

/* 测试用户进程 */
void u_prog_a(void)
{
   char *name = "prog_a";
   printf(" I am %s, my pid:%d%c", name, getpid(), '\n');
   while (1)
      ;
}

/* 测试用户进程 */
void u_prog_b(void)
{
   char *name = "prog_b";
   printf(" I am %s, my pid:%d%c", name, getpid(), '\n');
   while (1)
      ;
}

编译运行

内存释放

内存块的释放是基于页面的------假如所有内存块都空闲,则直接将该页面释放,否则就只是将该内存块插入到空闲链表中

因此我们首先需要构建内存页的释放,内存页的释放是内存页分配的逆过程

  1. 在物理内存池中释放物理内存页(只需将位图置为0即可)

  2. 清除页表中的页表项,即清除掉虚拟内存和物理内存的映射关系

  3. 在虚拟内存池中释放虚拟内存页(只需将位图置为0即可)

内存页的释放

释放物理内存页

/kernel/memory.c

cpp 复制代码
/*将物理地址pg_phy_addr回收到物理内存池*/
void pfree(uint32_t pg_phy_addr)
{
	struct pool *mem_pool;
	uint32_t bit_idx = 0;
	// 用户物理内存池
	if (pg_phy_addr >= user_pool.phy_addr_start)
	{
		mem_pool = &user_pool;
		bit_idx = (pg_phy_addr - user_pool.phy_addr_start) / PG_SIZE;
	}
	else
	{ // 内核物理内存池
		mem_pool = &kernel_pool;
		bit_idx = (pg_phy_addr - kernel_pool.phy_addr_start) / PG_SIZE;
	}
	// 将位图中该位清0
	bitmap_set(&mem_pool->pool_bitmap, bit_idx, 0);
}
删除映射关系

/kernel/memory.c

cpp 复制代码
/* 去掉页表中虚拟地址vaddr的映射,只去掉vaddr对应的pte */
static void page_table_pte_remove(uint32_t vaddr)
{
	uint32_t *pte = pte_ptr(vaddr);
	// 将页表项pte的P位置0
	*pte &= ~PG_P_1;
	// 更新tlb
	asm volatile("invlpg %0" ::"m"(vaddr) : "memory");
}
释放虚拟内存页

/kernel/memory.c

cpp 复制代码
/*在虚拟地址池中释放以_vaddr起始的连续pg_cnt个虚拟页地址,实质就是清除虚拟内存池位图的位*/
static void vaddr_remove(enum pool_flags pf, void *_vaddr, uint32_t pg_cnt)
{
	uint32_t bit_idx_start = 0, vaddr = (uint32_t)_vaddr, cnt = 0;
	// 内核虚拟内存池
	if (pf == PF_KERNEL)
	{
		bit_idx_start = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
		while (cnt < pg_cnt)
			bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 0);
	}
	else
	{ // 用户虚拟内存池
		struct task_struct *cur_thread = running_thread();
		bit_idx_start = (vaddr - cur_thread->userprog_vaddr.vaddr_start) / PG_SIZE;
		while (cnt < pg_cnt)
			bitmap_set(&cur_thread->userprog_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 0);
	}
}
内存页释放过程封装
cpp 复制代码
/* 释放以虚拟地址vaddr为起始的cnt个物理页框 */
void mfree_page(enum pool_flags pf, void *_vaddr, uint32_t pg_cnt)
{
	uint32_t vaddr = (uint32_t)_vaddr, page_cnt = 0;
	ASSERT(pg_cnt >= 1 && vaddr % PG_SIZE == 0);
	uint32_t pg_phy_addr = addr_v2p(vaddr);
	/* 确保待释放的物理内存在低端1M+1k大小的页目录+1k大小的页表地址范围外 */
	ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= 0x102000);
	/* 判断pg_phy_addr属于用户物理内存池还是内核物理内存池 */
	if (pg_phy_addr >= user_pool.phy_addr_start)
	{ // 位于user_pool内存池
		vaddr -= PG_SIZE;
		while (page_cnt < pg_cnt)
		{
			vaddr += PG_SIZE;
			pg_phy_addr = addr_v2p(vaddr);

			/* 确保物理地址属于用户物理内存池 */
			ASSERT((pg_phy_addr % PG_SIZE) == 0 && pg_phy_addr >= user_pool.phy_addr_start);

			/* 先将对应的物理页框归还到内存池 */
			pfree(pg_phy_addr);

			/* 再从页表中清除此虚拟地址所在的页表项pte */
			page_table_pte_remove(vaddr);

			page_cnt++;
		}
		/* 清空虚拟地址的位图中的相应位 */
		vaddr_remove(pf, _vaddr, pg_cnt);
	}
	else
	{ // 位于kernel_pool内存池
		vaddr -= PG_SIZE;
		while (page_cnt < pg_cnt)
		{
			vaddr += PG_SIZE;
			pg_phy_addr = addr_v2p(vaddr);
			/* 确保待释放的物理内存只属于内核物理内存池 */
			ASSERT((pg_phy_addr % PG_SIZE) == 0 &&
				   pg_phy_addr >= kernel_pool.phy_addr_start &&
				   pg_phy_addr < user_pool.phy_addr_start);

			/* 先将对应的物理页框归还到内存池 */
			pfree(pg_phy_addr);

			/* 再从页表中清除此虚拟地址所在的页表项pte */
			page_table_pte_remove(vaddr);

			page_cnt++;
		}
		/* 清空虚拟地址的位图中的相应位 */
		vaddr_remove(pf, _vaddr, pg_cnt);
	}
}

sys_free

有了内存页的释放过程之后,内存块的释放就可以实现了,其逻辑同上所述

  1. 判断释放的内存块大小是否大于可分配的最大大小1024B,如果是就直接释放这个页面,否则转步骤2

  2. 判断当前内存块所在的arena是否所有块都空闲,如果是就直接释放这个页面,否则转步骤3

  3. 仅仅释放该内存块,将其插入空闲链表即可

cpp 复制代码
/* 回收内存块ptr */
void sys_free(void *ptr)
{
	ASSERT(ptr != NULL);
	if (ptr != NULL)
	{
		enum pool_flags PF;
		struct pool *mem_pool;

		/*判断是线程还是进程*/
		if (running_thread()->pgdir == NULL)
		{
			ASSERT((uint32_t)ptr >= K_HEAP_START);
			PF = PF_KERNEL;
			mem_pool = &kernel_pool;
		}
		else
		{
			PF = PF_USER;
			mem_pool = &user_pool;
		}

		lock_acquire(&mem_pool->lock);
		struct mem_block *b = ptr;
		struct arena *a = block2arena(b);
		ASSERT(a->large == 0 || a->large == 1);
		if (a->desc == NULL && a->large == true)
		{
			// 大于1024的内存
			mfree_page(PF, a, a->cnt);
		}
		else
		{
			// 小于等于1024的内存块先将内存块回收到free_list
			list_append(&a->desc->free_list, &b->free_elem);
			/* 再判断此arena中的内存块是否都是空闲,如果是就释放arena */
			if (++a->cnt == a->desc->blocks_per_arena)
			{
				uint32_t block_idx;
				for (block_idx = 0; block_idx < a->desc->blocks_per_arena; block_idx++)
				{
					struct mem_block *b = arena2block(a, block_idx);
					ASSERT(elem_find(&a->desc->free_list, &b->free_elem));
					list_remove(&b->free_elem);
				}
				mfree_page(PF, a, 1);
			}
		}
		lock_release(&mem_pool->lock);
	}
}

测试

/kernel/main.c

cpp 复制代码
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall-init.h"
#include "syscall.h"
#include "stdio.h"
#include "memory.h"

void k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);

int main(void)
{
   put_str("I am kernel\n");
   init_all();
   intr_enable();
   thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
   thread_start("k_thread_b", 31, k_thread_b, "I am thread_b ");
   while (1)
      ;
   return 0;
}

/* 在线程中运行的函数 */
void k_thread_a(void *arg)
{
   char *para = arg;
   void *addr1;
   void *addr2;
   void *addr3;
   void *addr4;
   void *addr5;
   void *addr6;
   void *addr7;
   console_put_str(" thread_a start\n");
   int max = 1000;
   while (max-- > 0)
   {
      int size = 128;
      addr1 = sys_malloc(size);
      size *= 2;
      addr2 = sys_malloc(size);
      size *= 2;
      addr3 = sys_malloc(size);
      sys_free(addr1);
      addr4 = sys_malloc(size);
      size *= 2;
      size *= 2;
      size *= 2;
      size *= 2;
      size *= 2;
      size *= 2;
      size *= 2;
      addr5 = sys_malloc(size);
      addr6 = sys_malloc(size);
      sys_free(addr5);
      size *= 2;
      addr7 = sys_malloc(size);
      sys_free(addr6);
      sys_free(addr7);
      sys_free(addr2);
      sys_free(addr3);
      sys_free(addr4);
   }
   console_put_str(" thread_a end\n");
   while (1)
      ;
}

/* 在线程中运行的函数 */
void k_thread_b(void *arg)
{
   char *para = arg;
   void *addr1;
   void *addr2;
   void *addr3;
   void *addr4;
   void *addr5;
   void *addr6;
   void *addr7;
   void *addr8;
   void *addr9;
   int max = 1000;
   console_put_str(" thread_b start\n");
   while (max-- > 0)
   {
      int size = 9;
      addr1 = sys_malloc(size);
      size *= 2;
      addr2 = sys_malloc(size);
      size *= 2;
      sys_free(addr2);
      addr3 = sys_malloc(size);
      sys_free(addr1);
      addr4 = sys_malloc(size);
      addr5 = sys_malloc(size);
      addr6 = sys_malloc(size);
      sys_free(addr5);
      size *= 2;
      addr7 = sys_malloc(size);
      sys_free(addr6);
      sys_free(addr7);
      sys_free(addr3);
      sys_free(addr4);

      size *= 2;
      size *= 2;
      size *= 2;
      addr1 = sys_malloc(size);
      addr2 = sys_malloc(size);
      addr3 = sys_malloc(size);
      addr4 = sys_malloc(size);
      addr5 = sys_malloc(size);
      addr6 = sys_malloc(size);
      addr7 = sys_malloc(size);
      addr8 = sys_malloc(size);
      addr9 = sys_malloc(size);
      sys_free(addr1);
      sys_free(addr2);
      sys_free(addr3);
      sys_free(addr4);
      sys_free(addr5);
      sys_free(addr6);
      sys_free(addr7);
      sys_free(addr8);
      sys_free(addr9);
   }
   console_put_str(" thread_b end\n");
   while (1)
      ;
}

/* 测试用户进程 */
void u_prog_a(void)
{
   char *name = "prog_a";
   printf(" I am %s, my pid:%d%c", name, getpid(), '\n');
   while (1)
      ;
}

/* 测试用户进程 */
void u_prog_b(void)
{
   char *name = "prog_b";
   printf(" I am %s, my pid:%d%c", name, getpid(), '\n');
   while (1)
      ;
}

编译运行

实现系统调用malloc和free

malloc和free的实现其实是对sys_malloc和sys_free的用户实现

添加内核系统调用例程

/userprog/syscall-init.c

cpp 复制代码
/* 初始化系统调用 */
void syscall_init(void)
{
    put_str("syscall_init start\n");
    syscall_table[SYS_GETPID] = sys_getpid;
    syscall_table[SYS_WRITE] = sys_write;
    syscall_table[SYS_MALLOC] = sys_malloc;
    syscall_table[SYS_FREE] = sys_free;
    put_str("syscall_init done\n");
}

添加用户系统调用函数malloc和free

/lib/user/syscall.h

cpp 复制代码
#ifndef __LIB_USER_SYSCALL_H
#define __LIB_USER_SYSCALL_H
#include "stdint.h"
/*定义系统调用号*/
enum SYSCALL_NR
{
    SYS_GETPID,
    SYS_WRITE,
    SYS_MALLOC,
    SYS_FREE
};
uint32_t getpid(void);
uint32_t write(char *str);
void *malloc(uint32_t size);
void free(void *ptr);
#endif

/lib/user/syscall.c

cpp 复制代码
/* 申请size字节大小的内存,并返回结果 */
void *malloc(uint32_t size)
{
    return (void *)_syscall1(SYS_MALLOC, size);
}

/* 释放ptr指向的内存 */
void free(void *ptr)
{
    _syscall1(SYS_FREE, ptr);
}

测试

/kernel/main.c

cpp 复制代码
#include "print.h"
#include "init.h"
#include "thread.h"
#include "interrupt.h"
#include "console.h"
#include "process.h"
#include "syscall-init.h"
#include "syscall.h"
#include "stdio.h"
#include "memory.h"

void k_thread_a(void *);
void k_thread_b(void *);
void u_prog_a(void);
void u_prog_b(void);

int main(void)
{
   put_str("I am kernel\n");
   init_all();
   intr_enable();
   process_execute(u_prog_a, "u_prog_a");
   process_execute(u_prog_b, "u_prog_b");
   thread_start("k_thread_a", 31, k_thread_a, "I am thread_a");
   thread_start("k_thread_b", 31, k_thread_b, "I am thread_b");
   while (1)
      ;
   return 0;
}

/* 在线程中运行的函数 */
void k_thread_a(void *arg)
{
   void *addr1 = sys_malloc(256);
   void *addr2 = sys_malloc(255);
   void *addr3 = sys_malloc(254);
   console_put_str(" thread_a malloc addr:0x");
   console_put_int((int)addr1);
   console_put_char(',');
   console_put_int((int)addr2);
   console_put_char(',');
   console_put_int((int)addr3);
   console_put_char('\n');

   int cpu_delay = 9999999;
   while (cpu_delay-- > 0)
      ;
   sys_free(addr1);
   sys_free(addr2);
   sys_free(addr3);
   while (1)
      ;
}

/* 在线程中运行的函数 */
void k_thread_b(void *arg)
{
   void *addr1 = sys_malloc(256);
   void *addr2 = sys_malloc(255);
   void *addr3 = sys_malloc(254);
   console_put_str(" thread_b malloc addr:0x");
   console_put_int((int)addr1);
   console_put_char(',');
   console_put_int((int)addr2);
   console_put_char(',');
   console_put_int((int)addr3);
   console_put_char('\n');

   int cpu_delay = 999999;
   while (cpu_delay-- > 0)
      ;
   sys_free(addr1);
   sys_free(addr2);
   sys_free(addr3);
   while (1)
      ;
}

/* 测试用户进程 */
void u_prog_a(void)
{
   void *addr1 = malloc(256);
   void *addr2 = malloc(255);
   void *addr3 = malloc(254);
   printf(" prog_a malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);

   int cpu_delay = 100000;
   while (cpu_delay-- > 0)
      ;
   free(addr1);
   free(addr2);
   free(addr3);
   while (1)
      ;
}

/* 测试用户进程 */
void u_prog_b(void)
{
   void *addr1 = malloc(256);
   void *addr2 = malloc(255);
   void *addr3 = malloc(254);
   printf(" prog_b malloc addr:0x%x,0x%x,0x%x\n", (int)addr1, (int)addr2, (int)addr3);

   int cpu_delay = 100000;
   while (cpu_delay-- > 0)
      ;
   free(addr1);
   free(addr2);
   free(addr3);
   while (1)
      ;
}

编译运行

相关推荐
袁庭新8 小时前
CentOS7通过yum无法安装软件问题解决方案
centos·操作系统
别说我什么都不会1 天前
鸿蒙轻内核M核源码分析系列十二 事件Event
操作系统·harmonyos
qq_437896432 天前
动态内存分配算法对比:最先适应、最优适应、最坏适应与邻近适应
操作系统
别说我什么都不会2 天前
鸿蒙轻内核M核源码分析系列十一 (2)信号量Semaphore
操作系统·harmonyos
别说我什么都不会2 天前
鸿蒙轻内核M核源码分析系列十 软件定时器Swtmr
操作系统·harmonyos
别说我什么都不会3 天前
鸿蒙轻内核M核源码分析系列九 互斥锁Mutex
操作系统·harmonyos
别说我什么都不会3 天前
鸿蒙轻内核M核源码分析系列七 动态内存Dynamic Memory
操作系统·harmonyos
别说我什么都不会4 天前
鸿蒙轻内核M核源码分析系列六 任务及任务调度(3)任务调度模块
操作系统·harmonyos
徐徐同学4 天前
【操作系统】操作系统概述
操作系统·计算机系统
守望时空335 天前
Linux内核升级指南
linux·操作系统