Linux设备驱动程序 第三版 读书笔记(一)

 Linux设备驱动程序 第三版 读书笔记(一)

  Bob Zhang

2017.08.25

编写基本的Hello World模块

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

// 声明模块的许可证书
MODULE_LICENSE("Dual BSD/GPL"); 
 
static __init hello_init(void) 
{ 
     // KERN_ALERT表示的是日志级别 printk(KERN_ALERT
"Hello, world "); return 0; } static __exit void hello_exit(void) { printk(KERN_ALERT "Goodbye, cruel world "); } // 注册模块初始化函数,在模块安装到内核时会被调用 module_init(hello_init); // 注册模块的退出函数,在模块从内核移除时会被调用 module_exit(hello_exit);

需要注意的地方是,内核代码不支持浮点数。

模块的编译

1 obj-m += hello.o

但是如果需要编译的模块由多个文件组成,则可以使用下面的代码:

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

 模块装载

一般使用insmod对模块进行装载:

1 insmod hello.ko

但是insmod不会对要装载的模块的依赖做检查,如果模块引用了内核中没有的符号,则会报“unresolved symbol”的错误。如果要想检查模块的依赖再装载,可以使用modprobe命令。

modprobe, 如同 insmod, 加载一个模块到内核. 它的不同在于它会查看要加载的模块, 看是否它引用了当前内核没有定义的符号. 如果发现有, modprobe 在定义相关符号的当前模块搜索路径中寻找其他模块. 当 modprobe 找到这些模块( 要加载模块需要的 ), 它也把它们加载到内核.如果你在这种情况下代替以使用 insmod , 命令会失败, 在系统日志文件中留下一条 " unresolved symbols "消息.

模块查看

当模块装载好之后,可以使用lsmod检查模块是否真的装载到内核中了:

1 #查看所有加载到内核的模块
2 lsmod
3 
4 #查看指定的模块,如hello.ko
5 lsmod | grep  hello

lsmod 程序生成一个内核中当前加载的模块的列表. 一些其他信息, 例如使用了一个特定模块的其他模块, 也提供了. lsmod 通过读取 /proc/modules 虚拟文件工作. 当前加载的模块的信息也可在位于 /sys/module 的 sysfs 虚拟文件系统找到.

模块卸载

1 #卸载hello.ko
2 rmmod hello.ko

我们可以使用rmmod工具从内核中移除模块。注意,如果内核认为模块仍然在使用状态(例如,某个程序正打开由该模块导出的设备文件),或者内核被配置为禁止移除模块,则无法移除该模块。配置内核并使得内核在模块忙的时候仍能“强制”移除模块也是可能的。但是,如果读者在某种情况下希望利用这种特性,则重新引导系统可能是更加合适的做法。 

内核符号表

Linux内核头文件提供了一个方便的方法来管理符号对模块外部可见性,从而减少了可能造成的名字空间污染(名字空间中的名称可能会和内核其他地方定义的名称发生冲突),并且适当隐藏信息。如果一个模块需要向其他模块导出符号,则应该使用下面的宏。

EXPORT_SYMBOL(name);
EXPORT_SYMBOL_GPL(name);

这两个宏均用于将给定的符号导出到模块外部。_GPL版本使得要导出的模块只能被GPL许可证下的模块使用。符号符号必须在模块文件的全局部分导出,不能在函数中导出,这是因为上面这两个宏江被扩展成为一个特殊的变量声明,而该变量必须是全局的。该变量将在模块许可执行文件的特殊部分(即一个“ELF段”)中保存,在装载时,内核通过这个段来寻找模块导出的变量。

其他宏定义

大部分内核代码中都要包含相当数量的头文件,以便获得函数、数据类型和变量的定义。我们将在用到这些文件时向读者介绍,但是有几个头文件是专门用于模块的,因此必须出现在每个可装载的模块中。故而,所有的模块代码中都包含下面两行代码:

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

module.h包含有可装载模块需要的大量符号和函数的定义。包含init.h的目的是指定初始化和清理函数,就像我们在hello模块中看到的那样。大部分的模块还包括了“moduleparam.h”头文件,这样我们就可以在装载模块的时候向模块传递参数。接下来介绍一些常用的宏。

尽管不是严格要求,但模块应该制定代码所使用的许可证。为此我们只需要包含MODULE_LICENSE行:

1 MODULE_LICENSE("GPL");

