最近公司处理器要换核,由小端处理器ARM换成大端处理器POWERPC,bootloader以及kernel的移植工作交给了我,这是一个很有挑战的工作,自己也非常兴奋。
如此一来,当今主流的嵌入式处理器(MIPS ARM PPC)也都算接触过啦。
这几天开始动手做移植,首先要解决的是大小端的差异,进过学习思考,感觉大小端还是很有研究的必要,自己的思考总结记录在此,与大家分享,以备后用。
从网上可以查到的大小端的解释,小端是低端数据存放在低端地址,大端是高端数据存在低端地址。大小端真的就这么简单吗,不是这样的。
字节序大端小端是针对超过一个byte的数据类型在内存中的存储布局来讲的。对象是数据类型的存储布局,为什么要超过一个byte呢,这涉及到内存寻址,内存寻址的最小单位就是byte,一个byte内的数据排布顺序是固定的(小端)。
打个比方,拿我们自己来说,有一句4个字的话,我们是从右向左读,还是从左向右读,这就是我们的字节序啊。
所以对于处理器在操作超过一个byte的数据类型时,如何排布数据在内存中的顺序,就由其字节序来决定。
可以这样理解:
对于小端处理器,如果要寻址一个word型数据,处理器首先由地址总线发出地址,之后对于由32位数据总线(32位处理器)返回的数据,小端处理器认为0-7位数据线是低端数据,而24-31位数据线为高端数据。
相反,对于大端处理器,寻址一个word型数据,处理器对于数据线返回的数据,认为24-31位数据线为低端数据,而0-7位数据线为高端数据。
字节序大小端还远没有这么简单,对于字节序的理解,我觉得可以分为2种情况:
(1)操作内存,首先说明内存本身是没有字节序一说的,内存中同样一段数据,小端处理器读出来的数据意义和大端处理器读出来的数据意义是不同的,所以其存储数据的顺序是由处理器字节序来决定的。
操作内存,无非就是读和写。那这里又可以分为2种情况,
一种是处理器读处理器写,也就是处理器主动发起对于内存的读写,因为使用同一处理器,采用同样的字节序去读写同一数据(修改其值,进行位操作等)是没有差别的,唯一的差别是在对于同一段内存,读写时操作了不同的数据类型。这种情况就不细说了,因为现在街是大小端的文章都会解释这个问题,这也是验证处理器是大端还是小端很好的方法。
另一种是另一主设备与处理器异步的操作了内存,如DMA,假如处理器由小端改为大端,而外设是小端(我这次的移植就是这种情况),在外围硬件设计不变的情况下(处理器0-31数据线与外设0-31数据线一一对应),外设DMA写入内存的数据,由处理器读取回来,数据就已经被翻转了,已经失去了原来的意义。这也是做大小端移植需要注意的一点。
(2)操作寄存器,这跟DMA一样,也是一个异步操作,现在大部分外设控制器寄存器都是小端设计(这里的设计是指寄存器描述是小端的,数据排布也是小端的),寄存器也可以分为2种。
一种是位意义的寄存器,这种寄存器的每一位都有意义,可能是某种功能的开关,这种寄存器在外设中非常常见。对于这类寄存器,小端处理器操作没有差别,因为字节序一致,但是对于大端处理器,其获得寄存器数据是翻转的,所以对于每一位的定义也是翻转的,不过我们可以通过修改软件上(如kernel)对寄存器的位宏定义来获取其正确的位意义,这一点在做大小端移植时需要注意。
另一种是数据意义的寄存器,这种寄存器上存储的是有意义的数据,如串口收发数据寄存器,网卡DMA描述符首地址寄存器等,对于大端处理器,该类寄存器是无法通过修改位宏定义来保证正确,因为其是一个整体数据,这种寄存器只能是在获取其值后将数据再翻转(大端转小端),来获取寄存器中原有意义的数据,在进行操作。
但是如果是这样的修改,就有一个原子操作的问题了,因为读写寄存器本来是由一条指令完成的,但是现在却添加了翻转操作,在进行读写指令,这就不能保证寄存器读写的原子性。在高性能,中断频发,不停切换的操作系统下,就有可能产生问题。
对于这个问题,也可以在硬件上进行协作,如将处理器的(0-7)(8-15)(16-23)(24-31)与外设的(0-7)(8-15)(16-23)(24-31)进行反接,但是这样也可能有问题,如对于大于1byte却小于4byte的数据,处理器如何获取的问题。
这些在由小端到大端移植的问题我还在探索和学习中,还是很有意思的。
不过对于本来设计为大端,寄存器描述也是大端的外设,与大端处理器相连,就不会有这些问题。也就是说外设与处理器字节序同一,这些棘手的问题都可以避免。