显存总大小是1024^3
所以用mmap申请一篇内存,大小是1024^3,一页大小是4096,所以我们一共有1024^3/4096 页
然后页表项也需要占用空间,类似这样
那么一共需要 1024^3/4096*sizeof(page)/4096+1页存储page项,
所以显存的空间需要从起始地址加上给page分配的内存之后开始
然后剩余的页数就是总页数减去page用掉的页数
那么剩下物理页就是从kernel_page占掉的后面一页开始使用,是线性映射关系,简单来说就是page的存储本身占掉一些物理页,
这里,显存的起始地址是mmap所映射的虚拟地址m_start_ptr,对于模拟器来说就是物理地址的起始
关于拿物理页
就是需要多少也传进来,链表做相应改动,然后传出所拿到page的头部
这里的第一个值记录的是这个page头的物理地址,第二个记录的是往后连续用了多少个物理页号。
管理物理页号的是一个链表,由于物理页号本身要存储在内存的关系,一开始就会消耗掉一些物理页和物理页号(前面提到的)
所以这个函数返回一个page变量的指针,那么信息就是这个段分配的页号头地址和用了多少页号。
另外当我们要操作实际的物理页的时候,只需要根据简单的映射关系,就能找到实际的物理页物理地址进行内存操作
pg是分配出去的page地址,和起始地址相减,就得到两个page之间的差,也就直到中间有多少页page,然后左移12位,就是*4096,就得到内存消耗实际字节,再加上起始地址,就得到实际分到的物理页的起始地址
所以createtask的任务就是创建设置好根页表,并把物理地址返回,并设置好起始的虚拟地址0x100000
之后的每一次分配,都是根据这个虚拟地址和分配空间大小,是会消耗实际的虚拟地址,并在根页表上分配项目的
首先根据要分配的大小确认有多少页,然后消耗page项,这里是最下面子级页表的项所指向的物理页。
然后根据虚拟地址来walk根页表。
比如现在是刚开始分配,虚拟地址位0x100000,我们有
L2 L1 L0 offset
|00000 0000|00000 0000 | 01000 0000 | 0000 0000 0000 0000 |
那么第一个索引是0,所以我们在根页表会来到最后一项,因为一开始根页表是空的,我们发现这里什么都没有,那么我们需要拿到一个物理页,并消耗一个page,并且把根页表0号设置为这个物理页的起始地址
同理其他页也是如此,当我们找到最后一级页表的时候,我们返回第128项的地址。
接着我们操作这个地址,把之前所已经拿到的物理页分配给他的项,建立映射关系
最后的结果就是,子页表的第128项(1000 0000)设置一个页表的地址,那么从这个地址开始的虚拟地址就会有4096字节的内容(现在为空),
假设我们需要分配的总内容x是大于4096字节的,那么我们还需要分配x-4096,这时候虚拟地址也要变化,即现在的虚拟地址变成
L2 L1 L0 offset
|00000 0000|00000 0000 | 01000 0001 | 0000 0000 0000 0000 |
然后之前不是拿到了一个连续大小为x的物理页吗我们拿到的是他的物理地址,因为我们映射了一页,所以在这个物理地址pa的基础上加4096,就拿到下一个物理页的地址
所以变化在于,我们不这次walk不需要给L2和L1去分配一个实际的物理页,而是在L0的第129项设置一个页表的地址,假设这次分配完,内容已经超过了需要分配的综合,我们就停止分配。
这样我们完成了从当前剩下的虚拟地址分配一定大小的显存的任务。
那么怎么往分配好的物理页里写内容呢。
我们操作这个虚拟地址,也是走根页表,这样是能索引到最后子集页表对应项的物理页,也是只要有虚拟地址和需要操作的size,我们就能完成赋值。
关于释放
当我们像释放一个虚拟地址的内容,我们通过页表可以找到他最后一的子页表项,也就找到了物理地址,我们分配的时候是基于这个物理地址往后连续分配的,那么根据这个物理地址,我们是能回推到page的,这个page记录我们用了多少page,这里回收的任务一个是回收page,一个是消除记录的条目
所以总的来说,就是根据虚拟地址->找到映射的起始->根据映射找到page->根据page项里面记录一共使用多少page来释放物理页(最低级存数据的)->循环清清楚释放的子页表物理页条目->把释放过后的page放回list回收
关于list管理page,就是假设我们一开始分了ABC
如果B先放会,那么list里会有空的和B两个项目
如果这时候A回来了,就会逐个项找有没有和自己连续的,如果有就合并。
最后销毁任务的时候,要把页表也回收