内核驱动开发数据怎样进行c语言强制类型转换换

隐式类型转换与强制类型转换
服务器君一共花费了81.257 ms进行了3次数据库查询,努力地为您提供了这个页面。
试试阅读模式?希望听取您的建议
PHP是弱类型的动态语言,在前面的章节中我们已经介绍了PHP的变量都存放在一个名为ZVAL的容器中, ZVAL包含了变量的类型和各种类型变量的值。 PHP中的变量不需要显式的数据类型定义,可以给变量赋值任意类型的数据, PHP变量之间的数据类型转换有两种:隐式和显式转换。
隐式类型转换
隐式类型转换也被称为自动类型转换,是指不需要程序员书写代码,由编程语言自动完成的类型转换。 在PHP中,我们经常遇到的隐式转换有:
1.直接的变量赋值操作
在PHP中,直接对变量的赋值操作是隐式类型转换最简单的方式,也是我们最常见的一种方式,或许我们已经习以为常,从而没有感觉到变量的变化。 在直接赋值的操作中,变量的数据类型由赋予的值决定,即左值的数据类型由右值的数据类型决定。 比如,当把一个字符串类型的数据赋值给变量时,不管该变量以前是什么类型的变量,此时该变量就是一个字符串类型的变量。 看一段代码:
$string = "To love someone sincerely means to love all the people,
to love the world and life,
$integer = 10;
$string = $
上面的代码,当执行完第三行代码,$string变量的类型就是一个整形了。 通过VLD扩展可以查到第三次赋值操作的中间代码及操作数的类型,再找到赋值的最后实现为zend_assign_to_variable函数。 这在前面的小节中已经详细介绍过了。我们这个例子是很简单的一种赋值,在源码中是直接将$string的ZVAL容器的指针指向$integer变量指向的指针, 并将$integer的引用计数加1。这个操作在本质上改变了$string变量的内容,而原有的变量内容则被垃圾收集机制回收。关于赋值的具体细节,请返回上一节查看。
2.运算式结果对变量的赋值操作
我们常说的隐式类型转换是将一个表达式的结果赋值给一个变量,在运算的过程中发生了隐式的类型转换。 这种类型转换不仅仅在PHP语言,在其它众多的语言中也有见到,这是我们常规意义上的隐式类型转换。 这种类型转换又分为两种情况:
表达式的操作数为同一数据类型 这种情况的作用以上面的直接变量的类型转换是同一种情况,只是此时右值变成了表达式的运算结果。
表达式的操作数不为同的数据类型 这种情况的类型转换发生在表达式的运算符的计算过程中,在源码中也就是发生在运行符的实现过程中。
看一个字符串和整数的隐式数据类型转换:
$b = 'a string ';
echo $a . $b;
上面例子中字符串连接操作就存在自动数据类型转化,$a变量是数值类型,$b变量是字符串类型, 这里$b变量就是隐式(自动)的转换为字符串类型了。通常自动数据类型转换发生在特定的操作上下文中, 类似的还有求和操作"+"。具体的自动类型转换方式和特定的操作有关。 下面就以字符串连接操作为例说明隐式转换的实现:
脚本执行的时候字符串的连接操作是通过Zend/zend_operators.c文件中的如下函数进行:
ZEND_API int concat_function(zval *result, zval *op1, zval *op2 TSRMLS_DC) /* {{{ */
zval op1_copy, op2_
int use_copy1 = 0, use_copy2 = 0;
if (Z_TYPE_P(op1) != IS_STRING) {
zend_make_printable_zval(op1, &op1_copy, &use_copy1);
if (Z_TYPE_P(op2) != IS_STRING) {
zend_make_printable_zval(op2, &op2_copy, &use_copy2);
可用看出如果字符串链接的两个操作数如果不是字符串的话, 则调用zend_make_printable_zval函数将操作数转换为"printable_zval"也就是字符串。
ZEND_API void zend_make_printable_zval(zval *expr, zval *expr_copy, int *use_copy)
if (Z_TYPE_P(expr)==IS_STRING) {
*use_copy = 0;
switch (Z_TYPE_P(expr)) {
case IS_NULL:
Z_STRLEN_P(expr_copy) = 0;
Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
case IS_BOOL:
if (Z_LVAL_P(expr)) {
Z_STRLEN_P(expr_copy) = 1;
Z_STRVAL_P(expr_copy) = estrndup("1", 1);
Z_STRLEN_P(expr_copy) = 0;
Z_STRVAL_P(expr_copy) = STR_EMPTY_ALLOC();
case IS_RESOURCE:
// ...省略
case IS_ARRAY:
Z_STRLEN_P(expr_copy) = sizeof("Array") - 1;
Z_STRVAL_P(expr_copy) = estrndup("Array", Z_STRLEN_P(expr_copy));
case IS_OBJECT:
// ... 省略
case IS_DOUBLE:
*expr_copy = *
zval_copy_ctor(expr_copy);
zend_locale_sprintf_double(expr_copy ZEND_FILE_LINE_CC);
*expr_copy = *
zval_copy_ctor(expr_copy);
convert_to_string(expr_copy);
Z_TYPE_P(expr_copy) = IS_STRING;
*use_copy = 1;
这个函数根据不同的变量类型来返回不同的字符串类型,例如BOOL类型的数据返回0和1, 数组只是简单的返回Array等等,类似其他类型的数据转换也是类型, 都是根据操作数的不同类型的转换为相应的目标类型。在表达式计算完成后,表达式最后会有一个结果, 这个结果的数据类型就是整个表达式的数据类型。当执行赋值操作时,如果再有数据类型的转换发生, 则是直接变量赋值的数据类型转换了。
显式类型转换(强制类型转换)
在前面介绍了隐式类型转换,在我们的日常编码过程也会小心的使用这种转换, 这种不可见的操作可能与我们想象中的不一样,如整形和浮点数之间的转换。 当我们是一定需要某个数据类型的变量时,可以使用强制的数据类型转换,这样在代码的可读性等方面都会好些。 在PHP中的强制类型转换和C中的非常像:
$double = 20.10;
echo (int)$
PHP中允许的强制类型有:
(int), (integer) 转换为整型
(bool), (boolean) 转换为布尔类型
(float), (double) 转换为浮点类型
(string) 转换为字符串
(array) 转换为数组
(object) 转换为对象
(unset) 转换为NULL
在Zend/zend_operators.c中实现了转换为这些目标类型的实现函数convert_to_*系列函数, 读者自行查看这些函数即可,这些数据类型转换类型中有一个我们比较少见的unset类型转换:
ZEND_API void convert_to_null(zval *op) /* {{{ */
if (Z_TYPE_P(op) == IS_OBJECT) {
if (Z_OBJ_HT_P(op)->cast_object) {
TSRMLS_FETCH();
ALLOC_ZVAL(org);
if (Z_OBJ_HT_P(op)->cast_object(org, op, IS_NULL TSRMLS_CC) == SUCCESS) {
zval_dtor(org);
FREE_ZVAL(org);
zval_dtor(op);
Z_TYPE_P(op) = IS_NULL;
转换为NULL非常简单,对变量进行析构操作,然后将数据类型设为IS_NULL即可。 可能读者会好奇(unset)$a和unset($a)这两者有没有关系,其实并没有关系, 前者是将变量$a的类型变为NULL,这只是一个类型的变化,而后者是将这个变量释放,释放后当前作用域内该变量及不存在了。
除了上面提到的与C语言很像,在其它语言中也经常见到的强制数据转换,PHP中有一个极具PHP特色的强制类型转换。 PHP的标准扩展中提供了两个有用的方法settype()以及gettype()方法,前者可以动态的改变变量的数据类型, gettype()方法则是返回变量的数据类型。在ext/standard/type.c文件中找到settype的实现源码:
PHP_FUNCTION(settype)
int type_len = 0;
if (zend_parse_parameters(ZEND_NUM_ARGS() TSRMLS_CC, "Zs", &var, &type, &type_len) == FAILURE) {
if (!strcasecmp(type, "integer")) {
convert_to_long(*var);
} else if (!strcasecmp(type, "int")) {
convert_to_long(*var);
} else if (!strcasecmp(type, "float")) {
convert_to_double(*var);
} else if (!strcasecmp(type, "double")) { /* deprecated */
convert_to_double(*var);
} else if (!strcasecmp(type, "string")) {
convert_to_string(*var);
} else if (!strcasecmp(type, "array")) {
convert_to_array(*var);
} else if (!strcasecmp(type, "object")) {
convert_to_object(*var);
} else if (!strcasecmp(type, "bool")) {
convert_to_boolean(*var);
} else if (!strcasecmp(type, "boolean")) {
convert_to_boolean(*var);
} else if (!strcasecmp(type, "null")) {
convert_to_null(*var);
} else if (!strcasecmp(type, "resource")) {
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot convert to resource type");
RETURN_FALSE;
php_error_docref(NULL TSRMLS_CC, E_WARNING, "Invalid type");
RETURN_FALSE;
RETVAL_TRUE;
这个极具PHP特色的强制类型转换就是这个函数,而这个函数是作为一个代理方法存在, 具体的转换规则由各个类型的处理函数处理,不管是自动还是强制类型转换,最终都会调用这些内部转换方法, 这和前面的强制类型转换在本质上是一样的。
延伸阅读此文章所在专题列表如下:
本文地址:,欢迎访问原出处。
不打个分吗?
转载随意,但请带上本文地址:
如果你认为这篇文章值得更多人阅读,欢迎使用下面的分享功能。
小提示:您可以按快捷键 Ctrl + D,或点此 。
大家都在看
现代魔法研究协会欢迎你
阅读一百本计算机著作吧,少年
吴军 (作者)
近一百多年来,总有一些公司很幸运地、有意识或无意识地站在技术革命的浪尖之上。在长达十年甚至几十年的时间里,它们代表着科技的浪潮,直到下一波浪潮的来临。从19世纪末算起,AT&T公司、IBM公司、苹果公司、英特尔公司、微软公司、思科公司、雅虎公司和Google公司都先后被幸运地推到了浪尖。虽然,它们来自不同的领域,中间有些已经衰落或正在衰落,但是它们都极度辉煌过。吴军的这本《浪潮之巅》系统地介绍了这些公司成功的本质原因及科技工业一百多年的发展。在这些公司兴衰的背后,有着它必然的规律。《浪潮之巅》不仅讲述科技工业的历史,更重在揭示它的规律性。
扫一扫,在手机上阅读
栏目最新博文
13,954 views
2,127 views
12,490 views
17,050 views
11,284 views
29,328 views
20,866 views
13,670 views
31,803 views
10,848 views
栏目博文推荐
7,627 views
5,463 views
3,343 views
6,077 views
3,487 views
4,796 views
6,163 views
4,640 views
5,745 views
2,647 views
即使爬到最高的山上,一次也只能脚踏实地地迈一步。
1,182 views
关于网站与作者
互联网信息太多太杂,各互联网公司不断推送娱乐花边新闻,SNS,微博不断转移我们的注意力。但是,我们的时间和精力却是有限的。这里是互联网浩瀚的海洋中的一座宁静与美丽的小岛,供开发者歇息与静心潜心修炼(愿景)。
“Veda”的本义是知识、启示,希望这里能为开发者提供充足的技术资料。
我的电子邮件gonnsai(,腾讯微博:,欢迎与我联系。linux 驱动开发 - 经天纬地之奇才,济世匡时之智略
- 博客频道 - CSDN.NET
1267人阅读
& & linux驱动开发,听这个名词好像是很高深的东西!其实不然,对于开发人员来讲最总要的是理解几个概念!知道开发思路,下面将从概念,到应用做一个详细的阐述。
& & linux驱动:什么是linux驱动,其实很简单,就是基于linux操作系统,在系统下面想对外设进行操作。需要通过linux内核提供的驱动操作接口,对外设的寄存器进行设置,通过设置这些寄存器后能够方便linux系统对其进行操作。
下面就提到了几个问题,我在哪里编程?通过什么方式编译?我能不能像操作一般无MMU的MCU一样进行操作linux下的寄存器呢?我对特定的CPU该怎样来确定(假设为AT91SAM9260)或是说我怎么知道我设计的驱动是这个平台的驱动?对于操作过程中的头文件在哪里寻找?带着这些问题开始下面的解释。
& & 首先是在哪里编程:对于开发linux驱动这项工作来讲,肯定是在linux系统下的环境里面,这里就包括fedora、redhat、ubuntu等等linux平台的开发环境,我用的环境就是fedora 16。在fedora官网下载linux系统,然后在pc上安装。开发位置没有限定,如果是开发模块驱动。不需要将驱动放到对于的文件夹然后修改内核配置文件。通过vim就可以开发驱动了。这里同时解释一个问题,就是头文件的问题,linux驱动开发过程中需要调用大量的头文件,其中的头文件放在什么里面?开始开发前,系统必须下载linux内核,然后将linux内核编译通过。我开发过程中linux内核用的是linux-2.6.30。其中对于开发过程中的linux内核中的头文件的调用主要是根据需要来确定,这和实际开发相关。如果要开发对对应CPU的驱动,必须对要打对应linux内核的补丁。解决linux内核配置问题和linux内核中某些驱动支持的问题。
& & 编译,在一般PC平台上开发的工程师,一般就认同为编译。其实打多编译都属于交叉编译过程。除非是编译当前PC机平台上的应用或驱动。很简单,用keil开发C51或是M3的程序。编译生成的hex文件。这个过程就属于交叉编译。但是linux下的交叉编译不同于window下下的IDE的是,linux下的交叉编译环境是需要自己搭建和通过环境变量的设置进行切换的。其中我使用的交叉编译器是在Sourcery CodeBench Lite Edition
for ARM 其中提供两种交叉编译器,建议是使用arm-none-linux-gnueabi。安装完成可以通过export查看环境变量和设置。
& & 怎样操作linux下的CPU的寄存器呢?在ARM9以上的处理器都带有MMU单元。MMU单元屏蔽了应用程序对处理器外设的直接操作。将linux分为两层,即应用层和驱动层。对于linux驱动开发人员来讲,主要理解的是怎样进行对应的寄存器操作。个人觉得这是整个开发中的重点也是难点。这里将提到几个概念,4GB地址空间,内核空间,用户空间。对于这些概念网上有很多解释可以去问度娘。linux内核将4GB地址空间划分为1GB的内核空间和3GB的用户空间。实际CPU中没有这么大的空间,从这里将引入一个概念。那就是虚拟地址空间。在linux开发过程中,有物理地址和虚拟地址之分。这是两个很总要的概念,其中MMU的TBL实现物理地址到虚拟地址的转换。前面是在科普,下面进入正题。在linux内核中,CPU内核是只能识别虚拟地址的。虚拟地址必须通过linux内核中提供的文件进行转换,函数为:void
* ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags);其中相关的东西可以参考这篇优秀的博文《Linux内核中ioremap映射的透彻理解》。将物理地址转换成对应的虚拟地址或是虚拟地址块,提供给CPU内核操作。就能实现对linux系统下的带MMU处理器的寄存器进行操作。
& & 然后对于当前编写的驱动,我怎么知道我编写的是当前平台的驱动。刚刚开始的时候这个问题困扰了我很长时间。后来发现这是一个小问题。在linux内核里面有很多的平台支持。根据配置文件就能设置当前的CPU。如果是模块驱动,就只需要编译对应CPU的驱动。然后通过insmod命令加载到运行的系统中就行。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:70979次
积分:1101
积分:1101
排名:第19557名
原创:32篇
转载:17篇
评论:38条
(1)(2)(2)(1)(1)(1)(1)(2)(3)(1)(1)(2)(5)(3)(1)(2)(6)(2)(2)(4)(6)(1)《Linux内核修炼之道》 之 高效学习Linux驱动开发 - fudan_abc的Linux内核专栏
- 博客频道 - CSDN.NET
40598人阅读
这本《Linux内核修炼之道》已经开卖(网上的链接为:&、、&),虽然是严肃文学,但为了保证流畅性,大部分文字我还都是斟词灼句,反复的念几遍才写上去的,尽量考虑到写上去的每段话能够让读者产生什么疑惑,然后也都会紧接着尽量的去进行解释清楚,中间的很多概念也有反复纠结过怎么解释能够更容易的理解,力求即使对于初学者也可以有很少阻碍的一气读完。同时我也把书中一部分自己的感悟抽出来整理了精华版,share出来。当然水平有限,错漏之处有发现而修订时遗漏的,也有尚没有发现的。这本书如果对您有用,乃我之幸事,如果无用,就在此先诚惶诚恐的向大家拜个不是了。
下面仍然是之前5月份一次presentation的部分内容及讲义,不过当时的题目叫&驱动开发的方法论&,或许对大家有用吧。至于这几次讲座的视频貌似网上都能找到。
************************************************************************
&& & &&前一篇我们谈到了如何高效学习Linux内核,现在我们开始另外一个话题,就是如何高效学习
驱动开发。至于为什么会选择这样一个
,主要是基于这样两个原因:
第一个原因是:目前几乎所有的驱动开发方面的参考书,内容结构都是先介绍介绍什么是
驱动,它分为哪些种类,然后是各种类型设备的驱动程序的内容细节。大都是只注重各种驱动本身的细节,而没有站在一个全局整体的角度讲解一下驱动开发的方法。这样导致的后果就是,大多数的驱动开发者虽然可以正确的编写驱动程序,但往往都是只知其一不知其二,知其然而不知其所以然。
第二个原因是:目前很多驱动开发者,即使是已经有多年经验的开发者,在开发驱动的时候也就是填充填充
的结构体,对于比较成熟的平台,就是网上找个类似的驱动修改一下,即使写十个百个千个驱动,也就是对某些硬件比较熟,遇到全新的芯片全新的平台就束手无策。应该说这样对驱动的理解是很有限的。这也是目前
驱动开发领域的现状。
我们首先认识一下
驱动的基本面,我们认识一个新事物的的第一件事就是了解它的一些基本信息,就像我们人与人之间互相认识首先也是通过个人的基本信息一样。
驱动在本质上就是一种软件程序,上层软件可以在不用了解硬件特性的情况下,通过驱动提供的接口,和计算机硬件进行通信。
系统调用是内核和应用程序之间的接口,而驱动程序是内核和硬件之间的接口,也就是内核和硬件之间的桥梁。它为应用程序屏蔽了硬件的细节,这样在应用程序看来,硬件设备只是一个设备文件,应用程序可以象操作普通文件一样对硬件设备进行操作。
驱动程序是内核的一部分,管理着系统中的设备控制器和相应的设备。它主要完成这么几个功能:对设备初始化和释放;传送数据到硬件和从硬件读取数据;检测和处理设备出现的错误。
一般来说,一个驱动可以管理一种类型的设备。例如不同的
mass storage
设备,我们不需要为每一个
盘编写驱动,而只需要一个驱动就可以管理所有这些
mass storage
为方便我们加入各种驱动来支持不同的硬件,内核抽象出了很多层次结构,这些层次结构是
设备驱动的上层。它们抽象出各种的驱动接口,驱动只需要填写相应的回调函数,就能很容易把新的驱动添加到内核。
一般来说,
驱动可以分为三类,就是块设备驱动,字符设备驱动和网络设备驱动。块设备的读写都有缓存来支持,并且块设备必须能够随机存取。块设备驱动主要用于磁盘驱动器。
而字符设备的
操作没有通过缓存。字符设备操作以字节为基础,但不是说一次只能执行一个字节操作。例如对于字符设备我们可以通过
一次进行大量数据交换。字符设备实现比较简单和灵活。
网络设备在
里做专门的处理。
的网络系统主要是基于
机制。网络设备驱动为网络操作提供接口,管理网络数据的接送和收发。为了屏蔽网络环境中物理网络设备的多样性,
对所有的物理设备进行抽象并定义了一个统一的概念,称之为接口(
)。所有对网络硬件的访问都是通过接口进行的,接口对上层协议提供一致化的操作集合来处理基本数据的发送和接收,对下层屏蔽硬件差异。它与字符设备及块设备不同之处其一就是网络接口不存在于
的设备文件系统
和前一篇的介绍一样,看完外表,我们再看内涵,就是
驱动的工作流程。大概有四个部分:使用
加载,模块的初始化,进行设备操作,使用
驱动有两种存在形式,一种是直接编译进内核,就是我们在配置内核的时候,在相应选项上选
,另外一种就是编译成模块,按需加载和卸载。通常我们使用
命令完成模块的加载,在加载时还可以指定模块参数。另外一个常用的加载工具是
的不同在于它会检查模块之间的依赖关系,将该模块依赖的模块也加载到内核。
每个驱动都有自己的初始化函数,完成一些新功能的注册,这个初始化函数只是在初始化的时候被使用。在
系统里,设备以文件的形式存在,应用程序可以通过
等函数操作设备,通过设备文件实现对设备的访问。设备不再使用时,我们使用
命令来卸载它,卸载的过程会调用到驱动的推出函数,每个驱动都必须有一个退出函数,没有的话,内核就不会允许去卸载它。
驱动的外表和内涵都有了一个初步的认识之后,我们来看看作为一个驱动开发者,我们需要注意哪些问题。
首先,对模块机制的了解是开发
驱动的基础,因为我们编写驱动的过程也就是在编写一个内核模块的过程。早期版本的内核是整体式的,也就是说所有的部分都静态地连接成一个很大的执行文件。但是现在的内核采用的是新的机制,即模块机制:许多功能包含在模块内,当你需要时可以使用
去拥抱它,将它动态地载入到内核里,当你不需要时,则可以使用
将它一脚踢开。这就使得
的内核很小,而且在运行的时候可以不用
就能够载入和替代模块。
其次,我们要注重对设备模型的理解。其实从
内核开始,随着设备模型的出现,驱动的开发就不再是个困难的问题,毫不夸张得说,理解了设备模型,再去看那些五花八门的驱动程序,你会发现自己站在了另一个高度,从而有了一种俯视的感觉,就像凤姐俯视知音和故事会,韩峰同志俯视女下属。不过貌似大部分驱动开发者都没意识到这个问题。
最后,是要养成使用协议的
、内核参考代码去解决问题的习惯,而不是一碰到问题就到处寻找所谓的牛人去问怎么解决。
中间的那些内容和前面精华版的博文里差不多,就不贴了,&&&&
前面介绍了我个人感觉开发驱动需要注意的三个方面,现在说个实际的例子。前些天一个网友在自己的
博客上写了篇文章,名字就叫给
的一封信,信里说了自己的问题,我觉得应该很多人都存在这样类似的问题,这里咱们来看一下。
先说他个人的情况:有一定的
基础,熟悉
驱动的开发环境搭建和编译。现在想做个
驱动,该驱动要跑在
手边的资料有:
看了几次,基本了解
linux + arm
文件。但是对应的
kernel source
一遍,从函数名
可以知道他的功能。
怎么移植这个
平台上。之后遇到了一些列的问题,比如
的地址信息等等,具体我就不叙述了。
然后他就去请教了一些人
很简单,你填充那个
我对里边的
流程和调用不熟悉啊
你把他们想象成一个黑盒就可以了
无法想象,我怎么想象?我想看看里边的代码到底是怎样的。
&A:"....."
的请教结果,无法解决我的问题:我到底该怎么办?
移植就是注册那几个函数
你想看内核代码实现,内核这么大你怎么可能搞清楚,我做了那么多移植,有时候连芯片手册都搞不清,直接
吧代码给调出来的
无法理解,无话可说。
其实这里边的
说的也没错,很多人写驱动大概就这么做的,但是这样子就是写成百上千个驱动也不能说就理解
驱动了,面试时碰到的绝大部分人都属于这种情况,能回答自己做了什么,但一谈到一些相关的基本的问题就往往回答不上来。
我觉得首要的问题是缺乏好奇心,做技术的好奇心应该是原动力,特别对于搞
内核和驱动的,好奇心有多强,你的水平就可能会增长到多高。
其次,对于做驱动的来说,对于
内核,重要的是去理解设备模型,很多人都本末倒置了,很多专门写驱动的书也不注重设备模型的理解,只去应付一种种类型的协议和设备的驱动,即使写个一万个也仅仅是对比较成熟的芯片熟了些。而且,不理解设备驱动,难道写驱动的时候不觉得很多东西很朦胧么?这也是我个人觉得很奇怪的一方面。
像我之前设备模型的文章里说的那样,理解了设备模型,对各种类型的驱动就有种俯视的感觉了。这个时候再你去看特定类型的协议和设备的实现,脉络就很清晰,比如你看
的实现。这个时候重要的就是
的协议,具体芯片的
,加上看看内核里现有的
驱动作为参考,就是我所说的驱动开发的三件宝。
所以说如果希望做
的驱动话,关键还是要先去理解下设备模型,将这个比较抽象的概念在心里形象化,然后再去看具体的驱动比较好。
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:2005158次
积分:22529
积分:22529
排名:第128名
原创:296篇
评论:1995条
文章:40篇
阅读:194563
文章:25篇
阅读:394559
(1)(1)(1)(1)(1)(1)(4)(5)(9)(6)(4)(1)(1)(5)(4)(1)(8)(11)(6)(4)(4)(11)(18)(26)(37)(51)(25)(29)(20)内核UART串口驱动开发文档,console,tty,kernel_百度文库
两大类热门资源免费畅读
续费一年阅读会员,立省24元!
评价文档:
喜欢此文档的还喜欢
内核UART串口驱动开发文档,console,tty,kernel
内​核​U​A​R​T​串​口​驱​动​开​发​文​档​,​c​o​n​s​o​l​e​,​t​t​y​,​k​e​r​n​e​l
阅读已结束,如果下载本文需要使用
想免费下载本文?
把文档贴到Blog、BBS或个人站等:
普通尺寸(450*500pix)
较大尺寸(630*500pix)
你可能喜欢第八讲&Linux&驱动开发必备的内核知识(4)内存管理
第&四&章&内存管理
Linux&内存管理(黑板绘图表示)
&在&Linux系统中,进程的4GB内存空间被分为两个部分:用户空间与内核空间。用户空间地址一般分布为0~3GB,这样,剩下的3~4GB为内核空间。用户进程通常情况下只能访问用户空间的虚拟地址,不能访问内核空间虚拟地址。用户只有通过系统调用(代表用户进程在内核态执行)等方式才可以访问到内核空间。
&Linux中1GB的内核空间又被划分为物理内存映射区、虚拟内存分配区、高端页面映射区、专用页面映射区和系统保留映射区。
&一般情况下,物理内存映射区最大长度为&896MB,系统的物理内存被顺序映射在内核空间的这个区域中。当系统物理内存大于&896MB时,超过物理内存映射区的那部分内存称为高端内存,而且超过的那部分称之为常规内存,内核在存取高端内存时必须将它们映射到高端页面映射区。
&当系统物理内存超过4GB时,必须使用CPU的扩展分页(PAE)模式所提供的64位页目录项才能存取到4GB以上的物理内存。(x86_64 or PAE kernel)。
&4.2&用户空间内存动态申请
&在用户空间动态申请内存的函数为malloc(),释放函数为free()。Malloc()的内存一定要被free(),否则造成内存泄漏。
&4.3&内核空间内存动态申请&
&代码示例:&memory.c
&内核空间内存动态申请函数:kmalloc(),vmalloc()。
&Kmalloc()申请的内存位于物理内存映射区域,而且在物理上也是连续的,而vmalloc()在虚拟内存空间给出一块连续的内存区,这片连续的虚拟内存在物理内存中不一定是连续的
&4.3.1 Kmalloc(),kfree()
&Void *kmalloc(size_t size,int flags);
&给kmalloc的第一个参数是分配的块的大小,第二个参数为分配标志,用于控制kmalloc()的行为。
&:GFP_KERNEL&其含义是在内核空间的进程中申请内存,使用这个标志申请内存时,若暂时不能满足,则进程会睡眠等待页,即会引起阻塞,因此不能在中断上下文,或者持有自旋锁的时候使用这个标志分配内存。
&在中短处理函数,tasklet、和内核定时器等非进程上下文中不能阻塞,此时应当使用GFP_ATOMIC&标志来申请内存。当使用GFP_ATOMIC标志申请内存时,若不存在空闲页,则不等待,直接返回。
&  注意kmalloc最大只能开辟128k-16,16个字节是被页描述符结构占用了。kmalloc用法参见kh
&kmalloc ()分配的内存需用kfree()函数来释放
&实验用kmalloc分配一个大于&128k
4.3.2 vmalloc() vfree()
&vmalloc()一般只为较大的顺序缓冲区分配内存,vmalloc()分配,新的页表需要被建立,开销比较的大,因此只是用vmalloc()来分配少量的内存是不妥的。
&Vmalloc()分配用&vfree()来释放。
&Void *vmalloc(unsigned long size);
&Void *vfree(void *addr);
&Vmalloc()不能用在原子上下文中,因为它的内部实现标志用了&GFP_KERNEL的kmalloc()。
&在驱动或者操作系统的运行过程中,经常会涉及到大量对象的重复生成、使用释放问题。在Linux系统中所用到的对象,比较典型的例子是inode,task_struct,在驱动开发中比较常用到的例子是&request,slab&使得对象前后两次被使用时分配在同一块内存或者同一类内存空间,且保留基本的数据结构,使得效率大大提高。
&1.创建新&slab&缓存、向缓存中增加内存、销毁缓存的应用程序接口(API)以及&slab&中对对象进行分配和释放操作的函数。
&第一个步骤是创建&slab&缓存结构,您可以将其静态创建为:
struct struct kmem_cache *my_
2.创建&slab&缓存kmem_cache_create
&struct kmem_cache *
&kmem_cache_create(
&const char *name,
&size_t size,
&size_t align,
&unsigned long flags,
&void (*ctor)(void*, struct kmem_cache *, unsigned
&void (*dtor)(void*, struct kmem_cache *, unsigned
&kmem_cache_create()用于创建一个&slab&缓存,它是一个可以驻留任意数目全部同样大小的后备缓存,name&参数定义了缓存名称,proc&文件系统(在&/proc/slabinfo&中)使用它标识这个缓存。&size&参数指定了为这个缓存创建的每个对象的大小,&align&参数定义了每个对象必需的对齐。&flags&参数指定了为缓存启用的选项。
&SLAB_RED_ZONE&在对象头、尾插入标志,用来支持对缓冲区溢出的检查。
&SLAB_POISON&使用一种己知模式填充&slab,允许对缓存中的对象进行监视(对象属对象所有,不过可以在外部进行修改)。
&SLAB_HWCACHE_ALIGN&每个缓存对象必须与硬件缓存行对齐。
&SLAB_NO_REAP(即使内存紧缺也不自动收缩这块缓存)
&SLAB_CACHE_DMA(要求数据对象在DMA&内存区分配)
&ctor&和&dtor&参数定义了一个可选的对象构造器和析构器。构造器和析构器是用户提供的回调函数。当从缓存中分配新对象时,可以通过构造器进行初始化。
&在创建缓存之后,&kmem_cache_create&函数会返回对它的引用。注意这个函数并没有向缓存分配任何内存。相反,在试图从缓存(最初为空)分配对象时,refill&操作将内存分配给它。当所有对象都被使用掉时,也可以通过相同的操作向缓存添加内存。
&3.分配slab&缓存&kmem_cache_alloc
&要从一个命名的缓存中分配一个对象,可以使用&kmem_cache_alloc&函数。调用者提供了从中分配对象的缓存以及一组标志:
&void kmem_cache_alloc( struct kmem_cache *cachep, gfp_t
&这个函数从缓存中返回一个对象。注意如果缓存目前为空,那么这个函数就会调用cache_alloc_refill&向缓存中增加内存。&kmem_cache_alloc&的&flags&选项与&kmalloc&的flags&选项相同。
&kmem_cache_alloc&和&kmalloc&内核函数的标志选项
&标志&说明
&GFP_USER&为用户分配内存(这个调用可能会睡眠)。
&GFP_KERNEL&从内核&RAM&中分配内存(这个调用可能会睡眠)。
&GFP_ATOMIC&使该调用强制处于非睡眠状态(对中断处理程序非常有用)。
&GFP_HIGHUSER&从高端内存中分配内存。
&4.释放slab&缓存&kmem_cache_free
&内核函数&kmem_cache_free用来释放缓存。
&void kmem_cache_free( struct kmem_cache
*cachep&,void *objp);
.&收回slab&缓存kmem_cache_destroy
&内核函数&kmem_cache_destroy&用来销毁缓存。这个调用是由内核模块在被卸载时执行的。在调用这个函数时,缓存必须为空,就是已经执行过&kmem_cache_free
&void kmem_cache_destroy( struct kmem_cache *cachep
&示例&:&参考&memory.c
&DMA&是一种无需CPU&的参与就可以让外设与系统内存之间进行双向数据传输的硬件机制。使用DMA可以使系统CPU从实际的I/O数据传输过程中摆脱出来,从而大大提高系统的吞吐率。
&4.5.1 DMA&与CACHE一致性
&Cache&被用作CPU针对内存的缓存,利用程序的空间局部性,和时间局部性原理,达到较高的命中率从而避免CPU每次都一定要与相对慢速的内存交互数据来提高数据的访问速率。DMA可以用作内存与外设之间传输数据的方式,这样的传输方式之下,数据并不需要经过CPU中转。
&假设DMA针对内存的目的地址与cache缓存的对象没有重叠区域(如图1),DMA与Cache之间将相安无事。但是,如果,DMA的目的地址与cache缓存的内存地址访问有重叠(如图2),经过DMA操作,cache缓存对应的内存数据已经被修改,而CPU本身不知道,仍然认为cache中的数据就是内存中的数据,以后访问cache映射的内存时,仍然使用旧的cache数据。这样就造成cache与内存之间数据“不一致性”错误。
&解决DMA&导致的cache&数据不一致性问题的简单的方法就是禁止DMA目标地址范围内的内存cache功能。
&硬件&禁止或者软件&禁止。
&4.5.2 Linux下的DMA编程
&内存中用于与外设交互数据的一块区域被称作DMA缓冲区,在设备不支持Scatter/Gather(SG)的情况下,DMA缓冲区必须是物理上连续的。
&1.__get_dma_pages()
&对于ISA设备,DMA只能在16M以下内存中进行,因此,在使用kamlloc()和
&__get_free_pages()申请DMA缓冲区的时候应该使用GFP_DMA标志,这样能保证获得的内存具备DMA能力的。
&内核中定义了__get_free_pages()针对DMA的函数:
&#define __get_dma_pages(gfp_mask, order \&
&&__get_free_pages((gfp_mask)|GFP_DMA,(order))
&static unsigned long dma_mem_alloc(int size)
order = get_order(size);
&return __get_dma_pages(GFP_KERNEL,order);
&基于DMA的硬件使用总线地址而非物理地址(CPU角度看到的未经转换的地址)虚拟地址/总线地址unsigned long virt_bus(volatile void
*address);void
*bus_to_virt(unsigned long address);
DMA地址掩码
&设备并不一定能在所有的内存地址上执行DMA操作,在种情况下应该设置DMA地址掩码:
dma_set_mask(struct device *dev, u64 mask);
&3.&分配DMA一致性的内存区域
&DMA映射包括两个方面的工作:分配一片DMA缓冲区,为这片缓冲区产生设备可访问的地址,同时DMA也必须考虑cache一致性问题。这个函数是:
&void *dma_alloc_coherent(struct device *dev,size_t size,
dma_addr_t *handl,gfp_t gfp);
&返回DMA缓冲的虚拟地址,handle返回总线地址
&对应的释放函数为:
&void *dma_free_coherent(struct device *dev,size_t size,
dma_addr_t );
&Linux内核还提供了PCI设备申请DMA缓冲区的函数pci_alloc_consistent(),
&void *pci_alloc_consistent(struct pci_dev *dev, size_t
size, dma_addr_t *dma)
&对应的释放函数为:
&void pci_free_consistent(struct pci_dev *, size_t, void *,
dma_addr_t);
&4.&流式DMA映射
&相对于一致性DMA映射而言,流式DMA映射的接口较为复杂,对于单个已经分配的缓冲区而言,使用dma_map_single能实现流式DMA映射:
&dma_addr_t dma_map_single(struct device *dev, void *buffer,
size_t dize, enumdma_data_direction&
direction);
&DMA&的方向为direction:&DMA_TO_DEVICE
DMA_FROM_DEVICE
DMA_BIDIRIRECTIONAL&和&DMA_NONE。
&释放函数:
&dma_addr_t dma_unmap_single(struct device *dev, dma_addr_t
dma_addr, size_t dize, enum&dma_data_direction&
direction);&
&申请大缓冲区&SG模式下申请不连续小缓冲
dma_map_sg(struct device *dev,struct scatterlist *sg,int nents,
enum&&&dma_data_direction&
direction);返回缓冲区数量&&&对每个项,dma_map_sg为设备产生恰当的总线地址,它会合并物理上临近的内存区域。
&Scatterlist&结构体的定义如:
&struct scatterlist{
page *//page结构体指针&unsigned&&//缓冲区在page中的偏移
&&dma_addr_t dma_//总线地址
&&//缓冲区长度
&执行dma_map_sg()后,sg_dma_address可返回scatterlist&&对应缓冲区总线地址,sg_dma_len()dma_addr_t sg_dma_address(struct scatterlist
*sg);unsigned
int sg_dma_len(struct scatterlist *sg);使用dma_unmap_sg()去除映射:
dma_unmap_sg(struct device *dev,struct scatterlist *sg,int nents,
enum dma_data_direction& direction);
&4.7&实例:分析一个module issmod过程中动态的内存分配与释放:memory.c
&linux/init.h&
&linux/module.h&
&linux/mm.h&
&linux/slab.h&
&linux/vmalloc.h&
&linux/hardirq.h&
#ifndef false
#define false 0
#ifndef true
#define true 1
struct mem_get{
u8 tmp[512];
struct mem_get *my_
static struct kmem_cache
static void slab_init( void
&my_slab_cachep =
kmem_cache_create(&
& "slab_cache", &
&sizeof(struct mem_get), &
& SLAB_HWCACHE_ALIGN, &
& NULL, NULL ); &
int slab_alloc( void )
printk(KERN_INFO "Enter
%s\n",__func__);
printk(KERN_INFO "Cache name is %s\n",
kmem_cache_name( my_slab_cachep ) );
printk(KERN_INFO "Cache size is %d\n",
kmem_cache_size( my_slab_cachep ) );
my_buf = kmem_cache_alloc( my_slab_cachep,
GFP_KERNEL );
static void slab_free( void
printk(KERN_INFO "Enter slab_free
if (my_buf){
&& kmem_cache_free(
my_slab_cachep,my_buf );
if(my_slab_cachep)&
& kmem_cache_destroy(
my_slab_cachep );
void &*mem_alloc(u32 size,u8
slab_init();
slab_alloc();
printk(KERN_INFO "Enter %s,size=0x%x
\n",__func__,size);
if( size &=
4*PAGE_SIZE)
my_buf-&buf =
kmalloc(size,GFP_ATOMIC);
else if(dma_use)
my_buf-&buf =
kmalloc(size,GFP_DMA);
else if ((size &
4*PAGE_SIZE)&&(!in_interrupt())&&(!irqs_disabled()))
my_buf-&buf =
vmalloc(size);
my_buf-&buf =
kmalloc(size,GFP_ATOMIC);
BUG_ON(!my_buf-&buf);
void mem_free(u32 size,u8
printk(KERN_INFO "leave %s,size=0x%x
\n",__func__,size);
if( size &=
4*PAGE_SIZE)
kfree(my_buf-&buf);
else if(dma_use)
kfree(my_buf-&buf);
else if( (size &
4*PAGE_SIZE)&&(!in_interrupt())&&(!irqs_disabled()))
vfree(my_buf-&buf);
kfree(my_buf-&buf);
slab_free();
static int __init
test_mem_init(void)
& printk(KERN_INFO "Enter
%s\n",__func__);
mem_alloc(17*1024,true);
static void __exit
test_mem_exit(void)
& printk(KERN_INFO "Enter
%s\n",__func__);
mem_free(17*1024,true);
module_init(test_mem_init);
module_exit(test_mem_exit);
MODULE_LICENSE("Dual
BSD/GPL");
MODULE_AUTHOR("memory");
MODULE_DESCRIPTION("A simple mem
MODULE_ALIAS("a simplest
已投稿到:
以上网友发言只代表其个人观点,不代表新浪网的观点或立场。

我要回帖

更多关于 c 强制类型转换 的文章

 

随机推荐