看O'REILY的《Linux设备驱动程序》一书(一)

看O'REILY的《Linux设备驱动程序》一书(一)
2007年08月09日 上午 11:52

买了一本Linux超经典的书,对于刚开始了解内核的我来说,阅读它是一件很刺激,也很有挑战的事;我买这本书,因为我刚对Linux内核机制有了一点认识,我想深入一点,听说搞Linux驱动是认识内核及实践内核编程技术及思想的最好方式(对于搞硬件的人来说吧!),我想也是的,太“软件抽象”的东西(内核的一些具体部分)很难懂,但对于硬件编程我想这下可以刨根寻底到家了(可以解决在我脑中困扰已久的几个问题),另一方面可以从侧面认知内核,待来日好好的学它好了。以后我要没看完一个章节都要总结一下,因为看这本书就像在品一道佳肴,不回味怎么行呢

Linux驱动的大致认识

最终形式是-模块

大模块,小模块要按类来分的话,有:字符模块、块模块、网络模块。

模块有几个特性:可配置装载,也可按需卸载;具有层叠性,通过模块之间导出的符号来实现。

装载与移除模块的技术实现

装载时由insmod完成,如insmod ./hellop.ko param=0 ,与insmod类似,modprobe也可以装载模块到内核,以insmod区别在于,它会check模块所引用的在当前内核符号表中的符号,如果需要其他模块(模块导出的符号全都在内核符号表中)它会在当前模块目录下寻找所需模块,并把它们装上去,相当于用了多个insmod;

insmod使用时还可以添加选项,这些选项是从驱动模块向insmod内核调用的参数,编程时,如:

static char *whom = "world" ;

static int howmany = 1 ;

module_param(howmany , int , S_IRUGO ) ;

module_param(whom , charp , S_IRUGO|S_IWUSR ) ;

卸载模块时用rmmod,如果内核认为模块正在被使用,或者禁止移除,则该命令无效;

此外还有lsmod,用于列出装载到当前内核中的所有模块

层叠性的实现

模块层叠性是靠公共内核符号表来联系的。模块可以导出自己的符号,也可以使用别的模块的导出符号,这样新模块可以在原来低层模块的基础上实现,如:USB输入设备模块层叠在usbcore和input之上;当然也可以由上层提供符号与具体硬件的相关驱动使用。

怎样导出符号

导出符号到公共内核符号表中,使用一个宏来实现的(也为了防止内核中名字空间的污染):

EXPORT_SYMBOL(name)

或EXPORT_SYMBOL_GPL(name)

注意:符号必须在模块文件的全局部分导出,不可在函数中导出;这是由于上面的宏将拓展成特殊变量的声明(该变量在模块可执行文件的特殊部分中保存,装载时内核通过在各个段来寻找模块导出的变量),当然要把声明部分放到全局部分了。

驱动模块的初始化和清除函数

由于内核资源很宝贵,所有在调用驱动初始化和清除函数完了之后,会自动清理它们;其实也体现了驱动与用户程序的不同。

初始化的内容:初始化时可以注册的设施(facilities,如串口、杂项设备、sysfs入口、/proc文件、可执行域以及线路规程(line discipline)等,有相应的内核函数来完成注册)。注意一旦注册了某个设施,内核就有可能立即启用它,可能这时初始化函数还没运行完。

一般形式:

static int __init init_func(void)

{

                /* ... */

}

module_init(init_func) ;

__init 表明该函数使用在初始化期间的,对内核的一种暗示,在模块装载完成之后,就会把它扔掉,把它占用的内存释放出来,同样有专在初始化期间使用的数据得用__initdata声明。

注意:不要在初始化完成后还用__init或__inidata来声明函数或者数据,内核代码中还可以用__devinit或__devinitdata

(小知识,在内核编程时遇到某个函数或者数据类型前缀是“_ _”的说明请你慎用该函数或者小心声明该中类型的数据结构)

清理函数:如果模块不定义清理函数,则说明内核不允许卸载该模块

static void __exit cleanup_func(void)

{

               /* ... */

}

module_exit(cleanup_func) ;

__exit或__exitdata来修饰的代码会被编译器放在特殊的ELF段里

驱动编程得处处谨慎,每写到一处,就得瞻前顾后,考虑各种可能发生的情况,所以驱动编写也是费脑筋的事;比如在初始化中注册设施时可能会失败,这时首先要判断模块是否可继续初始化(通常在某个注册失败后可以通过降低功能来继续运行,只要有可能,某快应该继续向前并尽可能的提供功能),如果在发生某个错误之后无法在继续前进,这时就得“撤了”-注销已注册的设施。

注销已注册的设施的技术

错误处理有时用goto比较有效,如:

int __init my_initfunc(void)

{

           int err ;

           /*           使用指针和名称注册           */

           err = register_this (ptr1 , "skull" ) ;

           if (err ) goto fail_this ;

           err = register_that (ptr2 , "skull" ) ;

           if (err ) = goto fail_that ;

           err = register_those (ptr3 , "skull" ) ;

           if (err ) = goto fail_those ;

           return 0 ; /*           成功           */

           fail_those : unregister_that (ptr2 , "skull" ) ;

           fail_that : unregister_this (ptr1 , "skull" ) ;

           fail_this : return err ; /*           返回错误           */

}

机制与策略-Unix设计背后隐含的最好思想之一

机制是指“需要提供什么功能”-最大地发掘设备价值,

策略是指“如何使用这些功能”-最有效地管理这些功能

驱动(内核空间运行的驱动)在Linux/Unix中的定位是提供机制,而驱动也可以泡在用户空间中,它是为了更有效地提供策略

如:X Windows系统(分为X服务器,客户程序以及通信渠道),X服务器的窗口及会话管理器运行在用户空间,实现管理桌面的一种策略;而对图形显示的低层驱动则在内核空间中实现,提供一种图形表达的机制。

不带策略的驱动程序包含一些特征:同时支持同步和异步操作、驱动程序能够被多次打开、充分利用硬件特性,以及不具备用来“简化任务”的或提供策略相关的软件层等。实际上,不带策略的驱动是软件设计者的一个共同目标。

其实说具体点,模块化的驱动代码是运行在内核态的,内核具有并发性,而内核本身用了大量的机制来保证能并发的提供服务已经不容易了,而驱动作为内核的一部分(模块中某些函数作为系统调用的一部分而执行,其他则负责中断),要与内核一致,也不会很容易实现,因为你要考虑内核运行起来遇到各种问题。为了适应内核的特性,驱动也必须具备内核的某些特征,如并发性,优良的并发管理(暗示出策略的特性)是必需的

驱动也可以运行在用户态的,与运行在内核态的相比,对于不同目的驱动有不同的特性相配,如用户态的驱动被实现为一个服务器进程,其任务是替代内核作为控制硬件的唯一代理。客户程序可连接到该服务器并和设备执行实际的通信;如此一来,好的驱动进程可允许对设备的并发访问。其实这就是X服务器的本质。

凡事都要按实际情况来做出响应的代码实现啊。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值