4.3 启动函数start系统启动的具体过程在m40.s中的start函数中实现:1. .globl start, _end, _edata, _main2. start:3. bit $1,SSR04. bne start / loop if restart5. reset 6. / initialize systems segments7. mov $KISA0,r0 8. mov $KISD0,r19. mov $200,r410. clr r211. mov $6,r3 12. 1:13. mov r2,(r0)+ 14. mov $77406,(r1)+ / comment. 4k rw15. add r4,r216. sob r3,1b17. / initialize user segment18. mov $_end+63.,r219. ash $-6,r220. bic $!1777,r221. mov r2,(r0)+ / ksr = sysu22. mov $USIZE-1\8|6,(r1)+23. / initialize io segment24. / set up counts on supervisor segments25. mov $IO,(r0)+ 26. mov $77406,(r1)+ / rw 4k27. / get a sp and start segmentation28. mov $_u+[USIZE*64.],sp29. inc SSR0 30. / clear bss31. mov$_edata,r032. 1:33. clr (r0)+34. cmp r0,$_end 35. blo 1b36. / clear user block37. mov $_u,r038. 1:39. clr (r0)+40. cmp r0,$_u+[USIZE*64.]41 blo 1b42. / set up previous mode and call main43. / on return, enter user mode at 0R44. mov $30000,PS45. jsr pc,_main46. mov $170000,-(sp)47. clr -(sp)48. rtt m40.s的末尾是一些全局变量定义:/* -------------------------*/ .globl _u _u = 140000 //u变量地址固定在0o140000处 USIZE= 16. //u变量和内核栈大小是16个内存块(16*64=1024字节) PS= 177776 //PSW寄存器地址 SSR0= 177572 SSR2= 177576 KISA0= 172340 //0号内核页地址寄存器地址 KISA6= 172354 //6号内核页地址寄存器地址 KISD0= 172300 //0号内核页描述寄存器地址 MTC= 172522 UISA0= 177640 //0号用户页地址寄存器地址 UISA1= 177642 //1号用户页地址寄存器地址 UISD0= 177600 //0号用户页描述寄存器地址 UISD1= 177602 //1号用户页描述寄存器地址 IO = 7600 .data //已初始化的全局变量 /* -------------------------*/ .globl _ka6, _cputype _ka6:KISA6 _cputype:40. //CPU类型是PDP 11/40 .bss //未初始化的全局变量 /* -------------------------*/ .globl nofault, ssr, badtrap nofault:.=.+2 //变量nofault,且占用2个字节 ssr:.=.+6 //变量ssr,且占用6个字节,实际是数组int ssr[3] badtrap:.=.+2 //变量badtrap,且占用2个字节这里我们先了解一下内存管理单元(MMU)相关寄存器的分布,如图4-4所示,它们位于最高物理地址空间248K~256K。虽然在UNIX代码中所有的寄存器地址值都是位于56K~64K之间,但PDP 11芯片会把16位物理内存的最高8K地址空间(56K~64K)自动映射到18位最高8K地址空间(248K~256K)。500)this.width=500;" border="0">图4-4 MMU相关寄存器start函数首先测试内存管理单元(MMU)状态寄存器SSR0的“启动位”(最低位)。SSR0寄存器地址是0o177572,转换成10进制为65402。第2~4行转成C语言是:while(*((int *)SR0)&1);如果检测到MMU启动(SR0的最低位是1),则继续检测,直至MMU未启动(SR0的最低位是0),退出循环。为什么需要这样的逻辑?因为在操作员启动系统时,他通常会触发终端的“清除”按钮,从而把SR0寄存器清成0。所以,在正常启动时,MMU应该是被关闭的。另外,最重要的一点是:在双总线超时错的情况下,CPU将转移到地址0处执行,也就会跳转到start函数中执行,而这时MMU是被打开的。所以,在系统启动时,如果MMU打开,表明系统已出错,程序则不应该继续执行(当然,如果这时再判断SSR0的其他出错位来进一步确定系统是否出错,就更加严密了,但是为简洁起见,当时并没有这样做)。循环后紧跟的reset指令复位所有总线设备(当然包括I/O设备)及相关寄存器。现在,系统运行在内核模式,而且内存管理单元未启动。也就是说,现在所有的地址引用都是物理地址。从第7~16行设定0~5号内核活动页寄存器(Kernel Active Page Register)。第13行设定内核页地址寄存器的值,从而指定页起始物理内存块号为r2。14行设定内核页描述寄存器的值,ED=0,PLF=127,ACF=11,从而指定页朝高地址方向自动延伸,页大小为8K字节(注释是指4K字),页操作权限为“读写”。注意第9行赋r4的值为8进制数200,等于10进制数128。所以在第21行把物理块号r2增加128,作为下一个页地址寄存器的值(128*64 = 8K)。注意第16行的sob指令,它自减计数器r3,若不为0则跳转,类似于do{}while(--i)。关于页地址/描述寄存器的具体描述,请参照2.1.4节。第18~22行设定第6号内核活动页寄存器的值(想想为什么)。第18~19行设定r2为_end下一个紧邻的物理块号(64字节边界,第19行右移r2寄存器6位),等效于C语法:r2= (_end+63)/64。这里_end指向bss段的结尾,也是整个UNIX启动时,系统程序数据空间的结尾地址。也就是说,UNIX程序数据空间的最后一个字节是_end-1,记为_end_vaild,则_end=_end_valid+1,所以r2=(_end_valid+1+63)/64=_end_valid/64+1,这样r2就指向了内存中最后一个有效数据区的下一个紧邻物理块。第20行保留r2的低10位,这样使r2 ,从而使r2指向的物理块位于64K地址空间内。第21行设定6号内核页地址寄存器的值;第22行设定6号内核页描述寄存器的值:PLF=USIZE(16)-1,ED=0,ACF=11。从而该页大小为USIZE*64=1024字节,“读写”权限。它用来存放进程u变量和进程内核栈空间。第25~26行设定7号内核页地址寄存器和页描述寄存器的值。其中,页地址寄存器的值为IO,IO = 7600(八进制,等于十进制3968,在m40.s文件末定义),从而它所对应的物理地址为:3968*64=248K。页描述寄存器值77406(8进制)表示该页长度为8K字节,“读写”权限。至此,8个内核活动页寄存器已经全部设定完毕,64K内核虚拟空间中的57K都已经映射到物理内存(第6号活动页寄存器只映射了1K)。系统启动后的内核虚拟地址空间映射图如图4-5所示。图4-5中虚线标识的虚拟空间和物理空间并未映射。而虚存中的[48K,49K]空间被映射到[_end, _end+1K],并作为内核栈,这从第28行可以看出来。_u引用C程序中的全局变量u,它在.c第行定义(struct user{…}u),该结构变量所占空间为1K字节,该变量被分配到固定地址0o140000(在m40.s文件末定义),可以知道它是属于6号活动页寄存器KISA6/KISD6空间。第29行启动内存管理单元,从这一步开始,下面所有的地址引用(包括指令地址)都被解释成为虚拟地址,它首先被内存管理单元翻译成物理地址,再进行访问。这里有一个问题,既然6号活动页寄存器被设置为u变量和内核栈空间,那为什么它的页描述寄存器的ED不设置成为1呢?因为栈空间的该位通常都是设成1、以使空间自动向上增长,而且UNIX在设置用户栈空间的时候也是这样做的。那是因为进程的内核栈空间通常提供给中断服务函数和系统调用使用,而它们所使用的栈空间是很有限的,除去u变量的289字节,还有1024-289=735字节作为栈空间,这已经足够,所以内核栈空间不需要自动增长功能。第31~35行清除bss段,即设置所有未初始化的全局变量为0。注意这里的_edata和_end都是伪变量,由编译器生成,分别指向data结束(bss段的起始)和bss段的结束地址。UNIX程序大小(0~_end)大概为29K。上一章 虚拟内存 目录 下一章 进程管理和调度本书在全国各大书店及网城均有销售:亚马逊 China pub上学吧 1号店 11-23 06:14