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
- #define readl(addr) __le32_to_cpu(__raw_readl(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
- * arch/arm/include/asm/byteorder.h
- *
- * ARM Endian-ness. In little endian mode, the data bus is connected such
- * that byte accesses appear as:
- * 0 = d0...d7, 1 = d8...d15, 2 = d16...d23, 3 = d24...d31
- * and word accesses (data or instruction) appear as:
- * d0...d31
- *
- * When in big endian mode, byte accesses appear as:
- * 0 = d24...d31, 1 = d16...d23, 2 = d8...d15, 3 = d0...d7
- * and word accesses (data or instruction) appear as:
- * d0...d31
- */
- #ifndef __ASM_ARM_BYTEORDER_H
- #define __ASM_ARM_BYTEORDER_H
- #ifdef __ARMEB__
- #include <linux/byteorder/big_endian.h>
- #else
- #include <linux/byteorder/little_endian.h>
- #endif
- #endif
arch/mips/include/asm/byteorder.h
- /*
- * This file is subject to the terms and conditions of the GNU General Public
- * License. See the file "COPYING" in the main directory of this archive
- * for more details.
- *
- * Copyright (C) 1996, 99, 2003 by Ralf Baechle
- */
- #ifndef _ASM_BYTEORDER_H
- #define _ASM_BYTEORDER_H
- #if defined(__MIPSEB__)
- #include <linux/byteorder/big_endian.h>
- #elif defined(__MIPSEL__)
- #include <linux/byteorder/little_endian.h>
- #else
- # error "MIPS, but neither __MIPSEB__, nor __MIPSEL__???"
- #endif
- #endif /* _ASM_BYTEORDER_H */
arch/powerpc/include/asm/byteorder.h
- #ifndef _ASM_POWERPC_BYTEORDER_H
- #define _ASM_POWERPC_BYTEORDER_H
- /*
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version
- * 2 of the License, or (at your option) any later version.
- */
- #include <linux/byteorder/big_endian.h>
- #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中
- /**
- * __swab32 - return a byteswapped 32-bit value
- * @x: value to byteswap
- */
- #define __swab32(x) \
- (__builtin_constant_p((__u32)(x)) ? \
- ___constant_swab32(x) : \
- __fswab32(x))
宏定义展开,是一个条件判断符。
__builtin_constant_p是一个gcc的内建函数, 用于判断一个值在编译时是否是常数,如果参数是常数,函数返回 1,否则返回 0。
如果数据是常数,则__constant_swab32,实现如下:
- #define ___constant_swab32(x) ((__u32)( \
- (((__u32)(x) & (__u32)0x000000ffUL) << 24) | \
- (((__u32)(x) & (__u32)0x0000ff00UL) << 8) | \
- (((__u32)(x) & (__u32)0x00ff0000UL) >> 8) | \
- (((__u32)(x) & (__u32)0xff000000UL) >> 24)))
对于常数数据,采用的是普通的位移然后拼接的方法,对于常数,这样的消耗是有必要的(这是kernel的解释,不是很理解)
如果数据是运行时计算数据,则使用__fswab32,实现如下:
- static inline __attribute_const__ __u32 __fswab32(__u32 val)
- {
- #ifdef __arch_swab32
- return __arch_swab32(val);
- #else
- return ___constant_swab32(val);
- #endif
- }
如果未定义__arch_swab32,则还是采用__constant_swab32方法翻转数据,但是arm mips ppc都定义了各自平台的__arch_swab32,来实现一个针对自己平台的高效的swap,分别定义如下:
arch/arm/include/asm/swab.h
- static inline __attribute_const__ __u32 __arch_swab32(__u32 x)
- {
- __asm__ ("rev %0, %1" : "=r" (x) : "r" (x));
- return x;
- }
arch/mips/include/asm/swab.h
- static inline __attribute_const__ __u32 __arch_swab32(__u32 x)
- {
- __asm__(
- " wsbh %0, %1 \n"
- " rotr %0, %0, 16 \n"
- : "=r" (x)
- : "r" (x));
- return x;
- }
arch/powerpc/include/asm/swab.h
- static inline __attribute_const__ __u32 __arch_swab32(__u32 value)
- {
- __u32 result;
- __asm__("rlwimi %0,%1,24,16,23\n\t"
- "rlwimi %0,%1,8,8,15\n\t"
- "rlwimi %0,%1,24,0,7"
- : "=r" (result)
- : "r" (value), "0" (value >> 24));
- return result;
- }
可以看出,arm使用1条指令(rev数据翻转指令),mips使用2条指令(wsbh rotr数据交换指令),ppc使用3条指令(rlwimi数据位移指令),来完成了32 bit数据的翻转。这相对于普通的位移拼接的方法要高效的多!
其实从函数名__fswab也可以看出是要实现fast swap的。
我们反过来思考下,kernel针对小端处理器的寄存器读写数据没有做任何处理,而对于大端处理器却做了swap,这也说明了外设寄存器数据排布是小端字节序的。
linux kernel如何处理大端小端字节序的更多相关文章
- 大端字节序&小端字节序(网络字节序&主机字节序)
大端字节序:整数的高位字节存储在内存的低地址处,低字节存储在内存的高地址处. 小端字节序:整数的高位字节存储在内存的高地址处,低字节存储在内存的低地址处. 一般pc大多采用小端字节序,也称为主机字节序 ...
- 写一个c程序辨别系统是大端or小端字节序
字节序有两种表示方法:大端字节序(big ending),小端字节序(little ending) 看一个unsigned short 数据,它占2个字节,给它赋值0x1234.若采用的大端字节序, ...
- C/C++字节序(大端/小端)判断
C/C++大端小端判断 说的是变量的高字节.低字节在内存地址中的排放顺序. 变量的高字节放到内存的低地址中(变量的低字节放到内存的高地址中)==>大端 变量的高字节放到内存的高地址中(变量的低字 ...
- Linux网络编程1——小端模式与大端模式
数据存储优先顺序的转换 计算机数据存储有两种字节优先顺序:高位字节优先(称为大端模式)和低位字节优先(称为小端模式).内存的低地址存储数据的低字节,高地址存储数据的高字节的方式叫小端模式.内存的高地址 ...
- C/C++学习笔记---高地址、低地址、大段字节序、小段字节序
字节顺序是指占内存多于一个字节类型的数据在内存中的存放顺序,通常有小端.大端两种字节顺序. 小端字节序指低字节数据存放在内存低地址处,高字节数据存放在内存高地址处: 大端字节序是高字节数据存放在低地址 ...
- Java中如何判断当前环境是大端字节顺序还是小端字节顺序
Java非字节类型的基本类型,除了布尔型都是由组合在一起的几个字节组成的.这些数据类 型及其大小总结在表 2-1 中. 表:基本数据类型及其大小 数据类型 大小(以字节表示) Byte 1 Char ...
- 从inet_pton()看大小端字节序
#include<stdio.h> #include<netinet/in.h> #include<stdlib.h> #include<string.h&g ...
- c# 16进制大端小端解析长度
//前两个字节为长度的解析string hexstr = "00 13 59 02 80 00 E7 00 80 00 E9 00 80 00 EA 00 80 00 EB 00 80&qu ...
- 不同生产商的CPU以及大端/小端对齐
● 不同生产商的CPU以及大端/小端对齐 ※ ARM.AMD.Atom和intel之间的关系 intel公司和AMD公司生产的是相同的x86架构的CPU,这种CPU属于CISC(Complex I ...
随机推荐
- Windows类标识符及其妙用
Windows类标识符 百度百科这样解释: Windows的类标识符class identifier也称为CLASSID或CLSID,是与某一个类对象相联系的唯一标记(UUID).一个准备创建多个对象 ...
- 使用JMETER进行REST API测试(分步指南)
我确定你在这里是因为你需要加载测试Json Rest API.这并不奇怪,因为Rest API现在越来越受欢迎. 这本指南的目的:帮助您进行负载测试一个Json的 REST API 通过一个具体的例子 ...
- 移动端 Web 网页调试技巧
原文出处: 盛瀚钦 本文主要列举了调试本地网页.查看测试环境网页的各种方法,涵盖了PC.iPad.移动端的调试技巧. 本文的不足之处在于,小溪里暂时还没有找到调试位于微信中和安卓各国产浏览器上的网页. ...
- JAVA本地项目手机访问
http://192.168.40.56:7082/carloan/#newOrderView ipconfig 找到本地ip,然后把localhost改成本地ip就行
- html 手机端click 事件去掉黑色阴影效果
添加css样式 html{-webkit-text-size-adjust: 100%;-webkit-tap-highlight-color: rgba(0, 0, 0, 0);} 1. -web ...
- ubuntu 下 docker安装
1移除以前安装docker sudo apt-get remove docker docker-engine docker-ce docker.io 2 安装包以允许apt通过HTTPS使用存储库 s ...
- 难道这就是gin中间件的原理,一个装饰者模式而已?
func wrapCtx(handler func(ctx *gin.Context)) gin.HandlerFunc { return func(c *gin.Context) { //获取请求的 ...
- ubuntu中使用eclipse开发android,logcat显示问题
〜/工作区/ .metadata / .plugins / org.eclipse.core.runtime / .settings / com.android.ide.eclipse.ddms.pr ...
- 粗谈Android未来前景
Andriod作为智能手机机兴起的操作系统,有着非同寻常的地位.而相对于他的竞争对手ios,两大系统各有自身的优缺点,有太多的不同点,但相比较用户体验来说ios略胜一筹. Android系统极具开发性 ...
- 关键字: on
关键字: on 数据库在通过连接两张或多张表来返回记录时,都会生成一张中间的临时表,然后再将这张临时表返回给用户. 在使用left jion时,on和where条件的区别如下: 1. on条件是在生成 ...