Linux内核的数据类型.doc_第1页
Linux内核的数据类型.doc_第2页
Linux内核的数据类型.doc_第3页
Linux内核的数据类型.doc_第4页
Linux内核的数据类型.doc_第5页
已阅读5页,还剩4页未读 继续免费阅读

下载本文档

版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领

文档简介

Linux设备驱动程序学习(7)-内核的数据类型来源: ChinaUnix博客 日期: 2008.10.13 21:03(共有0条评论) 我要评论Linux设备驱动程序学习(7)-内核的数据类型 由于前面的学习中有用到 第十一章 内核数据结构类型 的知识,所以我先看了。要点如下: 将linux 移植到新的体系结构时,开发者遇到的若干问题都与不正确的数据类型有关。坚持使用严格的数据类型和使用 -Wall -Wstrict-prototypes 进行编译可能避免大部分的 bug。内核数据使用的数据类型主要分为 3 个类型: 标准 C 语言类型、确定大小的类型和特定内核对象的类型。 标准 C 语言类型 当需要“一个2字节填充符”或“用一个4字节字串来代表某个东西”,就不能使用标准C语言类型,因为在不同的体系结构,C语言的数据类型所占的空间大小不同。后面的datasize 程序实验展示了用户空间各种 C的数据类型在当前平台所占空间的大小。而且有的构架,内核空间和用户空间的C数据类型所占空间大小也可能不同。kdatasize模块显示了当前模块的内核空间C 数据类型所占空间大小。尽管概念上地址是指针,但使用一个无符号整型可以更好地实现内存管理; 内核把物理内存看成一个巨型数组, 内存地址就是该数组的索引。 我们可以方便地对指针取值,但直接处理内存地址时,我们几乎从不会以这种方式对他取值。使用一个整数类型避免了这种取值,因此避免了 bug。所以,利用至少在 Linux 目前支持的所有平台上,指针和长整型始终是相同大小的这一事实,内核中内存地址常常是 unsigned long。C99 标准定义了 intptr_t 和 uintptr_t 类型,它们是能够保存指针值的整型变量。但没在 2.6 内核中几乎没使用。 确定大小的类型 当需要知道你定义的数据的大小时,可以使用内核提供的下列数据类型(所有的数据声明在 , 被包含在): 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特定的变体要好。 接口特定的类型(_t 类型) 内核中最常用的数据类型由它们自己的 typedef声明,阻止了任何移植性问题。“接口特定(interface-specific)”由某个库定义的一种数据类型,以便为了某个特定的数据结构提供接口。很多 _t 类型在中定义。注意:近来已经很少定义新的接口特定的类型。有许多内核开发者已经不再喜欢使用 typedef 语句,他们宁愿看到代码中直接使用的真实类型信息。很多老的接口特定类型在内核中保留,他们不会很快消失。即使没有定义接口特定类型,也应该始终是用和内核其他部分保持一致、适当的数据类型。只要驱动使用了这种“定制”类型的函数,但又不遵照约定,编译器会发出警告,这时使用 -Wall 编译器选项并小心去除所有的警告,就可以确信代码的可移植性了。_t 类型的主要问题是:打印它们时,常常不容易选择正确的 printk 或 printf 格式。打印接口特定的数据的最好方法是:将其强制转换为可能的最大类型(常常是 long 或 unsigned long ) 并用相应的格式打印。 其他移植性问题 移植的一个通常规则是:避免使用显式的常量值,要使用预处理宏使常量值参数化。时间间隔当处理时间间隔时,不要假定每秒的jiffies个数,不是每个 Linux 平台都以固定的速度运行.当计算时间间隔时,要使用 HZ ( 每秒的定时器中断数 ) 来标定你的时间。s3c2410的HZ值默认为200。页大小当使用内存时,记住一个内存页是 PAGE_SIZE 字节, 不是 4KB。相关的宏定义是 PAGE_SIZE 和 PAGE_SHIT(包含将一个地址移位来获得它的页号的位数),在 中定义。如果用户空间程序需要这些信息,可以使用 getpagesize 库函数。若一个驱动需要 16 KB 来暂存数据,一个可移植得解决方法是 get_order: #include asm/page.hint order = get_order(16*1024);buf = get_free_pages(GFP_KERNEL, order);/*get_order 的参数必须是 2 的幂*/字节序不要假设字节序。 代码应该编写成不依赖所操作数据的字节序的方式。头文件定义:#ifdef _ARMEB_#include linux/byteorder/big_endian.h#else#include linux/byteorder/little_endian.h#endif在linux/byteorder/big_endian.h中定义了_BIG_ENDIAN ,而在/byteorder/little_endian.h中定义了_LITTLE_ENDIAN,这些依赖处理器的字节序当处理字节序问题时,需要编码一堆类似 #ifdef _LITTTLE_ENDIAN 的条件语句。但是还有一个更好的方法:Linux 内核有一套宏定义来处理处理器字节序和特定字节序之间的转换。例如: u32 cpu_to_le32 (u32);u32 le32_to_cpu (u32);/*这些宏定义将一个CPU使用的值转换成一个无符号的32位小头数值,无论 CPU 是大端还是小端,也不管是不是32 位处理器。在没有转换工作需要做时,返回未修改的值。*/*有很多类似的函数在和中定义*/数据对齐编写可移植代码而值得考虑的最后一个问题是如何访问未对齐的数据。存取不对齐的数据应当使用下列宏: #include asm/unaligned.hget_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 header5; 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 message4; 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);指针和错误值许多内核接口通过将错误值编码到指针值中来返回错误信息。这样的信息必须小心使用,因为它们的返回值不能简单地与 NULL 比较。为帮助创建和使用这类接口,提供了这样的函数:void *ERR_PTR(long error);/*将错误值编码到指针值中,error 是常见的负值错误码*/long IS_ERR(const void *ptr); /*测试返回的指针是不是一个错误码*/long PTR_ERR(const void *ptr); /*抽取实际的错误码,只有在IS_ERR 返回一个真值时使用,否则一个有效指针*/链表操作系统内核常需要维护数据结构的链表。Linux 内核已经同时有几个链表实现。为减少复制代码的数量, 内核已经创建了一个标准环形双向链表,并鼓励需要操作链表的人使用这个设施.使用链表接口时,应当记住列表函数没做加锁。若驱动可能同一个列表并发操作,就必须实现一个锁方案。为使用链表机制,驱动必须包含文件,它定义了一个简单的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)ARM9实验板实验datasize实验实验中有用到uname函数,介绍如下(载自UNIX环境高级编程,第6章系统数据文件和信息 6.8 系统标识): POSIX.1定义了uname函数,它返回与主机和操作系统有关的信息。#include int uname(struct utsname *n a m e) ;返回:若成功则为非负值,若出错则为-1通过该函数的参数向其传递一个utsname结构的地址,然后该函数填写此结构。POSIX.1只定义了该结构中至少需提供的字段(它们都是字符数组),而每个数组的长度则由实现确定。某些实现在该结构中提供了另外一些字段。在历史上,系统 V为每个数组分配9个字节,其中有1个字节用于字符串结束符( null字符)。struct utsname char sysname9; /* name of the operating system */char nodename9;/* name of this node */char release9; /* current release of operating system */char version9; /* current version of this release */char machine9; /* name of hardware type */ ;utsname结构中的信息通常可用uname(1)命令打印。实验程序源码链接:datasizekdatasize模块实验实验中有用到utsname函数,源码如下: /struct new_utsname char sysname65; char nodename65; char release65; char version65; char machine65; char domainname65;static inline struct new_utsname *utsname(void) return ¤t-nsproxy-uts_ns-name;实验模块源码链接:kdatasizekdataalign模块实验具体试验原理请看源码实验模块源码链接:kdataalign实验现象:Tekkaman2440SBC2440V4#cd /tmp/Tekkaman2440SBC2440V4#./datasizearch Size: char short int long ptr long-long u8 u16 u32 u64armv4tl 1 2 4 4 4 8 1 2 4 8Tekkaman2440SBC2440V4#cd /lib/modules/Tekkaman2440SBC2440V4#insmod kdatasize.koarch Size: char short int long ptr long-long u8 u16 u32 u64armv4tl 1 2 4 4 4 8 1 2 4 8insmod: cannot insert kdatasize.ko: No such device (-1): No such deviceTekkaman2440SBC2440V4#insmod kdataalign.koarch Align: char short int long ptr long-long u8 u16 u32 u64armv4tl 1 2 4 4 4 4 1 2 4 4insmod: cannot insert kdataalign.ko: No such device (-1): No such deviceLinux内核可移植性(一)-字长和数据类型(2008-11-18 10:52:53) 转载标签: linuxit分类: 技术文章 注:linux内核可移植性文章 都转自陈老师博客 /26540/viewspace-47629.html 能够由机器一次就完成处理的数据被称为字。这和我们在文档中用字符(8位)和页(许多字,通常是4K或8K)来计量数据是相似的。字是指位的整数数目比如说,1,2,4或8等。但人们说某个机器是多少“位”的时候,他们其实说的就是该机的字长。比如说,当人们说奔腾是32位芯片时,他们的意思是奔腾的字长为32位,也就是4字节。 处理器通用寄存器(GPRs)的大小和它的字长是相同的。一般来说,对于一个的体系结构来说,它各个部件的宽度比如说内存总线最少要和它的字长一样大。而一般来说,地址空间的大小也等于字长,至少Linux支持的体系结构中都是这样的不过实际上可寻址的内存空间也可能会比字长小一些。比如,一个64位的体系结构虽然可能会提供64位的指针,但可能只会用48位来寻址。此外,如果支持Intel的PAE,那么实际的物理内存也有比字长还大的可能。此外,C语言定义的long类型总对等于机器的字长,而int类型有时会比字长小。比如说,Alpha是64位机。所以它的寄存器、指针和long类型都是64位长度的,而int类型是32位的。Alpha机每一次可以访问和操作一个64位长的数据。1.字、双字的含义 有些操作系统和处理器不把它们的标准字长称作字,相反,出于历史原因和某种主观的命名习惯,它们用字来代表一些固定长度的数据类型。比如说,一些系统根据长度把数据划分为字节(byte,8位),字(word,16位),双字(double words 32位)和四字(quad words 64位),而实际上该机是32位的。在本书中在Linux中一般也是这样象我们前面所讨论的那样,一个字就是代表处理器的字长。对于支持的每一种体系结构,Linux都要将中的BITS_PER_LONG定义为C long类型的长度,也就是系统的字长。表1是Linux支持的体系结构和它们的字长的对照表。表1 Linux支持的体系结构体系结构 描述 字长alpha Digital Alpha 64位arm Arm and StrongArm 32位cris CRIS 32位h8300 H8/300 32位i386 Intel x86 32位ia64 IA-64 64位m32r M32xxx 32位m68k motorola 68k 32位m68knommu M68k without MMU 32位mips MIPS 32位mips64 64位MIPS 64位parisc HP PA_RISC 32位或64位ppc PowerPC 32位ppc64 POWER 64位s390 IBM S/390 32位或64位sh Hitachi SH 32位sparc SPARC 32位sparc64 UltraSPARC 64位um Usermode Linux 32位或64位v850 v850 32位x86_64 x86-64 64位 C语言虽然规定了变量的最小长度,但是没有规定变量具体的标准长度,它们可以根据实现变化唯一的例外是char,它的长度总是8位。C语言的标准数据类型长度随体系结构变化这一特性不断引起争议。好的一面是标准数据类型可以充分利用不同体系结构变化的字长而无需明确定义长度。C语言中long类型的长度就被确保为机器的字长。不好的一面是在编程时不能对标准的C数据类型进行大小的假定,没有什么能够保障int一定和long的长度是相同的事实上对于Linux支持的64位体系结构来说,long和int长度是不同的,int是32位的,而long是64位的。但对于我们所熟悉的32位体系结构而言,两种数据类型都是32位的。情况其实还会更加复杂,因为用户空间使用的数据类型和内核空间的数据类型不一定要相互关联。sparc64体系结构就提供了32位的用户空间,其中指针、int和long的长度都是32位。而在内核空间,它的int长度是32位,指针和long的长度却是64。没有什么标准来规范这些。牢记下述准则:* ANSI C标准规定,一个char的长度一定是8位。* 尽管没有规定int类型的长度是32位,但在Linux当前所有支持的体系结构中,它都是32位的。* short类型也类似,在当前所有支持的体系结构中,虽然没有明文规定,但是它都是16位的(although no rule explicitly decrees that)。* 决不应该假定指针和long的长度,在Linux当前支持的体系结构中,它们就可以在32位和64位中变化。* 由于不同的体系结构long的长度不同,决不应该假设sizeof( int ) = sizeof( long )。* 类似地,也不要假设指针和int长度相等。2. 不透明类型 不透明数据类型隐藏了它们内部格式或结构。在C语言中,它们就像黑盒一样。支持它们的语言不是很多。作为替代,开发者们利用typedef声明一个类型,把它叫做不透明类型,希望其他人别去把它重新转化回对应的那个标准C类型。通常开发者们在定义一套特别的接口时才会用到它们。比如说用来保存进程标识符的pid_t类型。该类型的实际长度被隐藏起来了尽管任何人都可以偷偷撩开它的面纱,发现它就是一个int。如果所有代码都不显式地利用它的长度(显式利用长度这里指直接使用int类型的长度,比如说在编程时使用sizeof(int)而不是sizeof(pid_t)译者注),那么改变时就不会引起什么争议,这种改变确实可能会出现:在老版本的Unix系统中,pid_t的定义是short类型。 另外一个不透明数据类型的例子是atomic_t,它放置的是一个可以进行原子操作的整型值。尽管这种类型就是一个int,但利用不透明类型可以帮助确保这些数据只在特殊的有关原子操作的函数中才会被使用。不透明类型还帮助我们隐藏了类型的长度,但是该类型也并不总是完整的32位,比如在32位SPARC体系下长度被限制。 内核还用到了其他一些不透明类型,包括dev_t、gid_t和uid_t等等。处理不透明类型时的原则是:* 不要假设该类型的长度。* 不要将该类型转化回其对应的C标准类型使用。* 编程时要保证在该类型实际存储空间和格式发生变化时代码不受影响。3. 指定数据类型内核中还有一些数据虽然无需用不透明的类型表示,但它们被定义成了指定的数据类型。jiffy数目和在中断控制时用到的flags参数就是两个例子,它们都应该被存放在unsigned long类型中。当存放和处理这些特别的数据时,一定要搞清楚它们对应的类型后再使用。把它们存放在其他如unsigned int等类型中是一种常见错误。在32位机上这没什么问题,可是64位机上就会捅娄子了。4. 长度明确的类型 作为一个程序员,你往往需要在程序中使用长度明确的数据。像操作硬件设备,进行网络通信和操作二进制文件时,通常都必须满足它们明确的内部要求。比如说,一块声卡可能用的是32位寄存器,一个网络包有一个16位字段,一个可执行文件有8位的cookie。在这些情况下,数据对应的类型应该长度明确。内核在中定义了这些长度明确的类型,而该文件又被包含在文件中。表2有完整的清单。表2 长度明确的数据类型类型 描 述s8 带符号字节u8 无符号字节s16 带符号16位整数u16 无符号16位整数s32 带符号32位整数u32 无符号32位整数s64 带符号64位整数u64 无符号

温馨提示

  • 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
  • 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
  • 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
  • 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
  • 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
  • 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
  • 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。

评论

0/150

提交评论