第17章 设备与模块
一、设备类型
除了以上3种典型的设备之外,其实Linux中还有一些其他的设备类型,其中见的较多的应该算是"伪设备"。所谓"伪设备",其实就是一些虚拟的设备,仅提供访问内核功能而已,没有物理设备与之关联。典型的"伪设备"就是 /dev/random(内核随机数发生器), /dev/null(空设备), /dev/zero(零设备), /dev/full(满设备)
二、内核模块
Linux内核是模块化组成的,内核中的模块可以按需加载,从而保证内核启动时不用加载所有的模块,即减少了内核的大小,也提高了效率。通过编写内核模块来给内核增加功能或者接口是个很好的方式,既不用重新编译内核,也方便调试和删除。
内核模块可以带参数也可以不带参数,不带参数的内核模块比较简单。带参数的内核模块:内核中已经提供了简单的框架来给我们声明参数。
1.module_param(name, type, perm) : 定义一个模块参数
参数 name :: 既是用户可见的参数名,也是模块中存放模块参数的变量名
参数 type :: 参数的类型(byte, short, int, uint, long, ulong, charp, bool...) byte型存放在char变量中,bool型存放在int变量中
参数 perm :: 指定模块在 sysfs 文件系统中对应的文件权限(关于 sysfs 的内容后面介绍)
static int stu_id = 0; // 默认id
module_param(stu_id, int, 0644);
2.module_ param_ named(name, variable, type, perm) : 定义一个模块参数,并且参数对内对外的名称不一样
参数 name :: 用户可见的参数名
参数 variable :: 模块中存放模块参数的变量名
参数 type和perm :: 同 module_param 中的 type 和 perm
static char* stu_name_in = "default name"; // 默认名字
module_param_named(stu_name_out, stu_name_in ,charp, 0644);
/* stu_name_out 是对用户开放的名称
*stu_name_in 是内核模块内部使用的名称
*/
3. module_ param_ string(name, string, len, perm) : 拷贝字符串到指定的字符数组
参数 name :: 用户可见的参数名
参数 string :: 模块中存放模块参数的变量名
参数 len :: string 参数的缓冲区长度
参数 perm :: 同 module_param 中的 perm
static char str_in[BUF_LEN];
module_param_string(str_out, str_in, BUF_LEN, 0);
/* perm=0 表示完全禁止 sysfs 项 */
4.module_ param_ array(name, type, nump, perm) : 定义数组类型的模块参数
参数 name :: 同 module_param 中的 name
参数 type :: 同 module_param 中的 type
参数 nump :: 整型指针,存放数组的长度
参数 perm :: 同 module_param 中的 perm
#define MAX_ARR_LEN 5
static int arr_len;
static int arr_in[MAX_ARR_LEN];
module_param_array(arr_in, int, &arr_len, 0644);
5.module_ param_ array_named(name, array, type, nump, perm) : 定义数组类型的模块参数,并且数组参数对内对外的名称不一样
参数 name :: 数组参数对外的名称
参数 array :: 数组参数对内的名称
参数 type,nump,perm :: 同 module_ param_ array 中的 type,nump,perm
#define MAX_ARR_LEN 5
static int arr_len;
static int arr_in[MAX_ARR_LEN];
module_param_array_named(arr_out, arr_in, int, &arr_len, 0644);
6.参数描述宏
可以通过 MODULE_ PARM_ DESC() 来给内核模块的参数添加一些描述信息。这些描述信息在编译完内核模块后,可以通过modinfo命令查看。
static int stu_id = 0; // 默认id
module_param(stu_id, int, 0644);
MODULE_PARM_DESC(stu_id, "学生ID,默认为 0"); // 这句就是描述内核模块参数 stu_id 的语句
<font color==blue size=4>内核模块相关操作:
1.模块安装
make modules_install <-- 把随内核编译出来的模块安装到合适的目录中( /lib/modules/version/kernel )
2. 模块依赖性
linux中自动生产模块依赖性的命令:
depmod <-- 产生内核依赖关系信息
depmod -A <-- 只为新模块生成依赖信息(速度更快)
3. 模块的载入
内核模块实验时已经用过:
insmod module.ko
<-- 推荐使用以下的命令, 自动加载依赖的模块
modprobe module [module parameters]
4. 模块的卸载
内核模块实验时已经用过:
rmmod module.ko
<-- 推荐使用以下的命令, 自动卸载依赖的模块
modprobe -r module
5. 模块导出符号表
内核模块被载入后,就动态的加载到内核中,为了能让其他内核模块使用其功能,需要将其中函数导出。内核模块中导出函数的方法:
EXPORT_SYMBOL(函数名) <-- 接在要导出的函数后面即可
EXPORT_SYMBOL_GPL(函数名) <-- 和EXPORT_SYMBOL一样,区别在于只对标记为GPL协议的模块可见
内核对象:
2.6内核中增加了一个引人注目的新特性--统一设备模型(device model)。
统一设备模型的最初动机是为了实现智能的电源管理,linux 内核为了实现智能电源管理,需要建立表示系统中所有设备拓扑关系的树结构,这样在关闭电源时,可以从树的节点开始关闭。实现了统一设备模型之后,还给内核带来了如下的好处:
1.代码重复最小化(统一处理的东西多了)
2. 可以列举系统中所有设备,观察它们的状态,并查看它们连接的总线
3. 可以将系统中的全部设备以树的形式完整,有效的展示出来--包括所有总线和内部连接
4. 可以将设备和其对应的驱动联系起来,反之亦然
5. 可以将设备按照类型加以归类,无需理解物理设备的拓扑结构
6. 可以沿设备树的叶子向其根的反向依次遍历,以保证能以正确的顺序关闭设备电源
第19章 可移植性
linux内核的移植性非常好, 目前的内核也支持非常多的体系结构(有20多个).但是刚开始时, linux也只支持 intel i386 架构, 从 v1.2版开始支持 Digital Alpha, Intel x86, MIPS和SPARC(虽然支持的还不是很完善).从 v2.0版本开始加入了对 Motorala 68K和PowerPC的官方支持, v2.2版本开始新增了 ARMS, IBM S390和UltraSPARC的支持.v2.4版本支持的体系结构数达到了15个, v2.6版本支持的体系结构数目提高到了21个.考虑到内核支持如此之多的架构, 在内核开发的时候就需要考虑编码的可移植性。
数据对齐也是增强可移植性的一个重要方面(有的体系结构对数据对齐要求非常严格, 载入未对齐的数据可导致性能下降, 甚至错误)。数据对齐的意思就是: 数据的内存地址可以被 4 整除。
对于结构体, 保证结构体中每个元素能够正确对齐即可。如果结构体中的元素没有对齐, 编译器会自动填充结构体, 保证它是对齐的. 比如下面的代码, 预计应该输出12, 实际却输出了24。
#include <stdio.h>
struct animal_struct
{
char dog; /* 1个字节 */
unsigned long cat; /* 8个字节 */
unsigned short pig; /* 2个字节 */
char fox; /* 1个字节 */
};
int main(int argc, char *argv[])
{
/* 在我的64bit 系统中是按8位对齐, 下面的代码输出 24 */
printf ("sizeof(animal_struct)=%d\n", sizeof(struct animal_struct));
return 0;
}
结构体应该被填充成如下形式:
struct animal_struct
{
char dog; /* 1个字节 */
/* 此处填充了7个字节 */
unsigned long cat; /* 8个字节 */
unsigned short pig; /* 2个字节 */
char fox; /* 1个字节 */
/* 此处填充了5个字节 */
};
通过调整结构体中元素顺序, 可以减少填充的字节数, 比如上述结构体如果定义成如下顺序:
struct animal_struct
{
unsigned long cat; /* 8个字节 */
unsigned short pig; /* 2个字节 */
char dog; /* 1个字节 */
char fox; /* 1个字节 */
/* 此处填充了4个字节 */
};
注意: 虽然调整结构体中元素的顺序可以减少填充的字节, 从而降低内存的消耗.但是对于内核中已有的那些结构, 千万不能随便调整其元素顺序, 因为内核中很多现存的方法都是通过元素在结构体中位置偏移来获取元素的.
第20章 补丁,开发和社区
1. 加入社区
如果想为linux贡献代码, 那么加入linux社区是必须的, 加入了社区, 不仅可以及时内核的最新消息, 而且可以及时和社区内有经验的内核开发者交流经验.同时也是提交代码和讨论代码的地方, 了解社区的规则, 融入社区环境之中, 才能更好的学习内核, 体会内核开发的乐趣和成就感.内核社区说白了就是内核邮件列表(LKML linux kernel mail list)
2. 编码风格
社区给我们提供了学习和贡献内核的地方, 但是为了避免不必要的麻烦(被别人指责或者无人理睬), 首先得好好了解一些内核代码的编码风格.linux的编码风格都记录在 Documentation/CodingStyle 内核开发前要好好研读以下, 之后有时间我会整理到博客中.
3. 提交补丁
准备工作都完成之后, 就可以开始内核开发之旅了 :)只要坚持不断的学习和尝试, 总有一天会为了内核贡献自己的代码, 这时候, 就需要了解如何提交代码, 也就是内核补丁.如果是发现了BUG或者有改善, 可以将BUG的描述或者改善代码发送给对应的维护者.(内核各个子系统的维护者信息在内核代码根目录下的 MAINTAINERS 文件中)生成BUG或者改善代码的补丁有2种方法:
(1)、用diff命令创建补丁
生成patch
diff -urN linux-old/ linux-new/ > my-patch # 比对整个内核代码文件夹
OR
diff -u linux-old/some/file linux-new/some/file > my-patch # 比对某个文件
应用patch, 应用了patch之后, linux-old 和 linux-new 中的代码就一样了
cd linux-old
patch -p1 < ../my-patch # 这个命令是进入linux内核代码根目录内执行的
PS. 还有个很有用的工具 diffstat
diffstat -p1 my-patch # 列出补丁所引起的变更的统计(加入或移去的代码行)
(2)、用git命令创建补丁
提交修改的或新增的代码
git commit -a # 提交所有修改的代码
OR
git commit linux-src/some/file.c # 提交某个修改的代码
OR
git add linux-src/some/new-file.c # 把新增的文件加入版本库
git commit -a # 提交新增的文件
# 生成patch
git format-patch -N # N 是正整数, 这条命令生成最后N次提交产生的补丁
OR
git format-patch -1 # 最后1次提交产生的补丁