Linux内核源码学习之 内核页表打印

本学期Linux内核实验最后是打印内核页表,线性地址----物理地址

我看到这个实验题目的时候想到的就是这个init函数(因为这部分当时就是我讲的^_^),这个函数是初始化linux内核页表的,也就是将32位系统中3G以上的896M线性地址映射到物理地址的0-896M,在其调用者paging_init函数中还处理了其他的情况,比如固定映射之类的。那属于高端内存映射那一块的内容,目前我们先看一下如何将内核页表3G~3G+896M的线性地址对应的物理地址打印出来。

一下的源码是linux2.6.11版本的,应该是和白皮书上的是对应的版本

static void __initkernel_physical_mapping_init(pgd_t *pgd_base)

{

unsignedlong pfn;

pgd_t*pgd;

pmd_t*pmd;

pte_t*pte;

intpgd_idx, pmd_idx, pte_ofs;

/*因为内核的线性地址空间是从0xC0000000开始的,所以这里我们只需要初始化内核全局页目录从0x300开始的项*/

pgd_idx= pgd_index(PAGE_OFFSET);      /*768*/

pgd= pgd_base + pgd_idx;                         /*pgd指向当前的目录项 */

pfn= 0;                                                             /*需要被映射的物理页框号,从物理地址0开始 */

/*初始化从768开始的每个页全局目录项,PTRS_PER_PGD为总项数1024 */

for(; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {

pmd= one_md_table_init(pgd);

if(pfn >= max_low_pfn)                     /*max_low_pfn代表被内核直接映射的最后一个物理页框的页框号*/

continue;

/*初始化每个页中间目录项,前面说到启用了物理地址扩展的32位x86系统中,使用三级映射,

而没有启用物理地址扩展的32位系统,其实只使用了其中的两级,虽然在软件结构中PMD依然存在,

但实际只是一个摆设。内核通过将PTRS_PER_PMD设为1,并且在one_md_table_init初始化PMD的函数中

直接将PMD的第一项初始化为指向其地址的PGD项本身,完成了一个"原地"的映射。也就是说,

此时的每一个页目录项,既表示一个页中间目录描述符,也表示一个页表 */

for(pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++,pmd_idx++) {

unsignedint address = pfn * PAGE_SIZE + PAGE_OFFSET;

/*Map with big pages if possible, otherwise create normal page tables. */

if(cpu_has_pse) {

unsignedint address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET +PAGE_SIZE-1;

if(is_kernel_text(address) || is_kernel_text(address2))

set_pmd(pmd,pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));

else

set_pmd(pmd,pfn_pmd(pfn, PAGE_KERNEL_LARGE));

pfn+= PTRS_PER_PTE;

}else {

pte= one_page_table_init(pmd);

/*最后初始化每个页表项,也就是每个物理页框的描述符。注意pfn++表示页框号依次加1,

而其初始值为0,也就是把物理地址从0开始的页框,直接映射到内核线性地址0xC0000000开始的空间内

,映射的方式与临时内核页表相似,只不过范围更大了 */

for(pte_ofs = 0;

pte_ofs < PTRS_PER_PTE && pfn< max_low_pfn;

pte++, pfn++, pte_ofs++, address +=PAGE_SIZE) {

if(is_kernel_text(address))

set_pte(pte,pfn_pte(pfn, PAGE_KERNEL_EXEC));

else

set_pte(pte,pfn_pte(pfn, PAGE_KERNEL));

}

}

}

}

}

注:上面的注释是针对没有开启PAE模式的,如果开启PAE,就是 4----512----512----也就是上面的768变为3,PTRS_PER_PGD为512 PTRS_PER_PMD为1,PTRS_PER_PTE为512

看完这个函数,我们来想一下怎么打印内核页表,我的系统是默认开启PAE的,也就是内存中是2M的页和4KB的页并存的,页目录和页表的组织形式是:

Cr3--àPDPT--àpmd [--àpte]-àpage 中间的pte之所以要加方括号,表示的是这一级不一定有,即如果对应的是2M的页,那么这个pmd中存放的就是2M页的物理地址和标志位,此时page的大小是2M;如果有这一级,那么对应的页是4KB的。这个从数量上很好理解,pmd一个表项对应的是2M的线性地址空间,如果页的大小是2M那么pmd相当于是页表,如果页的大小是4KB,那么有512项,正好需要借助再加上一级的pte(512)项来表示,这也就是上面函数对应的在pmd下判断是否有2M页的结构。

所以打印函数可以写成这样了:

for (; pgd_idx < PTRS_PER_PGD;pgd_idx++) {

unsignedlong pgd_cur = pgd_idx * PGDIR_SIZE;

pmd= pmd_offset((pud_t *)(pgd_base + pgd_idx), pgd_cur);

for(pmd_idx = 0; pmd_idx < 448/*PTRS_PER_PMD*/; pmd_idx++) {

unsignedlong pmd_cur = pgd_cur + pmd_idx * PMD_SIZE;

if(pmd_present(pmd[pmd_idx])){

pte= pte_offset_kernel((pmd_t *)(pmd + pmd_idx), pmd_cur);

pte1= pmd_val(*((pmd_t *)(pmd + pmd_idx)));

if((((unsignedlong)pte1)>>7)&0x1)

{

//打印2M页

}

else

{

for(pte_ofs = 0; pte_ofs < PTRS_PER_PTE; pte_ofs++) {

unsignedlong pg_cur = pmd_cur + pte_ofs * PAGE_SIZE;

if(pte_present(pte[pte_ofs]))

{

//打印4kb页

}

}

}

}

}

}

