这几天一直被一个叫Global Offset Table的东西困扰着。
在uboot的程序上看到一个叫_GLOBAL_OFFSET_TABLE_的东东,却到处找不到。经过一番Google,才明白这东东叫全局变量偏移表,用来做与位置无关的程序编译时使用的。并且编译选项要加上-mabicalls。另:与程序位置无关的编译选项是:-fpic和-fPIC。
Global Offset Table里,存得是一些函数地址,放在.got段。一般是使用gp来访问。
不过在资料中还看到,这个主要用于一些动态库。如果没用到动态库的话,应该也不是十分太需要吧,毕竟使用GOT会影响small data的使用,同时会增加每个函数调用所需的指令。
费了好大的劲,终于知道gp寄存器的用法了。
首先说说gp寄存器出现的原因吧,由于RISC体系的CPU,每条指令都是32Bit,而地址总线也是32Bit,这样,就不可能通过一条指令来实现Memory的访问。
MIPS中,访问Memory的指令格式为:
lw r1, offset(r2)
其中,offset为16Bit,也就是说,最多能访问以r2为基地址,前后32K的空间。
于是这个时候,gp就闪亮登场了。
我们知道,GCC在编译过程,会将变量分别放在data,bss,common等段。而编译mips程序时,会将小变量放入sdata,sbss,scommon等段。这个大小由编译选项-G来决定。-G0则表示不使用小数据段。默认是-G8,表示小于或等于8 byte的数据将放入小变量区。
这样的话,在上电时给gp初始化一个值,那么,所有小变量区的变量就可以通过
lw r1, offset(gp)
来访问了。
那么,GCC怎么知道gp初始化的值呢?只要在link文件中添加_gp符号,连接器就会认为这是gp的值。我们在上电时,将_gp的值赋给gp寄存器就行了。
最近,看了一小阵子的MIPS资料,做一下简单的记录吧。
第一是空间上的分配。MIPS空间上共分四个部份:
0x00000000 - 0x7fffffff 为kuser区,主要用于MMU映射,用于运行用户程序;<br />
0x80000000 - 0x9fffffff 为kseg0区,访问方式为Cache访问,主要用于运行无MMU系统的大部份程序或操作系统的核心程序;<br />
0xa0000000 - 0xbfffffff 为kseg1区,为无Cache方式访问。主要用于运行Bootloader程序,或映射寄存器。<br />
0xc0000000 - 0xffffffff 为kseg2区,MMU映射访问,用于运行一些管理态的程序。
需要注意的是:kseg0和kseg1都是映射上物理地址0x00000000-0x1fffffff上。
因些,程序运行的起始地址0xbfc00000实际上是物理地址0x1fc00000。
第二是寄存器。MIPS一共有32个通用寄存器。
$0 zero,该寄存器桓为0,用途较广,可用于清空其它寄存器,做一些空操作等。<br />
$1:at,主要保留用于汇编的一些宏指令。编译器可能使用到它。<br />
$2 - $3:v0 - v1,主要用于函数调用的定点返回值<br />
$4 - $7:a0 - a3,函数调用时传递的4个定点参数<br />
$8 - $15:t0 - t7,非保存的临时寄存器,可能被子程序改变其值。<br />
$16 - $23:s0 - s7,保存的临时寄存器,使用完毕需要还原原来的值。<br />
$24 - $25:t8 - t9,同t0 - t7;<br />
$28:gp,全局指针(Global Pointer),访问一些全局变量时会用到,具体用法暂时还不是太清楚 ;<br />
$29:sp,堆栈指针(Stack Pointer) ;<br />
$30:fp,帧指针(Frame Pointer) ,主要用于保存函数入口的堆栈指针。<br />
$31:ra ,执行jr,jalr后保存的返回PC地址。
第三是协处理器CP0。
用的最多的就是$12 SR和$13 CAUSE了。
暂时就先这些吧。等待以后补充。
用51移植一个小型OS时,遇到了中断方面的一个问题。
因为,51的中断有一个特性:如果多个中断同时产生中断(或者在关闭EA时发生了多个中断),在EA打开的时后,每执行一个中断,必须执行主程序的一条指令,才能进入下一个中断。据说这个主要是为了实现51的单步调试功能而设计的。我不知道是不是所有的51的特性都是这样,或者其它体系的芯片会不会这样。
这样会有什么问题呢?
在OS任务切换过程,我们会遇到以下形势的代码:
EA = 0; //进入临界区1<br />
任务1由就绪转入等待;<br />
任务1加入等待队列;<br />
产生任务切换软中断;<br />
EA = 1; //退出临界区1<br />
(这里将进入软中断,发生任务切换事件)<br />
EA = 0; //进入临界区2<br />
等待队列中删除任务1;<br />
EA = 1; //退出临界区2
我们的原意是要在退出临界区1的时候进行任务切换。然而,在临界区1的时候,有可能已经产生了其它中断,而任务切换软中断的自然优先级最低,这时候,由于中断的特性,执行完其它中断任务后,会关闭EA!!
于是,没有在我们预想的地方进行任务切换。真正的切换是等到退出临界区2后!!
接下来问题就大了:由于等待队列中已经删除了任务1,那么任务1将永远无法被唤醒了。任务1就成了一个死任务。
那么,有什么解决办法呢?
1、如果可以更改芯片设计,则改为中断后不执行任何主程序的执令。
2、提高切换任务的优先级至最高。
3、不使用软中断,而是采用直接调用函数的方法进行任务切换。
4、在临界区1与临界区2之间的区域加入足够多的NOP指令。
以上几种方法均能解决。至于详细方法,这里就不多说了。
经常看到SD/MMC这样的写法。Hanny在这里稍微总结一下SD卡和MMC卡的异同点吧。
首先,两者在外型的规格上是几乎一致的。而且两都的接口是兼容的。也就是说,两者可以用同一个卡座来进行读取。而且,两者在时序上也是一致的,读写命令控制也完全一样,这就是为什么经常把两者混在一起写的原因。
不过,虽说外型几乎一致,但还是有点差异的。MMC比SD卡要薄一些,并且,长度只有SD卡的一半。
下面,Hanny从主机驱动方面谈谈两者的差异。
在数据位宽方面,MMC卡最大支持8BIT,而SD卡只能支持4BIT传输。
在卡的激活过程,MMC使用CMD1来进行激活,而SD卡使用ACMD41来进行激活的。于是,这两条命令也成了主机区分两种卡的类型的关键。
在获取卡的RCA地址时,MMC卡是由主机分配RCA给设备,而SD卡则是由设备返回RCA给主机。
在CMD6的使用方法上也有很大不同。MMC有EXT_CSD的概念,主要用CMD8进行读取,CMD6进行设置。而SD卡则只用CMD6进行UserFunction的设置。SD卡的CMD8主要用于区别SD1.0和SD2.0。
MMC还支持CMD14和CMD19进行主线测试,从而选择合适总线进行通信。SD则不支持。
另外,MMC卡还支持CMD11、CMD20这类数据流操作,因此较多用于媒体设备上。MMC还支持Boot等高级的用法。