第二章 构造和运行模块

一、Hello World模块

 hello.c

#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("Dual BSD/GPL");

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

static void hello_exit(void)
{
    printk(KERN_ALERT"Goodbye, cruel world\n");
}

module_init(hello_init);
module_exit(hello_exit);

Makefile

# if KERNELRELEASE is defined, we've benn invoked from the
# kernel build system and can use its language.
ifneq ($(KERNELRELEASE),)

obj-m := hello.o

# Otherwise we were called directly from the command
# line; invoke the kernel build system.
else
    KERNELDIR ?= /lib/modules/$(shell uname -r)/build
    PWD := $(shell pwd)
default:
    $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif

moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核

printk函数和print函数类似,字串KERN_ALERT是消息优先级

与应用程序的不同

一个终止的应用程序可以在释放资源方面懒惰, 或者完全不做清理工作。

但是模块的退出函数必须小心恢复每个由初始化函数建立的东西, 否则会保留一些东西直到系统重启.。

和应用程序不同,如果没有库连接到模块中,源文件不应当包含通常的头文件。只有内核不部分的函数才可以在内核模块使用

一个内核的段错误至少会杀掉当前进程,如果不终止整个系统。

用户空间和内核空间

模块在内核空间运行,应用程序在用户控件运行。

系统至少有两种级别,内核(超级模式),最低级模式(用户模式),以此控制对硬件的直接存取以及对内存的非法访问。

内核空间和用户空间,都有自己对应的内存映射(自己的地址空间)

内核的并发

内核编程找那个有几个并发的来源,linux系统中运行多个进程,在同一时间,不止一个进程能够试图使用你的驱动

当你查看内核 API 时, 你会遇到以双下划线(__)开始的函数名. 这样标志的函数名通常是一个低层的接口组件, 应当小心使用.

内核的版本检查

UTS_RELEASE
这个宏定义扩展成字符串, 描述了这个内核树的版本. 例如, "2.6.10".

LINUX_VERSION_CODE

这个宏定义扩展成内核版本的二进制形式, 版本号发行号的每个部分用一个字节表示. 例如, 2.6.10 的编码是 132618 ( 就是, 0x02060a ). [4]4有了这个信息, 你可以(几乎是)容易地决定你在处理的内核版本.
KERNEL_VERSION(major,minor,release)
这个宏定义用来建立一个整型版本编码, 从组成一个版本号的单个数字. 例如,KERNEL_VERSION(2.6.10) 扩展成 132618. 这个宏定义非常有用, 当你需要比较当前版本和一个已知的检查点.

模块的堆叠

可以使用modeprobe,类似于insmod。

如果你的模块需要输出符号给其他模块使用,应当使用下面的宏定义:

EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);

几乎所有模块都有

#include <linux/module.h>

#include <linux/init.h>

MODULE_LICENSE("GPL")

MODULE_AUTHOR(声明谁编写了模块)

MODULE_DESCRIPION(一个人刻度的关于模块做什么的声明)

MODULE_VERSION(一个diamante修订版本号)

MODULE_ALIAS(模块为人所知的另一个名字)

MODULE_DEVICE_TABLE(来告知用胡空间,模块支持哪些设备)

模块的初始化和关停

模块的初始化通常是:

static int __init initialization_function(void)

{

  /* Initialization code here */

}

module_init(initializatioon_function);

__init标志是给内核的一个暗示,给定的函数只是在初始化使用,加载后会丢掉这个初始化函数。

__initdata给只在初始化时用的数据,后面可能还会有__devinit和__devinitdata

清理函数

static void __exit cleanup_function(void)

{

  /* Cleanup code here */

}

module_exit(cleanup_function);

清理函数没有返回值,__exit修饰符用于模块卸载

初始化汇总的错误处理

在之策内核设施时,注册可能失败,几遍最简单的动作常常需要内存分配,分配的内存可能不可用。

因此模块代码必须一致检查返回值,并且确认要求的操作实际上已经成功。

如果注册时发生任何错误,首先第一的事情是决定模块是否能够五路你如何继续初始化它自己。

任何时候,你的模块应当尽力向前,并提供事情失败后具备的能力。

如何模块证实失败,不能完全加载,必须取消失败前注册动作。因此初始化某个点失败,模块必须自己退回所有东西。否则内核就处于不稳定状态。

使用goto是处理错误回复的最好情况

int _init my_init_function(void)

{

  int err;

  err = register_this(ptr1, "skull");  /* registration takes a pointer and a name */
    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;            /* success */
fail_those:
    unregister_that(ptr2, "skull");
fail_that:
    unregister_this(ptr1, "skull");
fail_this:
    return err;        /* propagate the error */
}

err是错误码,在<linux/errno.h>中。显然在模块清理函数中,是按照注册时相反的顺序注销设施。

void __exit my_cleanup_function(void)
{
    unregister_those(ptr3, "skull");
    unregister_that(ptr2, "skull");
    unregister_this(ptr1, "skull");
    return;
}

goto难以管理,清理函数必须撤销注册前的每一项

struct something *item1;
struct somethingelse *item2;
int stuff_ok;

void my_cleanup(void)
{
    if(item1)
        release_thing(item1);
    if(item2)
        release_thing2(item2);
    if(stuff_ok)
        unregister_stuff();
    return;
}

int __init my_init(void)
{
    int err = -ENOMEM;

    item1 = allocate_thing(arguments);
    item2 = allocate_thing2(arguments2);
    if(!item2 || !item2)
        goto fail;

    err = register_stuff(item1, item2);
    if(!err)
        stuff_ok = 1;
    else
        goto fail;
    return 0;

fail:
    my_cleanup();
    return err;
}

模块加载竞争

内核的某些别的部分会在注册完成之后马上使用任何你注册的设施。换句话说,内核将调用进你的模块,在你初始化函数任然在运行时,所以你的代码必须准备好被调用,一旦完成注册。

模块参数

insmod hellop howmany=10 whom="Mom"

在模块中参数用module_param宏定义来声明,它定义在moduleparam.h module_param使用了3个参数:变量名,类型,以及一个权掩码用来做一个辅助的sysfs入口。

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

模块参数支持许多类型:

bool、invbool  一个布尔型,invbool颠倒了值

charp      字符指针

int、long、short、uint、ulong、ushort   整型,u开头是无符号值

数组参数,用逗号间隔的列表提供的值,模块加载者也支持。

module_param_array(name, type, num, perm);

name:数组名

type:数组元素类型

num:整型变量

perm:通常的权限值

猜你喜欢

转载自www.cnblogs.com/ch122633/p/9148027.html