实践背景
目前Linux已经广泛应用在从手机、PC到服务器、巨型机各类计算设备上。考虑到在各种体系结构上的适用性,Linux需要一种与具体的体系架构相对独立的内存管理方法。目前越来越多的计算机设备采用的都是多处理机架构。多处理机架构适用的最为普遍的模型是共享存储多处理机SMP(Shared Memory multi-Processors)模型。SMP又可以细分为一致存储结构UMA(Uniform Memory Access)模型和非一致存储结构NUMA(NonUniform Memory)模型。Intel典型的IA32架构就属于UMA模型,而ARM通常采用NUMA模型。Linux的物理内存管理主要由内存节点node、内存区域zone和物理页框page三级架构组成。
分页机制通过把线性地址空间中的页重新定位到物理地址空间来进行管理。为了应对32位和64位系统的不同需求,从2.6.11内核开始Linux采用了一种同时使用于32位和64位系统的四级分页模型,分为页全局目录、页上级目录、页中间目录、页表。本实验通过获得不同的项来最终获得线性地址的物理地址。
实验原理
- 内存节点node:内存节点node是Linux对计算机系统物理内存的一种描述方法,一个总线主设备访问位于同一个结点node中的任意内存单元所花的代价相同,而访问任意两个不同结点中的内存单元所花的代价不同。Linux内核中使用数据结构pg_data_t来表示内存结点node,该结构定义在头文件
< linux/mmzone.h >
中。 - 内存结点zone:每个node结点又被划分为多个内存区zone。Linux使用结构体struct zone描述内存区域zone,在头文件
< linux/mmzone.h >
中给出了zone的具体定义。 - Zone有以下类型:ZONE_DMA(linux将低端的16MB物理内存保留以便与老式设备兼容), ZONE_NORMAL, ZONE_HIGHMEM
- 物理页框page:在使用分页机制的情况下,物理内存一般是由固定尺寸的物理页框来表示,而从逻辑视角上内存使用固定尺寸的页面来表示。
下图是IA32的分段、分页及其地址映射机制:
与IA32的结构相适应,Linux的内存地址映射机制也采用了段页式方法。但是与IA32不同的是,Linux内存寻址淡化了段的作用,而强调了页的作用。
Linux分页机制是在段机制之后进行的,它进一步将线性地址转换为物理地址。该过程通常涉及两个重要问题,第一个是如何找到页表,第二个则是如何实现页表映射。
从CR寄存器提取页表地址
每一个进程都有自己的页全局目录和自己的页表。当发生进程切换时,Linux把CR3控制寄存器的内容保存在前一个执行进程的task_struct结构体中,然后把下一个要执行进程的task_struct结构体的值装入CR3寄存器中。因此,当新进程重新开始在CPU上执行时,分页单元就指向一组正确的页表。
所以内核可以直接从CR3寄存器获取当前进程的最高级也目录地址。此时从段地址映射获得的线性地址提取相应的页目录表项,就可以获得该页目录的描述符了。
页表映射机制
分页机制通过把线性地址空间中的页重新定位到物理地址空间来进行管理。之所以要进行多级页表映射,主要是考虑到内存管理的方便。为了应对32位和64位系统的不同需求,从2.6.11内核开始linux采用了一种同时适用于32位和64位系统的四级分页模型。分别是页全局目录、页上级目录、页中间目录、页表
在C语言中输出的变量地址其实是一个虚拟地址,更准确地说是一个经过段机制变换后的线性地址
实验目的
在IA32架构的Linux中,从一个变量出发到其对应的物理地址需要经过两次变换,一次是段映射,一次是页映射。通常情况下程序执行时,都是由操作系统完成页映射,程序员并不知道进程在执行期间所发生的地址转换。为此设计了一个内核模块将根据输入的进程pid以及现行地址去查找该线性对应的物理地址。该内核模块仍然以proc伪文件作为用户和内核之间交换数据的接口。整个实验分为两个部分,一部分是完成分页地址映射的内核代码,一部分是用户测试程序。
在4.15.6中,开始使用5级目录页结构,分别是pgd,p4d,pud,pmd,pte。
实验流程
ubuntu16.04
4.15.6
1.运行make进行编译
2.内核模块添加 $sudo insmod logadd2phyadd
3.运行测试程序 ./logadd2phyadd_test
logadd2phyadd.c
#include <linux/module.h> // for init_module()
#include <linux/proc_fs.h> // for create_proc_info_entry()
#include <linux/sched.h> // for 'struct task_struct'
#include <linux/seq_file.h> // for sequence files
#include <linux/mm.h> // for 'struct mm_struct'
#include <linux/slab.h> // for kzalloc, kfree
#include <linux/uaccess.h>
char modname[] = "logadd2phyadd";
typedef struct data {
unsigned long addr;
int p;
}mydata;
static mydata indata;
static char tmp[1024];
static void get_pgtable_macro(struct seq_file *m)
{
seq_printf( m,"PAGE_OFFSET = 0x%lx\n", PAGE_OFFSET);
seq_printf( m,"PGDIR_SHIFT = %d\n", PGDIR_SHIFT);
//输出进程P4D_SHIFT,PUD_SHIFT,PMD_SHIFT,PAGE_SHIFT?
seq_printf( m,"P4D_SHIFT = %d\n", P4D_SHIFT);
seq_printf( m,"PUD_SHIFT = %d\n", PUD_SHIFT);
seq_printf( m,"PMD_SHIFT = %d\n", PMD_SHIFT);
seq_printf( m,"PAGE_SHIFT = %d\n", PAGE_SHIFT);
seq_printf( m,"PTRS_PER_PGD = %d\n", PTRS_PER_PGD);
//输出进程PTRS_PER_P4D,PTRS_PER_PUD,PTRS_PER_PMD,PTRS_PER_PTE?
seq_printf( m,"PTRS_PER_P4D = %d\n", PTRS_PER_P4D);
seq_printf( m,"PTRS_PER_PUD = %d\n", PTRS_PER_PUD);
seq_printf( m,"PTRS_PER_PMD = %d\n", PTRS_PER_PMD);
seq_printf( m,"PTRS_PER_PTE = %d\n", PTRS_PER_PTE);
seq_printf( m,"PAGE_MASK = 0x%lx\n", PAGE_MASK);
}
static unsigned long vaddr2paddr(struct seq_file *m, unsigned long vaddr,int pid)
{
pte_t *pte_tmp = NULL;
pmd_t *pmd_tmp = NULL;
pud_t *pud_tmp = NULL;
p4d_t *p4d_tmp = NULL;
pgd_t *pgd_tmp = NULL;
struct task_struct *pcb_tmp = NULL;
unsigned long paddr = 0;
/*
if(!(pcb_tmp = find_task_by_pid(pid))) {
printk(KERN_INFO"Can't find the task %d .\n",pid);
return 0;
}
*/
printk(KERN_INFO"in vaddr2paddr try to find the task %d .\n",pid);
pcb_tmp =pid_task(find_get_pid(pid),PIDTYPE_PID);
if(!pcb_tmp) {
printk(KERN_INFO"Can't find the task %d .\n",pid);
return 0;
}
pgd_tmp = pgd_offset(pcb_tmp->mm, vaddr);
if (pgd_none(*pgd_tmp)) {
printk("not mapped in pgd\n");
return -1;
}
seq_printf( m,"pgd_tmp = 0x%p\n",pgd_tmp);
seq_printf( m,"pgd_val(*pgd_tmp) = 0x%lx\n",pgd_val(*pgd_tmp));
p4d_tmp = p4d_offset(pgd_tmp, vaddr);
if (p4d_none(*p4d_tmp)) {
printk("not mapped in pud\n");
return -1;
}
seq_printf( m,"p4d_tmp = 0x%p\n",p4d_tmp);
seq_printf( m,"p4d_val(*pud_tmp) = 0x%lx\n",p4d_val(*p4d_tmp));
pud_tmp = pud_offset(p4d_tmp, vaddr);
if (pud_none(*pud_tmp)) {
printk("not mapped in pud\n");
return -1;
}
seq_printf( m,"pud_tmp = 0x%p\n",pud_tmp);
seq_printf( m,"pud_val(*pud_tmp) = 0x%lx\n",pud_val(*pud_tmp));
pmd_tmp = pmd_offset(pud_tmp, vaddr);
if (pmd_none(*pmd_tmp)) {
printk("not mapped in pmd\n");
return -1;
}
seq_printf( m,"pmd_tmp = 0x%p\n",pmd_tmp);
seq_printf( m,"pmd_val(*pmd_tmp) = 0x%lx\n",pmd_val(*pmd_tmp));
//pte = pte_offset_kernel(pmd, vaddr);
pte_tmp = pte_offset_kernel(pmd_tmp, vaddr);
if (pte_none(*pte_tmp)) {
printk("not mapped in pte\n");
return -1;
}
seq_printf( m,"pte_tmp = 0x%p\n",pte_tmp);
seq_printf( m,"pte_val(*pte_tmp) = 0x%lx\n",pte_val(*pte_tmp));
//ÎïÀíµØÖ·µÈÓÚ £šÖ¡µØÖ· | Æ«ÒÆÁ¿£©
paddr = (pte_val(*pte_tmp) & PAGE_MASK) | (vaddr & ~PAGE_MASK);
seq_printf( m, "frame_addr = %lx\n", pte_val(*pte_tmp) & PAGE_MASK);
seq_printf( m, "frame_offset = %lx\n", vaddr & ~PAGE_MASK);
seq_printf( m, "the input logic address is = 0x%08lX\n", vaddr);
seq_printf( m, "the corresponding physical address is= 0x%08lX\n",paddr);
return paddr;
}
static int my_seq_show(struct seq_file *m, void *p)
{
//unsigned int _cr0,_cr3, _cr4;
get_pgtable_macro(m);
printk("the get_pgtable_macro has been output\n");
if (indata.p !=0 && indata.addr !=0)
vaddr2paddr(m, indata.addr,indata.p);
printk("the vaddr2paddr has been done\n");
seq_printf( m, "\n" );
return 0;
}
static void * my_seq_start(struct seq_file *m, loff_t *pos)
{
if (0 == *pos)
{
++*pos;
return (void *)1;
}
return NULL;
}
static void * my_seq_next(struct seq_file *m, void *p, loff_t *pos)
{
// do nothing
return NULL;
}
static void my_seq_stop(struct seq_file *m, void *p)
{
//// do nothing
}
static struct seq_operations my_seq_fops = {
.start = my_seq_start,
.next = my_seq_next,
.stop = my_seq_stop,
.show = my_seq_show
};
static int my_open(struct inode *inode, struct file *file)
{
return seq_open(file, &my_seq_fops);
}
//ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
// file_operations -> write
//static ssize_t my_write(struct file *file, const char __user *buffer, size_t count)
static ssize_t my_write(struct file *file, const char __user *buffer, size_t count, loff_t *p)
{
//·ÖÅäÁÙʱ»º³åÇø
//char *tmp = kzalloc((count+1), GFP_KERNEL);
//if (!tmp)
// return -ENOMEM;
//copy_to|from_user(to,from,cnt)
if (copy_from_user(tmp, buffer, count)) {
//kfree(tmp);
return -EFAULT;
}
indata = *(mydata*)tmp;
printk("the useraddr is %lu\n",indata.addr);
printk("the pid is %d\n",indata.p);
//kfree(tmp);
return count;
}
static const struct file_operations my_proc =
{
.owner = THIS_MODULE,
.open = my_open,
.write = my_write,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release
};
static int __init my_init( void )
{
struct proc_dir_entry* my_proc_entry;
printk( "<1>\nInstalling \'%s\' module\n", modname );
my_proc_entry = proc_create(modname, 0x0666, NULL, &my_proc);
if (NULL == my_proc_entry)
{
return -ENOMEM;
}
return 0; //SUCCESS
}
static void __exit my_exit(void )
{
remove_proc_entry( modname, NULL );
printk( "<1>Removing \'%s\' module\n", modname );
}
module_init( my_init );
module_exit( my_exit );
MODULE_LICENSE("GPL");
logadd2phyadd_test.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#define BUFSIZE 4096
char buf[BUFSIZE];
typedef struct data {
unsigned long addr;
int p;
}mydata;
int main()
{
unsigned long tmp,addr;
int fd,len;
mydata wdata;
tmp = 0x12345678;
addr = (unsigned long)&tmp;
//输出tmp?
//输出addr?
printf("tmp value is : %lx \n",tmp);
printf("tmp address is : %lx \n",addr);
wdata.addr = addr;
wdata.p = getpid();
//输出进程号pid?
printf("the pid is : %d \n",wdata.p);
//打开文件
fd = open("/proc/logadd2phyadd",O_RDWR);
//将wdata写入文件?
//读文件?
//输出文件长度及内容?
write(fd,&wdata, sizeof(wdata));
read(fd,buf,BUFSIZE);
printf("%s\n",buf);
sleep(2);
close(fd);
return 0;
}
Makefile
ifneq ($(KERNELRELEASE),)
obj-m := logadd2phyadd.o
else
KDIR := /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KDIR) SUBDIRS=$(PWD) modules
rm -r -f .tmp_versions *.mod.c .*.cmd *.o *.symvers
endif