Byte and Bit Order Dissection解析字节序和比特序

 

Byte and Bit Order Dissection

 

作者:Kevin He

原文地址:http://www.linuxjournal.com/article/6788

 

译者:Love. Katherine 2007-04-14

译文地址:http://blog.csdn.net/lovekatherine/archive/ 2007/04/14 /1564731.aspx

 


转载时务必以超链接形式标明文章原始出处及作者、译者信息。


 

讨论大端与小端、比特序与节序的区别,以及它们的作用范围

 

编辑提示:本文自最初发表后已

                           

做过修改

 

 

那些不得不和比特序、字节序问题打交道的软件或硬件工程师,都很清楚这过程就像是走迷宫。尽管通常我们都能走出迷宫,但是每次都要牺牲数量可观的脑细胞。本文试图概括需要处理比特序和字节序问题的领域,包括CPU、总线、硬件设备以及网络协议。我们将深入问题的细节,并希望能在这个问题上提供有价值的参考。本文同时还试图提供一些从实践中总结出的指导和拇指法则。

 

 

大小端

 

我们对"endianness"这个名词估计都很熟悉了。它首先被Danny Cohen1980引入,用来表述计算机系统表示多字节整数的方式。

 

endianness分为两种:大端和小端。(从字节序的角度来看)大端方式是将整数中最高位byte存放在最低地址中。而小端方式则相反,将整数中的最高位byte存放在最高地址中。

 

对于某个确定的计算机系统,比特序通常与字节序保持一致。换言之,在大端系统中,每个byte中最高位bit存放在内存最低位;在小端系统中,最低位bit存放在内存最低位。

 

在设计计算机系统时,应该尽一切可能避免通过软件方式执行bit换位,因为这样不仅会产生巨大开销,也是件令程序员感到乏味的工作。后文将介绍如何通过硬件方式处理这一问题。

 

书写规则

 

正如大部分人是按照从左至右的顺序书写数字,一个多字节整数的内存布局也应该遵循同样的方式,即从左至右为数值的最高位至最低位。正如我们在下面的例子中所看到的,这是书写整数最清晰的方式。

 

根据上述规则,我们按以下方式分别在大端和小端系统中值为0x 0a 0b 0c 0d的整数。

 

 

在大端系统中书写整数:

 

byte  addr       0         1       2        3

bit   offset  01234567 01234567 01234567 01234567

     binary  00001010 00001011 00001100 00001101

      hex     0a        0b      0c         0d

 

 

 

在小端系统中书写整数

 

byte  addr      3         2       1        0

bit   offset  76543210 76543210 76543210 76543210

     binary  00001010 00001011 00001100 00001101

      hex     0a        0b      0c         0d

 

以上两种情形,我们都是按从左至右的顺序读,整数值为0X 0a 0b 0c 0d

 

假设我们不遵循上述的规则,也许我们会以如下方式书写整数:

 

byte  addr      0         1       2        3

bit   offset  01234567 01234567 01234567 01234567

     binary  10110000 00110000 11010000 01010000

 

正如你所看到的,这种方式下想要看出我们要表达的整数是件困难的事情。

 

本文中使用的简化计算机系统

 

在不失一般性的前提下,在本文中使用下图所描述的简化计算机系统:

 

 

 

CPU、内部总线和内存/Cache这些部件由于通常拥有相同的endianness,可以作为一个整体用CPU来代表。而对于总线endianness讨论,只涉及外部总线。CPU寄存器宽度、内存字宽和总线宽度在本文中被设定为32bits

 

CPUendianness

 

 

CPUendianness是指它在寄存器、内部总线、Cahce和内存中表示多字节整数时所采取的字节序和比特序。

 

小端的CPU包括IntelDEC。大端CPU包括Motorola 680x0,Sun Sparc and IBM (PowerPC)MIPs and ARM可以设定为任选其一。

 

 

 

CPUendianness影响着CPU的指令集。对于使用不同endiannessCPU,应该使用不同的GNU工具包来编译代码。例如,mips-linux-gccmipsel-linux-gcc分别用来编译生成运行于大端和小端模式的MIPS之上的代码。

 

