zoukankan      html  css  js  c++  java
  • linux kernel如何处理大端小端字节序

    (转)http://blog.csdn.net/skyflying2012/article/details/43771179

    最近在做将kernel由小端处理器(arm)向大端处理器(ppc)的移植的工作,现在kernel进入console稳定工作,基本工作已经完成,不过移植中有很多心得还是需要总结下,今天先将kernel对于大小端字节序的处理来总结下。

    之前写过大小端字节序的思考,文章链接地址:http://blog.csdn.NET/skyflying2012/article/details/42065427。

    根据之前的理解,字节序可以认为是处理器主观的概念,就像人如何去看待事物一样,处理器分大端和小端,对于内存的读写,只要保证数据类型一致,就不存在字节序的问题。

    因此我感觉,字节序不同造成的最大差异在于对于寄存器的读写。因为外设寄存器都是小端的(根据kernel代码得出结论,下面还会在详细解释)

    根据我之前字节序思考的文章,对于寄存器读写差异,有2种方案:

    (1)从硬件上解决这个问题,对于32位cpu,将32根数据总线反接,但是这样对于寻址小于32位数据可能有问题,并且不能所有模块都反接(如内存),这还涉及到编译器的问题。

    (2)从软件上解决这个问题,在底层读写寄存器函数中,将读/写的数据进行swap。

    作为软件人员,我最关心第二种方案是否可行,因为在读写寄存器时对数据进行swap,增加了寄存器读写的复杂度,原来一条存储/加载指令可以完成的工作,现在可能需要增加一些更swap相关的指令,无法保证寄存器操作的原子性了。对于高性能,大并发的系统,可能造成竞态。

    因此用最少的指令完成数据swap和r/w寄存器,才能保证Linux系统正常稳定运行。

    在移植bootloader中我是将数据进行位移来完成swap,因bootloader单进程,不会存在竞态问题。

    在kernel移植时很担心这个问题,但是发现kernel下已经提供了大小端处理器操作寄存器时的通用函数,就是readl/writel(以操作32位寄存器为例)。

    对于driver的开发者不需要关心处理器的字节序,寄存器操作直接使用readl/writel即可。

    网上有很多文章提到readl/writel,但是没有具体分析其实现。

    今天就主要来分析下readl/writel如何实现高效的数据swap和寄存器读写。我们就以readl为例,针对big-endian处理器,如何来对寄存器数据进行处理。

    kernel下readl定义如下,在include/asm-generic/io.h

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #define readl(addr) __le32_to_cpu(__raw_readl(addr))  

    __raw_readl是最底层的寄存器读写函数,很简单,就从直接获取寄存器数据。来看__le32_to_cpu的实现,该函数针对字节序有不同的实现,对于小端处理器,在./include/linux/byteorder/little_endian.h中,如下:

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #define __le32_to_cpu(x) ((__force __u32)(__le32)(x))  

    相当于什么都没做。而对于大端处理器,在./include/linux/byteorder/big_endian.h中,如下:

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #define __le32_to_cpu(x) __swab32((__force __u32)(__le32)(x))  

    看字面意思也可以看出,__swab32实现数据翻转。等下我们就来分析__swab32的实现,精髓就在这个函数。

    但是这之前先考虑一个问题,对于不同CPU,如arm mips ppc,怎么来选择使用little_endian.h还是big_endian.h的呢。

    答案是,针对不同处理器平台,有arch/xxx/include/asm/byteorder.h头文件,来看下arm mips ppc的byteorder.h分别是什么。

    arch/arm/include/asm/byteorder.h

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1.  *  arch/arm/include/asm/byteorder.h  
    2.  *  
    3.  * ARM Endian-ness.  In little endian mode, the data bus is connected such  
    4.  * that byte accesses appear as:  
    5.  *  0 = d0...d7, 1 = d8...d15, 2 = d16...d23, 3 = d24...d31  
    6.  * and word accesses (data or instruction) appear as:  
    7.  *  d0...d31  
    8.  *  
    9.  * When in big endian mode, byte accesses appear as:  
    10.  *  0 = d24...d31, 1 = d16...d23, 2 = d8...d15, 3 = d0...d7  
    11.  * and word accesses (data or instruction) appear as:  
    12.  *  d0...d31  
    13.  */  
    14. #ifndef __ASM_ARM_BYTEORDER_H  
    15. #define __ASM_ARM_BYTEORDER_H  
    16.   
    17. #ifdef __ARMEB__  
    18. #include <linux/byteorder/big_endian.h>  
    19. #else  
    20. #include <linux/byteorder/little_endian.h>  
    21. #endif  
    22.   
    23. #endif  

    arch/mips/include/asm/byteorder.h

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /*  
    2.  * This file is subject to the terms and conditions of the GNU General Public  
    3.  * License.  See the file "COPYING" in the main directory of this archive  
    4.  * for more details.  
    5.  *  
    6.  * Copyright (C) 1996, 99, 2003 by Ralf Baechle  
    7.  */  
    8. #ifndef _ASM_BYTEORDER_H  
    9. #define _ASM_BYTEORDER_H  
    10.   
    11. #if defined(__MIPSEB__)  
    12. #include <linux/byteorder/big_endian.h>  
    13. #elif defined(__MIPSEL__)  
    14. #include <linux/byteorder/little_endian.h>  
    15. #else  
    16. # error "MIPS, but neither __MIPSEB__, nor __MIPSEL__???"  
    17. #endif  
    18.   
    19. #endif /* _ASM_BYTEORDER_H */  

    arch/powerpc/include/asm/byteorder.h

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #ifndef _ASM_POWERPC_BYTEORDER_H  
    2. #define _ASM_POWERPC_BYTEORDER_H  
    3.   
    4. /*  
    5.  * This program is free software; you can redistribute it and/or  
    6.  * modify it under the terms of the GNU General Public License  
    7.  * as published by the Free Software Foundation; either version  
    8.  * 2 of the License, or (at your option) any later version.  
    9.  */  
    10. #include <linux/byteorder/big_endian.h>  
    11.   
    12. #endif /* _ASM_POWERPC_BYTEORDER_H */  


    可以看出arm mips在kernel下大小端都支持,arm mips也的确是可以选择处理器字节序。ppc仅支持big-endian。(其实ppc也是支持选择字节序的)

    各个处理器平台的byteorder.h将littlie_endian.h/big_endian.h又包了一层,我们在编写driver时不需要关心处理器的字节序,只需要包含byteorder.h即可。

    接下来看下最关键的__swab32函数,如下:

    在include/linux/swab.h中

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. /**  
    2.  * __swab32 - return a byteswapped 32-bit value  
    3.  * @x: value to byteswap  
    4.  */  
    5. #define __swab32(x)               
    6.     (__builtin_constant_p((__u32)(x)) ?   
    7.     ___constant_swab32(x) :           
    8.     __fswab32(x))  


    宏定义展开,是一个条件判断符。

     __builtin_constant_p是一个gcc的内建函数, 用于判断一个值在编译时是否是常数,如果参数是常数,函数返回 1,否则返回 0。
    如果数据是常数,则__constant_swab32,实现如下:

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. #define ___constant_swab32(x) ((__u32)(               
    2.     (((__u32)(x) & (__u32)0x000000ffUL) << 24) |          
    3.     (((__u32)(x) & (__u32)0x0000ff00UL) <<  8) |          
    4.     (((__u32)(x) & (__u32)0x00ff0000UL) >>  8) |          
    5.     (((__u32)(x) & (__u32)0xff000000UL) >> 24)))  

    对于常数数据,采用的是普通的位移然后拼接的方法,对于常数,这样的消耗是有必要的(这是kernel的解释,不是很理解)

    如果数据是运行时计算数据,则使用__fswab32,实现如下:

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static inline __attribute_const__ __u32 __fswab32(__u32 val)  
    2. {  
    3. #ifdef __arch_swab32  
    4.     return __arch_swab32(val);  
    5. #else  
    6.     return ___constant_swab32(val);  
    7. #endif  
    8. }  

    如果未定义__arch_swab32,则还是采用__constant_swab32方法翻转数据,但是arm mips ppc都定义了各自平台的__arch_swab32,来实现一个针对自己平台的高效的swap,分别定义如下:

    arch/arm/include/asm/swab.h

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static inline __attribute_const__ __u32 __arch_swab32(__u32 x)  
    2. {  
    3.     __asm__ ("rev %0, %1" : "=r" (x) : "r" (x));  
    4.     return x;  
    5. }  

    arch/mips/include/asm/swab.h

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static inline __attribute_const__ __u32 __arch_swab32(__u32 x)  
    2. {  
    3.     __asm__(  
    4.     "   wsbh    %0, %1           "  
    5.     "   rotr    %0, %0, 16       "  
    6.     : "=r" (x)  
    7.     : "r" (x));  
    8.   
    9.     return x;  
    10. }  


    arch/powerpc/include/asm/swab.h

    [plain] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. static inline __attribute_const__ __u32 __arch_swab32(__u32 value)  
    2. {  
    3.     __u32 result;  
    4.   
    5.     __asm__("rlwimi %0,%1,24,16,23 "  
    6.         "rlwimi %0,%1,8,8,15 "  
    7.         "rlwimi %0,%1,24,0,7"  
    8.         : "=r" (result)  
    9.         : "r" (value), "0" (value >> 24));  
    10.     return result;  
    11. }  


    可以看出,arm使用1条指令(rev数据翻转指令),mips使用2条指令(wsbh rotr数据交换指令),ppc使用3条指令(rlwimi数据位移指令),来完成了32 bit数据的翻转。这相对于普通的位移拼接的方法要高效的多!

    其实从函数名__fswab也可以看出是要实现fast swap的。

    我们反过来思考下,kernel针对小端处理器的寄存器读写数据没有做任何处理,而对于大端处理器却做了swap,这也说明了外设寄存器数据排布是小端字节序的。

  • 相关阅读:
    评教说明
    使用firebird2.1与dbEntry.net做的设备报修小程序
    不知道为什么IList.Contains()总是返回FALSE
    DbEntry.net复合索引设置
    招生网上报名程序090512.rar
    aspnetdb生成
    推荐工具ActiveWriter
    dbEntry.net CK.K的高级应用
    tomcat添加虚拟子目录
    短信网关与短信猫
  • 原文地址:https://www.cnblogs.com/lys0827/p/5947984.html
Copyright © 2011-2022 走看看