您当前的位置:五五电子网电子知识单片机-工控设备嵌入式系统-技术嵌入式Linux内核移植相关代码详解 正文
嵌入式Linux内核移植相关代码详解

嵌入式Linux内核移植相关代码详解

点击数:7165 次   录入时间:03-04 11:54:00   整理:http://www.55dianzi.com   嵌入式系统-技术
reserve_bootmem_node(pgdat, __pa(&_stext), &_end - &_stext); /*内核所占用地址空间*/
reserve_bootmem_node(pgdat, bootmap_pfn</*bootmem结构所占用地址空间*/

5.2.6 paging_init(&meminfo, mdesc)
创建内核页表,映射所有物理内存和IO空间,对于不同的处理器,该函数差别比较大。下面简单描述一下ARM体系结构的存储系统及MMU相关的概念。
在ARM存储系统中,使用内存管理单元(MMU)实现虚拟地址到实际物理地址的映射。利用MMU,可把SDRAM的地址完全映射到0x0起始的一片连续地址空间,而把原来占据这片空间的FLASH或者ROM映射到其他不相冲突的存储空间位置。例如,FLASH的地址从0x00000000~0x00FFFFFF,而SDRAM的地址范围是0x3000 0000~0x3lFFFFFF,则可把SDRAM地址映射为0x00000000~0xlFFFFFF,而FLASH的地址可以映射到0x90000000~0x90FFFFFF(此处地址空间为空闲,未被占用)。映射完成后,如果处理器发生异常,假设依然为IRQ中断,PC指针指向0xl8处的地址,而这个时候PC实际上是从位于物理地址的0x30000018处读取指令。通过MMU的映射,则可实现程序完全运行在SDRAM之中。在实际的应用中.可能会把两片不连续的物理地址空间分配给SDRAM。而在操作系统中,习惯于把SDRAM的空间连续起来,方便内存管理,且应用程序申请大块的内存时,操作系统内核也可方便地分配。通过MMU可实现不连续的物理地址空间映射为连续的虚拟地址空间。操作系统内核或者一些比较关键的代码,一般是不希望被用户应用程序访问。通过MMU可以控制地址空间的访问权限,从而保护这些代码不被破坏。
MMU的实现过程,实际上就是一个查表映射的过程。建立页表是实现MMU功能不可缺少的一步。页表位于系统的内存中,页表的每一项对应于一个虚拟地址到物理地址的映射。每一项的长度即是一个字的长度(在ARM中,一个字的长度被定义为4Bytes)。页表项除完成虚拟地址到物理地址的映射功能之外,还定义了访问权限和缓冲特性等。
MMU的映射分为两种,一级页表的变换和二级页表变换。两者的不同之处就是实现的变换地址空间大小不同。一级页表变换支持1 M大小的存储空间的映射,而二级可以支持64 kB,4 kB和1 kB大小地址空间的映射。

动态表(页表)的大小=表项数*每个表项所需的位数,即为整个内存空间建立索引表时,需要多大空间存放索引表本身。
表项数=虚拟地址空间/每页大小
每个表项所需的位数=Log(实际页表数)+适当控制位数
实际页表数 =物理地址空间/每页大小



--------------------------------------------------------------------------------
Send_linux 回复于:2007-03-06 15:44:27

下面分析paging_init()函数的代码。
在paging_init中分配起始页(即第0页)地址:
zero_page = 0xCXXXXXXX

memtable_init(mi); 如果当前微处理器带有MMU,则为系统内存创建页表;如果当前微处理器不支持MMU,比如ARM7TDMI上移植uCLinux操作系统时,则不需要此类步骤。可以通过如下一个宏定义实现灵活控制,对于带有MMU的微处理器而言,memtable_init(mi)是paging_init()中最重要的函数。
#ifndef CONFIG_UCLINUX
/* initialise the page tables. */
memtable_init(mi);
……(此处省略若干代码)
free_area_init_node(node, pgdat, 0, zone_size,
bdata->node_boot_start, zhole_size);
}
#else /* 针对不带MMU微处理器 */
{
/*****************************************************/
定义物理内存区域管理
/*****************************************************/
unsigned long zone_size[MAX_NR_ZONES] = {0,0,0};

zone_size[ZONE_DMA] = 0;
zone_size[ZONE_NORMAL] = (END_MEM - PAGE_OFFSET) >> PAGE_SHIFT;

free_area_init_node(0, NULL, NULL, zone_size, PAGE_OFFSET, NULL);
}
#endif