如果我们(程序员)需要访问多字节整数的一部分时,也必须考虑CPUendianness。以下的程序展示了该种情形。注意,在访问32-bit整数的整体时,CPUendianness对于软件(程序员)是不可见的。

 

union {

    uint32_t my_int;

    uint8_t  my_bytes[4];

} endian_tester;

endian_tester et;

et.my_int = 0x 0a 0b 0c 0d;

if(et.my_bytes[0] == 0x 0a )

    printf( "I'm on a big-endian system/n" );

else

    printf( "I'm on a little-endian system/n" );

 

 

总线的Endianness

 

此处我们所谈论的总线是在上图中显示的外部总线。下文以PCI总线为例。正如我们所知,总线是联接CPU、外设以及其它各种设备的媒介部件。总线的endianness是由总线协议定义的、所有联接到其上的部件都必须遵守的比特/字节序标准。

 

以类型为小端的PCI总线为例:对于PCI32位地址/数据线AD[31:0],要求所有联接到PCI上的32-bit设备将其最高位数据线联接到AD31,最低位数据线联接到AD0。类型为大端的总线协议则有相反的要求。

 

对于一个数据宽度不满总线宽度的设备,例如一个8-bit设备,小端的总线如PCI规定设备的8根数据线应联接到AD70],而对于大端的总线协议,则要求联接到AD2431]。

 

此外,对于PCI,总线协议要求每个PCI设备实现可配置空间——即一组与总线具有相同字节序的可配置寄存器。

 

正如所有的设备都需要遵守(外部)总线所规定的比特/字节序标准,CPU也一样。如果CPU(外部)总线工作于不同的endianness模式,那么总线控制器/桥通常是完成转换的部件。

 

一个机敏的读者现在会提出这样的疑问:“既然如此,如果设备的endianness模式与总线的endianness模式不匹配,会怎样?“ 在这种情况下,必须执行额外的转换工作才能进行信息传递,这将在下一节谈到。

 

 

设备的Endianness

 

Kevin定理#1: 当一个多字节数据单元在两个具有相反endianness系统之间传输时,需要执行转换以维护数据单元的内存空间连续性。

 

我们在下面的讨论中假设CPU和总线具备相同的endianness。如果设备的endiannessCPU/bus相同,那么不需要执行转换。

 

在设备与CPU/busendianness不同的情形下,从硬件接线的角度,我们在此提供两种解决方式。以下的讨论假设CPU/bus类型为小端,而设备类型为大端。

 

字一致方案

 

在该解决方案中,我们对整个32-bit的设备数据线进行变换。我们用D031]表示设备的数据线,其中D0]存放最高位,而对于总线用AD310]表示。该方案建议将Di]联到AD31-i],其中i=0,...,31。字一致意味着整个(32-bit)字的语义得到了维护。

 

下图显示的是一个类型为大端的NIC card中的32-bit描述符寄存器。

 

 

 

 

在执行字一致交换后,CPU/bus上的结果数据为:

 

 

 

注意,转化的结果自动符合CPU/bus的字节序和比特序要求,而不需要通过软件(程序员)进行字节或比特的交换。

 

上述例子是针对数据并未超过32-bit内存边界的简单情形。现在我们看一个穿越边界的例子。在下面的例子中,vlan[0:24]的值为0xabcdef,并且穿越了32-bit内存边界。

 

 

在字一致转化后,结果为:

 

 

看到这里发生了什么?转换后的vlan被分割为两个非连续的内存空间:bytes[1:0]byte[7]。这违背了Kevin定理#1,而且我们无法定义一个结构良好的C结构来访问内存空间非连续的vlan

 

 

因此,字一致方案只适用于数据位于字边界之内的情形,对于存在边界穿越的数据并不适用。第二种方案可解决该问题。

 

字节一致方案

 

在该方案中,我们不执行字节间的变换,但是我们还是要对每个字节中的比特通过硬件绕线进行变换(设备中偏移量为i的比特转换为bus中偏移量为7-i的比特,i=0...7)。字节一致意味着字节的语义得到了维护。

 

在应该了该方案后,上图所示大端NIC设备中的值转换后的结果为:

 

 

现在,vlan的三个字节位于连续的内存空间,并且每个字节的内容可以被正确读出。但是转换后的记过在字节序角度看来依然很乱。然而,由于我们现在拥有一块连续的内存空间,可以交给软件来完成图中5字节数据交换的任务。最终结果为:

 

 

我们看到,在这种解决方案中软件执行的字节交换作为第二阶段。字节交换是由软件完成的,这不同于比特交换。

 

Kevin定理#:2 C中一个包含位域的结构中,如果位域A在位域B之前定义,那么位域A所占据的内存空间永远低于B所占用的内存空间。

 

现在一切都已经分类的井井有条,我们可以定义如下的C结构来访问NIC中的描述符:

 

struct nic_tag_reg {

        uint64_t vlan:24 __attribute__((packed));

        uint64_t rx  :6  __attribute__((packed));

        uint64_t tag :10 __attribute__((packed));

};

 

 

网络协议的Endianness

 

网络协议的endianness定义了网络协议头部中整数域发送和传输时所遵循的比特序和字节序。我们在此还要引入一个概念:绕线地址。一个低绕线地址比特或字节在发送和接受时永远位于高绕线地址比特或字节之前。

 

实际上,对于网络endianness,它于我们之前所看到的endianness有些许不同。对于网络endianness,还存在另外一个影响因素:物理连线上比特的发送和接受顺序。底层协议,例如以太网,对于比特的传输和接受顺序有特定规定,有时这个规定是与上层协议的endianness相反的。我们将在下面的例子中考虑这种情形。

 

NIC设备的endianness通常遵循它们所支持的网络协议所使用的endianness类型,因此可能与系统中CPUendianness不同。多数网络协议是大端的。此处我们以以太网和IP为例。

 

 

 

以太网的endianness

 

以太网是大端的。这意味着一个整数域的最高字节存放于低绕线地址,并且在接受和发送时位于最低字节之前。例如,以太网头部值为0x0806(ARP)协议域有如下的绕线布局:

 

wire byte offset:               0       1

hex             :           08      06

 

注意,以太网头部中MAC地址被视为字符串,因此不受字节序的影响。例如,MAC地址12345678 9a :bc有如下的绕线布局,并且值为12的字节被首先传输。

 

比特传输/接收序

 

比特传输/接受序规定了一个字节内的所有bit在物理线路中传输的顺序。对于以太网,顺序是由最不重要bit(低绕线地址)至最重要bit(高绕线地址)。这显然属于小端的类型。字节序仍保持为大端,如前所叙。因此,我们看到在这种情况下,字节序和比特传输/接收序是相反的。

 

下图展示了以太网的比特传输/接收序:

 

 

我们看到,MAC地址第一个字节中的最不重要bit,即组(多播)位,作为第一个bit出现在物理线路上。以太网和802.3硬件按照上述字节发传输/接受顺序一致性的工作。

 

 

在协议字节序与比特传输/接收序不同的情形下:NIC必须在传输时完成由主机(CPU)至比特序到以太网比特比特传输序的转换,而在接受时完成由以太网比特接受序至主机(CPU)比特序的转换。这样,上层协议就不用担心比特序而只需保证字节序的正确。实际上,这是另一种形式的字节一致转换方案,它保证了数据通过不同endianness时字节级语义的完整性。

 

 

比特传输/接受序通常对于CPU和软件是不可见的,但是对于硬件而言是个重要的问题,例如物理层的串并转化,NIC的数据线与总线的联接。

 

基于软件的以太网头部语法分析

 

对于任何类型的endianness,以太网头部可以用下面的C结构来完成软件的语法分析:

 

struct ethhdr

{

        unsigned char   h_dest[ETH_ALEN];      

        unsigned char   h_source[ETH_ALEN];    

        unsigned short  h_proto;               

};

 

 

h_desth_source域是字节数组,因此不需要转换。h_proto域是整数,因此在主机访问该域前需调用ntohs(),而在填充该域前需调用htons()

 

IPendianness

 

IP的字节序也为大端。而IP的比特序从CPU处继承,并由NIC负责其与物理传输线路中的比特传输/发送序进行转化。

 

对于大端主机,IP头部中的域可以被直接访问。对于小端主机(多数为基于x86PC)需要对IP头部中的整数域进行字节变换才能进行访问和填充。

 

