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

Linux 系统判断大小端 大端模式:指CPU对操作数的存放方式是将数据的高位存放在内存中的低地址,而数据的低位存放在内存中的搞地址。 小模式:指CPU对操作数的存放方式是将数据的低位存放在内存中的高地址,而数据的低位存放在内存中的搞地址。 大多数ARM处理器都是采用的小模式,网络字节序采用的是大端模式。 判断程序 #include <stdio.h> int check_system... 阅读详情

最近在做将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,这也说明了外设寄存器数据排布是小端字节序的。












大小端详解 目录 一、大端模式与小模式的起源 二、什么是大端和小 三、数组在大端情况下的存储: 四、为什么会有大小端模式之分呢? 五、如何判断机器的字节序 六、常见的字节序 七、如何进行转换 八、从软件的角度理解模式 九、从系统的角度理解模式 十、实际中的例子 一、大端模式与小模式的起源 关于大端名词的由来,有一个有趣的故事,来自于Jonathan S... 阅读详情

相关推荐

Linux内核判断大小端,linux kernel 如何处理大小端

暂时在用MPC8309,不太清楚大小端内核是什么时候给转的。今天看了关于readl和writel具体实现的文章今天就主要来分析下readl/writel如何实现高效的数据swap和寄存器读写。我们就以readl为例,针对big-endian处理器,如何来对寄存器数据进行处理kernel下readl定义如下,在include/asm-generic/io.h#define readw(addr) ...

weixin_39831705的博客 702

linux源码分析之字节序(5)-- swab.h

linux源码分析之字节序(3)、linux源码分析之字节序(4)中都有看到,源码中包含了           #include 该头函数里面介绍了字节交换的具体方法。我们来看看具体代码: ----------------------------------------------------------------------------------------------------

刘锐群的笔记 2669

linux运行大端程序,linux kernel如何处理大端字节序

最近在做将kernel由小处理器(arm)向大端处理器(ppc)的移植的工作,现在kernel进入console稳定工作,基本工作已经完成,不过移植中有很多心得还是需要总结下,今天先将kernel对于大小端字节序处理来总结下。之前写过大小端字节序的思考,文章链接地址:http://blog.csdn.net/skyflying2012/article/details/42065427。根据之前...

weixin_28692153的博客 425

__asm__ __volatile__内嵌汇编用法简述

2012-12-21 17:27 __asm__ __volatile__内嵌汇编用法简述 =================================转摘他人不求甚解========================================== __asm__ __volatile__内嵌汇编用法简述 在阅读C/C++原码时经常会遇到内联汇编的情况,下面简要介绍下__as

wzw12315的专栏 1421

linux kernel struct regmap_config结构详解

* 基础标识与总线类型 */// Regmap 实例名称(调试用)// 总线类型(如 REGMAP_BUS_I2C、REGMAP_BUS_SPI、REGMAP_BUS_MMIO 等)/* 寄存器与值的位宽配置 */// 寄存器地址的位宽(如 8/16/32 位)// 寄存器值的位宽(如 8/16/32 位)// 地址/值与实际总线传输间的填充位(通常为 0)/* 字节序与对齐方式 */// 寄存器地址的字节序大端/小/不转换)// 寄存器值的字节序(同上)

jongden的专栏 1412

内核的驱动和在cpu的大小端无关,内核提供一套函数来处理大小端的区别

最近在做将kernel由小处理器(arm)向大端处理器(ppc)的移植的工作,现在kernel进入console稳定工作,基本工作已经完成,不过移植中有很多心得还是需要总结下,今天先将kernel对于大小端字节序处理来总结下。 之前写过大小端字节序的思考,文章链接地址:http://blog.csdn.NET/skyflying2012/article/details/420

one shot,one kill. 1251

linux kernel 驱动(of系列)

在驱动模块中声明 of_device_id 数组,并为每个条目指定 .compatible 字符串和对应的 .data:MODULE_DEVICE_TABLE // 注册到内核模块表。

weixin_52972671的博客 825

linux中cpu_to_be32,linux中sparce与__be32

