page_address_htable ===================== :: static struct page_address_slot { struct list_head lh; spinlock_t lock; } ___cacheline_aligned_in_smp page_address_htable[1< max_level) return 0; if(!val && !(params[i].ops->flags & KERNEL_PARAM_OPS_FL_NOARG)) return -EINVAL; kernel_param_lock(params[i].mod); param_check_unsafe(¶ms[i]); //执行kernel_param中包含的set函数,该函数就是用cmdline中参数设置内核中启动的参数 err = params[i].ops->set(val, ¶ms[i]); kernel_param_unlock(params[i].mod); return err; } } //执行handle_unknown函数 if(handle_unknown) return handle_unknown(param, val, doing, arg); return -ENOENT; } ``parse_one`` 用于解析cmdline里面的一个参数,并在内核启动过程中设置这个内核参数 .. note:: 内核将所有启动参数的钩子函数存放在"__param" section里,在这个section中,每个成员按struct kernel_param 格式存储. __per_cpu_offset =================== :: unsigned long __per_cpu_offset[NR_CPUS] __read_mostly; EXPORT_SYMBOL(__per_cpu_offset) per_cpu_offset ================== :: #ifndef __per_cpu_offset extern unsigned long __per_cpu_offset[NR_CPUS]; #define per_cpu_offset(x) (__per_cpu_offset[x]) #endif ``per_cpu_offset`` 用于获得特定CPU在__per_cpu_offset[]数组中的偏移 PFN_DOWN ========== :: #define PFN_DOWN(x) ((x) >> PAGE_OFFSET) PFN_PHYS ============= :: #define PFN_PHYS(x) ((phys_addr_t)(x) << PAGE_SHITF) ``PFN_PHYS`` 将物理页帧转换为物理地址.参数x指向物理页帧号 pfn_pte ========== :: #define pfn_pte(pfn, prot) __pte(__pfn_to_phys(pfn) | pgprot_val(prot)) #define __pfn_to_phys(pfn) PFN_PHYS(pfn) #define pgrpot_val(x) ((x).pgprot) ``pfn_pte`` 用于制作一个PTE入口项 PFN_UP ======== :: #define PFN_UP(x) (((x) + PAGE_SIZE - 1) >> PAGE_SHIFT) ``PFN_UP`` 将参数x对应的下一个页帧号 PHYS_OFFSET ============== :: config PHYS_OFFSET hex "Physical address of main memory" if MMU depends on !ARM_PATCH_PHYS_VIRT default DRAM_BASE if !MMU default 0x00000000 if ARCH_EBSA110 || \ ARCH_ROOTBRIDGE || ARCH_INTEGRATOR || ARCH_IOP13XX || \ ARCH_KS8695 || ARCH_REALVIEW default 0x10000000 if ARCH_OMAP1 || ARCH_RPC default 0x20000000 if ARCH_S5PV210 default 0xc0000000 if ARCH_SA1100 help Please provide the physical address corresponding to the localtion of main memory in your system ``PHYS_OFFSET`` 用于指明DRAM在地址总线上的偏移.其定义在"arch/arm/Kconfig" PHYS_PFN ============= :: #define PHYS_PFN(x) ((unsigned long)((x) >> PAGE_SHIFT)) ``PHYS_PFN`` 用于将物理地址转换成物理页帧号 __phys_to_pfn ================== :: #define __phys_to_pfn(paddr) PHYS_PFN(paddr) __phys_to_virt ================= :: #define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT)) ``__fix_to_virt`` 用于FIXMAP索引获得对应的虚拟地址 phys_to_virt ============== :: static inline void *phys_to_virt(phys_addr_t x) { return (void *)__phys_to_virt(x); } pgd_addr_end ================= :: #define pgd_addr_end(addr, end) \ ({ unsigned long __boundary = ((addr) + PGDIR_SIZE) & PGDIR_MASK; \ (__boundary - 1 < (end) - 1) ? __boundary : (end); \ }) ``pgd_addr_end`` 用于获得addr下一个PGDIR_SIZE地址 pgd_index ============= :: #define pgd_index(addr) ((addr) >> PGDIR_SHIFT) ``pgd_index`` 用于获得虚拟地址x在页目录中的索引 - 例如二级页表的32位地址上,页目录和页表的布局如下 :: 2-level Page Table 32 22 12 0 +-------------------------+------------------------+----------------------+ | Directory | Table | Offset | +-------------------------+------------------------+----------------------+ | <------------ PGDIR_SHIFT -----------------> | | <---PAGE_SHIFT-----> | pgd_offset ============= :: #define pgd_offset(mm, addr) ((mm)->pgd + pgd_index(addr)) ``pgd_offset`` 用于获得虚拟地址对应的页目录内容 - 例如在二级页表中,页目录索引与进程页目录的关系如下图 :: 2-level Page Table 32 22 12 0 +-------------------------+------------------------+----------------------+ | Directory | Table | Offset | +-------------------------+------------------------+----------------------+ | | <------------ PGDIR_SHIFT -----------------> | | | <---PAGE_SHIFT-----> | | | | | | | | +--------------+ | | | | +--------------+ | | | o------------>+--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | task_struct->mm->pgd----->+--------------+ pgd_offset_k =============== :: #deinf pgd_offset_k(addr) pgd_offset(&init_mm, addr) ``pgd_offset_k`` 用于获得内核空间虚拟地址对应的页目录入口地址 - 例如在二级页表中,内核虚拟地址对应的页目录关系如下 :: 2-level Page Table 32 22 12 0 +-------------------------+------------------------+----------------------+ | Directory | Table | Offset | +-------------------------+------------------------+----------------------+ | | <------------ PGDIR_SHIFT -----------------> | | | <---PAGE_SHIFT-----> | | | | | | | | +--------------+ | | | | +--------------+ | | | o------------>+--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | +--------------+ | | init_mm->pgd----->+--------------+ pgdat_end_pfn ================= :: static inline unsigned long pgdat_end_pfn(pg_data_t *pgdat) { return pgdat->node_start_pfn + pgdata->node_spanned_pages; } ``pgdat_end_pfn`` 用于获得pglist指向的结束页帧号.函数通过从pglist结构中获得起始页帧加上横跨页帧的数量,以此获得结束页帧号 __pgprot ============= :: #define __pgprot(x) ((pgprot_t) { (x) }) pgrot_val =============== :: #define pgrot_val(x) ((x).pgprot) ``pgrot_val`` 用于获得PTE入口标志 pmd_addr_end ================ :: #define pmd_addr_end(addr, end) \ ({ unsigned long __boundary = ((addr) + PMD_SIZE) & PMD_MASK; \ (__boundary - 1 < (end) - 1) ? __boundary: (end); \ }) ``pmd_addr_end`` 用于获得addr下一个PMD_SIZE地址 pmd_bad ========== :: #define pmd_bad(pmd) (pmd_val(pmd) & 2) ``pmd_bad`` 判断PMD入口项是否有效 pmd_clear ============ :: #define pmd_clear(pmdp) \ do { \ pmdp[0] = __pmd(0); \ pmdp[1] = __pmd(0); \ clean_pmd_entry(pmdp); \ } while(0) ``pmd_clear`` 用于清除一个PMD入口项 pmd_empty_section_gap ======================== :: static void __init pmd_empty_section_gap(unsigned long addr) { vm_reserve_area_early(addr, SECTION_SIZE, pmd_empty_section_gap); } void __init vm_reserve_area_early(unsigned long addr, unsigned long size, void *caller) { struct vm_struct *vm; struct static_vm *svm; svm = early_alloc_aligned(sizeof(*svm), __alignof__(*svm)); vm = &svm->vm; vm->addr = (void *)addr; vm->size = size; vm->flags = VM_IOREMAP | VM_ARM_EMPTY_MAPPING; vm->caller = caller; add_static_vm_early(svm); } ``pmd_empty_section_gap`` 将addr对应的虚拟空间加入到系统静态映射中预留区 pmd_none ========= :: #define pmd_none(pmd) (!pmd_val(pmd)) ``pmd_none`` 判断PMD入口项是否为空. 函数通过pmd_val函数读取PMD入口项的值,如果值为0,那么返回true,表示PMD入口项为空 pmd_off_k =========== :: static inline pmd_t *pmd_off_k(unsigned long virt) { return pmd_offset(pud_offset(pgd_offset_k(virt), virt), virt); } ``pmd_off_k`` 用于获得内核虚拟地址对应的PMD入口 pmd_offset ============= :: static inline pmd_t *pmd_offset(pud_t *pud, unsigned long address) { return (pmd_t *)pud; } ``pmd_offset`` 用于从PUD页表中获得PMD入口地址 pmd_page_vaddr ================= :: static inline pte_t *pmd_page_vaddr(pmd_t pmd) { return __va(pmd_val(pmd) & PHYS_MASK & (s32)PAGE_MASK); } ``pmd_page_vaddr`` 用于获得PMD入口对应的PTE页表基地址 __pmd_populate ================= :: static inline void __pmd_populate(pmd_t *pmdp, phys_addr_t pte, pmdval_t prot) { pmdval_t pmdval = (pte + PTE_HWTABLE_OFF) | port; pmdp[0] = __pmd(pmdval); #ifndef CONFIG_ARM_LPAE pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t)); #endif flush_pmd_entry(pmdp); } ``__pmd_populate`` 向指定的PMD入口填充PTE页表的物理地址与标志 pmd_populate_kernel ===================== :: static inline void pmd_populate_kernel(struct mm_struct *mm, pmd_t *pmdp, pte_t *ptep) { __pmd_populate(pmdp, __pa(ptep), _PAGE_KERNEL_TABLE); } ``pmd_populate_kernel`` 是为内核PMD入口填充PTE页表信息 pmd_val ========== :: #define pmd_val(x) ((x).pmd) populate_zone ================= :: static inline bool populate_zone(struct zone *zone) { return zone->present_pages; } ``populate_zone`` 用于判断ZONE是否已经包含物理内存 prepare_page_table ==================== :: static inline void prepare_page_table(void) { unsigned long addr; phys_addr_t end; for(addr = 0; addr < MODULES_VADDR; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr)); #ifdef CONFIG_XIP_KERNEL addr = ((unsigned long)_exiprom + PMD_SIZE - 1) & PMD_MASK; #endif for( ; addr < PAGE_OFFSET; addr += PMD_SIZE) pmd_clear(pmd_offset_k(addr)); end = memblock.memory.regions[0].base + memblock.memory.regions[0].size; if(end >= arm_lowmem_limit) end = arm_lowmem_limit; for(addr = __phys_to_virt(end); addr < VMALLOC_START; addr += PMD_SIZE) pmd_clear(pmd_off_k(addr)); } ``prepare_page_table`` 函数的作用是建立页表之前,清除未使用的页表.函数分别找了三段虚拟地址,分别为 - 0地址到MODULE_VADDR - MODULE_VADDR到PAGE_OFFSET - 第一块DRM大于arm_lowmem_limit部分 对这三段地址分别使用pmd_off_k计算出pmd入口之后,使用pmd_clear将pmd项清除,并刷新TLB和Cache