linux kernel如何處理大端小端字節序

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

最近在作將kernel由小端處理器(arm)向大端處理器(ppc)的移植的工做,如今kernel進入console穩定工做,基本工做已經完成,不過移植中有不少心得仍是須要總結下,今天先將kernel對於大小端字節序的處理來總結下。redis

 

以前寫過大小端字節序的思考,文章連接地址:http://blog.csdn.NET/skyflying2012/article/details/42065427。併發

根據以前的理解,字節序能夠認爲是處理器主觀的概念,就像人如何去看待事物同樣,處理器分大端和小端,對於內存的讀寫,只要保證數據類型一致,就不存在字節序的問題。app

所以我感受,字節序不一樣形成的最大差別在於對於寄存器的讀寫。由於外設寄存器都是小端的(根據kernel代碼得出結論,下面還會在詳細解釋)函數

根據我以前字節序思考的文章,對於寄存器讀寫差別,有2種方案:性能

(1)從硬件上解決這個問題,對於32位cpu,將32根數據總線反接,可是這樣對於尋址小於32位數據可能有問題,而且不能全部模塊都反接(如內存),這還涉及到編譯器的問題。ui

(2)從軟件上解決這個問題,在底層讀寫寄存器函數中,將讀/寫的數據進行swap。this

做爲軟件人員,我最關心第二種方案是否可行,由於在讀寫寄存器時對數據進行swap,增長了寄存器讀寫的複雜度,原來一條存儲/加載指令能夠完成的工做,如今可能須要增長一些更swap相關的指令,沒法保證寄存器操做的原子性了。對於高性能,大併發的系統,可能形成競態。spa

所以用最少的指令完成數據swap和r/w寄存器,才能保證Linux系統正常穩定運行。.net

在移植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          \n"  
  5.     "   rotr    %0, %0, 16      \n"  
  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\n\t"  
  6.         "rlwimi %0,%1,8,8,15\n\t"  
  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,這也說明了外設寄存器數據排布是小端字節序的。

相關文章
相關標籤/搜索