深入浅出大端和小端

深入浅出大端和小端端模式 Endian 的这个词出自 Jonathan Swift 书写的 格列佛游记 这本书根据将鸡蛋敲开的方法不同将所有的人分为两类 从圆头开始将鸡蛋敲开的人被归为 Big Endian 从尖头开始将鸡蛋敲开的人被归为 Littile Endian 小人国的内战就源于吃鸡蛋时是究竟从大头 Big Endian

大家好,我是讯享网,很高兴认识大家。

端模式(Endian)的这个词出自Jonathan Swift书写的《格列佛游记》。这本书根据将鸡蛋敲开的方法不同将所有的人分为两类,从圆头开始将鸡蛋敲开的人被归为Big Endian,从尖头开始将鸡蛋敲开的人被归为Littile Endian。小人国的内战就源于吃鸡蛋时是究竟从大头(Big-Endian)敲开还是从小头(Little-Endian)敲开。在计算机业Big Endian和Little Endian也几乎引起一场战争。在计算机业界,Endian表示数据在存储器中的存放顺序。下文举例说明在计算机中大小端模式的区别。

如果将一个32位的整数0x存放到一个整型变量(int)中,这个整型变量采用大端或者小端模式在内存中的存储由下表所示。为简单起见,本书使用OP0表示一个32位数据的最高字节MSB(Most Significant Byte),使用OP3表示一个32位数据最低字节LSB(Least Significant Byte)。


地址偏移
 大端模式
 小端模式
 
0x00
 12(OP0)
 78(OP3)
 
0x01
 34(OP1)
 56(OP2)
 
0x02
 56(OP2)
 34(OP1)
 
0x03
 78(OP3)
 12(OP0)
 

 

如果将一个16位的整数0x1234存放到一个短整型变量(short)中。这个短整型变量在内存中的存储在大小端模式由下表所示。


地址偏移
 大端模式
 小端模式
 
0x00
 12(OP0)
 34(OP1)
 
0x01
 34(OP1)
 12(OP0)
 

 

由上表所知,采用大小模式对数据进行存放的主要区别在于在存放的字节顺序,大端方式将高位存放在低地址,小端方式将高位存放在高地址。采用大端方式进行数据存放符合人类的正常思维,而采用小端方式进行数据存放利于计算机处理。到目前为止,采用大端或者小端进行数据存放,其孰优孰劣也没有定论。

有的处理器系统采用了小端方式进行数据存放,如Intel的奔腾。有的处理器系统采用了大端方式进行数据存放,如IBM半导体和Freescale的PowerPC处理器。不仅对于处理器,一些外设的设计中也存在着使用大端或者小端进行数据存放的选择。

因此在一个处理器系统中,有可能存在大端和小端模式同时存在的现象。这一现象为系统的软硬件设计带来了不小的麻烦,这要求系统设计工程师,必须深入理解大端和小端模式的差别。大端与小端模式的差别体现在一个处理器的寄存器,指令集,系统总线等各个层次中。

¨          #define ntohs(n)     //16位数据类型网络字节顺序到主机字节顺序的转换

¨          #define htons(n)     //16位数据类型主机字节顺序到网络字节顺序的转换

¨          #define ntohl(n)      //32位数据类型网络字节顺序到主机字节顺序的转换

¨          #define htonl(n)      //32位数据类型主机字节顺序到网络字节顺序的转换

其中互联网使用的网络字节顺序采用大端模式进行编址,而主机字节顺序根据处理器的不同而不同,如PowerPC处理器使用大端模式,而Pentuim处理器使用小端模式。

大端模式处理器的字节序到网络字节序不需要转换,此时ntohs(n)=n,ntohl = n;而小端模式处理器的字节序到网络字节必须要进行转换,此时ntohs(n) = __swab16(n),ntohl = __swab32(n)。__swab16与__swab32函数定义如下所示。

#define ___swab16(x)

