您的位置:首页 > 运维架构 > Linux

《LINUX设备驱动程序》第2章(建立和运行模块)学习笔记

2013-09-10 12:31 288 查看
第  2 章 建立和运行模块

Hello World 模块:

#include <linux/init.h>

#include <linux/module.h>

MODULE_LICENSE("DualBSD/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);

 

hello_init在模块加载到内核时被调用,hello_exit在模块被去除时被调用.moudle_init 和 module_exit 这几行使用了特别的内核宏来指出这两个函数的角色.

printk 函数在 Linux 内核中定义并且对模块可用;它与标准 C 库函数 printf 的行为相似,可以使用命令dmsg查看.字串 KERN_ALERT 是消息的优先级.

 

用户空间和内核空间:

模块在内核空间运行,  而应用程序在用户空间运行.

Unix 从用户空间转换执行到内核空间, 无论何时一个应用程序发出一个系统调用或者被硬件中断挂起时. 执行系统调用的内核代码在进程的上下文中工作(它代表调用进程并且可以存取该进程的地址空间. 换句话说, 处理中断的代码对进程来说是异步的, 不和任何特别的进程有关). 

 

当前进程:

通过存取全局项 current, 它在<asm/current.h> 中定义内核代码可以引用当前进程, 它产生一个指针指向结构 task_struct, 在 <linux/sched.h> 定义. current 指针指向当前在运行的进程.在一个系统调用执行期间, 例如 open 或者 read, 当前进程是发出调用的进程. 如需要,内核代码可以通过使用 current 来使用进程特定的信息.

一个设备驱动可以只包含<linux/sched.h> 并且引用当前进程. 例如, 下面的语句打印了当前进程的进程 ID 和命令名称, 通过存取结构 task_struct 中的某些字段.   

printk(KERN_INFO"The process is \"%s\" (pid %i)\n", current->comm,current->pid);

 

其他一些细节:

应用程序存在于虚拟内存中, 并且有一块很大的栈空间(堆栈), 当然, 是用来保存函数调用历史以及所有的由当前活跃的函数创建的自动变量. 而相反的是,内核有一个非常小的堆栈; 它可能小到一个4096 字节的页. 你的函数必须与这个内核空间调用链共享这个堆栈. 因此, 声明一个巨大的自动变量从来就不是一个好主意;
如果你需要大的结构, 你应当在调用时间内动态分配.

         前缀(__)的函数名称:这样标志的函数名通常是一个低层的接口组件, 应当小心使用. 本质上讲, 双下划线告诉程序员" 谨慎调用,否则后果自负"。

 

加载和卸载模块:

Insmod加载建立之后的模块到内核

rmmod从内核去除模块

 

预备知识:

几乎所有模块代码都有下面内容:   

#include<linux/module.h> //包含了大量加载模块需要的函数和符号的定义

#include<linux/init.h> //指定你的初始化和清理函数

#include <linux/moudleparam.h>//使得可以在模块加载时传递参数给模块

MODULE_LICENSE("GPL");

//内核认识的特定许可有,"GPL"( 适用 GNU 通用公共许可的任何版本 ), "GPL v2"( 只适用 GPL 版本 2 ),"GPL and additional rights", "Dual BSD/GPL", "DualMPL/GPL", 和 "Proprietary".

其他描述性定义有:

MODULE_AUTHOR (声明谁编写了模块)

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

MODULE_VERSION (一个代码修订版本号; 看<linux/module.h>的注释以便知道创建版本字串使用的惯例),

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

MODULE_DEVICE_TABLE ( 来告知用户空间, 模块支持那些设备 ).

 

初始化函数:

static int__init initialization_function(void)

{

 /* Initialization code here */

}

module_init(initialization_function);

初始化函数应当声明成静态的, 因为它们不会在特定文件之外可见; 没有硬性规定这个, 然而, 因为没有函数能输出给内核其他部分, 除非明确请求. 声明中的__init
标志,它是一个给内核的暗示, 给定的函数只是在初始化使用. 模块加载者在模块加载后会丢掉这个初始化函数, 使它的内存可做其他用途. 一个类似的标签(__initdata) 给只在初始化时用的数据.
使用 __init 和 __initdata 是可选的, 但是它带来的麻烦是值得的. 只是要确认不要用在那些在初始化完成后还使用的函数(或者数据结构)上. 你可能还会遇到 __devinit 和 __devinitdata 在内核源码里; 这些只在内核没有配置支持 hotplug 设备时转换成 __init 和 _initdata.

使用 moudle_init是强制的. 这个宏定义增加了特别的段到模块目标代码中,
表明在哪里找到模块的初始化函数. 没有这个定义, 你的初始化函数不会被调用.

 

清理函数:

         staticvoid __exit cleanup_function(void)

{

 /* Cleanup code here */

}

module_exit(cleanup_function);

清理函数没有返回值, 因此它被声明为 void.__exit 修饰符标识这个代码是只用于模块卸载( 通过使编译器把它放在特殊的 ELF 段). 如果你的模块直接建立在内核里, 或者如果你的内核配置成不允许模块卸载, 标识为 __exit 的函数被简单地丢弃. 因为这个原因, 一个标识 __exit 的函数只在模块卸载或者系统停止时调用; 任何别的使用是错的.

moudle_exit 声明对于使得内核能够找到你的清理函数是强制性的.

 

模块参数:

参数的值可由 insmod 或者 modprobe 在加载时指定; 后者也可以从它的配置文件(/etc/modprobe.conf)读取参数的值.

"hello world"模块(称为 hellop)的改进. 我们增加 2 个参数: 一个整型值, 称为howmany, 一个字符串称为 whom.这样一个模块可以用这样的命令行加载:

insmod hellophowmany=10 whom="Mom"(hellop 会说 "hello, Mom" 10 次.)

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

hellop 将声明它的参数:

static char *whom ="world";

static int howmany = 1;

module_param(howmany,int, S_IRUGO);

module_param(whom, charp,S_IRUGO);

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: