第281行,用关键字word指定了标号pgdt处的一个字数据,将其零扩展传送到寄存器EBX中。虽然段界限是16位的,允许64KB的大小,即8192个描述符,似乎不需要使用32位的寄存器EBX。事实上,还是需要的,因为后面要用它来计算新描述符的32位线性地址,加法指令add要求的是两个32位操作数。第282行,将GDT的界限值加1,就是GDT的总字节数,也是新描述符在GDT内的偏移量。这里用的inc bx,而不是inc ebx,这是有原因的。
一般情况来说,在这里用这两条指令的哪一条,都没有问题。但是,如果这是启动计算机以来,第一次在GDT中安装描述符,可能就会产生问题。在初始状态下,也就是计算机启动之后,这是还没有使用GDT,GDTR寄存器中的基地址为0x00000000,界限为0xFFFF。
当GDTR寄存器的界限部分是0xFFFF时,表明GDT中还没有描述符。因此,将此指加1,结果是0x10000,由于该寄存器的界限分只有16位,所以只能容纳16位的结果,即0x0000,这就是第一个描述符在表内的偏移量。同样的道理,因为EBX寄存器中的内容是GDT的界限值0x0000FFFF,如果执行的是指令inc ebx,那么EBX寄存器中的内容将使0x00010000,以它作为第一个描述符的偏移量显然是不对的。相反,如果执行的是指令inc bx,那么,因为BX寄存器只有16位,所以结果为0x0000,进位被丢弃(绝不会影响EBX寄存器的高16位)。此指令执行之后,EBX寄存器的内容是0x00000000。
第283行,标号pgdt+2处指向的是GDT的线性基地址,将其与新描述符在GDT内的偏移量相加,结果是新的要安装的描述符的线性地址。第285行,将描述符的低32位(放在EAX,由参数传入)放到段基地址为0x0000000,偏移量为EBX的位置;第286行,将描述符的高32位(放在EDX中,由参数传入)放到ebx+4的位置;第288行,新的描述符添加之后,GDT的大小发生了变化,界限也要跟着修改,将原来的界限值加上8。GDTR寄存器中的界限值总是单数(8的整数倍减1),包括它的初始值0xFFFF,所以,每次只要加上新描述符的实际大小就能得到正确的界限值;第290行,用lgdt指令重新加载GDTR,使修改生效。
第292~297行,根据GDT的新界限值,来生成相应的段选择子。具体的算法是,取得GDT的当前界限值,除以8,余数丢弃。描述符的索引是从0开始编号的,界限值总是比GDT的总字节数小1。因此,界限值除以8,一定会有余数(余7,丢弃不用),商就是我们所要得到的描述符索引号。最后,将索引号左移3次,留出TI位和RPL位(TI=0,指向GDT,RPL=00),这就是要生成的选择子。第299~306行,恢复调用之前的现场,返回调用者。返回时用了retf,说明这个过程只能通过远过程调用的方式进入。
第440行,将头部段的选择子回填到edi+0x04处,从用户程序头部段我们可以看到,原来这里是头部段长度的数据,用户程序加载后,这个长度数据不再需要,因此直接用头部段的选择子数据回填。第443~460行,用同样的方法构造并安装用户程序代码段、数据段的段描述符,并分别回填到edi+0x14、edi+0x1c处。原来这两个地方分别是用户程序代码段、数据段的起始汇编地址。
用户程序的栈段描述符创建有所不同。栈所用的空间不需要用户程序提供,而是由内核动态分配。内核分配栈空间时,是以4KB为单位的,也就是说,每次分配至少是4KB的倍数。至于到底分配多少,用户程序应该根据自己的实际需求提出建议。
第463行,从用户程序头部偏移为0x0C的地方获得一个建议的栈大小。这是一个倍率,至少应当为1,说明用户程序希望分配4KB。如果为2,说明希望分配8KB;为3则表明希望分配12KB,以此类推。将取得的倍率数值传送到寄存器ECX。第464、465行,计算栈段的界限。如果栈段的粒度是4KB,那么,用0xFFFFF减去倍率,就是用来创建描述符的段界限。举例来说,如果用户程序建议的倍率是2,那么,意味着他想创建的栈空间为8KB。因此,段的界限值为0xFFFFF-2=0xFFFFD。那么,当处理器访问该栈段时,实际使用的段界限为0xFFFFD×0x1000+0xFFF=0xFFFFDFFF。栈是向下扩展的,访问32位的栈,要使用栈指针寄存器ESP,其最大值是0xFFFFFFFF。因此,ESP的值只允许在0xFFFFDFFF和0xFFFFFFFF之间变化,共8KB。
第466~469行,用4096(4KB)乘以倍率,得到所需要的栈大小,然后,用这个值去申请内存。这是一个32位无符号数乘法,指令格式为:mul r/m32,这里,用EAX寄存器的值,乘以另一个32位的数(可以在通用寄存器或者内存单元里),在EDX:EAX得到64位的乘法结果。注意,allocate_memory过程返回所分配内存的低端地址。和一般的数据段不同,栈描述符中的基地址,应当是栈空间的高端地址。所以,第470行,用allocate_memory返回的低端地址,加上栈的大小,得到栈空间的高端地址。第471~473行,再次调用两个例程,生成和安装栈段的描述符。注意栈的属性值,它指明了这是一个32位的栈段,粒度为4KB。第474行,将栈段的选择子回填到用户程序头部,供用户程序接管处理器控制权之后使用。