3.5.1.205. page_address_htable
static struct page_address_slot {
struct list_head lh;
spinlock_t lock;
} ___cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
page_address_htable
是一个全局变量,维护高端内存线性地址中永久映射的全局变量
3.5.1.206. page_address_init
void __init page_address_init(void)
{
int i;
for(i = 0; i < ARRAY_SIZE(page_address_htable); i++)
{
INIT_LIST_HEAD(&page_address_htable[i].lh);
spin_lock_init(&page_address_htable[i].lock);
}
}
page_address_init
用于初始化高端内存线性地址中永久映射的全局变量.内核使用全局数组page_address_htable维护高端内存页表池的链表,
并带一个自旋锁.
3.5.1.207. PAGE_OFFSET
config PAGE_OFFSET
hex
default PHYS_OFFSET if !MMU
default 0x40000000 if VMSPLIT_1G
default 0x80000000 if VMSPLIT_2G
default 0xB0000000 if VMSPLIT_3G_OPT
default 0xC0000000
PAGE_OFFSET
宏用于指明内核空间虚拟地址的起始地址.其值与用户空间和内核空间虚拟地址划分有关,如果用于空间与内核空间按
1:3,那么内核空间虚拟地址从0x40000000开始.2:2则从0x80000000开始,3:1则从0xB0000000开始
3.5.1.208. __page_to_pfn
#define __page_to_pfn(page) ((unsigned long)((page) - mem_map) + ARCH_PFN_OFFSET)
__page_to_pfn
用于将struct page转换成物理页帧号
3.5.1.209. parameq
bool parameq(const char *a, const char *b)
{
return parameqn(a, b, strlen(a) + 1);
}
parameq
用于对比a字符串是否与b字符串相等
3.5.1.210. parse_args
//doing: 标识符字符串
//args: 包含启动参数的字符串
//params: 内核参数列表
//num: 内核参数的数量
//unknown: 私有函数
char *parse_args(const char *doing, char *args, const struct kernel_param *params, unsigned num,
s16 min_level, s16 max_level, void *arg, int(*unknown)(char *param, char *val, const char *doing, void *arg))
{
char *param, *val, *err = NULL;
//将args参数开始处的空格去掉
args = skip_spaces(args);
while(*args)
{
int ret;
int irq_was_disabled;
//从args字符串中获得一个参数的名字,并将其存储到param里,值存储在val中
args = next_arg(args, ¶m, &val);
if(!val && strcmp(param, "--") == 0)
return err ? : args;
//判断当前中断是否使能
irq_was_disabled = irqs_disabled();
//在内核参数中找到与参数名字相同的内核参数,并将内核参数的值设置成val的值
ret = parse_one(param, val, doing, params, num, min_level, max_level, arg, unknown);
if(irq_was_disabled && !irqs_disabled())
pr_warn("%s: option '%s' enabled irq!\n", doing, param);
swtich(ret)
{
case 9:
continue;
case -ENOENT:
pr_err("%s: Unknown parameter '%s'\n", doing, param);
break;
case -ENOSPC:
pr_err("%s: '%s' to large for paramters '%s'\n", doing, val ?:"", param);
break;
default:
pr_err("%s: '%s' invalid for paramter '%s'\n", doing, val ?:"", param);
break;
}
err = ERR_PTR(ret);
}
return err;
}
parse_args
用于从一个字符串中解析参数,并设置对应的内核参数
3.5.1.211. parse_early_options
void __init parse_early_options(char *cmdline)
{
parse_args("early options", cmdline, NULL, 0, 0, 0, NULL, do_early_param);
}
parse_early_options
用于初始化cmdline内早期启动参数
3.5.1.212. parse_early_param
void __init parse_early_param(void)
{
static int done __initdata;
static char tmp_cmdline[COMMAND_LINE_SIZE] __initdata;
if(done)
return;
strcpy(tmp_cmdline, boot_command_line, COMMAND_LINE_SIZE);
parse_early_options(tmp_cmdline);
done = 1;
}
parse_early_param
用于系统启动早期,设置指定的启动参数.函数定义了两个静态变量,函数首先判断done的值,以防止该函数被二次执行
3.5.1.213. parse_one
static int parse_one(char *param, char *val, const char *doing, const struct kernel_param *params,
unsigned num_params, s16 min_level, s16 max_level, void *arg, int (*handle_unknown)(char *param, char *val, const char *doing, void *arg))
{
unsigned int i;
int err;
for(i = 0; i < num_params; i++) {
//找到与参数param相同的kernel_param
if(parameq(param, params[i].name)) {
//检查kernel_param中成员进行检测
if(params[i].level < min_level || param[i].level > 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里面的一个参数,并在内核启动过程中设置这个内核参数
注解
内核将所有启动参数的钩子函数存放在”__param” section里,在这个section中,每个成员按struct kernel_param 格式存储.
3.5.1.214. __per_cpu_offset
unsigned long __per_cpu_offset[NR_CPUS] __read_mostly;
EXPORT_SYMBOL(__per_cpu_offset)
3.5.1.215. 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[]数组中的偏移
3.5.1.216. PFN_DOWN
#define PFN_DOWN(x) ((x) >> PAGE_OFFSET)
3.5.1.217. PFN_PHYS
#define PFN_PHYS(x) ((phys_addr_t)(x) << PAGE_SHITF)
PFN_PHYS
将物理页帧转换为物理地址.参数x指向物理页帧号
3.5.1.218. 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入口项
3.5.1.219. PFN_UP
#define PFN_UP(x) (((x) + PAGE_SIZE - 1) >> PAGE_SHIFT)
PFN_UP
将参数x对应的下一个页帧号
3.5.1.220. 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”
3.5.1.221. PHYS_PFN
#define PHYS_PFN(x) ((unsigned long)((x) >> PAGE_SHIFT))
PHYS_PFN
用于将物理地址转换成物理页帧号
3.5.1.222. __phys_to_pfn
#define __phys_to_pfn(paddr) PHYS_PFN(paddr)
3.5.1.223. __phys_to_virt
#define __fix_to_virt(x) (FIXADDR_TOP - ((x) << PAGE_SHIFT))
__fix_to_virt
用于FIXMAP索引获得对应的虚拟地址
3.5.1.224. phys_to_virt
static inline void *phys_to_virt(phys_addr_t x)
{
return (void *)__phys_to_virt(x);
}
3.5.1.225. 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地址
3.5.1.226. 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-----> |
3.5.1.227. 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----->+--------------+
3.5.1.228. 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----->+--------------+
3.5.1.229. 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结构中获得起始页帧加上横跨页帧的数量,以此获得结束页帧号
3.5.1.230. __pgprot
#define __pgprot(x) ((pgprot_t) { (x) })
3.5.1.231. pgrot_val
#define pgrot_val(x) ((x).pgprot)
pgrot_val
用于获得PTE入口标志
3.5.1.232. 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地址
3.5.1.233. pmd_bad
#define pmd_bad(pmd) (pmd_val(pmd) & 2)
pmd_bad
判断PMD入口项是否有效
3.5.1.234. pmd_clear
#define pmd_clear(pmdp) \
do { \
pmdp[0] = __pmd(0); \
pmdp[1] = __pmd(0); \
clean_pmd_entry(pmdp); \
} while(0)
pmd_clear
用于清除一个PMD入口项
3.5.1.235. 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对应的虚拟空间加入到系统静态映射中预留区
3.5.1.236. pmd_none
#define pmd_none(pmd) (!pmd_val(pmd))
pmd_none
判断PMD入口项是否为空. 函数通过pmd_val函数读取PMD入口项的值,如果值为0,那么返回true,表示PMD入口项为空
3.5.1.237. 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入口
3.5.1.238. pmd_offset
static inline pmd_t *pmd_offset(pud_t *pud, unsigned long address)
{
return (pmd_t *)pud;
}
pmd_offset
用于从PUD页表中获得PMD入口地址
3.5.1.239. 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页表基地址
3.5.1.240. __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页表的物理地址与标志
3.5.1.241. 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页表信息
3.5.1.242. pmd_val
#define pmd_val(x) ((x).pmd)
3.5.1.243. populate_zone
static inline bool populate_zone(struct zone *zone)
{
return zone->present_pages;
}
populate_zone
用于判断ZONE是否已经包含物理内存
3.5.1.244. 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