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个存储单元。

2)物理地址:线性地址经过页表查找后得出物理地址,这个地址将被送到地址总线上指示所要访问的物理内存单元。
 
段式映射即为将逻辑地址与线性地址映射起来,而页式映射则为将线性地址和物理地址对应起来。
 
段式映射阶段:i386CPU选择代码段寄存器CS的当前值作为段描述符表中的下标,段式寄存器的第2位为0时使用GDT,为1时使用LDT。Intel设计为内核使用GDT,各个进程使用自己的LDT,寄存器的最低两位表示特权级别。在Linux内核中其实只使用GDT,在4个权限级别中只是用了0代表kernel级,3代表用户级。内核在创建一个新进程时都会先设定其段寄存器,对i386处理器中,段寄存器的设置代码位于include/asm-i386/processor.h
#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)
 
从代码可以看出,Linux将i386处理器的DS,ES,SS寄存器均设置为USER_DS,这表示在Linux中对于进程的代码段,数据段和堆栈段是不区分的。__USER_CS和__USER_DS的设置位于include/asm-i386/segment.h
#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
 
由以上代码可以看出,CS寄存器中的内容是0x23,通过段寄存器各位的含义可知,CPU以4作为下标,从全局描述符表GDT中寻找段描述选项,GDT的内容在arch/i386/kernel/head.S中定义
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 */

 

到此段式映射已经完成,实际上对于Linux的页式映射来说这一步完全没用但又不得不做。通过段映射,进程的逻辑地址已经映射到了线性地址,但实际上通过段描述符的意义来看,二者是相同的。
 
页式映射阶段:在页式存储中,每个进程都有其自身的PGD,指向PGD的指针存在进程的mm_struct中,每当进程运行的时候,内核需要设定好控制寄存器CR3,MMU是从CR3中取得页面目录指针的。我们知道CPU在执行程序时使用的是虚拟的地址,而MMU硬件在映射时使用的是实际的物理内存地址,其具体实现是由include/asm-i386/mmu_context.h中的函数实现
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

(3)http://baike.baidu.com/view/3289301.htm

(4)http://www.eefocus.com/article/09-06/74736s.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天,而当我一大早突然听到了张楚的歌声时,我包容了你的一切,也开始认真的审视自我。你说爱你你就满足了,而我却想要的更多,你说假入不能结婚我们也要一辈子这样,我想着就信了,我将所有的过去都抛进了泥土和梦里。

今天索性将张楚的歌又听了个够,再听《冷暖自知》,再听《赵小姐》,再听《蚂蚁蚂蚁》,一切的一切都成了回忆。那些年,我听过的张楚,那些年,我懂得的爱情。

 

 

闲情

五月一日,我坐了48小时的火车从深圳来到兰州,去看望HXX,我和H的相识相知相爱实属偶然,期间充满了许多有趣的随机事件。初见H,她穿着深蓝色的T恤,浅蓝色短裤,黑色丝袜加高帮帆布鞋背着她漂亮的双肩书包,她高挑,初见时如小家碧玉,后来深入了才知道她原来是个女流氓。

在兰州和H一起的日子总得来说很愉快,我们更加了解对方了,我看她也不会心跳加速了。走在路上我们敢于公然勾肩搭背,想入非非。我们可以大声讨论当代中国年轻人的生活态度,然后在看到一个成人用品商店后,迅速将话题转换到两性的秘密。HXX喜欢音乐,可以很专业的演奏大提琴,但我一直没亲眼见她拉过琴,因为我不敢去她家,虽然她邀请了我很多次。在此期间,我们之间发生了一次不愉快,那天我们本来按原计划准备爬山,在山脚下时我随她意去了动物园,在看那些动物时我感到特别压抑,于是就抱怨说我讨厌人,然后就挺不高兴的,扔下她一人先走了,最后她在路上大哭了起来,朝我咆哮说那些动物又不是她关起来的,我为什么这样对她,然后我们相拥而泣,回到住的地方,我们放肆的躺在床上,有人进来也丝毫不理会,各自舔舐着受伤的心灵。

