深入理解linux内核:深入理解Linux内存映射机制

. 绪 论
2. X86硬件寻址思路方法
3. 内核对页表设置
4. 例子分析映射机制

. 绪 论
我们经常在反汇编代码中看到些类似0x32118965这样地址操作系统中称为线性地址或虚拟地址虚拟地址有什么用?虚拟地址又是如何转换为物理内存地址呢?本章将对此作个简要阐述
1.1 Linux内存寻址概述
现代意义上操作系统都处于32位保护模式下每个进程般都能寻址4G物理空间但是我们物理内存般都是几百M进程如何能获得4G物理空间呢?这就是使用了虚拟地址好处通常我们使用种叫做虚拟内存技术来实现可以使用硬盘中部分来当作内存使用例外点现在操作系统都划分为系统空间和用户空间使用虚拟地址可以很好保护内核空间被用户空间破坏
对于虚拟地址如何转为物理地址,这个转换过程有操作系统和CPU共同完成. 操作系统为CPU设置好页表CPU通过MMU单元进行地址转换
1.2 浏览内核代码工具
现在内核都很大 因此我们需要某种工具来阅读庞大源代码体系现在内核开发工具都选用vim ctag cscope浏览内核代码网上已有现成makefile文件用来生成ctags/cscope/etags
、使用方法:
个空目录把附件Makefile拷贝进去然后在该目录中选择性地运行如下make命令:
$ make
将处理/usr/src/linux下源文件在当前目录生成ctags, cscope
注:SRCDIR用来指定内核源代码目录如果没有指定则缺省为/usr/src/linux/
1) 只创建ctags
$ make SRCDIR=/usr/src/linux-2.6.12/ tags
2) 只创建cscope
$ make SRCDIR=/usr/src/linux-2.6.12/ cscope
3) 创建ctags和cscope
$ make SRCDIR=/usr/src/linux-2.6.12/
4) 只创建etags
$ make SRCDIR=/usr/src/linux-2.6.12/ TAGS

2、处理时包括内核源文件:
1) 不包括driverssound目录
2) 不包括无关体系结构目录
3) fs目录只包括顶层目录和ext2proc目录

3、最简单ctags命令
1) 进入
进入vim后
:tag func_name
跳到func_name
2) 看(identier)
想进入光标所在
CTRL ]
3) 回退
回退用 CTRL T

1.3 内核版本选取
本次论文分析 我选取是linux-2.6.10版本内核最新内核代码为2.6.25但是现在主流服务器都使用是RedHat AS4机器它使用2.6.9内核我选取2.6.10是它很接近2.6.9现在红帽企业Linux 4以Linux2.6.9内核为基础是最稳定、最强大商业产品在2004年期间Fedora等开源项目为Linux 2.6内核技术更加成熟提供了个环境这使得红帽企业 Linux v.4内核可以提供比以前版本更多更好

功能和算法具体包括:
• 通用逻辑CPU调度:处理多内核和超线程CPU
• 基于对象逆向映射虚拟内存:提高了内存受限系统性能
• 读复制更新:针对操作系统数据结构SMP算法优化
• 多I/O调度:可根据应用环境进行选择
• 增强SMP和NUMA支持:提高了大型服务器性能和可扩展性
• 网络中断缓和(NAPI):提高了大流量网络性能
Linux 2.6 内核使用了许多技术来改进对大量内存使用使得 Linux 比以往任何时候都更适用于企业包括反向映射(reverse mapping)、使用更大内存页、页表条目存储在高端内存中以及更稳定管理器因此我选取linux-2.6.10内核版本作为分析对象



2. X86硬件寻址思路方法
请参考Intel x86手册^_^



3. 内核对页表设置
CPU做出映射前提是操作系统要为其准备好内核页表而对于页表设置内核在系统启动初期和系统化完成后都分别进行了设置
3.1 和内存映射相关几个宏
这几个宏把无符号整数转换成对应类型
# __pte(x) ((pte_t) { (x) } )
# __pmd(x) ((pmd_t) { (x) } )
# __pgd(x) ((pgd_t) { (x) } )
# __pgprot(x) ((pgprot_t) { (x) } )

根据x把它转换成对应无符号整数
# pte_val(x) ((x).pte_low)
# pmd_val(x) ((x).pmd)
# pgd_val(x) ((x).pgd)
# pgprot_val(x) ((x).pgprot)

把内核空间线性地址转换为物理地址
# __pa(x) ((unsigned long)(x)-PAGE_OFFSET)

把物理地址转化为线性地址
# __va(x) ((void *)((unsigned long)(x) PAGE_OFFSET))

x是页表项值 通过pte_pfn得到其对应物理页框号 最后通过pfn_to_page得到对应物理页描述符
# pte_page(x) pfn_to_page(pte_pfn(x))

如果对应表项值为0 返回1
# pte_none(x) (!(x).pte_low)

x是页表项值 右移12位后得到其对应物理页框号
# pte_pfn(x) ((unsigned long)(((x).pte_low >> PAGE_SHIFT)))
根据页框号和页表项属性值合并成个页表项值
# pfn_pte(pfn, prot) __pte(((pfn) << PAGE_SHIFT) | pgprot_val(prot))

根据页框号和页表项属性值合并成个中间表项值
# pfn_pmd(pfn, prot) __pmd(((pfn) << PAGE_SHIFT) | pgprot_val(prot))

个表项中写入指定
# _pte(pteptr, pteval) (*(pteptr) = pteval)
# _pte_atomic(pteptr, pteval) _pte(pteptr,pteval)
# _pmd(pmdptr, pmdval) (*(pmdptr) = pmdval)
# _pgd(pgdptr, pgdval) (*(pgdptr) = pgdval)

根据线性地址得到高10位值 也就是在目录表中索引
# pgd_index(address) (((address)>>PGDIR_SHIFT) & (PTRS_PER_PGD-1))

根据页描述符和属性得到个页表项值
# mk_pte(page, pgprot) pfn_pte(page_to_pfn(page), (pgprot))
3.2内核页表
内核在进入保护模式前 还没有启用分页功能 在这的前内核要先建立个临时内核
Tags:  linux端口映射 linux映射 深入理解linux 深入理解linux内核

延伸阅读

最新评论

发表评论