{

            __u16 __x = (x);

            ((__u16)(

                        (((__u16)(__x) & (__u16)0x00ffU) << 8) |

                        (((__u16)(__x) & (__u16)0xff00U) >> 8) ));

}

#define ___swab32(x)

{

            __u32 __x = (x);

            ((__u32)(

                        (((__u32)(__x) & (__u32)0x000000ffUL) << 24) |

                        (((__u32)(__x) & (__u32)0x0000ff00UL) << 8) |

                        (((__u32)(__x) & (__u32)0x00ff0000UL) >> 8) |

                        (((__u32)(__x) & (__u32)0xff000000UL) >> 24) ));

程序员在对普通文件进行处理也需要考虑端模式问题。在大端模式的处理器下对文件的32,16位读写操作所得到的结果与小端模式的处理器不同。读者单纯从软件的角度理解上远远不能真正理解大小端模式的区别。事实上,真正的理解大小端模式的区别,必须要从系统的角度,从指令集,寄存器和数据总线上深入理解,大小端模式的区别。

PowerPC处理器主导网络市场,可以说绝大多数的通信设备都使用PowerPC处理器进行协议处理和其他控制信息的处理,这也可能也是在网络上的绝大多数协议都采用大端编址方式的原因。因此在有关网络协议的软件设计中,使用小端方式的处理器需要在软件中处理端模式的转变。而Pentium主导个人机市场,因此多数用于个人机的外设都采用小端模式,包括一些在网络设备中使用的PCI总线,Flash等设备,这也要求硬件工程师在硬件设计中注意端模式的转换。

本书中的小端外设是指这种外设中的寄存器以小端方式进行存储,如PCI设备的配置空间,NOR FLASH中的寄存器等等。

对于有些设备,如DDR颗粒,没有以小端方式存储的寄存器,因此从逻辑上讲并不需要对端模式进行转换。在设计中,只需要将双方数据总线进行一一对应的互连,而不需要进行数据总线的转换。

如果从实际应用的角度说,采用小端模式的处理器需要在软件中处理端模式的转换,因为采用小端模式的处理器在与小端外设互连时,不需要任何转换。

而采用大端模式的处理器需要在硬件设计时处理端模式的转换。大端模式处理器需要在寄存器,指令集,数据总线及数据总线与小端外设的连接等等多个方面进行处理,以解决与小端外设连接时的端模式转换问题。

在寄存器和数据总线的位序定义上,基于大小端模式的处理器有所不同。

一个采用大端模式的32位处理器,如基于E500内核的MPC8541,将其寄存器的最高位msb(most significant bit)定义为0,最低位lsb(lease significant bit)定义为31;而小端模式的32位处理器,将其寄存器的最高位定义为31,低位地址定义为0。

与此向对应,采用大端模式的32位处理器数据总线的最高位为0,最高位为31;采用小端模式的32位处理器的数据总线的最高位为31,最低位为0。如图4.5所示。

 

OP0
 
OP1
 
OP2
 
OP3
 
OP0
 
OP1
 
OP2
 
OP3
 
31
 
0
 
31
 
0
 
图4.5大小端模式处理器的寄存器的定义
 
大端模式处理器寄存器位序定义
 
小端模式处理器寄存器位序定义
 

¨          大端模式下32位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第31位,LSB是第24~31字段。小端模式下32位总线的msb是第31位,MSB是数据总线的第31~24位,lsb是第0位,LSB是7~0字段。

¨          大端模式下16位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第15位,LSB是第8~15字段。小端模式下16位总线的msb是第15位,MSB是数据总线的第15~7位,lsb是第0位,LSB是7~0字段。


讯享网

¨          大端模式下8位数据总线的msb是第0位,MSB是数据总线的第0~7的字段;而lsb是第7位,LSB是第0~7字段。小端模式下8位总线的msb是第7位,MSB是数据总线的第7~0位,lsb是第0位,LSB是7~0字段。

由上分析,我们可以得知对于8位,16位和32位宽度的数据总线,采用大端模式时数据总线的msb和MSB的位置都不会发生变化,而采用小端模式时数据总线的lsb和LSB位置也不会发生变化。

为此,大端模式的处理器对8位,16位和32位的内存访问(包括外设的访问)一般都包含第0~7字段,即MSB。小端模式的处理器对8位,16位和32位的内存访问都包含第7~0位,小端方式的第7~0字段,即LSB。

由于大小端处理器的数据总线其8位,16位和32位宽度的数据总线的定义不同,因此需要分别进行讨论在系统级别上如何处理端模式转换。

在一个大端处理器系统中,需要处理大端处理器对小端外设的访问。

大端处理器,如MPC8541,使用stw,sth,stb和lwz,lhz,lbz指令对32位的外部设备进行访问。在这些指令结束后,存放在外部设备的数据将被读入MPC8541的通用寄存器中。为保证软件的一致性,当访问结束后,存放在通用寄存器的字节序,即OP0,OP1,OP2和OP3必须要和存放在小端外设的字节序一致。此时在使用大端处理器的数据总线连接小端外设时必须要进行一定的处理,按照某种拓扑结构连接以保证软件的一致性。大端处理器数据总线与小端外设进行连接的拓扑结构如图4.6所示。


OP0
 
OP1
 
OP2
 
OP3
 
31
 
31
 
0
 
7
 
8
 
15
 
16
 
23
 
24
 
24
 
23
 
16
 
15
 
8
 
7
 
0
 
大端处理器的32位数据总线
 
小端设备的32位总线接口
 
图4.6 大端处理器与小端外设的32位连接
 
OP0
 
OP1
 
OP2
 
OP3
 

PowerPC处理器采用16位总线与16位的小端外设进行访问时,PowerPC处理器的16位数据总线的第0~7位用来处理OP0,第8~15位用来处理OP1。而16位的小端设备使用数据总线的第15~8位用来处理OP0,第7~0位用来处理OP1。

PowerPC处理器采用8位总线与8位的小端外设进行访问时,PowerPC处理器的8位数据总线的第0~7字段用来处理OP0。而8位的小端设备使用数据总线的第7~0位用来处理OP1。大端处理器与小端外设的连接关系如图4.7所示。

OP0
 
OP1
 
OP0
 
OP1
 
15
 
0
 
7
 
8
 
15
 
8
 
7
 
0
 
大端处理器的8/16位数据总线
 
小端设备的8/16位总线接口
 
图4.7 大端处理器与小端外设的8/16位连接
 
OP0
 
OP0
 
7
 
0
 
7
 
0
 

PowerPC处理器对8位的小端外设进行访问时,一个总线周期只能访问8位数据,如果处理器使用stw或者lwz指令访问8位的小端设备内的32位数据时,在数据总线上将OP0,OP1,OP2和OP3依次传递到PowerPC的通用寄存器中。

PowerPC处理器对16位的小端外设进行访问时,一个总线周期只能访问16位数据,如果处理器使用stw或者lwz指令访问16位的小端设备内的32位数据时,在数据总线上将OP0~1和OP2~3依次传递到PowerPC的通用寄存器中。

PowerPC处理器使用sth或者lhz指令访问16位的小端设备时,16位的小端设备将数据的第15~0位,传递到PowerPC处理器的总线的第0~15位,然后再将数据最终传递给相应的通用寄存器。这里有许多读者会感到困惑,因为为了保证软件的一致性,PowerPC处理器使用lhz指令访问16位的小端设备的16位寄存器时,需要将结果保存在通用寄存器的第16~31位,而不是0~15位。究竟PowerPC处理器是如何将系统总线中0~15位的数据搬移到寄存器的第16~31位中的呢?为此我们需要对lhz指令进行分析。

lhz rD,d(rA)

if rA = 0 then b ← 0

else b ← (rA)

EA ← b + EXTS(d)

PowerPC处理器使用stb或者lbz指令访问8位的小端设备时,8位的小端设备将数据的第7~0位,传递到PowerPC处理器的总线的第0~7位,然后再将数据最终传递给相应的通用寄存器,lbz指令的描述如下所示。

lbz rD,d(rA)

if rA = 0 then b ← 0

else b ← (rA)

EA ← b + EXTS(d)

文章二:

大端(big-endian)和小端(little-endian)<转>
2007-12-07 20:36
补:x86机是小端(修改分区表时要注意),单片机一般为大端

    今天碰一个关于字节顺序的问题,虽然看起来很简单,但一直都没怎么完全明白这个东西,索性就找了下资料,把它弄清楚.
    因为现行的计算机都是以八位一个字节为存储单位,那么一个16位的整数,也就是C语言中的short,在内存中可能有两种存储顺序big-endian和 litte-endian.考虑一个short整数0x3132(0x32是低位,0x31是高位),把它赋值给一个short变量,那么它在内存中的存 储可能有如下两种情况:
大端字节(Big-endian):
----------------->>>>>>>>内存地址增大方向
short变量地址
       0x1000                  0x1001
_____________________________
|                           |
|         0x31             |       0x32
|_______________ | ________________
高位字节在低位字节的前面,也就是高位在内存地址低的一端.可以这样记住(大端->高位->在前->正常的逻辑顺序)
小端字节(little-endian):
----------------->>>>>>>>内存地址增大方向
short变量地址
       0x1000                  0x1001
_____________________________
|                           |
|         0x32             |       0x31
|________________ | ________________
低位字节在高位字节的前面,也就是低位在内存地址低的一端.可以这样记住(小端->低位->在前->与正常逻辑顺序相反)
可以做个实验
在windows上下如下程序
#include <stdio.h>
#include <assert.h>
void main( void )
{
        short test;
        FILE* fp;
        
        test = 0x3132; //(31ASIIC码的’1’,32ASIIC码的’2’)
        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)
              assert(0);
        fwrite(&test, sizeof(short), 1, fp);
        fclose(fp);
}
    然后在C盘下打开test.txt文件,可以看见内容是21,而test等于0x3132,可以明显的看出来x86的字节顺序是低位在前.如果我们把这段 同样的代码放到(big-endian)的机器上执行,那么打出来的文件就是12.这在本机中使用是没有问题的.但当你把这个文件从一个big- endian机器复制到一个little-endian机器上时就出现问题了.
    如上述例子,我们在big-endian的机器上创建了这个test文件,把其复制到little-endian的机器上再用fread读到一个 short里面,我们得到的就不再是0x3132而是0x3231了,这样读到的数据就是错误的,所以在两个字节顺序不一样的机器上传输数据时需要特别小 心字节顺序,理解了字节顺序在可以帮助我们写出移植行更高的代码.
