arm-linux(kernel-2.6.13)的启动过程(1.2/2)
回到setup_arch()
经过这样的处理,setup.c文件中的meminfo可就不在是
static struct meminfo meminfo __initdata = { 0, };
而是
static struct meminfo meminfo __initdata = { 1,{0x30000000,0x4000000,0},{}, };
表示当前有一个内存区域,物理地址是从0x30000000开始,大小是64M,节点是0
paging_init(&meminfo, mdesc);
这是个庞大的函数,里面有很多好东西。显然他有根据机器描述符填充meminfo(setup.c)的倾向。一点一点看,估计看完天都黑了。
先把他考过来。
现在到了init.c了,这个文件里也有一个 meminfo,这个跟setup.c中的是两回事。
static struct meminfo meminfo __initdata = { 0, }; (在init.c中)
/*
* paging_init() sets up the page tables, initialises the zone memory
* maps, and sets up the zero page, bad page and bad page tables.
*/
void __init paging_init(struct meminfo *mi, struct machine_desc *mdesc)
{
void *zero_page;
int node;
bootmem_init(mi);
先分析下这个函数。
/*
* Initialise the bootmem allocator for all nodes. This is called
* early during the architecture specific initialisation.
*/
static void __init bootmem_init(struct meminfo *mi)
{
struct node_info node_info[MAX_NUMNODES], *np = node_info;
unsigned int bootmap_pages, bootmap_pfn, map_pg;
int node, initrd_node;
现来看看node_info这个结构是什么。
struct node_info {
unsigned int start;
unsigned int end;
int bootmap_pages;
};
#define NODES_SHIFT 2 /* Normally, Max 4 Nodes */
#define MAX_NUMNODES (1 << NODES_SHIFT)
很简单的数据结构。Max 4 Nodes
bootmap_pages = find_memend_and_nodes(mi, np);
把find_memend_and_nodes函数贴过来,我们已经下潜到第n层函数调用了。^_^
/*
* Scan the memory info structure and pull out:
* - the end of memory
* - the number of nodes
* - the pfn range of each node
* - the number of bootmem bitmap pages
*/
static unsigned int __init
find_memend_and_nodes(struct meminfo *mi, struct node_info *np)
{
unsigned int i, bootmem_pages = 0, memend_pfn = 0;
for (i = 0; i < MAX_NUMNODES; i++) {
np[i].start = -1U;
np[i].end = 0;
np[i].bootmap_pages = 0;
}
初始化这四个节点信息结构,在上一层函数中定义的。
for (i = 0; i < mi->nr_banks; i++) { 已经有一个bank了哦,nr_banks=1。
unsigned long start, end;
int node;
if (mi->bank[i].size == 0) { 我们ram的大小是64M,这里不成立。
/*
* Mark this bank with an invalid node number
*/
mi->bank[i].node = -1;
continue;
}
node = mi->bank[i].node; 这里node=0
/*
* Make sure we haven't exceeded the maximum number of nodes
* that we have in this configuration. If we have, we're in
* trouble. (maybe we ought to limit, instead of bugging?)
*/
if (node >= MAX_NUMNODES)
BUG();
node_set_online(node);这是个宏定义
#define node_set_online(node) set_bit((node), node_online_map.bits)
nodemask_t node_online_map = { { [0] = 1UL } };
EXPORT_SYMBOL(node_online_map);
typedef struct { DECLARE_BITMAP(bits, MAX_NUMNODES); } nodemask_t;
#define DECLARE_BITMAP(name,bits) \
unsigned long name[BITS_TO_LONGS(bits)]
#define BITS_TO_LONGS(bits) \
(((bits)+BITS_PER_LONG-1)/BITS_PER_LONG) (4+32-1)/32 = 1
BITS_PER_LONG = 32
经过处理后应该是
typedef struct {
unsigned long bits[1];
} nodemask_t;
#define set_bit(nr,p) ATOMIC_BITOP_LE(set_bit,nr,p)
#define ATOMIC_BITOP_LE(name,nr,p) \
(__builtin_constant_p(nr) ? \
____atomic_##name(nr, p) : \
_##name##_le(nr,p))
扩展后应该是
#define ATOMIC_BITOP_LE(name,nr,p) \
(__builtin_constant_p(0) ? \
____atomic_set_bit(0, node_online_map.bits) : \
_set_bit_le(0,node_online_map.bits))
似乎他的意图是想把node_online_map.bits的bit0 写为1,但是本来就是1呀?如下。
c02abbc0 :
c02abbc0: 00000001 andeq r0, r0, r1
好了,回到find_memend_and_nodes
/*
* Get the start and end pfns for this bank
*/
start = mi->bank[i].start >> PAGE_SHIFT;
end = (mi->bank[i].start + mi->bank[i].size) >> PAGE_SHIFT;
得到起始和结束页号0x30000 ~ 0x34000
if (np[node].start > start)
np[node].start = start;
if (np[node].end < end)
np[node].end = end;
填写到节点信息中。
if (memend_pfn < end)
memend_pfn = end;
填写memend_pfn为最末页号
}
/*
* Calculate the number of pages we require to
* store the bootmem bitmaps.
*/
for_each_online_node(i) {
if (np[i].end == 0)
continue;
np[i].bootmap_pages = bootmem_bootmap_pages(np[i].end -
np[i].start);
bootmem_pages += np[i].bootmap_pages;
}
#define for_each_online_node(node) for_each_node_mask((node), node_online_map)
#define for_each_node_mask(node, mask) \
for ((node) = first_node(mask); \
(node) < MAX_NUMNODES; \
(node) = next_node((node), (mask)))
#define first_node(src) __first_node(&(src))
static inline int __first_node(const nodemask_t *srcp)
{
return min_t(int, MAX_NUMNODES, find_first_bit(srcp->bits, MAX_NUMNODES));
}
#define next_node(n, src) __next_node((n), &(src))
static inline int __next_node(int n, const nodemask_t *srcp)
{
return min_t(int,MAX_NUMNODES,find_next_bit(srcp->bits, MAX_NUMNODES, n+1));
}
#define find_first_bit(p,sz) _find_first_bit_le(p,sz)
#define find_next_bit(p,sz,off) _find_next_bit_le(p,sz,off)
/*
* Purpose : Find a 'one' bit
* Prototype: int find_first_bit(const unsigned long *addr, unsigned int maxbit);
*/
ENTRY(_find_first_bit_le)
teq r1, #0
beq 3f
mov r2, #0
1: ldrb r3, [r0, r2, lsr #3]
movs r3, r3
bne .found @ any now set - found zero bit
add r2, r2, #8 @ next bit pointer
2: cmp r2, r1 @ any more?
blo 1b
3: mov r0, r1 @ no free bits
RETINSTR(mov,pc,lr)
/*
* Purpose : Find next 'one' bit
* Prototype: int find_next_zero_bit(void *addr, unsigned int maxbit, int offset)
*/
ENTRY(_find_next_bit_le)
teq r1, #0
beq 3b
ands ip, r2, #7
beq 1b @ If new byte, goto old routine
ldrb r3, [r0, r2, lsr #3]
movs r3, r3, lsr ip @ shift off unused bits
bne .found
orr r2, r2, #7 @ if zero, then no bits here
add r2, r2, #1 @ align bit pointer
b 2b @ loop for next bit
看看bootmem_bootmap_pages作了什么?
/* return the number of _pages_ that will be allocated for the boot bitmap */
unsigned long __init bootmem_bootmap_pages (unsigned long pages)
{
unsigned long mapsize;
mapsize = (pages+7)/8; 表示需要多少byte存放内存页面的bit映射表
mapsize = (mapsize + ~PAGE_MASK) & PAGE_MASK; 将这些byte页面对齐
mapsize >>= PAGE_SHIFT; 计算出这些byte需要多少页面。
return mapsize; 返回页面数。
}
我的内存64M,0x4000个页面,0x4000/8=2048bytes,也就需要一个页面了。
经过for_each_online_node(i)的处理,node_info[0]应该成了这个样子
node_info[0] = {0x30000,0x34000,1} 表示可用内存有0x30000 ~ 0x34000的物理页面,需要一个页的容量来存放页面位图
bootmem_pages = 1
返回find_memend_and_node。
high_memory = __va(memend_pfn << PAGE_SHIFT);
确定最高内存的地址,high_memory在memory.c中定义的全局指针。
/*
* A number of key systems in x86 including ioremap() rely on the assumption
* that high_memory defines the upper bound on direct map memory, then end
* of ZONE_NORMAL. Under CONFIG_DISCONTIG this means that max_low_pfn and
* highstart_pfn must be the same; there must be no gap between ZONE_NORMAL
* and ZONE_HIGHMEM.
*/
void * high_memory;
对解释很模糊
/*
* This doesn't seem to be used by the Linux memory
* manager any more. If we can get rid of it, we
* also get rid of some of the stuff above as well.
*
* Note: max_low_pfn and max_pfn reflect the number
* of _pages_ in the system, not the maximum PFN.
*/
max_low_pfn = memend_pfn - O_PFN_DOWN(PHYS_OFFSET); PHYS_OFFSET=0x30000000
max_pfn = memend_pfn - O_PFN_DOWN(PHYS_OFFSET);
return bootmem_pages;
}
在bootmem.c中的定义。
/*
* Access to this subsystem has to be serialized externally. (this is
* true for the boot process anyway)
*/
unsigned long max_low_pfn;
unsigned long min_low_pfn;
unsigned long max_pfn;
#define O_PFN_DOWN(x) ((x) >> PAGE_SHIFT)
这个函数的总体功能是找出页面位图需要的页数,也就是1页。
返回到bootmem_init
bootmap_pfn = find_bootmap_pfn(0, mi, bootmap_pages);
这段函数用来找到 内核镜像后面的那个没有用到的第一个页面的页号(物理的),关键代码 是 start_pfn = O_PFN_UP(__pa(&_end));
O_PFN_UP 使得内核镜像 与 内存使用bit位图 页面之间存在 hole。
initrd_node = check_initrd(mi);
没有用到initrd。check_initrd(mi)返回-2。
map_pg = bootmap_pfn;
/*
* Initialise the bootmem nodes.
*
* What we really want to do is:
*
* unmap_all_regions_except_kernel();
* for_each_node_in_reverse_order(node) {
* map_node(node);
* allocate_bootmem_map(node);
* init_bootmem_node(node);
* free_bootmem_node(node);
* }
*
* but this is a 2.5-type change. For now, we just set
* the nodes up in reverse order.
*
* (we could also do with rolling bootmem_init and paging_init
* into one generic "memory_init" type function).
*/
np += num_online_nodes() - 1;
#define num_online_nodes() nodes_weight(node_online_map)
for (node = num_online_nodes() - 1; node >= 0; node--, np--) {
/*
* If there are no pages in this node, ignore it.
* Note that node 0 must always have some pages.
*/
if (np->end == 0 || !node_online(node)) {
if (node == 0)
BUG();
continue;
}
/*
* Initialise the bootmem allocator.
*/
init_bootmem_node(NODE_DATA(node), map_pg, np->start, np->end);
在linux/mmzone.h中
#ifndef CONFIG_NEED_MULTIPLE_NODES 我的配置文件中,没有定义这个量
extern struct pglist_data contig_page_data;
#define NODE_DATA(nid) (&contig_page_data) 所以应该取这个宏定义,
#define NODE_MEM_MAP(nid) mem_map
#define MAX_NODES_SHIFT 1
#define pfn_to_nid(pfn) (0)
#else /* CONFIG_NEED_MULTIPLE_NODES */
#include 否则,就会取这里的宏定义
#endif /* !CONFIG_NEED_MULTIPLE_NODES */
也就是说NODE_DATA(node)返回节点的contig_page_data结构指针。我们只有节点0,所以总是返回这个指针。看看pglist_data结构是什么。
typedef struct pglist_data {
struct zone node_zones[MAX_NR_ZONES];
struct zonelist node_zonelists[GFP_ZONETYPES];
int nr_zones;
#ifdef CONFIG_FLAT_NODE_MEM_MAP 配置文件中定义了这个量。
struct page *node_mem_map;
#endif
struct bootmem_data *bdata; 这个结构这里显得比较重要,贴在了下面。
unsigned long node_start_pfn;
unsigned long node_present_pages; /* total number of physical pages */
unsigned long node_spanned_pages; /* total size of physical page
range, including holes */
int node_id;
struct pglist_data *pgdat_next;
wait_queue_head_t kswapd_wait;
struct task_struct *kswapd;
int kswapd_max_order;
} pg_data_t;
/*
* node_bootmem_map is a map pointer - the bits represent all physical
* memory pages (including holes) on the node.
*/
typedef struct bootmem_data {
unsigned long node_boot_start;
unsigned long node_low_pfn;
void *node_bootmem_map;
unsigned long last_offset;
unsigned long last_pos;
unsigned long last_success; /* Previous allocation point. To speed
* up searching */
} bootmem_data_t;
回来继续看
init_bootmem_node(&contig_page_data, map_pg, 0x30000, 0x34000);
他原封不动的调用了init_bootmem_core
unsigned long __init init_bootmem_node (pg_data_t *pgdat, unsigned long freepfn,unsigned long startpfn, unsigned long endpfn)
{
return(init_bootmem_core(pgdat, freepfn, startpfn, endpfn));
}
看init_bootmem_core。
/*
* Called once to set up the allocator itself.
*/
static unsigned long __init init_bootmem_core (pg_data_t *pgdat,
unsigned long mapstart, unsigned long start, unsigned long end)
{
bootmem_data_t *bdata = pgdat->bdata; 取得bdata.
unsigned long mapsize = ((end - start)+7)/8; 需要的字节数(整个物理内存的)
pgdat->pgdat_next = pgdat_list; //pgdat_list是全局指针,定义struct pglist_data *pgdat_list;
pgdat_list = pgdat; //这样就组成了一个双向链表
mapsize = ALIGN(mapsize, sizeof(long)); //四字节对其
bdata->node_bootmem_map = phys_to_virt(mapstart << PAGE_SHIFT); 得到那片即将作为bit位图的页面的虚拟地址
bdata->node_boot_start = (start << PAGE_SHIFT); 物理起始地址0x30000000
bdata->node_low_pfn = end; 结束页面号。
/*
* Initially all pages are reserved - setup_arch() has to
* register free RAM areas explicitly.
*/
memset(bdata->node_bootmem_map, 0xff, mapsize); 全部填充为0xff,表示0x30000000 ~ 0x34000000都不可用
return mapsize;
}
现在的物理内存都不可用了。
free_bootmem_node_bank(node, mi);
看看怎么释放物理内存的使用情况的。
/*
* Register all available RAM in this node with the bootmem allocator.
*/
static inline void free_bootmem_node_bank(int node, struct meminfo *mi)
{
pg_data_t *pgdat = NODE_DATA(node);
int bank;
for (bank = 0; bank < mi->nr_banks; bank++)
if (mi->bank[bank].node == node)
free_bootmem_node(pgdat, mi->bank[bank].start,
mi->bank[bank].size);
}
他的解释是注册这个节点的可用RAM,看free_bootmem_node
void __init free_bootmem_node (pg_data_t *pgdat, unsigned long physaddr, unsigned long size)
{
free_bootmem_core(pgdat->bdata, physaddr, size);
}
free_bootmem_core(bootmem_data_t *bdata, 0x30000000, 0x4000000)
static void __init free_bootmem_core(bootmem_data_t *bdata, unsigned long addr, unsigned long size)
{
unsigned long i;
unsigned long start;
/*
* round down end of usable mem, partially free pages are
* considered reserved.
*/
unsigned long sidx;
unsigned long eidx = (addr + size - bdata->node_boot_start)/PAGE_SIZE;
unsigned long end = (addr + size)/PAGE_SIZE;
BUG_ON(!size);
BUG_ON(end > bdata->node_low_pfn);
if (addr < bdata->last_success)
bdata->last_success = addr;
/*
* Round up the beginning of the address.
*/
start = (addr + PAGE_SIZE-1) / PAGE_SIZE;
sidx = start - (bdata->node_boot_start/PAGE_SIZE);
for (i = sidx; i < eidx; i++) {
if (unlikely(!test_and_clear_bit(i, bdata->node_bootmem_map)))
BUG();
}
}
这段代码是init_bootmem_core的反过程,现在所有的物理内存都可用了。(0x30000000 ~ 0x34000000)
map_pg += np->bootmap_pages; 指向下一个要当作内存位图的页面,已经无意义。
/*
* If this is node 0, we need to reserve some areas ASAP -
* we may use bootmem on node 0 to setup the other nodes.
*/
if (node == 0)
reserve_node_zero(bootmap_pfn, bootmap_pages);
}
BUG_ON(map_pg != bootmap_pfn + bootmap_pages);
}
这个函数 reserve_node_zero()真正的把需要保留的内存页保留了下来。
bootmap_pfn : 内核镜像后面的那个没有用到的第一个页面的页号(物理的)
bootmap_pages : 1
/*
* Reserve the various regions of node 0
*/
static __init void reserve_node_zero(unsigned int bootmap_pfn, unsigned int bootmap_pages)
{
pg_data_t *pgdat = NODE_DATA(0);
unsigned long res_size = 0;
/*
* Register the kernel text and data with bootmem.
* Note that this can only be in node 0.
*/
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext);
在0节点中保留内核镜像部分的内存,大概是0x30008000到以后的1.5M左右。
/*
* Reserve the page tables. These are already in use,
* and can only be in node 0.
*/
reserve_bootmem_node(pgdat, __pa(swapper_pg_dir),
PTRS_PER_PGD * sizeof(pgd_t));
需要的信息如下
.globl swapper_pg_dir
.equ swapper_pg_dir, TEXTADDR - 0x4000
extern pgd_t swapper_pg_dir[PTRS_PER_PGD];
#define PTRS_PER_PGD 2048
typedef struct { unsigned long pgd[2]; } pgd_t;
就是把0x30004000 ~ 0x30008000这段(16K)用于页目录的内容保留下来。
/*
* And don't forget to reserve the allocator bitmap,
* which will be freed later.
*/
reserve_bootmem_node(pgdat, bootmap_pfn << PAGE_SHIFT,
bootmap_pages << PAGE_SHIFT);
保留内核镜像后面的那些bit位图。我们好不容易创建的,可不能丟掉。
}
0x30000100不是还有参数吗?现在已经取得了参数了,不需要了。现在物理内存的布局大概是:
0x30000000 0x30004000 0x30008000 0x3xxxxxxx 0x34000000
| | | | |
|___________|___________|___________|______________________________________________|
| | | | | |
| | 页表 | image | 一页 (4k) | |
| | | | | |
|___________|___________|___________|_______________|______________________________|
可用 页目录(保留) kernel(保留) bit位图(保留)
bootmem_init到这里结束,回到paging_init中。
memcpy(&meminfo, mi, sizeof(meminfo));
拷贝到自己的meminfo中,以前我们用的meminfo保存在setup.c中,这次到了init.c中。
/*
* allocate the zero page. Note that we count on this going ok.
*/
zero_page = alloc_bootmem_low_pages(PAGE_SIZE);
分配一个0页,看看他在那里分配的,什么作用?
#define alloc_bootmem_low_pages(x) \
__alloc_bootmem((x), PAGE_SIZE, 0)
这样的参数
__alloc_bootmem(PAGE_SIZE, PAGE_SIZE, 0)
void * __init __alloc_bootmem (unsigned long size, unsigned long align, unsigned long goal)
{
pg_data_t *pgdat = pgdat_list; //这个指针不陌生 contig_page_data
void *ptr;
for_each_pgdat(pgdat) //这个循环只有一次了
if ((ptr = __alloc_bootmem_core(pgdat->bdata, size, 主体是这个函数了,希望他不要让我们失望,别返回NULL。
align, goal)))
return(ptr); 这样就可以退出历史舞台了
/*
* Whoops, we cannot satisfy the allocation request.
*/
printk(KERN_ALERT "bootmem alloc of %lu bytes failed!\n", size);
panic("Out of memory");
return NULL;
}
/*
* We 'merge' subsequent allocations to save space. We might 'lose' 合并了子分配去节省空间
* some fraction of a page if allocations cannot be satisfied due to
* size constraints on boxes where there is physical RAM space
* fragmentation - in these cases (mostly large memory boxes) this
* is not a problem.
*
* On low memory boxes we get it right in 100% of the cases.
*
* alignment has to be a power of 2 value.
*
* NOTE: This function is _not_ reentrant. 不可重入的函数
*/
看下参数
__alloc_bootmem_core(*bdata, PAGE_SIZE,PAGE_SIZE,0)
static void * __init
__alloc_bootmem_core(struct bootmem_data *bdata, unsigned long size,
unsigned long align, unsigned long goal)
{
unsigned long offset, remaining_size, areasize, preferred;
unsigned long i, start = 0, incr, eidx;
void *ret;
if(!size) {
printk("__alloc_bootmem_core(): zero-sized request\n");
BUG();
}
BUG_ON(align & (align-1));
eidx = bdata->node_low_pfn - (bdata->node_boot_start >> PAGE_SHIFT);
贴来需要的数据
bdata->node_boot_start = (start << PAGE_SHIFT); 物理起始地址0x30000000
bdata->node_low_pfn = end; 结束页面号。
eidx存放的是bit位图的最后一个索引
offset = 0;
if (align &&
(bdata->node_boot_start & (align - 1UL)) != 0) 显然后面的条件不满足
offset = (align - (bdata->node_boot_start & (align - 1UL)));
offset >>= PAGE_SHIFT;
这样offset还是0
/*
* We try to allocate bootmem pages above 'goal' 首先尝试获得引导内存页面在goal上面
* first, then we try to allocate lower pages. 然后尝试获得底端内存页面
*/
if (goal && (goal >= bdata->node_boot_start) && 显然不成立
((goal >> PAGE_SHIFT) < bdata->node_low_pfn)) {
preferred = goal - bdata->node_boot_start;
if (bdata->last_success >= preferred)
preferred = bdata->last_success;
} else
preferred = 0; 这里有效
preferred = ALIGN(preferred, align) >> PAGE_SHIFT;
看看是怎么对齐的。
#define ALIGN(x,a) (((x)+(a)-1)&~((a)-1))
还是0,这个是向上对齐的,如果x是1,那么就变成了align,这里就是page_size
preferred += offset;
areasize = (size+PAGE_SIZE-1)/PAGE_SIZE; 页为单位的区域,这里是1个页
incr = align >> PAGE_SHIFT ? : 1; incr=1
restart_scan:
for (i = preferred; i < eidx; i += incr) { 从0扫描到结束的内存bit位图
unsigned long j;
i = find_next_zero_bit(bdata->node_bootmem_map, eidx, i); 查找可用内存,别忘了0x30000000~0x30004000这4页可用页
i = ALIGN(i, incr); //这个多余
if (test_bit(i, bdata->node_bootmem_map)) 这里显然通过了
continue;
for (j = i + 1; j < i + areasize; ++j) { //由于areasize是1,这个扫描显得多余了。
if (j >= eidx)
goto fail_block;
if (test_bit (j, bdata->node_bootmem_map))
goto fail_block;
}
start = i; 所以我们很快得到了一页的可用空间,她在最顶端,也就是0x30000000~0x30001000这个页面
goto found; 此时的i=0
fail_block:
i = ALIGN(j, incr);
}
if (preferred > offset) {
preferred = offset;
goto restart_scan;
}
return NULL;
found:
bdata->last_success = start << PAGE_SHIFT; 填0,很讽刺,最后成功的是第一个页面
BUG_ON(start >= eidx);
/*
* Is the next page of the previous allocation-end the start
* of this allocation's buffer? If yes then we can 'merge'
* the previous partial page with this allocation.
*/
if (align < PAGE_SIZE && //显然这里不通过
bdata->last_offset && bdata->last_pos+1 == start) {
offset = ALIGN(bdata->last_offset, align);
BUG_ON(offset > PAGE_SIZE);
remaining_size = PAGE_SIZE-offset;
if (size < remaining_size) {
areasize = 0;
/* last_pos unchanged */
bdata->last_offset = offset+size;
ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +
bdata->node_boot_start);
} else {
remaining_size = size - remaining_size;
areasize = (remaining_size+PAGE_SIZE-1)/PAGE_SIZE;
ret = phys_to_virt(bdata->last_pos*PAGE_SIZE + offset +
bdata->node_boot_start);
bdata->last_pos = start+areasize-1;
bdata->last_offset = remaining_size;
}
bdata->last_offset &= ~PAGE_MASK;
} else {
bdata->last_pos = start + areasize - 1; 填0
bdata->last_offset = size & ~PAGE_MASK; 填1
ret = phys_to_virt(start * PAGE_SIZE + bdata->node_boot_start); 返回找到的页面的虚拟地址,也就是0xc0000000
}
/*
* Reserve the area now:
*/
for (i = start; i < start+areasize; i++) 保留这个区间的页面,这里只有一个页面
if (unlikely(test_and_set_bit(i, bdata->node_bootmem_map)))
BUG();
memset(ret, 0, size); 把0xc0000000~0xc0001000这第一个页面填充0
return ret; 返回0xc0000000这个指针
}
回到paging_init中
/*
* initialise the page tables.
*/
memtable_init(mi);
在汇编部分只映射了4m空间的ram,现在用c建立完整的页表。
/*
* Setup initial mappings. We use the page we allocated for zero page to hold
* the mappings, which will get overwritten by the vectors in traps_init().
* The mappings must be in virtual address order.
*/
void __init memtable_init(struct meminfo *mi)
{
struct map_desc *init_maps, *p, *q;
unsigned long address = 0;
int i;
build_mem_type_table();
看看他作什么。
/*
* Adjust the PMD section entries according to the CPU in use.
*/
static void __init build_mem_type_table(void)
{
struct cachepolicy *cp;
unsigned int cr = get_cr(); //得到cr寄存器的数值,cp15的控制寄存器。
unsigned int user_pgprot;
int cpu_arch = cpu_architecture(); //得到cpu的版本,这里是3,#define CPU_ARCH_ARMv4T 3
int i;
if (cpu_arch < CPU_ARCH_ARMv5) { //4<5
if (cachepolicy >= CPOLICY_WRITEALLOC) 不满足
cachepolicy = CPOLICY_WRITEBACK;
ecc_mask = 0;
}
需要的数据:
static unsigned int cachepolicy __initdata = CPOLICY_WRITEBACK;
static unsigned int ecc_mask __initdata = 0;
上面两个全局变量都是mm-armv.c中的私有数据。
#define CPOLICY_UNCACHED 0
#define CPOLICY_BUFFERED 1
#define CPOLICY_WRITETHROUGH 2
#define CPOLICY_WRITEBACK 3
#define CPOLICY_WRITEALLOC 4
dump中:
c001e8f8 :
c001e8f8: 00000000 andeq r0, r0, r0
c001e868 :
c001e868: 00000003 andeq r0, r0, r3
显然cachepolicy的初始化数据是CPOLICY_WRITEBACK=3
if (cpu_arch <= CPU_ARCH_ARMv5TEJ) {
for (i = 0; i < ARRAY_SIZE(mem_types); i++) {
if (mem_types[i].prot_l1)
mem_types[i].prot_l1 |= PMD_BIT4;
if (mem_types[i].prot_sect)
mem_types[i].prot_sect |= PMD_BIT4;
}
}
需要的数据:
有8种内存类型。
#define MT_DEVICE 0
#define MT_CACHECLEAN 1
#define MT_MINICLEAN 2
#define MT_LOW_VECTORS 3
#define MT_HIGH_VECTORS 4
#define MT_MEMORY 5
#define MT_ROM 6
#define MT_IXP2000_DEVICE 7
struct mem_types {
unsigned int prot_pte;
unsigned int prot_l1;
unsigned int prot_sect;
unsigned int domain;
};
static struct mem_types mem_types[] __initdata = {
[MT_DEVICE] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_WRITE,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PMD_TYPE_SECT | PMD_SECT_UNCACHED |
PMD_SECT_AP_WRITE,
.domain = DOMAIN_IO,
},
[MT_CACHECLEAN] = {
.prot_sect = PMD_TYPE_SECT,
.domain = DOMAIN_KERNEL,
},
[MT_MINICLEAN] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_MINICACHE,
.domain = DOMAIN_KERNEL,
},
[MT_LOW_VECTORS] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_EXEC,
.prot_l1 = PMD_TYPE_TABLE,
.domain = DOMAIN_USER,
},
[MT_HIGH_VECTORS] = {
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_USER | L_PTE_EXEC,
.prot_l1 = PMD_TYPE_TABLE,
.domain = DOMAIN_USER,
},
[MT_MEMORY] = {
.prot_sect = PMD_TYPE_SECT | PMD_SECT_AP_WRITE,
.domain = DOMAIN_KERNEL,
},
[MT_ROM] = {
.prot_sect = PMD_TYPE_SECT,
.domain = DOMAIN_KERNEL,
},
[MT_IXP2000_DEVICE] = { /* IXP2400 requires XCB=101 for on-chip I/O */
.prot_pte = L_PTE_PRESENT | L_PTE_YOUNG | L_PTE_DIRTY |
L_PTE_WRITE,
.prot_l1 = PMD_TYPE_TABLE,
.prot_sect = PMD_TYPE_SECT | PMD_SECT_UNCACHED |
PMD_SECT_AP_WRITE | PMD_SECT_BUFFERABLE |
PMD_SECT_TEX(1),
.domain = DOMAIN_IO,
}
};
根据需要填充PMD_BIT4。
cp = &cache_policies[cachepolicy]; 指向cache_policies[3]
user_pgprot = cp->pte;
需要的数据:
struct cachepolicy {
const char policy[16];
unsigned int cr_mask;
unsigned int pmd;
unsigned int pte;
};
static struct cachepolicy cache_policies[] __initdata = {
{
.policy = "uncached",
.cr_mask = CR_W|CR_C,
.pmd = PMD_SECT_UNCACHED,
.pte = 0,
}, {
.policy = "buffered",
.cr_mask = CR_C,
.pmd = PMD_SECT_BUFFERED,
.pte = PTE_BUFFERABLE,
}, {
.policy = "writethrough",
.cr_mask = 0,
.pmd = PMD_SECT_WT,
.pte = PTE_CACHEABLE,
}, {
.policy = "writeback", //名字,写回的方案
.cr_mask = 0,
.pmd = PMD_SECT_WB, // 使用cache和buffer
.pte = PTE_BUFFERABLE|PTE_CACHEABLE, //2级页表(1 << 2)|(1 << 3)
}, {
.policy = "writealloc",
.cr_mask = 0,
.pmd = PMD_SECT_WBWA,
.pte = PTE_BUFFERABLE|PTE_CACHEABLE,
}
};
将cache的使用组成若干不同的策略,存放在cache_policies中,方便使用。
if (cpu_arch >= CPU_ARCH_ARMv5) {
mem_types[MT_LOW_VECTORS].prot_pte |= cp->pte & PTE_CACHEABLE;
mem_types[MT_HIGH_VECTORS].prot_pte |= cp->pte & PTE_CACHEABLE;
} else {
mem_types[MT_LOW_VECTORS].prot_pte |= cp->pte;
mem_types[MT_HIGH_VECTORS].prot_pte |= cp->pte;
mem_types[MT_MINICLEAN].prot_sect &= ~PMD_SECT_TEX(1);
}
根据arm版本 设置内存类型 中 的相关标志。
mem_types[MT_LOW_VECTORS].prot_l1 |= ecc_mask;
mem_types[MT_HIGH_VECTORS].prot_l1 |= ecc_mask;
mem_types[MT_MEMORY].prot_sect |= ecc_mask | cp->pmd;
mem_types[MT_ROM].prot_sect |= cp->pmd;
通用的方案。
for (i = 0; i < 16; i++) {
unsigned long v = pgprot_val(protection_map[i]);
v &= (~(PTE_BUFFERABLE|PTE_CACHEABLE)) | user_pgprot;
protection_map[i] = __pgprot(v);
}
pgprot_kernel = __pgprot(L_PTE_PRESENT | L_PTE_YOUNG |
L_PTE_DIRTY | L_PTE_WRITE |
L_PTE_EXEC | cp->pte);
switch (cp->pmd) {
case PMD_SECT_WT:
mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_WT;
break;
case PMD_SECT_WB:
case PMD_SECT_WBWA:
mem_types[MT_CACHECLEAN].prot_sect |= PMD_SECT_WB;
break;
}
printk("Memory policy: ECC %sabled, Data cache %s\n",
ecc_mask ? "en" : "dis", cp->policy);
}
不看了,不懂,就学过v4了。^_^
回到memtable_init。
init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE);
这段函数已经分析过了,它得到第一个可用的内存页面,这里应该是第2个页面0x30001000~0x30002000。
init_maps = p=0x30001000,这个样子。
这里的alloc_bootmem_low_pages分配了一个空页,跟kmalloc()相似。看看map_desc这个数据结构
struct map_desc {
unsigned long virtual;
unsigned long physical;
unsigned long length;
unsigned int type; //内存的类型,就是那8种之一
};
内核用这个结构来描述ram的类型,物理,虚拟地址,和长度。
for (i = 0; i < mi->nr_banks; i++) { //只有一个内存bank,所以扫描1次。
if (mi->bank[i].size == 0)
continue;
p->physical = mi->bank[i].start;
p->virtual = __phys_to_virt(p->physical);
p->length = mi->bank[i].size;
p->type = MT_MEMORY;
p ++;
}
我们的0x30000000~0x34000000这片内存得到了描述。
#ifdef FLUSH_BASE
p->physical = FLUSH_BASE_PHYS;
p->virtual = FLUSH_BASE;
p->length = PGDIR_SIZE;
p->type = MT_CACHECLEAN;
p ++;
#endif
#ifdef FLUSH_BASE_MINICACHE
p->physical = FLUSH_BASE_PHYS + PGDIR_SIZE;
p->virtual = FLUSH_BASE_MINICACHE;
p->length = PGDIR_SIZE;
p->type = MT_MINICLEAN;
p ++;
#endif
似乎没有FLUSH_BASE FLUSH_BASE_MINICACHE的定义。
/*
* Go through the initial mappings, but clear out any
* pgdir entries that are not in the description.
*/
q = init_maps;
do {
if (address < q->virtual || q == p) {
clear_mapping(address);
address += PGDIR_SIZE;
} else {
create_mapping(q);
address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;
q ++;
}
} while (address != 0);
分析下这个do while
address = 0,q指向了第一个map_desc,q经过++指向第2个map_desc描述符。
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)
PGDIR_SHIFT determines what a third-level page table entry can map
#define PGDIR_SHIFT 21
看看清除映射函数 clear_mapping
/*
* Clear any PGD mapping. On a two-level page table system,
* the clearance is done by the middle-level functions (pmd)
* rather than the top-level (pgd) functions.
*/页目录的清除是由中间(pmd)目录函数完成的,而不是pgd的函数。
static inline void clear_mapping(unsigned long virt)
{
pmd_clear(pmd_off_k(virt));
}
看看pmd_clear的参数pmd_off_k(virt)是什么?
pmd_off_k(virt) = (init_mm->pgd+(addr) >> 21)
init_mm->pgd的类型是pgd_t类型的,而 typedef unsigned long pgd_t[2];
可见这个函数返回了virt这个虚拟地址在中间页目录中的索引值,他索引了2048个 8byte 长的pmd,从0xc0004000增长到0xc0008000-8,
#define INIT_MM(name) \
{ \
.mm_rb = RB_ROOT, \
.pgd = swapper_pg_dir, .pgd是页表所在的虚拟地址,在0xc0004000
...
}
#define pmd_clear(pmdp) \
do { \
pmdp[0] = __pmd(0); \
pmdp[1] = __pmd(0); \
clean_pmd_entry(pmdp); \
} while (0)
所以这个pmd_clear,可以擦除0xc0004000~0xc0008000之间的数据(页目录),当然它自己认为自己擦除了2048个8byte长度的pmd。
当addr到了0xc0000000这个虚拟地址的时候,转向else分支,创建这部分内存的页表。
看看create_mapping是怎么根据map_desc的内容来创建页表的?
/*
* Create the page directory entries and any necessary
* page tables for the mapping specified by `md'. We
* are able to cope here with varying sizes and address
* offsets, and we take full advantage of sections and
* supersections.
*/
static void __init create_mapping(struct map_desc *md)
{
unsigned long virt, length;
int prot_sect, prot_l1, domain;
pgprot_t prot_pte;
long off;
if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
printk(KERN_WARNING "BUG: not creating mapping for "
"0x%08lx at 0x%08lx in user region\n",
md->physical, md->virtual);
return;
}
只能给系统空间 或者 中断向量 所在的空间创建映射,绝对不可给用户虚拟空间创建映射。
if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END) {
printk(KERN_WARNING "BUG: mapping for 0x%08lx at 0x%08lx "
"overlaps vmalloc space\n",
md->physical, md->virtual);
}
这里只是为了让我们检查一下,映射的范围有没有覆盖到VMALLOC_START VMALLOC_END-1。
这里其实没有覆盖到
domain = mem_types[md->type].domain;
prot_pte = __pgprot(mem_types[md->type].prot_pte);
prot_l1 = mem_types[md->type].prot_l1 | PMD_DOMAIN(domain);
prot_sect = mem_types[md->type].prot_sect | PMD_DOMAIN(domain);
virt = md->virtual;
off = md->physical - virt;
length = md->length;
if (mem_types[md->type].prot_l1 == 0 &&
(virt & 0xfffff || (virt + off) & 0xfffff || (virt + length) & 0xfffff)) {
printk(KERN_WARNING "BUG: map for 0x%08lx at 0x%08lx can not "
"be mapped using pages, ignoring.\n",
md->physical, md->virtual);
return;
}
不是合法的一级描述符,并且虚拟地址,偏移量,长度 有不是1M对齐的,就不能进行映射了(创建页表)。
while ((virt & 0xfffff || (virt + off) & 0xfffff) && length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, prot_l1, prot_pte);
virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
/*
* A section mapping covers half a "pgdir" entry.
*/
while (length >= (PGDIR_SIZE / 2)) {
alloc_init_section(virt, virt + off, prot_sect);
virt += (PGDIR_SIZE / 2);
length -= (PGDIR_SIZE / 2);
}
在这里完成了ram 0xc0000000~0xc4000000的映射,就是为我的ram创建了页中间目录(pmd)。
这样,我们在汇编部分创建页表,就被覆盖了,好处是linux的视野变大了(他看到了所有物理ram:0x30000000~0x34000000)。
while (length >= PAGE_SIZE) {
alloc_init_page(virt, virt + off, prot_l1, prot_pte);
virt += PAGE_SIZE;
length -= PAGE_SIZE;
}
}
这几个 while 语句的含义为: 若虚拟地址与 1M (2^20)没有对齐(即低 20 位不全位 0) ,
则建立二级页面映射;然后为1M的部分逐段建立段(section)映射;最后为PAGE_SIZE部分建立二级页表映射。
可见长度小于PAGE_SIZE的部分是不会被映射的。
看看alloc_init_section。
static inline void
alloc_init_section(unsigned long virt, unsigned long phys, int prot)
{
pmd_t *pmdp = pmd_off_k(virt); //把指针强制转换成unsigned long *类型的。
if (virt & (1 << 20)) //对于虚拟地址在1,3,5,7...M部分的内容,pmdp指向 8byte部分的后4byte。
pmdp++; //个人感觉,linux 的pmd让代码显得愚蠢。让读代码的人困惑。
*pmdp = __pmd(phys | prot); //填充这个pgd,对cpu来说,这个条目就是一个pgd描述符(段描述符)。
flush_pmd_entry(pmdp);
}
回到do while中,在if部分,把虚拟地址余下的部分0xc4000000~0xffffffff对应的pmd条目清除了。
到了这里就完成了对pmd的创建过程。
忘了alloc_init_page()这个函数了,分析下建立中断向量所在页面的页表的过程。
/*
* Add a PAGE mapping between VIRT and PHYS in domain
* DOMAIN with protection PROT. Note that due to the
* way we map the PTEs, we must allocate two PTE_SIZE'd
* blocks - one for the Linux pte table, and one for
* the hardware pte table.
*/
static inline void
alloc_init_page(unsigned long virt, unsigned long phys, unsigned int prot_l1, pgprot_t prot)
{
pmd_t *pmdp = pmd_off_k(virt); //得到这个虚拟地址的pmd索引(相对0xc0004000来说)
pte_t *ptep;
if (pmd_none(*pmdp)) { // 中断向量的pmd还没有动过(是0),所以这里是真。
unsigned long pmdval;
ptep = alloc_bootmem_low_pages(2 * PTRS_PER_PTE *
sizeof(pte_t));
//这里应该是第3个页面0x30002000~0x30003000。PTRS_PER_PTE = 512,sizeof(pte_t) = sizeof(unsigned long) = 4
//也是打算取得一个页面。
pmdval = __pa(ptep) | prot_l1;
pmdp[0] = __pmd(pmdval);
pmdp[1] = __pmd(pmdval + 256 * sizeof(pte_t));
flush_pmd_entry(pmdp);
}
ptep = pte_offset_kernel(pmdp, virt);
set_pte(ptep, pfn_pte(phys >> PAGE_SHIFT, prot));
}
好多宏,我不看了,:-(
现在的页表不再是那个当初只有四个段描述符的页表了。
lzd> md 0x30007000
30007000: 3000041e 3010041e 3020041e 3030041e ...0...0.. 0..00
30007010: 3040041e 3050041e 3060041e 3070041e ..@0..P0..`0..p0
30007020: 3080041e 3090041e 30a0041e 30b0041e ...0...0...0...0
30007030: 30c0041e 30d0041e 30e0041e 30f0041e ...0...0...0...0
30007040: 3100041e 3110041e 3120041e 3130041e ...1...1.. 1..01
30007050: 3140041e 3150041e 3160041e 3170041e ..@1..P1..`1..p1
30007060: 3180041e 3190041e 31a0041e 31b0041e ...1...1...1...1
30007070: 31c0041e 31d0041e 31e0041e 31f0041e ...1...1...1...1
30007080: 3200041e 3210041e 3220041e 3230041e ...2...2.. 2..02
30007090: 3240041e 3250041e 3260041e 3270041e ..@2..P2..`2..p2
300070a0: 3280041e 3290041e 32a0041e 32b0041e ...2...2...2...2
300070b0: 32c0041e 32d0041e 32e0041e 32f0041e ...2...2...2...2
300070c0: 3300041e 3310041e 3320041e 3330041e ...3...3.. 3..03
300070d0: 3340041e 3350041e 3360041e 3370041e ..@3..P3..`3..p3
300070e0: 3380041e 3390041e 33a0041e 33b0041e ...3...3...3...3
300070f0: 33c0041e 33d0041e 33e0041e 33f0041e ...3...3...3...3
它已经映射了所有ram(0x30000000~0x34000000)区间,对应这虚拟地址0xc0000000~0xc4000000,对了还有
lzd> md 0x30007ff0
30007ff0: 00000000 00000000 30002031 30002431 ........1 .01$.0 //确实是第三个页面哦(30002xxx)!!!
给中断向量用的描述符。256 * sizeof(pte_t) = 0x400,显然linux在0xffff0000地址处寻找中断向量。
回到memtable_init()
/*
* Create a mapping for the machine vectors at the high-vectors
* location (0xffff0000). If we aren't using high-vectors, also
* create a mapping at the low-vectors virtual address.
*/
linux默认中断向量从0xffff0000开始,所以为倒数第16个页面创建映射,当然可能失败。
init_maps->physical = virt_to_phys(init_maps);
init_maps->virtual = 0xffff0000;
init_maps->length = PAGE_SIZE;
init_maps->type = MT_HIGH_VECTORS;
create_mapping(init_maps);
if (!vectors_high()) {
init_maps->virtual = 0;
init_maps->type = MT_LOW_VECTORS;
create_mapping(init_maps);
}
如果失败了,这里就一定成功。
中断向量从虚拟地址0开始,就映射这个第1(或者0)个页面(4k)
flush_cache_all();
local_flush_tlb_all();
冲洗缓冲池和页表缓存
top_pmd = pmd_off_k(0xffff0000);
保存最高pmd索引到top_pmd(全局的)。
}
回到paging_init中...
阅读(3161) | 评论(0) | 转发(0) |