memory.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. #include "memory.h"
  2. #include "debug.h"
  3. #include "../lib/kernel/print.h"
  4. #include "../lib/string.h"
  5. #include "../lib/stdint.h"
  6. #include "../thread/sync.h"
  7. #include "../thread/thread.h"
  8. /************************ 位图地址 ****************************************
  9. * 0xc009f000 是内核主线程栈顶,0xc009e000 是内核主线程的pcb
  10. * 一个页框大小的位图可表示128MB内存,位图位置安排在地址0xc009a000
  11. * 0xc009e000 - 0x4000 = 0xc009a000
  12. */
  13. #define MEM_BITMAP_BASE 0xc009a000
  14. /***********************************************************************/
  15. /*0xc0000000 是内核从虚拟地址 3G 起,0xc0100000 指跨过低端 1MB 内存,使虚拟地址在逻辑上连续 */
  16. #define K_HEAP_START 0xc0100000
  17. #define PDE_IDX(addr) ((addr & 0xffc00000) >> 22) // 获取虚拟地址的页目录项 pde 的索引(高 10 位)
  18. #define PTE_IDX(addr) ((addr & 0x003ff000) >> 12) // 获取虚拟地址的页表项 pte 的索引(中间 10 位)
  19. // static functions
  20. static void mem_pool_init(uint32_t all_mem); // 初始化内存池
  21. static void *vaddr_get(enum pool_flags pf, uint32_t pg_cnt); // 在 pf 指向的虚拟内存池中申请 pg_cnt 个虚拟页
  22. static void *palloc(struct pool *m_pool); // 从内存池 m_pool 中申请 1 个物理页,成功则返回页框的物理地址,失败则返回 NULL
  23. static void page_table_add(void *_vaddr, void *_page_pyaddr); // 在页表中添加虚拟地址 _vaddr 与物理地址 _page_pyaddr 的映射
  24. /* 内存池结构,生成两个实例用于管理内核内存池和用户内存池 */
  25. struct pool
  26. {
  27. struct bitmap pool_bitmap; // 本内存池用到的位图结构,用于管理物理内存
  28. uint32_t phy_addr_start; // 本内存池所管理物理内存的起始地址
  29. uint32_t pool_size; // 本内存池字节容量
  30. struct lock lock; // 申请内存时互斥
  31. };
  32. struct pool kernel_pool, user_pool; // 生成内核内存池和用户内存池
  33. struct virtual_addr kernel_vaddr; // 此结构是用来给内核分配虚拟地址
  34. /* 初始化内存池 */
  35. static void mem_pool_init(uint32_t all_mem)
  36. {
  37. put_str(" mem_pool_init start\n");
  38. lock_init(&kernel_pool.lock);
  39. lock_init(&user_pool.lock);
  40. // 页表大小 = 页目录项数 * 每个页目录项占用的字节数
  41. // 页目录项数 = 1 页的页目录表 + 第 0 和第 768(内核页目录项)个页目录项指向同一个页表 +
  42. // 第 769 ~ 1022 个页目录项指向 254 个页表 = 256 个页目录项
  43. //! 最后 1 个页目录项(第 1023 个 pde)指向页目录表自身
  44. uint32_t page_table_size = PG_SIZE * 256; // 256 个页目录项 * 4KB = 1MB
  45. // 1024(0x400) * 1024 = 0x100000 是低端 1MB 内存
  46. uint32_t used_mem = page_table_size + 0x100000;
  47. uint32_t free_mem = all_mem - used_mem;
  48. uint16_t all_free_pages = free_mem / PG_SIZE; // 1 页 = 4KB = 4096 字节
  49. uint16_t kernel_free_pages = all_free_pages / 2;
  50. uint16_t user_free_pages = all_free_pages - kernel_free_pages;
  51. /* 为了简化操作,余数不处理,坏处是这样做会丢失内存,好像是不用做内存的越界检查,因为位图表的内存少于实际物理内存 */
  52. uint32_t kbm_length = kernel_free_pages / 8; // Kernel BitMap 长度,位图中的一位表示一页,以字节为单位
  53. uint32_t ubm_length = user_free_pages / 8; // User BitMap 长度
  54. uint32_t kp_start = used_mem; // Kernel Pool 起始地址,内核内存池的起始地址
  55. uint32_t up_start = kp_start + kernel_free_pages * PG_SIZE; // User Pool 起始地址,用户内存池的起始地址
  56. kernel_pool.phy_addr_start = kp_start;
  57. user_pool.phy_addr_start = up_start;
  58. kernel_pool.pool_size = kernel_free_pages * PG_SIZE;
  59. user_pool.pool_size = user_free_pages * PG_SIZE;
  60. kernel_pool.pool_bitmap.btmp_bytes_len = kbm_length;
  61. user_pool.pool_bitmap.btmp_bytes_len = ubm_length;
  62. /********* 内核内存池和用户内存池的位图 ****************
  63. * 位图是全局的数据,长度不固定
  64. * 全局或静态的数组需要在编译时知道其长度
  65. * 而我们需要根据总内存大小算出需要多少字节
  66. * 所以改为指定一块内存来生成位图
  67. ************************************************/
  68. // 内核使用的最高地址是 0xc009f000,这是主线程的栈地址
  69. // 内核的大小预计为 70KB 左右
  70. // (32MB = 32 * 1024 * 1024 字节)内存占位图是 1KB
  71. // 内核内存池位图的位置安排在地址 0xc009a000
  72. kernel_pool.pool_bitmap.bits = (void *)MEM_BITMAP_BASE;
  73. // 用户内存池的位图紧跟在内核内存池位图之后
  74. user_pool.pool_bitmap.bits = (void *)((uintptr_t)MEM_BITMAP_BASE + kbm_length);
  75. /********************** 输出内存池信息 ******************************/
  76. put_str(" kernel_pool_bitmap_start: ");
  77. put_int((uintptr_t)kernel_pool.pool_bitmap.bits);
  78. put_str(", kernel_pool_phy_addr_start: ");
  79. put_int(kernel_pool.phy_addr_start);
  80. put_str("\n");
  81. put_str(" user_pool_bitmap_start: ");
  82. put_int((uintptr_t)user_pool.pool_bitmap.bits);
  83. put_str(", user_pool_phy_addr_start: ");
  84. put_int(user_pool.phy_addr_start);
  85. put_str("\n");
  86. bitmap_init(&kernel_pool.pool_bitmap);
  87. bitmap_init(&user_pool.pool_bitmap);
  88. /*********************** 初始化虚拟内存池 ********************************/
  89. kernel_vaddr.vaddr_bitmap.btmp_bytes_len = kbm_length; // 虚拟地址位图的长度,按实际物理内存大小生成数组
  90. /*位图的数组指向一块未使用的内存,目前定位在内核内存池和用户内存池之后*/
  91. kernel_vaddr.vaddr_bitmap.bits = (void *)((uintptr_t)MEM_BITMAP_BASE + kbm_length + ubm_length);
  92. kernel_vaddr.vaddr_start = K_HEAP_START;
  93. bitmap_init(&kernel_vaddr.vaddr_bitmap);
  94. put_str(" mem_pool_init end\n");
  95. }
  96. // 内存管理初始化
  97. void mem_init(void)
  98. {
  99. put_str("mem_init start\n");
  100. uint32_t mem_bytes_total = (*(uint32_t *)(0xb00)); // 从地址 0xb00 处取出总内存数
  101. mem_pool_init(mem_bytes_total); // 初始化内存池
  102. put_str("mem_init done\n");
  103. }
  104. // 从内核内存池中申请 pg_cnt 个页空间,成功则返回其虚拟地址,失败则返回 NULL
  105. static void *vaddr_get(enum pool_flags pf, uint32_t pg_cnt)
  106. {
  107. uintptr_t vaddr_start = 0;
  108. int bit_idx_start = -1;
  109. uint32_t cnt = 0;
  110. if (PF_KERNEL == pf)
  111. { // 内核内存池
  112. bit_idx_start = bitmap_scan(&kernel_vaddr.vaddr_bitmap, pg_cnt);
  113. if (-1 == bit_idx_start)
  114. {
  115. return NULL;
  116. }
  117. while (cnt < pg_cnt)
  118. {
  119. bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx_start + cnt++, 1);
  120. }
  121. vaddr_start = kernel_vaddr.vaddr_start + bit_idx_start * PG_SIZE;
  122. }
  123. else
  124. { // 用户内存池 -- 需要使用当前线程来分配
  125. struct task_struct *cur = running_thread();
  126. bit_idx_start = bitmap_scan(&cur->userprog_addr.vaddr_bitmap, pg_cnt);
  127. if (bit_idx_start == -1)
  128. {
  129. return NULL;
  130. }
  131. while (cnt < pg_cnt)
  132. {
  133. bitmap_set(&cur->userprog_addr.vaddr_bitmap, bit_idx_start + cnt++, 1);
  134. }
  135. vaddr_start = cur->userprog_addr.vaddr_start + bit_idx_start * PG_SIZE;
  136. // (0xc0000000 - PG_SIZE) 作为用户 3 级栈已经在 start_process 被分配
  137. ASSERT(((uint32_t)vaddr_start) < (0xc0000000 - PG_SIZE));
  138. }
  139. return (void *)vaddr_start;
  140. }
  141. /****************************************************************************
  142. * 处理器处理 32 位地址的三个步骤:
  143. * 1. 分段:通过段描述符找到段基址,然后加上段内偏移地址
  144. * 2. 分页:通过页目录表和页表找到物理地址
  145. * (1)页目录表的索引是虚拟地址的高 10 位,处理 pde 索引,得到页表物理地址
  146. * (2)页表的索引是虚拟地址的中间 10 位,处理 pte 索引,得到普通物理页的物理地址
  147. * (3)页内偏移地址是虚拟地址的低 12 位,加上普通物理页的物理地址,得到最终物理地址
  148. * 3. 访问内存
  149. ****************************************************************************/
  150. // 得到虚拟地址 vaddr 对应的 pte 指针(虚拟地址)
  151. uint32_t *pte_ptr(uint32_t vaddr)
  152. {
  153. // 先访问到页目录表地址(高 10 位)
  154. // 最后一个页目录项保存的是页目录表的地址,我们需要让高 10 们指向最后一个页目录项,即第 1023 个页目录项
  155. // 1023=0x3ff,移动到高 10 位,所以是 0xffc00000,此处是页目录表的物理地址 0x100000
  156. // 找到页表地址(中间 10 位)
  157. // 页表物理地址 = 页目录表的物理地址 + vaddr 的页目录项索引
  158. // 在页表中找到 pte 地址
  159. // 页表物理地址 + vaddr 的页表项索引 * 4
  160. uint32_t *pte = (uint32_t *)((uintptr_t)0xffc00000 + ((vaddr & 0xffc00000) >> 10) + PTE_IDX(vaddr) * 4);
  161. return pte;
  162. }
  163. // 得到虚拟地址 vaddr 对应的 pde 指针(虚拟地址)
  164. uint32_t *pde_ptr(uint32_t vaddr)
  165. {
  166. // 由于最后一个页目录项中存储的是页目录物理地址,所以 32 位地址中高 20 位为 0xfffff,时表示访问到的是最后一个目录项
  167. // 即获得了页目录表的物理地址
  168. // 因此新虚拟地址 new_vaddr = 0xfffff000 + vaddr 的页目录项索引 * 4
  169. return (uint32_t *)((uintptr_t)0xfffff000 + PDE_IDX(vaddr) * 4);
  170. }
  171. // 在 m_pool 指向的物理内存池中申请1个物理页,成功则返回页框的物理地址,失败则返回NULL
  172. static void *palloc(struct pool *m_pool)
  173. {
  174. int bit_idx = bitmap_scan(&m_pool->pool_bitmap, 1); // 找一个物理页面
  175. if (-1 == bit_idx)
  176. {
  177. return NULL;
  178. }
  179. bitmap_set(&m_pool->pool_bitmap, bit_idx, 1); // 将此位bit_idx置 1
  180. uint32_t page_phyaddr = ((m_pool->phy_addr_start + bit_idx * PG_SIZE));
  181. return (void *)(uintptr_t)page_phyaddr;
  182. }
  183. // 页表中添加虚拟地址 _vaddr 与物理地址 _page_phyaddr 的映射
  184. static void page_table_add(void *_vaddr, void *_page_pyaddr)
  185. {
  186. uint32_t vaddr = (uint32_t)(uintptr_t)_vaddr, page_phyaddr = (uint32_t)(uintptr_t)_page_pyaddr;
  187. uint32_t *pde = pde_ptr(vaddr);
  188. uint32_t *pte = pte_ptr(vaddr);
  189. /*********************************************************************
  190. * !! 注意:
  191. * !! 执行 *pte 时,会访问到空的 pte,所以确保 pte 的地址在 pde 创建之后,
  192. * !! 否则会引发 page_fault。因此 *pde o 0 时,*pte 不会执行
  193. ********************************************************************/
  194. // 先在页目录内判断目录项的 p 位,若为 1,则表示该表已存在
  195. if (*pde & 0x00000001)
  196. {
  197. // 页目录项和页表项的第 0 位为 P 位,为 1 表示存在
  198. ASSERT(!(*pte & 0x00000001));
  199. if (!(*pte & 0x00000001))
  200. {
  201. // 页表项不存在,创建页表项
  202. *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
  203. }
  204. else
  205. { // todo: 目前应该不会执行到这里,因为上面的 ASSERT 会先执行
  206. PANIC("pte repeat");
  207. *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1);
  208. }
  209. }
  210. else // 页目录项不存在,要先创建页目录再创建页表项
  211. {
  212. /* 页表中用到的页框一律从内核空间分配 */
  213. uint32_t pde_phyaddr = (uint32_t)(uintptr_t)palloc(&kernel_pool);
  214. *pde = (pde_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // 页目录项赋值页表的物理地址
  215. /* 分配到的物理页地址 pde_phyaddr 对应的物理内存清 0,
  216. * 避免里面的陈旧数据变成了页表项,从而让页表混乱
  217. * 访问到 pde 对应的物理地址,用 pte 取高 20 位。
  218. * 因为 pte 基于该 pde 对应的物理地址再寻址,把低 12 位置 0 便是该 pde 对应的物理页的起始
  219. */
  220. _memset((void *)((uintptr_t)pte & 0xfffff000), 0, PG_SIZE);
  221. ASSERT(!(*pte & 0x00000001));
  222. *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // 页表项赋值物理页的地址
  223. ASSERT(!(*pte & 0x00000001));
  224. *pte = (page_phyaddr | PG_US_U | PG_RW_W | PG_P_1); // 再次检查并确认
  225. }
  226. }
  227. // 分配 pg_cnt 个页空间,成功则返回虚拟地址,失败则返回 NULL
  228. void *malloc_page(enum pool_flags pf, uint32_t pg_cnt)
  229. {
  230. // todo: 内核和用户空间各约 16MB,所以暂时不考虑超过 16MB 的内存申请,保守起见 15MB
  231. // pg_cnt< 15 * 1024 / 4KB = 3840 页
  232. ASSERT(pg_cnt > 0 && pg_cnt < 3840);
  233. void *vaddr_start = vaddr_get(pf, pg_cnt);
  234. if (NULL == vaddr_start)
  235. {
  236. return NULL;
  237. }
  238. uint32_t vaddr = (uint32_t)(uintptr_t)vaddr_start, cnt = pg_cnt;
  239. struct pool *mem_pool = (PF_KERNEL == pf ? &kernel_pool : &user_pool);
  240. // 因为虚拟地址是连续的,但物理地址可以不连续,所以逐个做映射
  241. while (cnt-- > 0)
  242. {
  243. void *page_phyaddr = palloc(mem_pool);
  244. if (NULL == page_phyaddr)
  245. {
  246. // 失败时要将曾经已申请的虚拟地址和物理页全部回滚
  247. return NULL;
  248. }
  249. page_table_add((void *)(uintptr_t)vaddr, page_phyaddr);
  250. vaddr += PG_SIZE; // 下一个虚拟页
  251. }
  252. return vaddr_start;
  253. }
  254. // 申请内核内存,成功则返回其虚拟地址,失败则返回 NULL
  255. void *get_kernel_pages(uint32_t pg_cnt)
  256. {
  257. void *vaddr = malloc_page(PF_KERNEL, pg_cnt);
  258. if (NULL != vaddr)
  259. {
  260. // 若分配的地址不为空,则将页内存清 0 后返回
  261. _memset(vaddr, 0, pg_cnt * PG_SIZE);
  262. }
  263. return vaddr;
  264. }
  265. // 申请用户内存,成功则返回其虚拟地址,失败则返回 NULL
  266. void *get_user_pages(uint32_t pg_cnt)
  267. {
  268. lock_acquire(&user_pool.lock);
  269. void *vaddr = malloc_page(PF_USER, pg_cnt);
  270. if (NULL != vaddr)
  271. {
  272. // 若分配的地址不为空,则将页内存清 0 后返回
  273. _memset(vaddr, 0, pg_cnt * PG_SIZE);
  274. }
  275. lock_release(&user_pool.lock);
  276. return vaddr;
  277. }
  278. // 将地址 vaddr 与 pf 池中的物理地址关联,仅支持一页空间分配
  279. void *get_a_page(enum pool_flags pf, uint32_t vaddr)
  280. {
  281. struct pool *mem_pool = pf & PF_KERNEL ? &kernel_pool : &user_pool;
  282. lock_acquire(&mem_pool->lock);
  283. /* 先将虚拟地址对应的位图置 1 */
  284. struct task_struct *cur = running_thread();
  285. int32_t bit_idx = -1;
  286. if (cur->pgdir != NULL && pf == PF_USER)
  287. { // 若当前是用户进程申请用户内存,就修改用户进程自己的虚拟地址位图
  288. bit_idx = (vaddr - cur->userprog_addr.vaddr_start) / PG_SIZE;
  289. ASSERT(bit_idx > 0);
  290. bitmap_set(&cur->userprog_addr.vaddr_bitmap, bit_idx, 1);
  291. }
  292. else if (cur->pgdir == NULL && pf == PF_KERNEL)
  293. { // 如果是内核线程申请内存,就修改 kernel_vaddr
  294. bit_idx = (vaddr - kernel_vaddr.vaddr_start) / PG_SIZE;
  295. ASSERT(bit_idx > 0);
  296. bitmap_set(&kernel_vaddr.vaddr_bitmap, bit_idx, 1);
  297. }
  298. else
  299. {
  300. PANIC("get_a_page: not allow kernel alloc userspace or user alloc kernelspace by get_a_page");
  301. }
  302. /* 然后申请物理页 */
  303. void *page_phyaddr = palloc(mem_pool);
  304. if (page_phyaddr == NULL)
  305. return NULL;
  306. page_table_add((void *)(uintptr_t)vaddr, page_phyaddr);
  307. lock_release(&mem_pool->lock);
  308. return (void *)(uintptr_t)vaddr;
  309. }
  310. // 得到虚拟地址映射到的物理地址
  311. uint32_t addr_vaddr2phy(uint32_t vaddr)
  312. {
  313. uint32_t *pte = pte_ptr(vaddr);
  314. /* (*pte)是值是页表所在的物理页框地址,去掉其低 12 位的页表项属性+虚拟地址 vaddr 的低 12 位 */
  315. return ((*pte & 0xfffff000) + (vaddr & 0xfff));
  316. }