Linux内存管理之地址映射
写在前面:由于地址映射涉及到各种寄存器的设置访问,Linux对于不同体系结构处理器的地址映射采用不同的方法,例如对于i386及后来的32位的Intel的处理器在页式映射时采用的是2级页表映射,而对于IA64的处理器则采用3级分页。对于其他类型的处理器,例如MK68000等其他许多处理器,在地址映射时则忽略了段式映射,只是因为Intel的X86系列需要兼容早期的段式映射,才在后来的设计中即使用了段式映射,也采用了页式映射。以后关于Linux的笔记,除特别说明外,均是在i386体系结构之上,笔记中所有源码除特别说明外均摘自linux-2.4.0源码树。
现代操作系统在内存管理上均使用高效的页式管理,Linux也不例外。对于i386处理器则有些例外,为了兼容早期的处理器,Intel强制要求必须先经过段式映射。在地址映射时,虚拟地址被划分成固定的页面大小,由MMU将虚拟地址映射到实际的物理地址。在访问一个虚拟地址表示的内存空间中,CPU必须经过若干次的内存访问才能完成映射,具体访问次数为N+1(N为页表级数),同时还需要N次加法运算。
在Linux进行段式映射和页式映射之前,需要搞清楚X86系列的地址描述方式:
0)逻辑地址:出现在机器指令中,用来制定操作数的地址。
1)线性地址:逻辑地址经过分段单元处理后得到线性地址,这是一个32位的无符号整数,可用于定位4G个存储单元。
#define start_thread(regs, new_eip, new_esp) do { \ __asm__("movl %0,%%fs ; movl %0,%%gs": :"r" (0));\ set_fs(USER_DS); \ regs->xds = __USER_DS; \ regs->xes = __USER_DS; \ regs->xss = __USER_DS; \ regs->xcs = __USER_CS; \ regs->eip = new_eip; \ regs->esp = new_esp; \ } while (0)
#ifndef _ASM_SEGMENT_H #define _ASM_SEGMENT_H #define __KERNEL_CS 0x10 #define __KERNEL_DS 0x18 #define __USER_CS 0x23 #define __USER_DS 0x2B #endif
ENTRY(gdt_table) .quad 0x0000000000000000 /* NULL descriptor */ .quad 0x0000000000000000 /* not used */ .quad 0x00cf9a000000ffff /* 0x10 kernel 4GB code at 0x00000000 */ .quad 0x00cf92000000ffff /* 0x18 kernel 4GB data at 0x00000000 */ .quad 0x00cffa000000ffff /* 0x23 user 4GB code at 0x00000000 */ .quad 0x00cff2000000ffff /* 0x2b user 4GB data at 0x00000000 */ .quad 0x0000000000000000 /* not used */ .quad 0x0000000000000000 /* not used */ /* * The APM segments have byte granularity and their bases * and limits are set at run time. */ .quad 0x0040920000000000 /* 0x40 APM set up for bad BIOS's */ .quad 0x00409a0000000000 /* 0x48 APM CS code */ .quad 0x00009a0000000000 /* 0x50 APM CS 16 code (16 bit) */ .quad 0x0040920000000000 /* 0x58 APM DS data */ .fill NR_CPUS*4,8,0 /* space for TSS's and LDT's */
static inline void switch_mm(struct mm_struct *prev, struct mm_struct *next, struct task_struct *tsk, unsigned cpu) { if (prev != next) { /* stop flush ipis for the previous mm */ clear_bit(cpu, &prev->cpu_vm_mask); /* * Re-load LDT if necessary */ if (prev->context.segments != next->context.segments) load_LDT(next); #ifdef CONFIG_SMP cpu_tlbstate[cpu].state = TLBSTATE_OK; cpu_tlbstate[cpu].active_mm = next; #endif set_bit(cpu, &next->cpu_vm_mask); /* Re-load page tables */ asm volatile("movl %0,%%cr3": :"r" (__pa(next->pgd))); } #ifdef CONFIG_SMP else { cpu_tlbstate[cpu].state = TLBSTATE_OK; if(cpu_tlbstate[cpu].active_mm != next) BUG(); if(!test_and_set_bit(cpu, &next->cpu_vm_mask)) { /* We were in lazy tlb mode and leave_mm disabled * tlb flush IPI delivery. We must flush our tlb. */ local_flush_tlb(); } } #endif } #define activate_mm(prev, next) \ switch_mm((prev),(next),NULL,smp_processor_id()) #endif
重点关注其中的asm volatile("movl %0,%%cr3": :"r" (__pa(next->pgd)));它实现的功能即为将页目录指针读入CR3寄存器中。通过线性地址的最高10位可以从页面目录中知道具体的目录项,在找到进程的目录项之后,在目录项中,高20位指向页面表,在得到页面表之后,CPU再从线性地址的中间10位得到页表中的表项。在32位处理器上页表中的高20位指向物理内存的初始地址,在其后添加12个0,然后加上线性地址中的低12位(即为线性地址中的偏移量),这样就得到了一个具体的物理地址了。
在地址映射这个问题上,内核只提供页表,实际的转换是由硬件去完成的。那么内核如何生成这些页表呢?这就有两方面的内容,虚拟地址空间的管理和物理内存的管理。实际上只有用户态的地址映射才需要管理,内核态的地址映射是写死的即为[0xC000 0000] (3 GB)到[0xFFFF FFFF] (4 GB)。在这一部分中,内核要实现的一个重要功能就是通过高速缓存来提高查找速度。
参考资料:
(0)http://book.douban.com/subject/1231584/
(1)http://hi.baidu.com/_kouu/item/4c73532902a05299b73263d0
(2)http://docs.huihoo.com/joyfire.net/3-1.html
一些近况
0)上周拿到了正式工作半个月的工资,钱不算太多,但花着自己的钱心里舒坦。
1)新工作是做一些底层的东西,所以可以名正言顺的在工作中使用linux了,每天上班开机后首先C-A-e打开emacs,写下一天的ToDoList是件很开心的事^_^。
2)买了好多书,有自己最期待的《哥德尔、艾舍尔、巴赫》,看了一部分,真的很好,很喜欢。
3)新工作要求各种系统编程语言通吃,老板希望懂的越多越好,所以又把丢了好久的C++捡起来了。
4)和一个台湾的上司打交道,感觉台湾人很礼貌。
5)感情很顺利,和hxx已经到了谈婚论嫁的地步。
6)新租的房子环境很好,对面有坐小山加个漂亮的花园,离海边也挺近,不过每天下班回去的晚,偶尔才会去散步。
7)养成了每天早上爬楼梯的习惯(15层哦),每天早上穿着短袖+短裤+拖鞋提着麦当劳的早餐爬楼梯都能遇到打扫卫生的大爷,我们俨然已经有了默契。
8)刚接触新工作,一直想系统学习一下mathematica也不得不丢开了,因为工作了实在很少能用上,虽然一直对其抱有极其浓厚的兴趣,早晚会再捡起来的。
9)在hxx的影响下越来越喜欢古典音乐了,特别想听她给我拉《巴赫无伴奏大提琴BWV1007号》。
10)最近每天在练习英语听力和口语,不过进步不大,我觉得需要改善方法。
11)每天会抽出1个小时的时间玩儿分形,工作累了,奇妙的分形几何总能让人愉悦。
好了,最近一切都不错,心态很平和。“我是个年轻人,我心情很不错”,哈哈这是在和阿澜.卢作对:)
祖国,或以梦为马(海子)
我要做远方的忠诚的儿子
和物质的短暂情人
和所有以梦为马的诗人一样
我不得不和烈士和小丑走在同一道路上
万人都要将火熄灭 我一人独将此火高高举起
此火为大 开花落英于神圣的祖国
和所有以梦为马的诗人一样
我借此火得度一生的茫茫黑夜
此火为大 祖国的语言和乱石投筑的梁山城寨
以梦为土的敦煌--那七月也会寒冷的骨骼
如雪白的柴和坚硬的条条白雪 横放在众神之山
和所有以梦为马的诗人一样
我投入此火 这三者是囚禁我的灯盏 吐出光辉
万人都要从我刀口走过 去建筑祖国的语言
我甘愿一切从头开始
和所有以梦为马的诗人一样
我也愿将牢底坐穿
众神创造物中只有我最易朽 带着不可抗拒的 死亡的速度
只有粮食是我珍爱 我将她紧紧抱住 抱住她 在故乡生儿育女
和所有以梦为马的诗人一样
我也愿将自己埋葬在四周高高的山上 守望平静的家园
面对大河我无限惭愧
我年华虚度 空有一身疲倦
和所有以梦为马的诗人一样
岁月易逝 一滴不剩 水滴中有一匹马儿一命 归天
千年后如若我再生于祖国的河岸
千年后我再次拥有中国的稻田 和周天子的雪山 天马踢踏
和所有以梦为马的诗人一样
我选择永恒的事业
我的事业 就是要成为太阳的一生
他从古至今--"日"--他无比辉煌无比光明
和所有以梦为马的诗人一样
最后我被黄昏的众神抬入不朽的太阳
太阳是我的名字
太阳是我的一生
太阳的山顶埋葬 诗歌的尸体--千年王国和我
骑着五千年凤凰和名字叫"马"的龙--我必将失败
但诗歌本身以太阳必将胜利
那些年,我听过的张楚
《西出阳关》录制那年我还没有出生,第一次听《姐姐》那年,他早已摆脱“魔岩三杰”的称号消声匿迹。
最早听说张楚是先知道了许巍,那时许巍名气大,沙哑的嗓音总能透出春天般的温暖,一个人背着一把吉他走天涯,后来知道了这叫摇滚,再后来张楚,郑钧,窦唯,何勇,唐朝,姜昕,筠子就自然而然的了解了,中国摇滚之父崔健也了解一二。我知道我错过了中国摇滚的黄金年代,但我却不可抑制的喜欢上了这种风格,所以我不知疲倦一遍一遍的听着,那年我17岁,只是一个没见过什么世面的少年。
后来我去了许巍,张楚和郑钧都待过的城市念书,从那时起,我便真正喜欢上了张楚,这个嗓音浑厚充满苍凉,可以穿着破乱背心在不起眼的角落拨动吉他嘶声力竭的唱着《姐姐》的男人。第一次听《姐姐》,最初的那段旋律就瞬间让我沉醉其中,空灵而悲伤,接着是一个浑厚的嗓音,唱出“这个冬天雪还不下...”,那么的陶醉,那么的深情,这是他真实感情的流露,他想反抗,但他还太弱小,他想保护姐姐,但最终还是姐姐拉着他的手。听完《姐姐》,我便不可抑制的喜欢上了他,我听了他的《一颗不肯媚俗的心》,《孤独的人是可耻的》,《造飞机的工厂》这三张专辑。在《一颗不肯媚俗的心》中,张楚完全是以一个叛逆者的姿态来审视自我,阅读历史和传统,他的声音氤氲在了西北大漠的苍凉之中,《西出阳关》中一句“我读不出方向,读不出时光,读不出是否最后一定是死亡”,在无尽的时空中一语道破,直指历史直指自我。真正对我影响最大的三首歌都来自后面两张专辑,《孤独的人是可耻的》中收录的同名歌曲《孤独的人是可耻的》和《爱情》,以及《造飞机的工厂》中收录的《结婚》。听《孤独的人是可耻的》那时我喜欢着我的高中同学,那年暑假我先是独自去拜访了在成都的两个朋友,离开成都前打听到她还留在武汉那边,我壮着胆子忐忑不安的给她发了短信告诉她我想来武汉那边找她,她欣然接受。记得她去车站接我的那天,太阳很大,她撑着浅色的伞,站在太阳底下,脸红红的,那时我们已经有一年多没见面了,随便寒暄了几句便混熟了,就这样所谓的初恋就在《孤独的人是可耻的》的歌声中开始了,因为“这是一个恋爱的季节...”。真正听着《爱情》的时候大概是在年初,那时初恋早已消湮在岁月的痕迹中,而且我刚刚结束了第二次成都之行,还处于旅行后的恢复期。偶然的不经意我遇到了我现在的女朋友,我们相见恨晚,我们谈天说地,她总能跟上我的节奏,我们开了张居正的玩笑,我们住了“花儿”的青年旅馆,我们相隔万里听着同样的广播,我们争吵,我们拥抱,我们相互崇拜,我们相互赞赏,我们有相同的梦想,那是一个愉快的季节。真正听懂《结婚》应该就是今天,这离我们最近的一次争吵刚过去了3天,而当我一大早突然听到了张楚的歌声时,我包容了你的一切,也开始认真的审视自我。你说爱你你就满足了,而我却想要的更多,你说假入不能结婚我们也要一辈子这样,我想着就信了,我将所有的过去都抛进了泥土和梦里。
今天索性将张楚的歌又听了个够,再听《冷暖自知》,再听《赵小姐》,再听《蚂蚁蚂蚁》,一切的一切都成了回忆。那些年,我听过的张楚,那些年,我懂得的爱情。