内核能够识别的许可证有“GPL”(任一版本的GNU(GNU's Not Unix)通用公共许可证)、“GPL v2”(GPL版本2)、“GPL and additional rights(GPL及附加权利)”、“Dual BSD/GPL(BSD/GPL双重许可证)”、“Dual MPL/GPL(MPL/GPL双重许可证)”以及“Proprietary(专有)”。如果一个模块没有显示地标记为上述内核可识别的许可证,则会被假定是专有的,而内核装载这种模块就会被“污染”。

可在模块中包含的其他描述性定义包括:

// 描述模块作者
MODULE_AUTHOR(BobZhang<zhangbob@email.com>);
// 简短说明模块用途
MODULE_DESCRIPTION("This is a hello world demo module.");
// 代码修订号;有关版本字串的创建惯例,请参考<linux/module.h>中的注释
MODULE_VERSION(version);
// 模块的别名
MODULE_ALIAS("hello_world");
// 告诉用户空间模块所支持的设备
MODULE_DEVICE_TABLE(device_table);

上述MODULE_声明可出现在源文件中源代码函数以外的任何地方。但新近的内核编码习惯是将这些声明放在文件的最后。 

模块参数

内核提供一种方式传递参数给模块,那就是在运行insmod或modprobe命令时,将要传递给模块的参数给出,而modeprobe还可以从他的配置文件(/etc/modprobe.conf)中读取参数值。这两个命令可在命令行接受几种参数类型的赋值。为了演示这种功能,我们假定对前面的hello模块做一些必要的增强。我们添加了两个参数:一个是整数值,其名称为howmany;另一个是字符串,名称为whom。在装载这个增强的模块时,将相whom问候howmany次。这样我们可用下面的命令来装载该模块:

1 insmod hello.ko howmany=10 whom="Bob"

上面这条命令的效果会让hello模块打印10次“hello, Bob”。

更改后的hello模块如下:

#include <linux/init.h> 
#include <linux/module.h> 
#include <linux/moduleparam.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 count = 0;
        // KERN_ALERT表示的是日志级别
        for(; count < howmany; ++count)
            printk(KERN_ALERT "hello, %s
", whom); 
        return 0; 
} 
static __exit void hello_exit(void) 
{ 
        printk(KERN_ALERT "Goodbye, cruel world
"); 
} 

// 注册模块初始化函数,在模块安装到内核时会被调用 
module_init(hello_init); 
// 注册模块的退出函数,在模块从内核移除时会被调用
module_exit(hello_exit);         

内核支持的模块参数类型如下:

类型

描述

bool

invbool

布尔值(取true或false),关联的变量应该是int型。invbool类型反转其值,也就是说,true值变成false,而false变成true。

charp

字符指针值。内核会为用户提供的字符串分配内存,并相应设置指针。

int

long

short

uint

ulong

ushort

具有不同长度的基本整数类型。以u开头的类型用于无符号值。

  

  

模块装载器也支持数组参数,贼提供数组值时用逗号划分各数组成员。要声明数组参数,需要使用下面的宏:

1 module_param_array(name, type, num, perm);

其中,name是数组的名称(也就是参数的名称),type是数组原书的类型,num是一个证书变量,而perm是常见的访问许可值。如果装载时设置数组参数,则num会被设置为用户提供的值的个数。模块装载器会拒绝接受超过数组大小的值。

module_param中的最后一个成员是访问许可值,我们应使用<linux/stat.h>存在的定义。这个值用来控制水能够访问sysfs中对模块参数的表述。如果perm被设置为0,就不会有对应的sysfs入口项;否则,模块参数会在/sys/module中出现,并设置为给定的访问许可。如果参数使用S_IRUGO,则任何人均可读取该参数,但不能修改;S_IRUGO|S_IWUSR允许root用户修改该参数。注意,如果一个参数通过sysfs而被修改,则如同模块修改了这个参数的值一样,但是内核不会以任何方式通知模块。大多数情况下,我们不应该让模块参数是可写的,除非我们打算检测这种修改并作为相应的动作。

在用户空间编写驱动

可以在用户空间编写驱动,欲知详情请google或者bing搜索

当前进程

虽然内核模块不像应用程序那样顺序执行,然而内核执行的大多数操作还是和某个特定的进程相关。内核代码可以通过访问全局项current来获得当前进程。current在<asm.current.h>中定义,是一个指向struct task_struct的指针,而task_struct结构在<linux/sched.h>文件中定义。current指针指向当前正在运行的进程。在open、read等操作系统调用的执行过程中,当前进程指的是调用这些系统调用的进程。如果需要,内核代码可以通过current获得与当前进程相关的信息。

1 printk(KERNE_INFO "The process is "%s" [pid %i]
",
2         current->comm, current->pid);

存储在current->comm成员中的命令是当前进程所执行的程序文件的基本名称(base name),如果必要,会裁剪到15个字符以内。

未完待续~


一切伟大的思想和行动都有一个微不足道的开始。

Any great thoughts and actions has a small beginning.

原文地址:https://www.cnblogs.com/zhanghang-BadCoder/p/7427641.html