下面是Linux Kernel中定义的iphdr结构。我们在读取整数前调用ntohs(),在填写整数前调用htons()。本质上,这两个函数在大端主机上不执行任何操作,而在小端主机上执行字节变换。

 

struct iphdr {

#if defined(__LITTLE_ENDIAN_BITFIELD)

        __u8    ihl:4,

                version:4;

#elif defined (__BIG_ENDIAN_BITFIELD)

        __u8    version:4,

                ihl:4;

#else

#error  "Please fix <asm/byteorder.h>"

#endif

        __u8    tos;

        __u16   tot_len;

        __u16   id;

        __u16   frag_off;

        __u8    ttl;

        __u8    protocol;

        __u16   check;

        __u32   saddr;

        __u32   daddr;

        /*The options start here. */

};

 

 

我们来查看IP头部中一些有意思的域。

 

 

version and ihl :根据IP标准,IP头部第一个字节中的最高4bit表示IP协议的版本。ihl表示第一个字节低4bit

 

有两种方法可以用来访问这些域。方法1直接从数据中进行提取。假设ver_ihl存放着IP头部的第一个字节,那么(ver_ihl 0x 0f )可得到ihl域,而(ver_ihl>>4)可得到verion域。这种方法对于任何一种endianness类型都适用。

 

方法二是定义上述的结构,然后通过结构来访问这些域。在上述结构中,如果主机为小端,那么我们定义ihlversion之前;如果主机为大端,我们定义versionihl之后。如果我们在此应用Kevin 定理#2—— 一个先定义的的域永远占据低地址空间,我们可以发现以上的C结构定义很好的符合了IP标准。

 

saddr and daddr fields:这两个域可以被视为整数或字节数组。如果视为字节数组的话,没有必要进行转化。如果被视为整数,那么则需要转化,以下是一个基于整数解释的函数

 

/*  dot2ip - convert a dotted decimal string into an

 *           IP address

 */

uint32_t dot2ip(char *pdot)

{

  uint32_t i,my_ip;

  my_ip=0;

  for (i=0; i<IP_ALEN; ++i) {

    my_ip = my_ip*256+atoi(pdot);

    if ((pdot = (char *) index(pdot,'.')) == NULL)

        break;            

        ++pdot;

    }

    return my_ip;

}

 

 

下面则是基于字节数组的函数:

 

uint32_t dot2ip2(char *pdot)

{

  int i;

  uint8_t ip[IP_ALEN];

  for (i=0; i<IP_ALEN; ++i) {

    ip[i] = atoi(pdot);

       if ((pdot = (char *) index(pdot,'.')) == NULL)

        break;         

     ++pdot;

  }

  return *((uint32_t *)ip);

}

 

总结

 

本文所讨论的关于字节序和比特序的问题还可以进一步深入 。希望本文已经介绍了该问题的主要方面。迷宫里下次见吧。

 

Kevin Kaichuan He 是一名Solustek Corp的高级软件工程师。他目前的工作致力于borad bring-up、嵌入式Linux和网络协议栈工程。他之前曾是Cisco公司的软件工程师、Purdue大学计算机系的助教。业余时间,他喜欢数码摄像,PS2游戏和电影。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