正因为有字节顺序的差别,所以在网络传输的时候定义了所有字节顺序相关的数据都使用big-endian,BSD的代码中定义了四个宏来处理:
#define ntohs(n)     //网络字节顺序到主机字节顺序 n代表net, h代表host, s代表short
#define htons(n)     //主机字节顺序到网络字节顺序 n代表net, h代表host, s代表short
#define ntohl(n)      //网络字节顺序到主机字节顺序 n代表net, h代表host, l代表 long
#define htonl(n)      //主机字节顺序到网络字节顺序 n代表net, h代表host, l代表 long
举例说明下这其中一个宏的实现:
#define sw16(x) \
    ((short)( \
        (((short)(x) & (short)0x00ffU) << 8) | \
        (((short)(x) & (short)0xff00U) >> 8) ))
这里实现的是一个交换两个字节顺序.其他几个宏类似.

我们改写一下上面的程序
#include <stdio.h>
#include <assert.h>

#define sw16(x) \
    ((short)( \
        (((short)(x) & (short)0x00ffU) << 8) | \
        (((short)(x) & (short)0xff00U) >> 8) ))

// 因为x86下面是低位在前,需要交换一下变成网络字节顺序
#define htons(x) sw16(x)
void main( void )
{
        short test;
        FILE* fp;
       
        test = htons(0x3132); //(31ASIIC码的’1’,32ASIIC码的’2’)
        if ((fp = fopen ("c:\\test.txt", "wb")) == NULL)
              assert(0);
        fwrite(&test, sizeof(short), 1, fp);
        fclose(fp);
}
    如果在高字节在前的机器上,由于与网络字节顺序一致,所以我们什么都不干就可以了,只需要把#define htons(x) sw16(x)宏替换为 #define htons(x) (x).
    一开始我在理解这个问题时,总在想为什么其他数据不用交换字节顺序?比如说我们write一块buffer到文件,最后终于想明白了,因为都是unsigned char类型一个字节一个字节的写进去,这个顺序是固定的,不存在字节顺序的问题,够笨啊..