be表示big endian,大端,le表示小。__be32,__le32都是一样的,其实就是__u32,具体是什么数据又系统决定的。网络协议也是采用大端数据。目前主要是用来发现大小端不匹配的错误。比如往big-endian的寄存器里面写入little-endian的数据。cpu_to_be32() /*convertcpu's byte ordertobig-endian*/be32_...

weixin_42146086的博客 3031

linux内核 大小端,Linux系统的大小端模式

大端模式所谓的大端模式,是指数据的低位(就是权值较小的后面那几位)保存在内存的高地址中,而数据的高位,保存在内存的低地址中,这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;小模式所谓的小模式,是指数据的低位保存在内存的低地址中,而数 据的高位保存在内存的高地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低,和...

weixin_35879493的博客 435

ARM cpu架构Linux内核启动流程分析

处理uboot传入的参数(机器ID 、启动参数)判断是否支持这个CPU(架构)判断是否支持这个单板(机器ID)建立页表(一级页表)使能MMU复制数据段、清楚BSS段、设置栈指针、保存CPU ID跳转到start_kernel

qq_41882586的博客 575

编写具有良好移植Linux C代码的若干技巧

摘要:本文主要讲述了linux内核开发过程中,需要注意的有关提高代码可移植性的若干技巧,同样对编写Linux应用程序也有较好的参考意义。干货满满,请君一阅。

Leon的博客 539

.NET封装哲学:字节序、句柄与并发集合的跨平台生存指南

在.NET开发中,封装不是隐藏复杂性,而是构建跨平台、线程安全、硬件无关的可靠契约。网络字节序(如IPAddress.HostToNetworkOrder)解决大端/小通信一致性问题;IntPtr抽象屏蔽了32/64位指针差异,是Windows/Linux/macOS句柄互操作的基石;ConcurrentDictionary和Lazy<T>则通过内存屏障与分段锁,将CLR内存模型与CPU缓存一致性封装为开箱即用的线程安全原语。这些设计共同支撑着高可用、可迁移、易维护的企业级应用——尤其在工业物联网、微服务

diaoqi6581的博客 320

Linux设备驱动程序学习笔记——第十一章 内核的数据结构

Linux设备驱动程序学习笔记 第十一章 内核的数据结构 坚持使用严格的数据类型,并且使用-Wall -Wstrict-prototypes选项编译可以防止大多数的代码缺陷,提高可移植性。 内核使用的数据类型主要被分为三大类: 类似int的标准C语言类型 类似u32的有确定大小的类型 类似pid_t这样用于特定内核对象的类型 一、使用标准C语言类型 要避免类型冲突和潜在的风险。标准C语言类型会随体系架构发生变化,所占空间大小并不相同,主要表现在long类型。 内核的普通内存地址通常是unsigned lon

qq_43527944的博客 267

Preempt-RT --- Linux kernel 实现欣赏

static int __init irqpoll_setup(char *str) { #ifdef CONFIG_PREEMPT_RT pr_warn("irqpoll boot option not supported w/ CONFIG_PREEMPT_RT\n"); return 1; #endif irqfixup = 2; printk(KERN_WARNING "Misrouted IRQ fixup and polling support " "enabled\n");

500 937

Linux网络】深入理解 TCP 协议(一):报头设计与可靠性基石

在互联网世界中,TCP 协议无疑是最核心的基石之一 —— 我们每天使用的 HTTP、HTTPS、SSH、FTP 等几乎所有可靠通信都建立在 TCP 之上。很多人知道 TCP 是 “可靠的传输控制协议”,但很少有人深入理解它的可靠性究竟是如何实现的,以及它的报头设计背后隐藏着怎样的精妙考量。本文将从最基础的 TCP 数据发送流程讲起,深度拆解 TCP 报头的每一个字段,结合 Linux 内核源码分析其底层实现,并详细讲解 TCP 可靠性的两大核心机制:确认应答与超时重传。所有内容均严格基于 TCP 协议规范和

会当凌绝顶,一览众山小 3489
上一篇: 移植linux kernel,应该怎么做
下一篇: 简析PPC的Device Tree机制
kerneler_
博客等级 码龄15年 1911粉丝 128原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值