五月十一日晚上我离开兰州去了敦煌,HXX送我到火车站,叮嘱我早点回去,这是今年最后相聚的日子了,我满口答应,然后抱着她吻了一下,问她“爽不爽?”结果她大声嘲笑我说“她爽点怎么可能这么低。”哈哈,我被“爽点”这个词逗乐了。在去敦煌的火车上,我们聊了会天,后来才听说原来十二号晚上青海发了地震,甘肃遇到冰雹灾害,但是我们什么都不知道,我们沉醉在了自己的世界里。

我和HXX有一个4年后的间隔年计划,我们都觉得这是我们人生必须的一部分,虽然她妈妈看似很顽固,而我母亲好像也不是那么好说话,但我们都觉得问题不大。我们都是随性之人,爱憎分明,同时偶尔会很伤感,我们喜欢用与众不同的眼光看待世界。

现在我正坐在敦煌一个露天的庭院里,远方是广袤的沙漠,抬头是蔚蓝的天空,偶尔还能听到不知名的鸟叫声,我大口吃着牛肉面喝着啤酒,听着古筝,好不惬意。

                                                               2012年5月12日记于敦煌

本周计划

0)修改毕设论文

1)读完http://book.douban.com/subject/6865092/

2)读读勒庞的《乌合之众》

3)读读冯唐的《不二》,看看他到底二不二

4)买好去兰州的车票

 

我爱这土地

原来,我也写过这样的

假如我是一只鸟,
我也应该用嘶哑的喉咙歌唱:
这被暴风雨所打击着的土地,
这永远汹涌着我们的悲愤的河流,
这无止息地吹刮着的激怒的风,
和那来自林间的无比温柔的黎明……

然后我死了
连羽毛也腐烂在土地里面。
为什么我的眼里常含泪水?
因为我对这土地爱得深沉……
--《我爱这土地》(艾青)
----------------------------
艾青先生在1938年11月17日写下了这首诗,当时祖国内忧外患,诗人写下了这样发自肺腑的文字,而此时那句“为什么我的眼里常含泪水? 因为我对这土地爱得深沉…… ”又被许多人挖了出来。
7月23日的事件大家都了解的很清楚,记得第二天晚上我喝了点酒,回来看到一些照片后,眼中真的噙着泪水,毫无做作,只是发自内心的呼喊,当时看到许多朋友都很激动,大家在这个虚伪好面子的政府面前无能为力,有人骂它,有人咒它…最后事件的结果我也懒得再细说了,总之,很多人哭了,因为他们爱这土地爱得深沉。
以前看过韩寒的一个采访,他告诉记者,无论他多么讨厌憎恨这个政府,他永远不会离开这个国家,除非政府驱逐他,因为这里有他最喜欢的人。当时,我没什么感觉,而我至723事件前的一个愿望还是迫切希望自己可以走出去,离开这个政府统治下的土地,或许我不知道一个普通的中国人在海外是什么遭遇,会不会比在这片带有阴影的土地过得更好,但正是因为这未知性而让自己又增添了许多冲动。但723之后,在刚开始的很短的时间内,我想要逃离这片土地的心情出现了从未有过的强烈,我在内心对自己说,我一定要离开这肮脏的鬼地方,我一定不能让我的孩子生活在这样的鬼地方,我希望自己可以生活在一片可以自由呼吸的土地上。现在,我突然哪儿也不想走了,我就想待在这里,虽然她被那群龌龊的人搞脏了,她的名声被那群眼不见为净的人搞臭了,虽然我的力量很小很小,但至少我不会让她变得更加恶心,哪怕我的正面效应微小的忽略不计,但我还是要留下来,哪怕这一生是平凡的一辈子,其实这个世界本就是平凡的。
我不能也不打算像那些伟大的人物一样去改变别人,影响这个社会,我只想做个好人,我爱这片土地。记得有人调笑说:“为什么我的眼中常含泪水,因为我的祖国总是丢人。”而我只想告诉自己:“为什么我的眼中会噙着泪水,因为我对这片土地爱得深沉!”