linux device drivers 读书笔记(第二章)

搭建测试系统

(告诉你应该自己搭一个测试系统)

hello world模块

示例模块

#include <linux/init.h>//init所需要
#include <linux/module.h>//module都应该包含的,包括一些symbol
MODULE_LICENCE("Dual BSD/GPL");//协议要求

static int hello_init(void)
{
    printk(KERN_ALERT "Hello, world!");
    return 0;
}

static void hello_exit(void)
{
    printk(KERN_ALERT "Goodbye, world!");
}

module_init(hello_init);//设定Init函数
module_exit(hello_exit);//设定退出函数

编译(编译方法见后)后通过在root下,insmod和rmmod可以在tty看到提示

内核模块VS应用程序

应用程序存在链接过程,可以使用libc定义的函数,而内核模块限制较多,只与内核链接,不能使用libc中的函数,只能使用内核当中所定义的,所以与应用程序编程差距较大.比如不能使用一般的头文件,比如stdarg.h等,另外一个比较大的区别是对于错误的解决办法,对于应用程序,无非是出现段错误,然后等待解决,但是对于内核模块来说可能就会有很严重的错误了.

用户空间和内核空间

模块是在内核空间运行的 ,应用程序在用户空间运行,内核的运行权限更高,高于用户程序,它们运行在不同的cpu模式下,我们将运行模式称为内核空间和用户空间,这两种说法不仅指明了权限问题,也指明了它们个字有自己的内存映射,也就是地址空间.

内核中的并行

驱动代码作为内核代码,必须是可重入的,所以在写驱动程序的时候就要考虑到关于并行时可能带来的问题.

当前进程

内核也是通过特定的一些进程来运行的,内核代码通过进入全局实例current,在 < asm/current.h>当中定义的来找到当前运行的进程.而current可以得到一个task_struct的结构体的指针,在< linux/sched.h>当中定义.
其实current并不是一个完全的全局变量,由于需要快速存取current,内核选择将task_struct结构体放在内核栈上,然而具体实现对于其他子系统来说是屏蔽的,其他部分只需要通过包括linux/sched.h然后获取到current就可以了.

其他的一些细节

  1. 应用程序在虚拟内存中有很大的一片栈区域,内核只有很小的一段栈区域,可以小到就只有一页,4096字节.
  2. 内核的API有一些函数名用双下划线开头,表示是底层组件的接口,使用的时候需要多加注意
  3. 内核代码不能进行浮点运算

编译和装载

编译

和用户空间应用程序的构建有显著的区别,而且和之前版本的内核也会有区别. 在内核源码Documentation/kbuild目录下有详细的介绍.

内核源码中的Documentation/Changes目录列出了所需要的工具版本.需要注意所使用的工具版本符合要求.

准备好所有工作之后,你需要写一个makefile. 我们以之前的helloworld模块为例,一行就可以解决问题

obj-m := hello.o

这不是一个传统的makefile,而是由内核构建系统来处理剩下的工作.最终会编译出一个hello.ko

如果你有一个modole.ko由两个源文件(file1.c file2.c)创造,则makefile为

obj-m := module.o
module-objs := file1.o file2.o

如果你的内核源码树在你的~/kernel-2.6目录下,那么make命令就是

make -c ~/kernel-2.6 M=`pwd` modules

-C用来切换目录,M=使得makefile在构造模块之前移动回模块源码目录.

使用上面的make可能会有些麻烦,所以内核开发者开发了一些方法使其变得更容易,以下是这种方法的makefile

#如果KERNELRELEASE被定义了,说明我们是从内核编译系统调用,直接使用他的语言
ifneq ($(KERNELRELEASE),)
    obj-m := hello.o
#否则我们是直接从命令行调用,所以需要调用内核编译系统
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)

default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

装载和移除模块

通过insmod可以装载模块,它和ld的作用比较类似,将为解析的符号链接到内核的符号表中,但是内核不改变模块的磁盘文件,而是在它在内存中的映像中改变.

内核支持insmod是通过一个在kernel/module.c中定义的系统调用来实现的,sys_init_module分配了内核内存来保存一个模块,然后把模块文本复制到这块区域中,解析引用然后调用模块初始化函数开始工作.

系统调用函数在内核源码中为sys_前缀.

modprobe和insmod一样,也是装载一个模块进入内核,但是它还会看是否这个模块引用了一些没有在内核符号表中的函数,然后会去其他模块中寻找,之后将其加载进入内核,但是insmod在这种情况下就会直接报告无法解析符号.

rmmod用来卸载模块,但是如果内核认为这个模块还在使用或者内核被配置为不允许模块卸载,就会报错,当然可以把内核配置为可强制移除模块,但是可能会造成一些问题

版本依赖

在模块build过程中,由一步是将模块链接到一个叫做vermagic.o的文件,这个文件位于当前内核树中,包含了一些关于内核的信息,比如目标内核版本,编译器版本等.在装载的时候,这些信息会被用来作兼容性检验,如果不符合,则不会被装载
比如出现以下错误

