linux kernel 如何处理大小端

暂时在用MPC8309,不太清楚大小端内核是什么时候给转的。

今天看了关于readl和writel具体实现的文章

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

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

#define readw(addr) __le32_to_cpu(__raw_readw(addr))

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

#define __le32_to_cpu(x) ((__force __u32)(__le32)(x))

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

#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

  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. #ifdef __ARMEB__
  17. #include <linux/byteorder/big_endian.h>
  18. #else
  19. #include <linux/byteorder/little_endian.h>
  20. #endif
  21. #endif

arch/mips/include/asm/byteorder.h

  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. #if defined(__MIPSEB__)
  11. #include <linux/byteorder/big_endian.h>
  12. #elif defined(__MIPSEL__)
  13. #include <linux/byteorder/little_endian.h>
  14. #else
  15. # error "MIPS, but neither __MIPSEB__, nor __MIPSEL__???"
  16. #endif
  17. #endif /* _ASM_BYTEORDER_H */

arch/powerpc/include/asm/byteorder.h

  1. #ifndef _ASM_POWERPC_BYTEORDER_H
  2. #define _ASM_POWERPC_BYTEORDER_H
  3. /*
  4. * This program is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU General Public License
  6. * as published by the Free Software Foundation; either version
  7. * 2 of the License, or (at your option) any later version.
  8. */
  9. #include <linux/byteorder/big_endian.h>
  10. #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中

  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,实现如下:

  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,实现如下:

  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

  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

  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. return x;
  9. }

arch/powerpc/include/asm/swab.h

  1. static inline __attribute_const__ __u32 __arch_swab32(__u32 value)
  2. {
  3. __u32 result;
  4. __asm__("rlwimi %0,%1,24,16,23\n\t"
  5. "rlwimi %0,%1,8,8,15\n\t"
  6. "rlwimi %0,%1,24,0,7"
  7. : "=r" (result)
  8. : "r" (value), "0" (value >> 24));
  9. return result;
  10. }

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

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

时间: 2024-10-09 02:35:53

linux kernel 如何处理大小端的相关文章

linux kernel如何处理大端小端字节序

最近在做将kernel由小端处理器(arm)向大端处理器(ppc)的移植的工作,现在kernel进入console稳定工作,基本工作已经完成,不过移植中有很多心得还是需要总结下,今天先将kernel对于大小端字节序的处理来总结下. 之前写过大小端字节序的思考,文章链接地址:http://blog.csdn.net/skyflying2012/article/details/42065427. 根据之前的理解,字节序可以认为是处理器主观的概念,就像人如何去看待事物一样,处理器分大端和小端,对于内存

Linux中判断大小端的一种方法

大小端的定义无需赘言,常用的方法有使用联合体和指针法,如: int checkCPU() { union w { int a; char b; }c; c.a = 1; return (c.b == 1); // 小端返回TRUE,大端返回FALSE } 实际上Linux操作系统的源码中,其判断更为简洁: static union { char c[4]; unsigned long mylong; } endian_test = {{ 'l', '?', '?', 'b' } }; #defi

Linux内核中大小端判定宏

#include <stdio.h> static union{ char c[4];unsigned long mylong;} endian_test = { {'l','?','?','b'} }; #define ENDIANNESS ( (char) endian_test.mylong ) int main() { printf("%c",ENDIANNESS); } 略显简洁

【转】htonl(),htons(),ntohl(),ntons()--大小端模式转换函数

转自 http://www.cnblogs.com/kungfupanda/archive/2013/04/24/3040785.html 不同机器内部对变量的字节存储顺序不同,有的采用大端模式(big-endian),有的采用小端模式(little-endian).大端模式是指高字节数据存放在低地址处,低字节数据放在高地址处.小端模式是指低字节数据存放在低地址处,高字节数据放在高地址处. 在网络上传输数据时,由于数据传输的两端可能对应不同的硬件平台,采用的存储字节顺序也可能不一致,因此 TCP

linux kernel对于浮点运算的支持

目前大多数CPU都支持浮点运算单元FPU,FPU作为一个单独的协处理器放置在处理器核外,但是对于嵌入式处理器,浮点运算本来就少用,有些嵌入式处理器就会去掉浮点协处理器. X86处理器一般都是有FPU的.而ARM PPC MIPS处理器就会出现没有FPU的现象. linux kernel如何处理浮点运算,我们就分为带FPU的处理器和不带FPU的处理器来讨论. (以下为个人知识总结,研究不深,错误之处希望大家指正,共同学习) 一 对于带FPU的处理器 1 对于linux kernel来说,kerne

Linux程序设计学习笔记----网络编程之网络数据包拆封包与字节顺序大小端

网络数据包的封包与拆包 过程如下: 将数据从一台计算机通过一定的路径发送到另一台计算机.应用层数据通过协议栈发到网络上时,每层协议都要加上一个数据首部(header),称为封装(Encapsulation),如下图所示: 不同的协议层对数据包有不同的称谓,在传输层叫做段(segment),在网络层叫做数据包(packet),在链路层叫做帧(frame).数据封装成帧后发到传输介质上,到达目的主机后每层协议再剥掉相应的首部,最后将应用层数据交给应用程序处理. 上图对应两台计算机在同一网段中的情况,

[Linux] Big-endian and Little-endian (大小端模式)

大小端模式 大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放: 小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和我们的逻辑方法一致.

如何处理错误消息Please install the Linux kernel header files

Please install the Linux kernel "header" files matching the current kernel 当我启动minilkube时遇到如下错误消息: Minikube setup with driver virtualbox Starting local Kubernetes v1.10.0 cluster... Starting VM... E1010 03:57:24.565157 9896 start.go:174] Error s

Linux Kernel - Debug Guide (Linux内核调试指南 )

http://blog.csdn.net/blizmax6/article/details/6747601 linux内核调试指南 一些前言 作者前言 知识从哪里来 为什么撰写本文档 为什么需要汇编级调试 ***第一部分:基础知识*** 总纲:内核世界的陷阱 源码阅读的陷阱 代码调试的陷阱 原理理解的陷阱 建立调试环境 发行版的选择和安装 安装交叉编译工具 bin工具集的使用 qemu的使用 initrd.img的原理与制作 x86虚拟调试环境的建立 arm虚拟调试环境的建立 arm开发板调试环