《Linux设备驱动程序》第二章 笔记

来源:互联网 发布:域用户安装软件权限 编辑:程序博客网 时间:2024/06/08 06:25
本系列博客为《Linux设备驱动程序》一书的笔记。

#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);
特别的宏: MODULE_LICENSE 用来告知内核,该模块带有一个自由的许可证。
KERN_ALERT 消息的优先级

Makefile 文件
obj-m := hello.o
module-objs := hello.o
KERNEL_DIR := /lib/modules/2.6.32-21-generic/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) modules
obj-m: 表示该文件作为模块编译,不会编译到zImage,会生成一个独立的xxx.ko 静态编译
obj-y: 把由foo.c 或者foo.s 文件编译得到foo.o 并连接进内核。
module-objs:如果模块由N个文件组成,那么其他文件就应该描述如下:module-objs:= file1.o file2.o
-C 表示kernel source目录,在/lib/modules/2.6.32-21-generic/build,在那里可以找到kernel的最高lenvel的Makefile, M = 表示在建立模块target的时候,Makefile回归到我们模块程序的目录。

printk 打印级别 -------- 数字越小说明打印级别越高
#define KERN_EMERG "<0>" /*system is unusable*/
#define KERN_ALERT "<1>" /*action must be taken immediately*/
#define KERN_CRIT "<2>" /*critical conditions*/
#define KERN_ERR "<3>" /*error conditions*/
#define KERN_WARNING "<4>" /*warning conditions*/
#define KERN_NOTICE "<5>" /*normal but significant condition*/
#define KERN_INFO "<6>" /*informational*/
#define KERN_DEBUG "<7>" /*debug-level messages*/
查看当前系统的打印级别: cat /proc/sys/kernel/printk
dmesg 查看打印级别不够的那些信息

模块: 它的角色是扩展内核的功能;模块化的代码在内核空间运行,经常地一个驱动进行之前提到的两种任务: 模块中一些的函数作为系统调用的一部分执行,一些负责中断处理。

几个细节:
1、应用程序存在于虚拟内存中,有一个非常大的堆栈区。堆栈,用来保存函数调用历史以及所有的的由当前活跃的函数创建的自动变量。内核,相反,有一个非常小的堆栈,可能小到一个4096字节的页。函数必须与这个内核空间调用链共享这个堆栈。因此,如果你需要大的结构,你应当在调用时间内动态分配。
2、查看内核API时,遇到双下划线(__)开始的函数,这样的标识函数名通常是一个低层的接口组件,应当小心使用。
3、内核代码不能做浮点算术。

指定模块使用哪个许可: MODULE_LICENSE("GPL");
内核认识的特定许可有: ”GPL“ (适用 GNU 通用公共许可的任何版本)
”GPL v2“ (只适用 GPL 版本 2)
”GPL and additional rights“ (GPL 及附加权利)
"Dual BSD/GPL" (BSD/GPL 双重许可证)
"Dual MPL/GPL" (MPL/GPL 双重许可证)
"Proprietary" (专有)
内核模块中其他描述性定义:
MODULE_AUTHOR (模块作者)
MODULE_DESCRIPION(模块用途的简短描述)
MODULE_VERSION(版本号)
MODULE_ALIAS(模块的别名)
MODULE_DEVICE_TABLE(告知用户空间模块支持哪些设备)

模块的初始化
static int __init init_function(void)
{
/*Initialization code here*/
}
module_init(init_function);
__init 标识是给内核的暗示,给定的函数只是在初始化使用。模块加载者在模块加载后会丢掉这个初始化函数,使它的内存可做其他用途。

清理函数
static void __exit cleanup_function(void)
{
/*Cleanup code here*/
}
module_exit(cleanup_function);
清理函数没有返回值,因此它被声明为void。 __exit 修饰符标识这个代码是只用于模块卸载

初始化中的错误处理
模块代码必须一直检查返回值,并且确认要求的操作实际上已经成功。
如果在注册工具时发送任何错误,首先第一的事情是决定模块是否能够无论如何继续初始化它自己。在一个注册失败后模块可以继续操作,如果需要可以功能降级。在任何可能得时候,模块应当尽力向前,并提供事情失败后具备的能力
如果证实了你的模块在一个特别类型的失败后完全不能加载,必须取消任何在失败前注册的动作。如果无法注销你获取的东西,内核就被置于不稳定状态。
错误恢复有时用goto语句处理。

模块竞争
(简要提及)
内核的某些其它部分会在注册完成之后马上使用任何你注册的设施。
必须考虑如果你的初始化函数决定失败会发生什么,但是内核的一部分已经在使用你的模块已注册的设施。

模块参数
例:
insmod hello.ko howmany=10 whom="Mom"
一旦以那样的方式加载,hello.ko 会说 “hello, Mom" 10次。
#include <linux/init.h>
#include <linux/module.h>
static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
MODULE_LICENSE("Dual BSD/GPL");
static int hello_init(void)
{
int i =0;
for(i=0; i<howmany; i++)
{
printk(KERN_ALERT "Hello, %s \n", whom);
}
return 0;
}
static void hello_exit(void)
{
printk(KERN_ALERT "Goodbye, cruel world\n");
}
module_init(hello_init);
module_exit(hello_exit);
在insmod 可以修改模块参数前,模块必须使他们可用。参数用 module_param 宏定义来声明。
模块参数支持的类型:
bool
invbool
一个布尔型(true 或者 false)值(相关的变量应当是 int 类型)。invbool 类型颠倒了值,所以真值变成了 false, 反之亦然。
charp 一个字符指针值,内存为用户提供的字串分配,指针因此设置。
int
long
short
uint
ulong
ushort
数组参数,用逗号间隔的列表提供的值,模块加载者也支持。声明一个数组参数,使用:
module_param_array(name, type, num, perm); 这里 name 是你的数组的名字,type 是数组元素的类型,num 是一个整型变量名, perm 是通常的权限值。
perm的值: #define S_IRUSE 00400 文件所有者可读
#define S_IWUSR 00200 文件所有者可写
#define S_IXUSR 00100 文件所有者可执行
#define S_IRGRP 00040 与文件所有者同组的用户可读
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IROTH 00004 与文件所有者不同组的用户可读
#define S_IWOTH 00002
#define S_IXOTH 00001
将数字最后三位转化为二进制:xxx xxx xxx,高位往低位依次看,第一位为1 表示文件所有者可读, 第二位为1 表示文件所有者可写, 第三位为1 表示文件所有者可执行;接下来三位表示文件所有者同组成员的权限;再下来三位为不同组用户权限。

在用户空间做
优点:
• 完整的 C 库可以连接. 驱动可以进行许多奇怪的任务, 不用依靠外面的程序(实现
使用策略的工具程序, 常常随着驱动自身发布).
• 程序员可以在驱动代码上运行常用的调试器, 而不必走调试一个运行中的内核的弯
路.
• 如果一个用户空间驱动挂起了, 你可简单地杀掉它. 驱动的问题不可能挂起整个系
统, 除非被控制的硬件真的疯掉了.
• 用户内存是可交换的, 不象内核内存. 一个不常使用的却有很大一个驱动的设备不
会占据别的程序可以用到的 RAM, 除了在它实际在用时.
• 一个精心设计的驱动程序仍然可以, 如同内核空间驱动, 允许对设备的并行存取.
• 如果你必须编写一个封闭源码的驱动, 用户空间的选项使你容易避免不明朗的许可
的情况和改变的内核接口带来的问题.
缺点:
• 中断在用户空间无法用. 在某些平台上有对这个限制的解决方法, 例如在 IA32 体系上的 vm86 系统调用.
• 只可能通过内存映射 /dev/mem 来使用 DMA, 而且只有特权用户可以这样做.
• 存取 I/O 端口只能在调用 ioperm 或者 iopl 之后. 此外, 不是所有的平台支持这些系统调用, 而存取/dev/port 可能太慢而无效率. 这些系统调用和设备文件都要求特权用户.
• 响应时间慢, 因为需要上下文切换在客户和硬件之间传递信息或动作.
• 更不好的是, 如果驱动已被交换到硬盘, 响应时间会长到不可接受. 使用 mlock 系统调用可能会有帮助, 但是常常的你将需要锁住许多内存页, 因为一个用户空间程序依赖大量的库代码. mlock, 也, 限制在授权用户上.
• 最重要的设备不能在用户空间处理, 包括但不限于, 网络接口和块设备.

总结
#include<linux/sched.h>
最重要的头文件中的一个。这个文件包含很多驱动使用的内核API的定义,包括睡眠函数和许多变量声明。
struct task_struct *current; 当前进程
current->pid current->comm 进程ID 和 当前进程的命令名。
/sys/module /proc/modules
/sys/module 是一个sysfs 目录层次,包含当前加载模块的信息。 /proc/moudles 是旧式的,那种信息的单个文件版本。其中的条目包含了模块名,每个模块占用的内存数量,以及使用计数。另外的字串追加到每行的末尾来指定标志,对这个模块当前是活动的。

阅读全文
0 0