文章三:

big-endian和little-endian这两个术语来自Jonathan Swift在十八世纪的嘲讽作品Gulliver’s Travels。 Blefuscu帝国的国民被根据吃鸡蛋的方式划分为两个部分:一部分在吃鸡蛋的时候从鸡蛋的大端(big end)开始,而另一部分则从鸡蛋的小端(little end)开始。

x86的CPU使用的是LE(Windows中称为“主机字节序”),而SocksAddr中使用的则是BE(就是“网络字节序”),所以在使用网络编程时需要使用htns,htnl,nths,nthl来倒字节序。

其实对汇编熟了就清楚了,惨,我的汇编很惨的

LE little-endian
最符合人的思维的字节序
地址低位存储值的低位
地址高位存储值的高位
怎么讲是最符合人的思维的字节序,是因为从人的第一观感来说
低位值小,就应该放在内存地址小的地方,也即内存地址低位
反之,高位值就应该放在内存地址大的地方,也即内存地址高位

BE big-endian
最直观的字节序
地址低位存储值的高位
地址高位存储值的低位
为什么说直观,不要考虑对应关系
只需要把内存地址从左到右按照由低到高的顺序写出
把值按照通常的高位到低位的顺序写出
两者对照,一个字节一个字节的填充进去

内存地址
4000 4001 4002 4003
LE 04 03 02 01
BE 01 02 03 04