文章浏览阅读903次。文章主要介绍了收益聚合器Beefy协议在币安智能链测试网网上的编译测试部署流程,以Pancake上的USDC-BUSD最新Curve版流动池的农场质押为例,详细介绍了完整的操作流程。_怎么在bsc网络上部署应用
文章浏览阅读952次。比特币的主要思路是,构建一个无中心、去信任的分布式记账系统。交易签名只能保证交易不是他人伪造的,却不能阻止交易的发起者自己进行多重交易,即交易的发起者将一个比特币同时转账给两个人,也就是所谓的双花。比特币应用的区块链场景也叫做公链,因为这个区块链对所有人都是公开的。除此之外,还有一种区块链应用场景,被称作联盟链。区块链的出现,使得低成本,去信任的跨组织合作成为可能,将重构组织间的关系,这个关系既包括企业间的关系,也包括政府和企业间的关系,还有政府部门间的关系。
文章浏览阅读2.5k次。虚拟人从最初的不温不火,到现在步入“出生高峰期”,元宇宙可以说是功不可没。此前,量子位发布了《虚拟数字人深度产业报告》,报告显示,到2030年我国虚拟数字人整体市场规模将达到2700亿元。其中,“身份型虚拟人”市场规模预计达到1750亿元,占主导地位,而“服务型虚拟人”总规模也将超过950亿元。得益于AI、VR/AR 等技术的发展,虚拟人的应用场景正在从传统的虚拟偶像等娱乐行业迈向更多元化的领域。_最喜欢的虚拟角色
文章浏览阅读1.3k次,点赞25次,收藏13次。通过调查和分析用户需求、兴趣和行为,你可以更好地定位你的目标受众,并在市场中找到你的定位。在设计你的Web3.0项目时,注重用户界面的友好性、交互流畅性和功能的创新性,以提供独特的用户体验。通过与有影响力的人或组织进行合作,推广你的Web3.0项目。通过与他们分享你的项目并抓住他们的推荐,可以迅速获得更多的关注度。通过优化你的网站和内容,将有助于提高你的排名,并增加有机流量。通过提供奖励激励计划,如空投、奖励机制等,激励用户参与你的Web3.0项目。的人或组织合作,可以增加你的项目的曝光度。
文章浏览阅读1.7k次。这个智能合约安全系列提供了一个广泛的列表,列出了在 Solidity 智能合约中容易反复出现的问题和漏洞。Solidity 中的安全问题可以归结为智能合约的行为方式不符合它们的意图。我们不可能对所有可能出错的事情做一个全面的列表。然而,正如传统的软件工程有常见的漏洞主题,如 SQL 注入、缓冲区超限和跨网站脚本,智能合约中也有反复出现的。_solidity安全漏洞
文章浏览阅读1.3k次。本文描述了比特币核心的编译与交互方法_编译比特币
文章浏览阅读884次。四水归堂,是中国建筑艺术中的一种独特形式。这种形式下,由四面房屋围出一个天井,房屋内侧坡向天井内倾斜,下雨时雨水会从东西南北四方流入天井,从而起到收集水源,防涝护屋的作用,寓意水聚天心,天人合一。在科技产业当中,很多时候我们需要学习古人的智慧与意蕴,尝试打通各个生态,聚四方之力为我所用,这样才能为最终用户带来最大化价值。随着数字化、智能化的发展,算力成为生产力的根基。在这一大背景下,算力需要贯通软..._超聚变csdn
文章浏览阅读1k次,点赞24次,收藏19次。云计算和区块链是当代科技领域两个备受关注的核心技术。本文将深入探讨云计算和区块链的发展历程,详细剖析其起初阶段的奠基、面临的问题、业务内容、当前研究方向、用到的技术、实际应用场景、未来发展趋势,并提供相关链接供读者深入了解。
文章浏览阅读1.5k次。融入对等网络的奥妙,了解集中式、全分布式和混合式对等网络的差异,以及区块链网络的结构与协议,让你跃入区块链的连结网络。揭开密码学的神秘面纱,探寻对称密码学、非对称密码学、哈希函数、数字签名等关键技术,让你了解信息安全的核心。解码共识算法的精髓,从理论到实践,从PoW、PoS到PBFT,让你深入了解区块链如何达成共识。探索智能合约的世界,从定义到生命周期,从执行引擎到开发与部署,带你进入无限可能的合约领域。了解令人惊叹的区块链世界,从概念到价值,从发展历程到政策法规,一篇章串联出区块链的精髓。
文章浏览阅读777次。8 月份,加密货币市场经历了明显的波动,比特币价格波动幅度较大。与此同时,NFT 市场出现大幅下跌,引发了人们对这一新兴行业未来发展趋势的担忧
文章浏览阅读8.8k次,点赞53次,收藏37次。近二十年来,我国信息科技发展日益成熟,出现的网络完全问题也是“百花齐放”。而元宇宙作为5G技术、AR/VR技术、云计算以及区块链等技术的组合体,其安全性指定会被人们所广泛关注。根据前面所讲,元宇宙融合了虚拟世界和现实世界,通过数据将现实世界的各种元素映射到数字化的虚拟世界中。所以没有数据,就等于没有元宇宙的一切;没有信息安全,元宇宙的社会生产、生活就不能正常有序地进行。所以足以可见数据安全、信息安全对元宇宙发展起到的重要作用!!_元宇宙 安全计算
文章浏览阅读1.4k次。最早使用历史 1991年采用 时间戳 追溯 数字文档,之后 2009年后创始人**中本聪** (satoshi nakamoto )日裔美国人,在设计比特币数字货币中将此理念写入应用程序中_web3.0学习
文章浏览阅读1.7k次。DeFi收益来源全面概述_drfi收益
文章浏览阅读941次,点赞17次,收藏21次。号外:教链内参1.28《从BTC现货ETF的近期数据看到的》隔夜BTC经历现货ETF通过后的情绪冷静,一度破位40k后又逐渐修复至42k上方。请珍惜42k的BTC吧。也许到下个周期,我们将不再有机会见到这个高度的BTC了。下面,让我们重温,42k的BTC,在过去四年穿越牛熊的过程中,带给我们的启迪吧。需要提醒的是,历史文字,自有历史局限性,回顾,也须带着批判性的目光阅读和审视。2021年2月8日,...
文章浏览阅读1.2k次,点赞23次,收藏21次。其实一开始我也是这么想的,但根据PoW算法机制,如果你的计算量不够大,是无法控制区块链的走向的,也就是说,即使你投入了大量的成本用于完成任务,也不能保证自己成功。例如,你持有100个币,总共持有了30天,那么,此时你的币龄就为3000,这个时候,如果你发现了一个PoS区块,那么你的币龄就会被减去一定的值,每减少365个币龄,将会从区块中获得0.05个币的利息(可理解为年利率5%),那么在这个案例中,利息=3000×5%/365=0.41个币。前面说过,谁的算力强,谁最先解决问题的概率就越大。
文章浏览阅读1.9k次。这里主要实现的部分继续下去,对 Blockchain 这个对象有一些修改,如果使用 TS 的话可能要修改对应的 interface,但是如果是 JS 的话就无所谓了。需要安装的依赖有:express现在的 express 已经不内置 body-parser,需要作为单独的依赖下载request不下载会报错,是使用 request-promise 所需要的依赖和已经 deprecated 了,具体 reference 可以参考。_js区块链
文章浏览阅读1k次,点赞19次,收藏19次。作者:Zach Pandl Grayscale编译:象牙山首席村民 碳链价值以太坊在2023年取得了丰厚的回报。但表现不如比特币以及其他一些智能合约公链代币。我们认为,这反映了今年比特币特有的积极因素以及以太坊链上活动的缓慢复苏。尽管以太坊的涨幅低于比特币,但从绝对值和风险调整值来看,今年以太坊的表现优于传统资产类别。以太坊不断增长的L2生态系统的发展可能会吸引新用户,并在2024年支撑以太币的...
文章浏览阅读908次,点赞20次,收藏20次。通证是以数字形式存在,代表的是一种权利、一种固有和内在的价值。徐教授告诉我:多年的职业经历,多年的为易货贸易的思考,认识到在处理贸易和经济领域的关系时,应以提高人民生活水平、保证社会成员充分就业、保证就业成员实际收入和有效需求的大幅稳定增长、实现世界资源的充分利用以及扩大货物的生产和交换为目的,期望通过达成互惠互利安排,实行公开、公平、公正的“三公原则”,开展国家与国家、企业与企业之间的易货贸易,规避因信用问题引起的各类风险,消除国际贸易中的歧视待遇,促进全球国家的经济发展,从而为实现上述目标做出贡献。
文章浏览阅读2.5k次。由于webase文档原因,查找起来比较局限,有时候想找一个api却又忘了在哪个模块的目录下,需要一步一步单独点,而利用文档自带的检索功能又因为查找文档全部信息,显得十分缓慢,所以整理了有关WeBASE的api列表但不可否认,现在只有列表,没有对应的页面跳转,文章目的也只是为了多了解webase的接口_webase私钥管理里获取
文章浏览阅读1.4k次,点赞28次,收藏21次。基于​openzeppelin来构建我们的NFT,并用一个例子来手把手的说明如何在opensea快速发布自己的NFT智能合约(ERC721)。