虚拟地址的位数
[root@new ~]# cat /proc/cpuinfo | grep virtu | tail -1
address sizes : 46 bits physical, 48 bits virtual
第零层,每一项是4KB,每一个大项512个4KB是2MB,
第一层,每一项是2MB,每一大项是512个2MB是1GB
第二层,每一项是1GB,每一个大项是512个1GB是512GB
第三层,每一项是512GB,512个526GB是256TB
下在内核模块:
#include <iostream>
#include <fstream>
#include <string>
#include <stdint.h>
#include <unistd.h>
#include <sys/mman.h>
struct Page
{
uint64_t address;
// 页表中每一项是一个64bit的页表条目
uint64_t entry[512];
};
uint64_t get_phys_address(uint64_t entry)
{
// 获得物理地址,是48bit的真是物理地址
// 第63位置有个1,低11个位置都是1,总共是12个1
// ~mask是总共
static const uint64_t mask = (1LL << 63) | ((1 << 12) - 1);
return entry & ~mask;
}
bool writable(uint64_t entry)
{
return (entry & (1 << 1)) != 0;
}
bool executable(uint64_t entry)
{
return (entry & (1LL << 63)) == 0;
}
bool user_mode(uint64_t entry)
{
return (entry & (1 << 2)) != 0;
}
void print_entry(FILE* fp, int level, uint64_t entry, uint64_t virtual_address)
{
fprintf(fp, "%d\t0x%016lx\t0x%016lx\t%d\t%d\t%d\n", level, get_phys_address(entry), virtual_address, writable(entry), executable(entry), user_mode(entry));
}
void dump(FILE* fp, const Page*& page, int level, uint64_t virtual_address)
{
const Page* cur_page = page++;
// 每页表项都是512个
for (int i = 0; i < 512; i++)
{
// 页表条目
const uint64_t entry = cur_page->entry[i];
// 获取当前的页表条目对应的虚拟地址
const uint64_t child_virtual_address = (virtual_address << 9) | i;
if (level > 0)
{
if (entry & 1)
{
if (!(entry&(1<<7)))
{
// 继续递归下一层
dump(fp, page, level - 1, child_virtual_address);
}
else
{
// 打印当前页表条目对应的信息
print_entry(fp, level, entry, child_virtual_address << (level * 9 + 12));
}
}
}
else
{
// 第0层是,512个4KB的页表条目
if (entry)
{
print_entry(fp, level, entry, child_virtual_address << 12);
}
}
}
}
void dump_pagetable(FILE* fp)
{
std::ifstream ifs("/proc/page_table_3", std::ios::binary);
if (!ifs)
{
return;
}
std::string content((std::istreambuf_iterator<char>(ifs)), std::istreambuf_iterator<char>());
const Page* page = (const Page*)&content[0];
const Page* end_page = (const Page*)(&content[0] + content.length());
dump(fp, page, 3, 0);
std::cout << (const void*)end_page << '\t' << (const void*)page << std::endl;
std::flush(std::cout);
}
int main()
{
const int N = 1024 * 1024 * 8;
// 是否开启大页
const bool hugetable = true;
const bool do_fork = false;
char* m = (char*)mmap(NULL, N, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_SHARED | (hugetable ? MAP_HUGETLB: 0), -1, 0);
std::cout << *m << std::endl;
FILE* fp = NULL;
if (do_fork)
{
pid_t pid = fork();
if (pid == 0)
{
fp = fopen("/home/fractal/lecture/child.log", "w");
}
else
{
fp = fopen("/home/fractal/lecture/father.log", "w");
}
}
else
{
fp = fopen("/home/fractal/lecture/father.log", "w");
}
fprintf(fp, "mmap address: %p\n", m);
dump_pagetable(fp);
fclose(fp);
while (true)
{
usleep(10000);
}
return 0;
}
1.只有向mmap申请的地址写入东西,操作系统才会分配物理地址
2.是大页,系统需要预先分配大页
3.for父子进程公用的内存页,被设置为不可写。子进程,如果往页写入东西,会出发异常,操作系统会捕获,分配新的物理内容,把内容copy一份(孩子的),并且设置为可写。父亲的页面还是不可以写入的。fork机制可以省内存,相对于多线程,不会有数据冲突。多线程需要考虑数据冲突。