因为打印的内容很多,用seq文件实现比较方便。

时间: 2024-08-02 14:11:49

Linux内核源码学习之 内核页表打印的相关文章

Linux内核源码学习之 数据结构

本篇记录在学习Linux内核源码过程中对一些知道但不熟悉不会用的数据结构进行记录. union 是在学习进程复制函数do_fork中遇到的: <sched.h> union thread_union { struct thread_info thread_info; unsigned long stack[THREAD_SIZE/sizeof(long)]; }; struct  thread_info和stack被声明为union 共享空间 "联合"是一种特殊的类,也是一

Linux内核源码学习之进程切换细节整理

linux中的进程是个最基本的概念,进程从运行队列到开始运行有两个开始的地方, 一个就是switch_to宏中的标号1:"1:/t",//只要不是新创建的进程,几乎都是从上面的那个标号1开始的,而switch_to宏则是除了内核本身,所有的进程要 想运行都要经过的地方 另 一个就是ret_form_fork 这样看来,虽然linux的进程体系以及进程调度非常复杂,但是总体看来就是一个沙漏状, 对于系统中的每个新进程它首次被执行的过程必然是: sys_fork---->do_for

Linux内核源码学习之 基本知识

GNOME GNOME是一种让使用者容易操作和设定电脑环境的工具,GNOME 包含了 Panel (用来启动此程式和显示目前的状态).桌面(应用程式和资料放置的地方).及一系列的标准桌面工具和应用程式,并且能让各个应用程式都能正常地运作.不管之前使用何种操作系统,都能轻易地使用 GNOME 功能强大的图形接口工具. KDE KDE,K桌面环境(KoolDesktop Environment)的缩写.一种著名的运行于 Linux.Unix 以及FreeBSD 等操作系统上面自由图形工作环境,整个系

Linux内核源码学习之僵尸进程

孤儿进程和僵尸进程 正常的子进程fork其父进程后,二者建立父子关系. 当子进程终结时,它会通知父进程,并清空自己所占据的内存,并在kernel里留下自己的退出信息(exit code,如果顺利运行,为0:如果有错误或异常状况,为>0的整数).在这个信息里,会解释该进程为什么退出.父进程在得知子进程终结时,有责任对该子进程使用wait系统调用.这个wait函数能从kernel中取出子进程的退出信息,并清空该信息在kernel中所占据的空间.这是正常的一般情况. 如果父进程早于子进程终结,子进程就

Linux内核源码学习之fork的缓冲区

上面的代码看上去很简单,子子孙孙fork就是了,基本上符合我们的一般的猜想,逻辑上很正正确,但是要说的是: 为什么同样的代码运行的时候得到的输出会有不同? ./fork执行完成之后很正常回到了shell,但是右边却没有回到shell,这是为什么呢? 这和fork的性质[fork之后并不能确定究竟是哪个进程首先执行相关] 左边的情况是:最后一个进程4742执行完之后,父进程还是没有结束的.然后父进程结束,回到父进程的父进程也就是shell 右边的情况是:在输出[email protected]等等

转载 :Linux有问必答:如何在Debian或Ubuntu上安装完整的内核源码

http://linux.cn/article-5015-1.html 问题:我需要为我的Debian或Ubuntu下载并安装完整树结构的内核源码以供编译一个定制的内核.那么在Debian或Ubuntu上有什么可行的方法来下载完整的内核源码呢? 在给你的Linux安装完整内核源码之前,先问问自己是否真的需要这样做.如果你仅仅是尝试去编译一个内核模块或是为内核定制驱动,你并不需要完整的内核源码树.你只需要安装一些与内核对应的头文件,这样就足够了. 只有在你需要生成一个定制的内核,而且内核源码中的一

轻松学习linux内核源码的方法

轻松学习Linux操作系统内核源码的方法 针对好多Linux 爱好者对内核很有兴趣却无从下口,本文旨在介绍一种解读linux内核源码的入门方法,而不是解说linux复杂的内核机制:一.核心源程序的文件组织:1.Linux核心源程序通常都安装在/usr/src/linux下,而且它有一个非常简单的编号约定:任何偶数的核心(例如2.0.30)都是一个稳定地发行的核心,而任何奇数的核心(例如2.1.42)都是一个开发中的核心. 本文基于稳定的2.2.5源代码,第二部分的实现平台为 RedHat Lin

Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7)【转】

原文地址:Linux内核源码分析--内核启动之(5)Image内核启动(rest_init函数)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938395.html 前面粗略分析start_kernel函数,此函数中基本上是对内存管理和各子系统的数据结构初始化.在内核初始化函数start_kernel执行到最后,就是调用rest_init函数,这个函数的主要使命就是创建并启动内核线

Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 【转】

原文地址:Linux内核源码分析--内核启动之(3)Image内核启动(C语言部分)(Linux-3.0 ARMv7) 作者:tekkamanninja 转自:http://blog.chinaunix.net/uid-25909619-id-4938390.html 在构架相关的汇编代码运行完之后,程序跳入了构架无关的内核C语言代码:init/main.c中的start_kernel函数,在这个函数中Linux内核开始真正进入初始化阶段, 下面我就顺这代码逐个函数的解释,但是这里并不会过于深入