uCLinux与其它嵌入式Linux最大的区别就是MMU管理这一块,从上面代码就明显可以看到这点区别。下面继续讨论针对带MMU的微处理器的内存管理。
void __init memtable_init(struct meminfo *mi)
{
struct map_desc *init_maps, *p, *q;
unsigned long address = 0;
int i;
init_maps = p = alloc_bootmem_low_pages(PAGE_SIZE);
/*******************************************************/
其中map_desc定义为:
struct map_desc {
unsigned long virtual;
unsigned long physICal;
unsigned long length;
int domain:4, // 页表的domain
prot_read:1, // 读保护标志
prot_write:1, // 写保护标志
cacheable:1, // 是否使用cache
bufferable:1, // 是否使用write buffer
last:1; //空
};init_maps /* map_desc是区段及其属性的定义 */

下面代码对meminfo的区段进行遍历,在嵌入式系统中列举所有可映射的内存,例如32M SDRAM, 4M FLASH等,用meminfo记录这些内存区段。同时填写init_maps 中的各项内容。meminfo结构如下:
struct meminfo {
int nr_banks;
unsigned long end;
struct {
unsigned long start;
unsigned long size;
int node;
} bank[NR_BANKS];
};
/********************************************************/

for (i = 0; i < mi->nr_banks; i++)
{
if (mi->bank.size == 0)
continue;

p->physical = mi->bank.start;
p->virtual = __phys_to_virt(p->physical);
p->length = mi->bank.size;
p->domain = DOMAIN_KERNEL;
p->prot_read = 0;
p->prot_write = 1;
p->cacheable = 1; //使用Cache
p->bufferable = 1; //使用write buffer
p ++; //下一个区段
}

/* 如果系统存在FLASH,执行以下代码 */
#ifdef FLUSH_BASE
p->physical = FLUSH_BASE_PHYS;
p->virtual = FLUSH_BASE;
p->length = PGDIR_SIZE;
p->domain = DOMAIN_KERNEL;
p->prot_read = 1;
p->prot_write = 0;
p->cacheable = 1;
p->bufferable = 1;

p ++;
#endif

/***********************************************************/
接下来的代码是逐个区段建立页表
/***********************************************************/
q = init_maps;
do {
if (address < q->virtual || q == p) {

/*******************************************************************************/
由于内核空间是从某个地址开始,如0xC0000000,所以0xC000 0000 以前的页表项全部清空
clear_mapping在mm-armv.c中定义,其中clear_mapping()是个宏,根据处理器的不同,可以被展开为如下代码
cpu_XXX_set_pmd(((pmd_t *)(((&init_mm )->pgd+ (( virt) >> 20 )))),((pmd_t){( 0 )}));
其中init_mm为内核的mm_struct,pgd指向 swapper_pg_dir,在arch/arm/kernel/init_task.c中定义。cpu_XXX_set_pmd定义在 proc_armXXX.S文件中,参见ENTRY(cpu_XXX_set_pmd) 处代码。
/*********************************************************************************/
clear_mapping(address);

/* 每个表项增加1M */
address += PGDIR_SIZE;
} else {

/* 构建内存页表 */
create_mapping(q);

address = q->virtual + q->length;
address = (address + PGDIR_SIZE - 1) & PGDIR_MASK;

q ++;
}
} while (address != 0);

/ * create_mapping函数也在mm-armv.c中定义 */
static void __init create_mapping(struct map_desc *md)
{
unsigned long virt, length;
int prot_sect, prot_pte;
long off;

/*******************************************************************************/
大部分应用中均采用1级section模式的地址映射,一个section的大小为1M,也就是说从逻辑地址到物理地址的转变是这样的一个过程:
一个32位的地址,高12位决定了该地址在页表中的index,这个index的内容决定了该逻辑section对应的物理section;低20位决定了该地址在section中的偏移(index)。例如:从0x0~0xFFFFFFFF的地址空间总共可以分成0x1000(4K)个 section(每个section大小为1M),页表中每项的大小为32个bit,因此页表的大小为0x4000(16K)。

每个页表项的内容如下:
bit: 31 20 19 12 11 10 9 8 5 4 3 2 1 0
content: Section对应的物理地址 NULL AP 0 Domain 1 C B 1 0
最低两位(10)是section分页的标识。
AP:ACCess Permission,区分只读、读写、SVC&其它模式。
Domain:每个section都属于某个Domain,每个Domain的属性由寄存器控制。一般都只要包含两个Domain,一个可访问地址空间; 另一个不可访问地址空间。

www.55dianzi.com
C、B:这两位决定了该section的cache&write buffer属性,这与该段的用途(RO or RW)有密切关系。不同的用途要做不同的设置。

C B 具体含义
0 0 无cache,无写缓冲,任何对memory的读写都反映到总线上。对 memory 的操作过程中CPU需要等待。
0 1 无cache,有写缓冲,读操作直接反映到总线上。写操作CPU将数据写入到写缓冲后继续运行,由写缓冲进行写回操作。
1 0 有cache,写通模式,读操作首先考虑cache hit;写操作时直接将数据写入写缓冲,如果同时出现cache hit,那么也更新cache。

上一页  [1] [2] [3] [4]  下一页


本文关键字:嵌入式  Linux  嵌入式系统-技术单片机-工控设备 - 嵌入式系统-技术