把固定的线性地址映射到指定的物理地址。
fixmap是一段固定地址映射。kernel预留一段虚拟地址空间。因此虚拟地址是在编译的时候确定。fixmap可以用来做什么?kernel启动初期,由于此时的kernel已经运行在虚拟地址上。因此我们访问具体的物理地址是不行的,必须建立虚拟地址和物理地址的映射,然后通过虚拟地址访问才可以。例如:dtb中包含bootloader传递过来的内存信息,我们需要解析dtb,但是我们得到的是dtb的物理地址。因此访问之前必须创建映射,创建映射又需要内存。但是由于所有的内存管理子系统还没有ready。因此我们不能使用ioremap接口创建映射。为此kernel提出fixmap的解决方案。(摘自wowo科技)
fixmap addresses原理
a. 提供接口1: 可供选择宏线性地址范围(这范围的线性地址可用于固定映射),
b . 提供接口2: 根据这范围的线性地址,定位到相应的页目录项,分配页表,并且把页表的物理地址填充到相应的页目录项中。
c. 提供接口3: 根据映射线性地址和物理地址和页访问权限,创建映射。实现原理,根据线性地址找到相应的页表项,然后把物理地址和权限合成后,写入其中的页表项中。
d. 接口4: 撤销线性地址和物理地址的映射关系。
a. 接口1: 宏对应线性地址
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
#define FIXADDR_TOP ((unsigned long)__FIXADDR_TOP)
define __FIXADDR_TOP 0xfffff000
53 enum fixed_addresses {
54 FIX_HOLE,
55 FIX_VSYSCALL,
56 #ifdef CONFIG_X86_LOCAL_APIC
57 FIX_APIC_BASE, /* local (CPU) APIC) -- required for SMP or not */
58 #endif
59 #ifdef CONFIG_X86_IO_APIC
60 FIX_IO_APIC_BASE_0,
61 FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS-1,
62 #endif
63 #ifdef CONFIG_X86_VISWS_APIC
64 FIX_CO_CPU, /* Cobalt timer */
65 FIX_CO_APIC, /* Cobalt APIC Redirection Table */
66 FIX_LI_PCIA, /* Lithium PCI Bridge A */
67 FIX_LI_PCIB, /* Lithium PCI Bridge B */
68 #endif
69 #ifdef CONFIG_X86_F00F_BUG
70 FIX_F00F_IDT, /* Virtual mapping for IDT */
71 #endif
72 #ifdef CONFIG_X86_CYCLONE_TIMER
73 FIX_CYCLONE_TIMER, /*cyclone timer register*/
74 #endif
75 #ifdef CONFIG_HIGHMEM
76 FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */
77 FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
78 #endif
79 #ifdef CONFIG_ACPI_BOOT
80 FIX_ACPI_BEGIN,
81 FIX_ACPI_END = FIX_ACPI_BEGIN + FIX_ACPI_PAGES - 1,
82 #endif
83 #ifdef CONFIG_PCI_MMCONFIG
84 FIX_PCIE_MCFG,
85 #endif
86 __end_of_permanent_fixed_addresses,
87 /* temporary boot-time mappings, used before ioremap() is functional */
88 #define NR_FIX_BTMAPS 16
89 FIX_BTMAP_END = __end_of_permanent_fixed_addresses,
90 FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS - 1,
91 FIX_WP_TEST,
92 __end_of_fixed_addresses
93 }
线性地址起始地址:
__end_of_fixed_addresses=FIXADDR_TOP - ((N) << PAGE_SHIFT)=0xffff f000-N<<12
一个枚举变量对应着一个页表项。
线性地址结束地址:FIXADDR_TOP=0xffff f000
b . 接口2: 根据这范围的线性地址,定位到相应的页目录项,分配页表,并且把页表的物理地址填充到相应的页目录项中。
线性地址从0xffff f000-N<<12到0xffff f000,这样只需要修改一个目录项,也就是页目录最后一项0x3ff。
一个页目录项,对应着1024个页表项,一个页表项对应着1024个页。
一个页目录项,可寻址2^22=4M
arch/i386/mm/init.c
310 static void __init pagetable_init (void)
311 {
312 unsigned long vaddr;
341 vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
342 page_table_range_init(vaddr, 0, pgd_base);
}
在341行,vaddr=0xffc00000
page_table_range_int:分配页表,并且把页表的物理地址填充到相应的页目录项中
103 static void __init page_table_range_init (unsigned long start, unsigned long end, pgd_t *pgd_base)
104 {
105 pgd_t *pgd;
106 pud_t *pud;
107 pmd_t *pmd;
108 int pgd_idx, pmd_idx;
109 unsigned long vaddr;
110
111 vaddr = start;
112 pgd_idx = pgd_index(vaddr);
113 pmd_idx = pmd_index(vaddr);
114 pgd = pgd_base + pgd_idx;
115
116 for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) {
117 if (pgd_none(*pgd))
118 one_md_table_init(pgd);
119 pud = pud_offset(pgd, vaddr);
120 pmd = pmd_offset(pud, vaddr);
121 for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end); pmd++, pmd_idx++) {
122 if (pmd_none(*pmd))
123 one_page_table_init(pmd);
124
125 vaddr += PMD_SIZE;
126 }
127 pmd_idx = 0;
128 }
129 }
在123行,one_page_table_init给pmd地址进行分配页表,并在页目录0x3ff项,对应项填充页表的物理地址。
在116行,PTRS_PER_PGD=0x400,pgd_idx=0x3ff,循环只执行一次。
c. 接口3:根据映射线性地址和物理地址和页访问权限,创建映射。
参数:提供固定映射的idx和物理地址,物理地址以页为单位。
131 void __set_fixmap (enum fixed_addresses idx, unsigned long phys, pgprot_t flags)
132 {
133 unsigned long address = __fix_to_virt(idx);
134
135 if (idx >= __end_of_fixed_addresses) {
136 BUG();
137 return;
138 }
139 set_pte_pfn(address, phys >> PAGE_SHIFT, flags);
140 }
在139行,phys右移12位,通过set_pte_pfn实现,虚拟地址和物理地址和权限关联。
d. 接口4: 撤销线性地址和物理地址的映射关系。
06 #define clear_fixmap(idx) \
107 __set_fixmap(idx, 0, __pgprot(0))
调用__set_fixmap函数向其线性地址对应的页表项中写0.
vaddr1 = __fix_to_virt(FIX_WP_TEST) & PMD_MASK;
##创建线性地址的页目录,页表,把页表项的内容写为0
page_table_range_init(vaddr1, 0, pgd_base);
103 static void __init page_table_range_init (unsigned long start, unsigned long end, pgd_t *pgd_base)
104 {
105 pgd_t *pgd;
106 pud_t *pud;
107 pmd_t *pmd;
108 int pgd_idx, pmd_idx;
109 unsigned long vaddr;
110
111 vaddr = start;
112 pgd_idx = pgd_index(vaddr);
113 pmd_idx = pmd_index(vaddr);
114 pgd = pgd_base + pgd_idx;
115 printk(KERN_ERR "tom page_table_range_init1 =%x %x\n",start,end);
116 printk(KERN_ERR "tom page_table_range_init =%x %x %x %x\n",pgd_idx,pmd_idx,pgd,pgd_base);
117 //tom page_table_range_init =3ff 0 c03cbffc c03cb000
118 //tom page_table_range_init =3fe 0 c03cbff8 c03cb000
119 for ( ; (pgd_idx < PTRS_PER_PGD) && (vaddr != end); pgd++, pgd_idx++) {
120 if (pgd_none(*pgd)) //页目录项已经存在,所以不需要床架页目录项目
121 {
123 one_md_table_init(pgd);
124 }
125 pud = pud_offset(pgd, vaddr);
126 pmd = pmd_offset(pud, vaddr);
127 printk(KERN_ERR "tom page_table_range_init12 =%x %x\n",pud,pmd);
128 for (; (pmd_idx < PTRS_PER_PMD) && (vaddr != end); pmd++, pmd_idx++) {
129 if (pmd_none(*pmd))
//因为页目录项是第1023项,没有初始化,896MB,只初始化到第 991项。
130 {
131 one_page_table_init(pmd); //初始化
132 printk(KERN_ERR "tom page_table_range_init13 =%x\n",pmd);
133 }
134
135 printk(KERN_ERR "tom page_table_range_init14 =%x\n",pmd);
136 vaddr += PMD_SIZE;
137 }
138 pmd_idx = 0;
139 }
140 }
__set_fixmap(FIX_WP_TEST, __pa(&swapper_pg_dir), PAGE_READONLY);
131 void __set_fixmap (enum fixed_addresses idx, unsigned long phys, pgprot_t flags)
132 {
133 unsigned long address = __fix_to_virt(idx);
134
135 if (idx >= __end_of_fixed_addresses) {
136 BUG();
137 return;
138 }
139 set_pte_pfn(address, phys >> PAGE_SHIFT, flags);
140 }
06 #define clear_fixmap(idx) \
107 __set_fixmap(idx, 0, __pgprot(0))
就是把相应页表项的内容设置为0
360 //Add by tom
361 vaddr1 = __fix_to_virt(FIX_WP_TEST) & PMD_MASK;
364 printk(KERN_ERR "tom FIX_WP_TEST value before=%x %x\n",*(unsigned int *)swapper_pg_dir, swapper_pg_dir);
365 __set_fixmap(FIX_WP_TEST, __pa(&swapper_pg_dir), PAGE_READONLY);
366 /*printk(KERN_ERR "tom FIX_WP_TEST value=%x\n",*(unsigned int *)vaddr1);*/
367 printk(KERN_ERR "tom FIX_WP_TEST value=%x %x\n",*(unsigned int *)__fix_to_virt(FIX_WP_TEST),*(unsigned int *)swapper_pg_dir);
把FIX_WP_TEST对应的线性地址固定到swapper_pg_dir的物理地址,这样访问FIX_WP_TEST的第一个地址,就是swapper_pg_dir指向的一个页目录项内容,实验测试通过.