MSDN中关于LE和BE的解释
Byte Ordering Byte ordering Meaning
big-endian The most significant byte is on the left end of a word.
little-endian The most significant byte is on the right end of a word.
这里这个最重要的字节可以解释成值的最高位,如果换成是钱的话就是最值钱的那一位
比如我有1234元人民币,最值钱的是1000元,最不值钱的是4元,那么这个1就是最重要的字节


Big endian machine: It thinks the first byte it reads is the biggest.
Little endian machine: It thinks the first byte it reads is the littlest.
举个例子,从内存地址0x0000开始有以下数据
0x0000    0x12
0x0001    0x34
0x0002    0xab
0x0003    0xcd
如果我们去读取一个地址为0x0000的四个字节变量,若字节序为big-endian,则读出
结果为0x1234abcd;若字节序位little-endian,则读出结果为0xcdab3412.
如果我们将0x1234abcd写入到以0x0000开始的内存中,则结果为
                big-endian    little-endian
0x0000    0x12              0xcd
0x0001    0x23              0xab
0x0002    0xab              0x34
0x0003    0xcd              0x12
x86系列CPU都是little-endian的字节序.

http://blog.chinaunix.net/u/29331/showart_356837.html
本文来自:我爱研发网(52RD.com) - R&D大本营
详细出处:http://www.52rd.com/Blog/Detail_RD.Blog_imjacob_14837.html

小讯
上一篇 2025-02-13 18:56
下一篇 2025-01-12 09:24

相关推荐

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/16244.html