没有合适的资源?快使用搜索试试~ 我知道了~
资源推荐
资源详情
资源评论
Linux 的驱动机制及实现
——by GeYingjun
1 Linux 驱动与模块
1.1 Linux 的模块
驱动程序为了与外设硬件进行交互,必须调用 Linux 内核提供的内存管理、文件系统等
多种功能函数。而用户态的程序只能通过系统调用来使用有限的内核功能,因此驱动程序在
linux 系统中被安排在内核态运行,也即可以访问所有的内核功能。
但是为了把内核通用功能与这些驱动程序区分开来,Linux 通常要求驱动程序作为一个
模块(module)在内核空间运行,并且由内核进行管理。当然,实际上模块并非只能用来编写
驱动程序,模块可以作为任何一个独立的功能块,操作内核函数并通过特定接口向应用程序
提供服务。
1.2 模块的编写要求
模块的代码中,必须加入以下头文件,才能正确地引用模块机制的若干函数:
#include <linux/module.h>
#include<linux/init.h>
当然,如果编写的模块是驱动程序,则还需要加入与驱动程序相关的内存管理、设 备注
册、中断管理等头文件。
模块与通常的应用程序不同,并 没 有一个 main 函数,而是一些相互作用的函数的集合。
当然,这些集合中必须有一个首先调用的初始化函数,以及一个退出函数。
初始化函数:在模块加载时被调用,负责申请并初始化模块运行时必须的数据结构,此
后 这些结构有可能在模块存续期间一直存在。
退出函数:在模块卸载时被调用,卸载后模块内任何数据结构都不该继续存在,因此退
出函数必须仔细的把模块运行带来的所有数据结构释放掉,否则将会造成内存泄露。
为了使内核知道模块中初始化函数和退出函数的函数名,必须用以下两个宏来定义:
module_init(s3c_ts_init);
module_exit(s3c_ts_exit);
上例中 s3c_ts_init 被定义为模块的初始化函数,s3c_ts_exit 被定义为模块的退出函数。该例
取自 S3C6410 平台的触摸屏驱动。
1.3 模块的编译
模块可以包含在内核之中,也可以在内核之外。包含在内核之中,Linux 启动时会初始
化模块(即调用模块的初始化函数); 在内核之外的模块,可以在内核启动之后,通过外部
命令 insmod 来将模块加载进内核,此时才调用模块初始化函数。两种方法对于模块的功能
没有任何差别,差别主要在模块的初始化时机,以及内核本身的大小。
外部模块的编译通常用如下 makefile:
obj-m := module.o
module-objs := file1.o file2.o
make –C ~/kernel-2.6 M=’pwd’ modules
例子中模块的名字为 module.o,由两个源文件(file1.c file2.c)生成,make 命令行指定了内核
文件所在的位置,即内核源码目录。
以上方法编译的模块,通常用于开发阶段的调试或者 PC 平台的驱动程序。而在外设数
量有限的嵌入式平台上,往往将驱动模块作为 Linux 内核内部模块编译。将内核与驱动生成
的单一镜像向外发布。
内核内部(驱动)模块的编译:
1、 通过 make menuconfig 等工具对驱动模块进行配置,可选择为内部模块或者外部模
块。配置工具根据配置选项,将生成一系列 CONFIG_变量,变量的值为 y(内部模
块)或者 m(外部模块),或者 n(不编译)。例如将 S3C 平台的 touchscreen driver
配置为内部模块,将生成 CONFIG_TOUCHSCREEN_S3C 变量,值为 y。
2、 对应目录下的 Makefile 将根据这种变量,把对应的.o 文件加入$(obj-y)列表中。
obj-$(CONFIG_TOUCHSCREEN_S3C) += s3c-ts.o
3、 在最顶层的 Makefile 中,所有的$(obj-y)内的.o 文件都将被编译,并被链接成内核镜
像。需要注意的是,除了驱动模块,$(obj-y)也会包括其他很多配置的内核自身功能。
因此$(obj-y)并不区分要编译的.o 文件是内核功能还是驱动模块。
内部驱动模块是本文讨论的重点,此后所有的分析均基于内部驱动模块的机制。
1.4 内部驱动模块的加载
上文说明,编译到 Linux 内核内部的驱动模块,将在 Linux 启动时自动加载(调用初始
化函数),本节将详细说明 Linux 何时加载这些驱动模块,以及各模块加载的顺序是如何确
定的。
1.4.1 module_init 的定义
要说明何时被加载,就要从 module_init 的定义说起。在 /include/linux/Init.h 中有如下声
明:
#define __define_initcall(level,fn,id) \
static initcall_t __initcall_##fn##id __used \
__attribute__((__section__(".initcall" level ".init"))) = fn
/*
* Early initcalls run before initializing SMP.
*
* Only for built-in code, not modules.
*/
#define early_initcall(fn) __define_initcall("early",fn,early)
/*
* A "pure" initcall has no dependencies on anything else, and purely
* initializes variables that couldn't be statically initialized.
*
* This only exists for built-in code, not for modules.
*/
#define pure_initcall(fn) __define_initcall("0",fn,0)
#define core_initcall(fn) __define_initcall("1",fn,1)
#define core_initcall_sync(fn) __define_initcall("1s",fn,1s)
#define postcore_initcall(fn) __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn) __define_initcall("2s",fn,2s)
#define arch_initcall(fn) __define_initcall("3",fn,3)
#define arch_initcall_sync(fn) __define_initcall("3s",fn,3s)
#define subsys_initcall(fn) __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn) __define_initcall("4s",fn,4s)
#define fs_initcall(fn) __define_initcall("5",fn,5)
#define fs_initcall_sync(fn) __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn) __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn) __define_initcall("6",fn,6)
#define device_initcall_sync(fn) __define_initcall("6s",fn,6s)
#define late_initcall(fn) __define_initcall("7",fn,7)
#define late_initcall_sync(fn) __define_initcall("7s",fn,7s)
#define __initcall(fn) device_initcall(fn)
#define __exitcall(fn) \
static exitcall_t __exitcall_##fn __exit_call = fn
#define module_init(x) __initcall(x);
#define module_exit(x) __exitcall(x);
这一段声明的前提条件是#ifndef MODULE,也 就 是 说 如果驱动没有定义为外部模块的情
况下才有效。从声明中可以看出,module_init(x)最终被定义为__define_initcall("6",x,6),该宏
定义会将函数名 x 添加入.initcall" level ".init 段,也就是.initcall6.init 段。
在 Linux 的链接脚本 vmlinux.lds 中对上述段有如下布局:
__early_begin = .;
*(.early_param.init)
__early_end = .;
__initcall_start = .;
*(.initcallearly.init) __early_initcall_end = .; *(.initcall0.init) *(.initcall0s.init) *(.initcall1.init) *(.initcall1s.init)
*(.initcall2.init) *(.initcall2s.init) *(.initcall3.init) *(.initcall3s.init) *(.initcall4.init) *(.initcall4s.init) *(.initcall5.init)
*(.initcall5s.init) *(.initcallrootfs.init) *(.initcall6.init) *(.initcall6s.init) *(.initcall7.init) *(.initcall7s.init)
__initcall_end = .;
可见,所有 module_init()定义的函数名,都被放入了__initcall_start 与__initcall_end 之间
的表中,level 越低,排在越靠前的位置。
1.4.2 Linux 初始化模块的过程
Linux 的初始化函数为 start_kernel(),函数内最后一个步骤调用 rest_init。内部模块就是
在这个函数执行时被初始化的。调用路径为:
rest_init()---------------------->kernel_thread(kernel_init,…)----------------------------->
do_basic_setup()----------------------->do_initcalls()
static void __init do_initcalls(void)
{
initcall_t *call;
for (call = __early_initcall_end; call < __initcall_end; call++)
do_one_initcall(*call);
/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
函数 do_initcalls()查找位于__early_initcall 和__initcall_end 之间的表内所有的函数,并用
do_one_initcall()来执行该函数。因此包括驱动在内的所有内部模块都会在这一步被加载,由
于 驱动使用 module_init(x)来定义,level 为 6,因此会有其他模块先于驱动模块被加载。而
所有的驱动模块之间的先后顺序则是由 Makefile 将.o 加入$(obj-y)的顺序决定的。因此在多
层次驱动模块加载的场合下,需要通过不同层次的 Makefile 来安排先后顺序。
2 Linux 系统中的设备与驱动
2.1 设备与驱动的关系
Linux 系统为了能操作和管理各种外设,将外设抽象成了设备(device),而将对外设进行
的各种操作逻辑包装进了驱动(driver)模型。
在 Linux 的设备模型中,所有的设备都被连接在某个总线(bus)上。总线的概念显然是从
X86 架构上继承而来的,X86 架构的 CPU 通过南桥芯片扩展出各种总线,外设必须插在某个
总线上才能与 CPU 相连接。然而在基于 ARM 的嵌入式 CPU 上,通常把很多外设控制器都集
成在 CPU 内,而且通常也不具备 PCI 等复杂总线控制器。为了使得嵌入式平台能够在形式上
与 X86 相似,普遍采用一种成为“platform”的总线来表示集成到 CPU 平台内的设备。
设备与驱动,这 两 个 概念对 CPU 操作外设都至关重要,但是设备本身在 Linux 内仅是划
分出了操作该外设时所必须要保留的一些资源(如内存映射区域、IRQ 号等),同时指定了
设备的名字。几乎所有的对外设操作的逻辑都在驱动模型内,而驱动与设备的关联则是通过
二者所具有相同的名字实现的。
以下仍然以触摸屏设备和驱动说明二者关系:
S3C6410 平台触摸屏设备的定义:
/arch/arm/plat-s3c/Dev-ts.c 中
struct platform_device s3c_device_ts = {
.name = "s3c-ts",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_ts_resource),
.resource = s3c_ts_resource,
};
static struct resource s3c_ts_resource[] = {
[0] = {
.start = S3C_PA_ADC,
.end = S3C_PA_ADC + SZ_4K - 1,
.flags = IORESOURCE_MEM,
},
[1] = {
.start = IRQ_PENDN,
.end = IRQ_PENDN,
.flags = IORESOURCE_IRQ,
},
[2] = {
.start = IRQ_ADC,
.end = IRQ_ADC,
.flags = IORESOURCE_IRQ,
}
};
触摸屏设备定义为 platform_device 类型的结构体,.name 为“s3c-ts”,它包括的设备资
源为 s3c_ts_resource[]数组内定义的 3 个 resource 结构体。resource 结构体内的成员含义及
作用将在后文设备注册中介绍。
S3C6410 平台触摸屏驱动的定义为:
/drivers/input/touchscreen/S3c-ts.c 中
static struct platform_driver s3c_ts_driver = {
.probe = s3c_ts_probe,
.remove = s3c_ts_remove,
.suspend = s3c_ts_suspend,
.resume = s3c_ts_resume,
.driver = {
.owner = THIS_MODULE,
.name = "s3c-ts",
},
};
该结构体为 platform_driver 类型,其中的 driver 成员的.name 设定为“s3c-ts”,与上面的设
备名字一致。
剩余53页未读,继续阅读
zhanglu231123
- 粉丝: 235
- 资源: 100
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功
- 1
- 2
前往页