# insmod hello.ko
Error inserting './hello.ko': -1 Invalid module format

在系统日志中,比如/var/log/messages或者其他地方会指明错误原因.

在linux/module.h默认包含了linux/version.h,定义了以下宏可以用来在#ifdef中进行版本校验,来使得模块可以用于多版本.

UTS_RELEASE
为一个版本的字符串,如"2.6.10"
LINUX_VERSION_CODE
用二进制表示的内核版本,比如2.6.10表示为0x02060a
KERNEL_VERSION(major,minor,release)
将major.minor.release转换为二进制表示的内核版本,如KERNEL_VERSION(2,6,10)即0x02060a

平台依赖

(似乎没有太多需要记住的东西)

内核符号表

insmod通过内核符号表解析符号,这个表包含了内核中的全局符号的地址,比如函数和变量.而模块被载入之后,模块中导出的符号也就成为了内核符号表的一部分.

新的模块可以使用被你的模块导出的符号,所以可以将其他模块堆叠上去,而在这种情况下,modprobe功能就十分有用了.

内核源码头文件提供了一些很方便的方法用来管理符号,比如需要导出的时候

EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);

带gpl的版本保证在GPL协议下可用.这些宏必须在全局的位置使用,不能在函数内使用,因为这些宏会展开为特殊用途的全局变量,然后存储在文件的ELF区.linux/module.h定义了一些细节.

预备

几乎所有的模块包含了以下两个头文件

#include <linux/module.h>//包含了很多符号的定义
#include <linux/init.h>//需要其用来指明初始化和清除函数

很多模块还包含了moduleparam.h,用来保证装载时的参数传递.

协议可以使用以下宏来定义

MODULE_LICENCE("GPL")

可用的协议如下描述(以下来自linux/module.h)


/*
 * The following license idents are currently accepted as indicating free
 * software modules
 *
 *  "GPL"               [GNU Public License v2 or later]
 *  "GPL v2"            [GNU Public License v2]
 *  "GPL and additional rights" [GNU Public License v2 rights and more]
 *  "Dual BSD/GPL"          [GNU Public License v2
 *                   or BSD license choice]
 *  "Dual MIT/GPL"          [GNU Public License v2
 *                   or MIT license choice]
 *  "Dual MPL/GPL"          [GNU Public License v2
 *                   or Mozilla license choice]
 *
 * The following other idents are available
 *
 *  "Proprietary"           [Non free products]
 *
 * There are dual licensed components, but when running with Linux it is the
 * GPL that is relevant so this is a non issue. Similarly LGPL linked with GPL
 * is a GPL combined work.
 *
 * This exists for several reasons
 * 1.   So modinfo can show license info for users wanting to vet their setup
 *  is free
 * 2.   So the community can ignore bug reports including proprietary modules
 * 3.   So vendors can do likewise based on their own policies
 */

另外一些描述性的定义包括MODULE_AUTHOR指明作者,MODULE_DESCRIPTION指明描述,MODULE_VERSION指明版本.MODULE_ALIAS指明别名,MODULE_DEVICE_TABLE指明模块支持.
MODULE_开头的定义在源文件函数外的任何地方都可以出现,不过大多会放在文件最后.

初始化和关闭

static int __init func(void)
{
//初始化
}
module_init(func);

这样的方式用来指明初始化函数,__init表明该函数仅在初始化时使用,之后会被处理掉.相应的还有__initdata,用来说明数据只在初始化时使用.
还有__devinit和__devinitdata,这些仅在内核被配置为热插拔时翻译为__init和__initdata.

module_init用来指明一个初始化函数,为模块的目标文件添加一个特殊的区域用来找到初始化函数.

清除函数

static void __exit func(void)
{
//清除
}
module_exit(func);

__exit和__init对应,用处也对应,module_exit和module_init对应,不过它们都是在模块移除时调用.注意这个函数不需要返回.如果没有定义清除函数,内核将不允许移除这个模块.

初始化时的错误处理

注意在初始化步骤中出现错误需要将已经进行的步骤逆转,使用方法可以使用goto来使得工作更加简化.

模块装载时的竞争

注意竞争条件,其他章会详细说明

模块参数

insmod可以指明模块参数,

insmod hellop howmany=10 whom="Mom"

howmany和whom即模块参数,这些模块参数用module_param宏来定义,module_param在moduleparam.h中定义.
module_param的原型为

module_param(name, type, permission);

name表示变量名,type表示类型,permission是一个权限掩码,是为文件系统准备的.

比如用法

static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);

支持的类型包括

bool 和 invbool :内部使用int类型表示,invbool是反过来的truefalse
charp :char指针
int ,long, short, uint, ulong, ushort : 基本的整形值,u表示无符号.

数组参数是用逗号隔开的列表,也是被支持的,数组参数的定义是

module_param_array(name,type,num,permission);

type是其中元素的类型,num是整型变量

permission值在

在用户空间做到

(一些在用户空间写程序代替模块的方法)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值