何时在驱动程序代码中使用 stdint.h 的标量

Fol*_*eld 4 c linux driver

我注意到,何时使用本机标量类型(整数、短整型、字符)或 stdint 提供的类型:uint32_t uint16_t uint8_t,似乎没有一致性或最佳实践。

这让我很烦恼,因为驱动程序是内核的重要组成部分,需要可维护、一致、稳定和良好。

这是 gcc 中的一个说明性示例(用于 raspberry pi 的业余项目):

// using native scalars
struct fbinfo {
        unsigned width, height;
        unsigned vwidth, vheight;
        unsigned pitch, bits;
        int x, y;
        void *ptr;
        unsigned size;
} __attribute__((aligned(16)));

// using stdint scalars
struct fbinfo {
        uint32_t width, height;
        uint32_t vwidth, vheight;
        uint32_t pitch, bits;
        int32_t x, y;
        uint32_t ptr; // convert to void* in order to use it
        uint32_t size;
} __attribute__((aligned(16)));
Run Code Online (Sandbox Code Playgroud)

对我来说,第一个例子似乎更合乎逻辑,因为这段代码只打算在树莓派上运行。在其他硬件上运行它是没有意义的。

第二个例子似乎更实用,因为它看起来更具描述性,因为 C 对整数的大小没有太多保证。它可能是 16 位或其他。uint32_t、uint_fast32_t 和变体保证精确或近似大小:例如至少或最多 X 字节。

操作系统开发社区倾向于使用 stdint 类型,而 linux 内核使用多种不同的技术:u32、__u32 和字节序特定的东西,如 __le32。

何时选择标量类型以及何时使用 typedef 标量类型应考虑哪些因素?在提供的示例中使用本机标量类型还是使用 stdint.h 更好?

ens*_*nsc 5

1. 固定与基本类型

固定宽度类型有时难以使用。例如printf()int32_tarePRIi32和需要拆分格式字符串的说明符:

printk("foo=" PRIi32 ", bar=" PRIi32 "\n", foo, bar);
Run Code Online (Sandbox Code Playgroud)

直接访问硬件时应该/必须使用固定宽度类型;例如,在写入 DMA 描述符时。但是对于简单的寄存器访问,writel()或者readl()可以使用与基本类型一起工作的函数。

根据经验,当假定某种内存布局时(如__attribute__((__aligned__(16)))您的示例中的 ,应使用固定宽度类型。

有符号的固定宽度类型(int32_t x,y在您的示例中)可能需要仔细检查它们的表示是否符合硬件预期。

请注意,在您的示例中,第二个结构依赖于架构,因为

    uint32_t ptr; // convert to void* in order to use it
Run Code Online (Sandbox Code Playgroud)

用普通的 C 写这样的东西,uintptr_t ptr在内核中写这样的东西是很常见的

    unsigned long ptr;
Run Code Online (Sandbox Code Playgroud)

或者,dma_addr_t可能是更好的类型。

2.uint32_t对比__u32

10 多年前,Linus Torvalds 反对,uint32_t因为此时非 C99 编译器很常见,并且在(导出的)linux 头文件中使用此类类型会污染命名空间。

但是现在,uint32_t类似的类型随处可见(您无法使用非 C99 编译器编译内核)并且内核头文件导出已得到显着改进,因此这些参数都消失了。

是使用标准类型还是使用 typedef 的变体(它们依赖于框架并且它们之间有所不同)是个人偏好的问题。

3.uint_fastX_t和变体

它们不在内核中使用,我会避免使用它们。它们结合了uint32_t(使用困难)和int(可变宽度)的缺点。

4.__le32对比__u32

当规范明确要求时使用字节序类型(例如在网络协议实现中)。这可以很容易地检测到错误的用法(例如像 的赋值endian_variable = native_variable)。

不要使用它们,例如填充处理器结构(例如 DMA 描述符);一些处理器可以在小端和大端模式下运行,本机数据类型通常是编写此类信息的正确方法。