`
mmdev
  • 浏览: 12949417 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

【Linux 驱动】第十一章 内核的数据类型

 
阅读更多

一,内核数据数据类型

主要分为: 标准 C 语言类型、确定大小的类型和特定内核对象的类型。

1)标准 C 语言类型

当需要"一个2字节填充符"或"用一个4字节字串来代表某个东西",就不能使用标准C语言类型,因为在不同的体系结构,C 语言的数据类型所占的空间大小不同。例如:long 在32位系统和64位系统中占用的字节数不同。

有的构架,内核空间和用户空间的C 数据类型所占空间大小也可能不同。kdatasize模块显示了当前模块的内核空间C 数据类型所占空间大小。

尽管概念上地址是指针,但使用一个无符号整型可以更好地实现内存管理; 内核把物理内存看成一个巨型数组, 内存地址就是该数组的索引。 我们可以方便地对指针取值,但直接处理内存地址时,我们几乎从不会以这种方式对他取值。使用一个整数类型避免了这种取值,因此避免了 bug。所以,利用至少在 Linux 目前支持的所有平台上,指针长整型始终是相同大小的这一事实,内核中内存地址常常是 unsigned long

2)确定大小的类型

当需要知道你定义的数据的大小时,可以使用内核提供的下列数据类型(所有的数据声明在 <asm/types.h>, 被包含在 <linux/types.h> ):

u8; /* unsigned byte (8 bits) */

u16; /* unsigned word (16 bits) */

u32; /* unsigned 32-bit value */

u64; /* unsigned 64-bit value */
/*虽然很少需要有符号类型,但是如果需要,只要用 s 代替 u*/

若一个用户空间程序需要使用这些类型,可在符号前加一个双下划线: __u8和其它类型是独立于 __KERNEL__ 定义的。

这些类型是 Linux 特定的,它们妨碍了移植软件到其他的 Unix 机器。

新的编译器系统支持 C99-标准 类型,如 uint8_t 和 uint32_t。若考虑移植性,使用这些类型比 Linux特定的变体要好。


3)接口特定的类型(_t 类型)

内核中最常用的数据类型由它们自己的 typedef 声明,阻止了任何移植性问题。"接口特定(interface-specific)"由某个库定义的一种数据类型, 以便为了某个特定的数据结构提供接口。很多 _t 类型在 <linux/types.h> 中定义。

注意:近来已经很少定义新的接口特定的类型。有许多内核开发者已经不再喜欢使用 typedef 语句,他们宁愿看到代码中直接使用的真实类型信息。很多老的接口特定类型在内核中保留,他们不会很快消失。

即使没有定义接口特定类型,也应该始终是用和内核其他部分保持一致、适当的数据类型。只要驱动使用了这种"定制"类型的函数,但又不遵照约定,编译器会发出警告,这时使用 -Wall 编译器选项并小心去除所有的警告,就可以确信代码的可移植性了。

_t 类型的主要问题是:打印它们时,常常不容易选择正确的 printk 或 printf 格式。打印接口特定的数据的最好方法是:将其强制转换为可能的最大类型(常常是 long 或 unsigned long ) 并用相应的格式打印。

二,其他移植性问题

移植的一个通常规则是:避免使用显式的常量值,要使用预处理宏使常量值参数化。

1)时间间隔

当处理时间间隔时,不要假定每秒的jiffies个数,不是每个 Linux 平台都以固定的速度运行.当计算时间间隔时,要使用 HZ ( 每秒的定时器中断数 ) 来标定你的时间。s3c2410的HZ值默认为200。

2)页大小

当使用内存时,记住一个内存页是 PAGE_SIZE 字节, 不是 4KB。相关的宏定义是 PAGE_SIZE 和 PAGE_SHIT(包含将一个地址移位来获得它的页号的位数),在 <asm/page.h> 中定义。如果用户空间程序需要这些信息,可以使用 getpagesize 库函数。

若一个驱动需要 16 KB 来暂存数据,一个可移植得解决方法是 get_order:
#include <asm/page.h>
int order = get_order(16*1024);
buf = get_free_pages(GFP_KERNEL, order);/*get_order 的参数必须是 2 的幂*/
3)字节序

不要假设字节序。 代码应该编写成不依赖所操作数据的字节序的方式。

头文件 <asm/byteorder.h> 定义:

#ifdef __ARMEB__

#include <linux/byteorder/big_endian.h> //大端

#else

#include <linux/byteorder/little_endian.h> //小端

#endif
在<linux/byteorder/big_endian.h>中定义了__BIG_ENDIAN ,而在<linux/byteorder/little_endian.h>中定义了__LITTLE_ENDIAN,这些依赖处理器的字节序当处理字节序问题时,需要编码一堆类似 #ifdef __LITTTLE_ENDIAN 的条件语句。

但是还有一个更好的方法:Linux 内核有一套宏定义来处理处理器字节序和特定字节序之间的转换。例如:

u32 cpu_to_le32 (u32);

u32 le32_to_cpu (u32);

/*这些宏定义将一个CPU使用的值转换成一个无符号的32位小头数值,无论 CPU 是大端还是小端,也不管是不是32 位处理器。在没有转换工作需要做时,返回未修改的值。*/

/*有很多类似的函数在 <linux/byteorder/big_endian.h> 和 <linux/byteorder/little_endian.h> 中定义*/

3)数据对齐

编写可移植代码而值得考虑的最后一个问题是如何访问未对齐的数据。存取不对齐的数据应当使用下列宏:

#include <asm/unaligned.h> //不对齐

get_unaligned(ptr);

put_unaligned(val, ptr);

这些宏是无类型的,并对各总数据项,不管是 1、2、4或 8 个字节,他们都有效,并且在所有内核版本中都有定义。

关于对齐的另一个问题是数据结构的跨平台移植性。同样的数据结构在不同的平台上可能被不同地编译。为了编写可以跨体系移植的数据结构,应当始终强制数据项的自然对齐

自然对齐(natural alignment)指的是:数据项大小的整数倍的地址上存储数据项。 应当使用填充符避免强制自然对齐时编译器移动数据结构的字段,在数据结构中留下空洞。
dataalign 程序实验展示了编译器如何强制对齐。

为了目标处理器的良好性能,编译器可能悄悄地插入填充符到结构中,来保证每个成员是对齐的。若定义一个和设备要求的结构体相匹配结构,自动填充符会破坏这个意图。解决这个问题的方法是告诉编译器这个结构必须是"紧凑的", 不能增加填充符。例如下列的定义:

struct
{
        u16 id;
        u64 lun;
        u16 reserved1;
        u32 reserved2;
}



__attribute__ ((packed)) scsi;

/*如果在 64-位平台上编译这个结构,若没有 __attribute__ ((packed)), lun 成员可能在前面被添加 2 个或 6 个填充符字节。指针和错误值*/
你还可以在利用ARM9和USB摄像头进行视频采集的servfox源代码的spcaframe.h头文件中找到这种方法的实际应用:

struct frame_t{
    char header[5];
    int nbframe;
    double seqtimes;
    int deltatimes;
    int w;
    int h;
    int size;
    int format;
    unsigned short bright;
    unsigned short contrast;
    unsigned short colors;
    unsigned short exposure;
    unsigned char wakeup;
    int acknowledge;
    } __attribute__ ((packed)); 
struct client_t{
    char message[4];
    unsigned char x;
    unsigned char y;
    unsigned char fps;
    unsigned char updobright;
    unsigned char updocontrast;
    unsigned char updocolors;
    unsigned char updoexposure;
    unsigned char updosize;
    unsigned char sleepon;
    } __attribute__ ((packed));


4)指针和错误值

许多内核接口通过将错误值编码到指针值中来返回错误信息。这样的信息必须小心使用,因为它们的返回值不能简单地与 NULL 比较。为帮助创建和使用这类接口, <linux/err.h>提供了这样的函数:

void *ERR_PTR(long error);/*将错误值编码到指针值中,error 是常见的负值错误码*/

long IS_ERR(const void *ptr); /*测试返回的指针是不是一个错误码*/

long PTR_ERR(const void *ptr); /*抽取实际的错误码,只有在IS_ERR 返回一个真值时使用,否则一个有效指针*/

三,链表

操作系统内核常需要维护数据结构的链表。Linux 内核已经同时有几个链表实现。为减少复制代码的数量, 内核已经创建了一个标准环形双向链表,并鼓励需要操作链表的人使用这个设施.
使用链表接口时,应当记住列表函数没做加锁。若驱动可能同一个列表并发操作,就必须实现一个锁方案。

为使用链表机制,驱动必须包含文件 <linux/list.h> ,它定义了一个简单的list_head 类型 结构:

struct list_head {

struct list_head *next, *prev;

};

实际代码中使用的链表几乎总是由某个结构类型组成, 每个结构描述链表中的一项. 为使用 Linux 链表,只需嵌入一个 list_head 在构成在这个链表的结构里面。链表头常常是一个独立的 list_head 结构。下图显示了这个简单的 struct list_head 是如何用来维护一个数据结构的列表的.


链表头必须在使用前初始化,有两种形式:

一是运行时初始化:

struct list_head todo_list;

INIT_LIST_HEAD(&todo_list);

二是编译时初始化:

LIST_HEAD(todo_list);

list_add(struct list_head *new, struct list_head *head);

在紧接着链表 head 后面增加新项 。注意:head 不需要是链表名义上的头; 如果你传递一个 list_head 结构, 它在链表某处的中间, 新的项紧靠在它后面。 因为 Linux 链表是环形的, 链表头通常和任何其他的项没有区别

list_add_tail(struct list_head *new, struct list_head *head); // 在给定链表头前面增加新项,即在链表的尾部增加一个新项。

list_del(struct list_head *entry);

list_del_init(struct list_head *entry);

给定的项从队列中去除。 如果入口项可能注册在另外的链表中, 你应当使用 list_del_init, 它重新初始化这个链表指针

list_move(struct list_head *entry, struct list_head *head);

list_move_tail(struct list_head *entry, struct list_head *head);

给定的入口项从它当前的链表里去除并且增加到 head 的开始。为安放入口项在新链表的末尾, 使用 list_move_tail 代替

list_empty(struct list_head *head);

如果给定链表是空, 返回一个非零值

list_splice(struct list_head *list, struct list_head *head);
将 list 紧接在 head 之后来连接 2 个链表.

list_entry是将一个 list_head 结构指针转换到一个指向包含它的结构体的指针 。看了源码你就会发现,似曾相识。是的,其实在模块的open方法中已经用到了container_of 。list_entry的变体好有很多,看源码就知道了

#define list_entry(ptr, type, member) \
container_of(ptr, type, member)

分享到:
评论

相关推荐

    LINUX 设备驱动程序(第二版)

    第1章 Linux内核简介 第2章 编写和运行模块 第3章 字符设备驱动程序 第4章 调试技术 第5章 字符设备驱动程序的扩展操作 第6章 时间流 第7章 获取内存 第8章 硬件管理 第9章 中断处理 第10章 合理使用数据类型 第11章...

    嵌入式linux设备驱动(共17章)

    第10章 合理使用数据类型 第11章 kerneld和高级模块化 第十二章 加载快设备驱动程序 第十三章 MMAP和DMA 第十四章 网络驱动程序 第十五章 外围总线概览 第十六章 核心源码的物理布局 第十七章 最新进展

    LINUX设备驱动第三版_588及代码.rar

    第十一章 内核的数据类型 使用标准C语言类型 为数据项分配确定的空间大小 接口特定的类型 其他有关移植性的问题 链表 快速参考 第十二章 PCI驱动程序 PCI接口 ISA回顾 PC/104和PC/104+ 其他的PC总线 ...

    嵌入式设计及linux驱动开发指南——基于ARM9处理器.pdf

    第11章 Java虚拟机的移植 11.1 Java虚拟机概述 11.1.1 Java虚拟机的概念 11.1.2 J2ME 11.1.3 KVM 11.2 Java虚拟机的移植 11.2.1 获得源码 11.2.2 编译环境的建立 11.2.3 JDK的安装 11.2.4 KVM的移植及编译 ...

    linux设备驱动程序第三版

    1. Linux 设备驱动第三版 .................................................................................................................... 5 2. 第 1 章 设备驱动简介 ....................................

    给自己的Linux写驱动

    第1章 Linux内核简介 第2章 编写和运行模块 第3章 字符设备驱动程序 第4章 调试技术 第5章 字符设备驱动程序的扩展操作 第6章 时间流 第7章 获取内存 第8章 硬件管理 第9章 中断处理 第10章 合理使用数据类型 第11章...

    Understanding the Linux Kernel

     第十一章信号  信号的作用  产生信号  传递信号  与信号处理相关的系统调用  第十二章虚拟文件系统  虚拟文件系统(VFS)的作用  VFS的数据结构  文件系统类型  文件系统处理  路径名查找  VFS系统调用的...

    Linux DeviceDrivers 3rd Edition

    第十一章 内核的数据类型 287 使用标准C语言类型 287 为数据项分配确定的空间大小 289 接口特定的类型 289 其他有关移植性的问题 291 链表 294 快速参考 298 第十二章 PCI驱动程序 300 PCI接口 300 ISA回顾...

    清华大学Linux操作系统原理与应用

    1.4.2 Linux内核的作用 11 1.4.3 Linux内核子系统 11 1.5 Linux内核源代码 13 1.5.1 多版本的内核源代码 13 1.5.2 Linux内核源代码的结构 13 1.5.3 Linux内核源代码分析工具 14 习题1 15 第2章 内存寻址 17 2.1 内存...

    嵌入式Linux C编程入门(第2版) PPT

    第11章 arm linux网络开发实例 322 11.1 tcp/ip协议简介 322 11.1.1 tcp/ip的分层模型 322 11.1.2 tcp/ip分层模型特点 324 11.1.3 tcp/ip核心协议 325 11.2 网络基础编程 328 11.2.1 socket概述...

    RED HAT LINUX 6大全

    第11章 域名服务 198 11.1 Internet历史简述 198 11.1.1 hosts.txt文件 199 11.1.2 /etc/hosts文件 199 11.2 BIND 8 200 11.3 本章例子说明 200 11.4 创建一个普通的缓冲DNS 201 11.4.1 真正的解决方案 201 11.4.2 ...

    LINUX安装与配置简明手册

    第11章 配置LAN 127 11.1 概述 127 11.1.1 计划一个LAN 127 11.1.2 网络硬件 128 11.1.3 服务 129 11.1.4 确定用户是否需要动态 分配路由 130 11.2 快速解决方案 130 11.2.1 检查网络当前的设置情况 130 11.2.2 设置...

    LINUX系统管理白皮书

    第11章 泰勒式UUCP 103 11.1 关于UUCP 103 11.1.1 UUCP传输和远程作业的执行 104 11.1.2 UUCICO的内部运行 104 11.1.3 UUCICO命令行选项 105 11.2 UUCP配置文件 105 11.2.1 泰勒式UUCP简介 105 11.2.2 UUCP...

    安装 SUSE Linux Enterprise Server --服务器版

    为此,请将内核 (linux) 和安装系统 (initrd) 从第一张 硬盘 CD 或 DVD 上的 /boot/loader 目录中复制到硬盘,并 向引导装载程序中添加相应的项。 1.3 安装源 当安装 SUSE Linux Enterprise Server 时,必须在网络、...

    疯狂内核之——虚拟文件系统

    2.1 VFS对象数据结构 11 2.1.1 超级块对象 11 2.1.2 索引节点对象 15 2.1.3 文件对象 18 2.1.4 目录项对象 22 2.2 把Linux中的VFS对象串联起来 24 2.2.1 与进程相关的文件 25 2.2.2 索引节点高速缓存 29 2.2.3 目录...

Global site tag (gtag.js) - Google Analytics