摘要:linux刚刚加电启动时,如何从实模式进入保护模式?启动分页机制的前提是什么?如何保证分页机制之前和之后通过实地址和虚拟地址都能访问到同一个物理地址呢?内核页表是如何进行初始化的?用户进程不能访问内核的数据是在初始化的哪个阶段决定的?这些内容,都牵扯到linu的进程页表和内核页表,以及内核页表的初始化。本文也主要为你解答这些疑问.
本文来源:进程页表和内核页表:页表的初始化
1.进程页表:
关键数据结构:
PAGE_OFFSET: 0xc000000
进程地址空间以0xc0000000(由宏PAGE_OFFSET定义)分割成两个部分,进程运行在用户态,产生的地址小于0xc0000000;进程运行在核心态,产生的地址大于0xc0000000.但是,在某些情况下,内核为了访问数据必须访问用户态线性地址空间。
进程地址空间,其中的内核态部分对于所有的进程都是一样的,等于主内核页全局目录的相应表项。
2.内核页表
内核维护着自己的页表,驻留在所谓的主内核页全局目录中。我们将在此处解释:内核如何初始化自己的页表。
1)第一阶段:内核镜像刚刚装入内存,CPU处于实模式,分页功能尚未开启,内核创建一个有限的地址空间,128K,仅仅将内核装入RAM并初始化核心数据。
2)第二阶段:内核充分利用剩余的RAM建立页表,下面,我们将详细讨论这个页表的建立过程。
2.1临时内核页表
关键数据结构:
swapper_pg_dir:临时页全局目录对应的虚拟地址。
pg0:第一个页所在的物理地址
临时页全局目录在编译内核过程中静态初始化,而临时页全局目录存放在swapper_pg_dir之中。临时页表从pg0变量处开始存放。这里,我们假设内核使用的段,临时页表和128KB的内存初始化数据可以存放在前8M的RAM之中。为了映射这8M,我们需要用到2个页表。
开启分页的首要任务是确保实模式和保护模式下都能对前8M进行寻址(参考其中有关控制寄存器CR3的部分)。就是说,从0x0000000到0x007fffff的线性地址,从0xc0000000到0xc07fffff均可映射到物理地址范围:0x00000000到0x007fffff。
是startup_32()来初始化的。它的等价代码(这段代码见于2.4.0内核,2.6内核以后不是这样)如下:
98 movl $swapper_pg_dir-__PAGE_OFFSET,%eax
99 movl %eax,%cr3 /* set the page table pointer.. */
100 movl %cr0,%eax
101 orl $0x80000000,%eax
102 movl %eax,%cr0 /* ..and set paging (PG) bit */
其中,swapper_pg_dir是一个数组变量的名称;内核通过将swapper_pg_dir的所有项都填充为0.除了0、1,ox300(十进制768),0x301(十进制769)除外。这四项按照下列方式进行初始化:
* 0和0x300设置位pg0的物理地址,1和0x301设置成pg1的地址
* 四项的present、R/W,U/S置位
* 四项的accessed、dirty、pcd和pagesize位置零
2.2RAM小于896M时候的最终内核页表
关键数据结构:
关键函数:
宏__pa和__va分别进行相应区域的物理地址和线性地址之间的转换:位于内核空间的转换
由内核页表所提供的最终映射必须把从0xc0000000开始的线性地址映射到从0开始的物理地址。其中宏__pa和__va分别进行相应区域的物理地址和线性地址之间的转换。
主内核全局目仍然在swapee_page_dir变量中,它由paging_init()函数进行初始化:
444 void __init paging_init(void)
445 {
446 pagetable_init();
447
448 __asm__( "movl %%ecx,%%cr3\n" ::"c"(__pa(swapper_pg_dir)));
449
450 #if CONFIG_X86_PAE
451 /*
452 * We will bail out later - printk doesnt work right now so
453 * the user would just see a hanging kernel.
454 */
455 if (cpu_has_pae)
456 set_in_cr4(X86_CR4_PAE);
457 #endif
458
459 __flush_tlb_all();
460
461 #ifdef CONFIG_HIGHMEM
462 kmap_init();
463 #endif
464 {
465 unsigned long zones_size[MAX_NR_ZONES] = {0, 0, 0};
466 unsigned int max_dma, high, low;
467
468 max_dma = virt_to_phys((char *)MAX_DMA_ADDRESS) >> PAGE_SHIFT;
469 low = max_low_pfn;
470 high = highend_pfn;
471
472 if (low < max_dma)
473 zones_size[ZONE_DMA] = low;
474 else {
475 zones_size[ZONE_DMA] = max_dma;
476 zones_size[ZONE_NORMAL] = low - max_dma;
477 #ifdef CONFIG_HIGHMEM
478 zones_size[ZONE_HIGHMEM] = high - low;
479 #endif
480 }
481 free_area_init(zones_size);
482 }
483 return;
484 }
函数执行过程如下:
1)使用pagetable_init()建立页表项
2)将swapper_pg_dir的物理地址写入cr3
3)如果CPU编译内核的时候支持PAE,则将CR4控制寄存器的PAE置位
4)调用__flush_tlb_all()使得所有的TLB无效
pagetable_init()执行的操作依赖于RAM容量和CPU模型,对全局目录的初始化代码等价如下:
314 static void __init pagetable_init (void)
315 {
316 unsigned long vaddr, end;
317 pgd_t *pgd, *pgd_base;
318 int i, j, k;
319 pmd_t *pmd;
320 pte_t *pte;
321
322 /*
323 * This can be zero as well - no problem, in that case we exit
324 * the loops anyway due to the PTRS_PER_* conditions.
325 */
326 end = (unsigned long)__va(max_low_pfn*PAGE_SIZE);
327
328 pgd_base = swapper_pg_dir;
329 #if CONFIG_X86_PAE
330 for (i = 0; i < PTRS_PER_PGD; i++) {
331 pgd = pgd_base + i;
332 __pgd_clear(pgd);
333 }
334 #endif
335 i = __pgd_offset(PAGE_OFFSET);
336 pgd = pgd_base + i;
337
338 for (; i < PTRS_PER_PGD; pgd++, i++) {
339 vaddr = i*PGDIR_SIZE;
340 if (end && (vaddr >= end))
341 break;
342 #if CONFIG_X86_PAE
343 pmd = (pmd_t *) alloc_bootmem_low_pages(PAGE_SIZE);
344 set_pgd(pgd, __pgd(__pa(pmd) + 0x1));
345 #else
346 pmd = (pmd_t *)pgd;
347 #endif
348 if (pmd != pmd_offset(pgd, 0))
349 BUG();
350 for (j = 0; j < PTRS_PER_PMD; pmd++, j++) {
351 vaddr = i*PGDIR_SIZE + j*PMD_SIZE;
352 if (end && (vaddr >= end))
353 break;
354 if (cpu_has_pse) {
355 unsigned long __pe;
356
357 set_in_cr4(X86_CR4_PSE);
358 boot_cpu_data.wp_works_ok = 1;
359 __pe = _KERNPG_TABLE + _PAGE_PSE + __pa(vaddr);
360 /* Make it "global" too if supported */
361 if (cpu_has_pge) {
362 set_in_cr4(X86_CR4_PGE);
363 __pe += _PAGE_GLOBAL;
364 }
365 set_pmd(pmd, __pmd(__pe));
366 continue;
367 }
368
369 pte = (pte_t *) alloc_bootmem_low_pages(PAGE_SIZE);
370 set_pmd(pmd, __pmd(_KERNPG_TABLE + __pa(pte)));
371
372 if (pte != pte_offset(pmd, 0))
373 BUG();
374
375 for (k = 0; k < PTRS_PER_PTE; pte++, k++) {
376 vaddr = i*PGDIR_SIZE + j*PMD_SIZE + k*PAGE_SIZE;
377 if (end && (vaddr >= end))
378 break;
379 *pte = mk_pte_phys(__pa(vaddr), PAGE_KERNEL);
380 }
381 }
382 }
383
384 /*
385 * Fixed mappings, only the page table structure has to be
386 * created - mappings will be set by set_fixmap():
387 */
388 vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;
389 fixrange_init(vaddr, 0, pgd_base);
390
391 #if CONFIG_HIGHMEM
392 /*
393 * Permanent kmaps:
394 */
395 vaddr = PKMAP_BASE;
396 fixrange_init(vaddr, vaddr + PAGE_SIZE*LAST_PKMAP, pgd_base);
397
398 pgd = swapper_pg_dir + __pgd_offset(vaddr);
399 pmd = pmd_offset(pgd, vaddr);
400 pte = pte_offset(pmd, vaddr);
401 pkmap_page_table = pte;
402 #endif
403
404 #if CONFIG_X86_PAE
405 /*
406 * Add low memory identity-mappings - SMP needs it when
407 * starting up on an AP from real-mode. In the non-PAE
408 * case we already have these mappings through head.S.
409 * All user-space mappings are explicitly cleared after
410 * SMP startup.
411 */
412 pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
413 #endif
414 }
由startup_32()函数创建的物理内存前8M的恒等转化在这种映射不再必要的时候,需要调用zap_low_mappings()进行撤销。
2.3当RAM在896M和4096M之间的最终内核页表
这种情况下,并不把RAM全部映射到内核地址空间。linux在初始化阶段将把一个具有896MB的窗口映射到内核线性地址空间。如果一个程序需要对896M以上的地址进行寻址,那么就必须把线性地址映射到对应的RAM,这意味这修改某些页表项的值。内核使用与前一种情况相同的代码来初始化页全局目录。
2.4当RAM大于4096MB时候的最终内核页表
此时,线性地址只有1G和RAM大于1G,此处的映射就可能涉及到PAE和高端内存,详细可以参考高端内存。此时,linux仅仅映射前896M的RAM,剩下的不进行映射(剩下的就用于存放用户数据了)。与前两种的主要差异在于,此时,采用三级分页模型,代码如下:
。。。。。。
页全局目录的前三项与用户线性地址空间对应,内核使用一个空页(empty_zero_page)进行初始化,第四项,采用页中间目录的地址进行初始化,改页中间目录是通过调用alloc_bootmem_low_pages()获得的。页中间目录的前448项用RAM的物理地址填充。
然后页全局目录的第四项被拷贝到第一项中,这样好为线性地址空间的前896M中的第物理内存映射做镜像。为了完成对SMP系统的初始化,这个映射是必须的,初始化完成以后,内核调用zap-low_mappings()清楚对应的页表项。
分享到:
相关推荐
- **初始化同步与互斥环境**: - **屏蔽中断**: 确保关键部分的代码执行期间不会被中断打断。 - **启动大内核锁**: 提供互斥机制以保护共享资源。 - **注册时钟通知链**: 管理系统时钟事件。 - **激活第一个CPU**...
任务一:进程内存初始化与页表建立 本资源主要介绍操作系统中进程内存初始化和页表建立的知识点,涵盖了虚拟内存映射结构、页表建立、进程内存管理等方面的内容。 一、虚拟内存映射结构 虚拟内存(Virtual Memory...
### 内核初始化知识点详解 #### 一、内核初始化概述 **内核初始化**是操作系统启动过程中的关键环节之一,主要涉及系统硬件资源的初始化、内核数据结构的建立、核心服务的启动等步骤。良好的初始化过程可以确保...
3. 内存初始化:包括物理内存分配、内存管理结构如页表的建立,以及早期的进程和线程创建。 4. 系统服务初始化:探讨系统服务如何加载和注册,以及驱动程序和服务控制管理器的作用。 5. 用户模式组件:解释用户...
init_task 是一个静态定义的进程,不存在用户空间,总是运行在内核空间中,并负责初始化内存、页表、必要数据结构、信号、调度器、硬件设备等。 三、中断初始化 在 start_kernel() 函数中,trap_init() 函数用于...
2. **初始化**:创建页表,并将所有页表项设置为未分配或无效。 3. **映射**:将虚拟地址映射到物理地址,涉及到分配新的页框,更新页表项,以及可能的内存分配。 4. **页表查询**:根据虚拟地址快速查找对应的物理...
#### 三、内核启动与页表初始化 在Linux内核启动过程中,特别是在ARM架构上,内存页表的初始化是非常关键的一步。根据提供的内容,我们可以看到,内核版本为2.6.18_pro,内存起始物理地址为0x80000000,结束地址为0x...
通过深入了解ARM架构下的MMU工作原理以及页表与TLB的关系,我们不仅可以更好地理解Linux内核是如何管理和控制内存资源的,还可以为以后深入研究操作系统内核或其他复杂的软件系统打下坚实的基础。在实践中,熟悉这些...
- **初始化内核页表**:在`swapper_pg_dir`中创建内核的页表,这是内核用于映射物理内存到虚拟地址空间的基础结构。 - **初始化内核数据**:包括各种与内存管理相关的数据结构,如页面缓存、内存区等。 - **启动...
接下来,KernelInit开始工作,初始化内存堆(HeapInit)、内存池(InitMemoryPool)、内核进程(ProcInit)、调度器(SchedInit和FirstSchedule)。KernelInit完成后,系统通过SystemStartupFunc调用第一个线程。...
7. **进入内核初始化函数** (`start_kernel()`):执行真正的内核初始化过程,包括设置中断控制器、初始化设备驱动等。 8. **设置多处理器ID** (`smp_setup_processor_id()`):针对多处理器系统,初始化处理器ID。 ...
- 从BIOS加载MBR到内核,内核解压缩后进行初始化。 - 创建第一个进程(init进程),init根据配置文件(如`/etc/inittab`)启动系统服务和脚本,设置运行级别(RUNLEVEL)。 6. **运行级别(RUNLEVEL)**: - 不同...
1. **内核初始化**:Linux启动时,会执行bootloader传递的地址上的代码,即`start_kernel()`函数。这个过程包括设置内存管理、初始化中断处理、设备驱动初始化等。 2. **进程管理**:Linux内核使用进程控制块( PCB...
Linux内核启动是操作系统核心部分的关键过程,它涉及硬件初始化、加载内核映像、启动设备驱动、设置内存管理等复杂任务。理解这一过程对于系统管理员、开发者以及想要深入理解Linux操作系统的人员至关重要。 首先,...
本文将深入探讨Linux内核的引导过程,重点介绍内核引导的第一部分——核心数据结构初始化以及第二部分——外设初始化的具体细节。 #### 二、内核引导第一部分:核心数据结构初始化 ##### 1. start_kernel() 函数 ...
3. **BSS段(Block Started by Symbol)**:存储未初始化的全局变量和静态变量,这部分在程序启动时会被清零。 4. **堆(Heap)**:动态分配的内存区域,程序员可以通过`malloc`、`calloc`、`realloc`和`free`等...
3. 驱动初始化:接着,内核加载并初始化各种硬件驱动,如磁盘控制器、网络接口卡等,以确保能与硬件设备交互。 4. 文件系统:内核会加载并初始化文件系统模块,识别根文件系统(/),这对于挂载根目录至关重要。 5. ...
- **初始化临时内核页表**:在系统启动阶段,会首先建立一个临时页表,用于初始化内核。 - **永久内核页表的初始化**:随后会建立一个更复杂的永久页表,用于完整的内核运行。 - **第一次进入用户空间**:当内核完成...
系结构相关的,用于在加载真正的Linux内核之前执行基本的硬件初始化。它们通常处理CPU的设置,内存检测,以及设置页表以便使处理器能够访问内存。`head.S`是这个阶段的核心,它会进行必要的CPU初始化,比如设置堆栈...