第二章 构造和运行模块——note
2014-10-23 08:21
204 查看
1.hello world 模块
代码是一个完整的 "hello world"模块:
#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);
这个模块定义了两个函数, 一个在模块加载到内核时被调用( hello_init )以及 一个在模块被去除时被调用( hello_exit ). moudle_init 和 module_exit 这几 行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核, 该模块带有一个自由的许可证; 没有这样 的说明, 在模块加载时内核会抱怨
2.内核模块和应用程序的区别
不同于大部分的小的和中型的应用程序从头至尾处理一个单个任务, 每个内核模块只注册自己以便来服务将来的请求, 并且它的初始化函数立刻终止. 换句话说, 模块初始化函数
的任务是为以后调用模块的函数做准备; 好像是模块说, " 我在这里, 这是我能做的."模块的退出函数( 例子里是 hello_exit )就在模块被卸载时调用. 它好像告诉内核, "我不再在那里了, 不要要求我做任何事了."这种编程的方法类似于事件驱动的编程, 但是虽然不是所有的应用程序都是事件驱动的, 每个内核模块都是. 另外一个主要的不同, 在事件驱动的应用程序和内核代码之间, 是退出函数: 一个终止的应用程序可以在释放资源方面懒惰, 或者完全不做清理工作, 但是模块的退出函数必须小心恢复每个由初始化函数建立的东西,
否则会保留一些东西直到系统重启.
3.用户空间和内核空间
一个模块在内核空间运行,而应用程序在用户空间运行,这个概念是操作系统的基础
在 Unix 下, 内核在最高级运行( 也称之为超级模式 ), 这里任何事情都允许, 而应用程序在最低级运行(所谓的用户模式), 这里处理器控制了对硬件的直接存取以及对内存的非法存取.
每个模式有它自己的内存映射 -- 它自己的地址
模块的角色是扩展内核的功能; 模块化的代码在内核空间运行. 经常地一个驱动进行之前提到的两种任务: 模块中一些的函数作为系统调用的一部分执行, 一些负责中断处理.
空间.
4.内核的并发
考虑并发来编写你的代码
5.当前进程
内核代码可以引用当前进程, 通过存取全局项 current, 它在 <asm/current.h> 中定义, 它产生一个指针指向结构 task_struct, 在 <linux/sched.h> 定义. current 指针指向当
前在运行的进程.
6.几个别的细节
应用程序存在于虚拟内存中, 有一个非常大的堆栈区. 堆栈, 当然, 是用来保存函数调用历史以及所有的由当前活跃的函数创建的自动变量. 内核, 相反, 有一个非常小的堆栈;
它可能小到一个, 4096 字节的页. 你的函数必须与这个内核空间调用链共享这个堆栈. 因此, 声明一个巨大的自动变量从来就不是一个好主意; 如果你需要大的结构, 你应当在调
用时间内动态分配.
当你查看内核 API 时, 你会遇到以双下划线(__)开始的函数名. 这样标志的函数名通常是一个低层的接口组件, 应当小心使用. 本质上讲, 双下划线告诉程序员:" 如果你调
用这个函数, 确信你知道你在做什么."
内核代码不能做浮点算术.
7.编译模块
有几个前提, 你必须在能建立内核模块前解决. 第一个是保证你有版本足够新的编译器, 模块工具, 以及其他必要工具. 在内核文档目录下的文件 Documentation/Changes 一直列
出了需要的工具版本; 你应当在向前走之前参考一下它. 试图建立一个内核(包括它的模块), 用错误的工具版本, 可能导致不尽的奇怪的难题. 注意, 偶尔地, 编译器的版本太新
可能会引起和太老的版本引起的一样的问题. 内核源码对于编译器做了很大的假设, 新的发行版本有时会一时地破坏东西.
8.加载和卸载模块
加载到内核insmod 为你完成这个工作、模块可以用 rmmod 工具从内核去除。lsmod 程序生成一个内核中当前加载的模块的列表
insmod: 它依赖一个在 kernel/module.c 中定义的系统调用. 函数 sys_init_module 分配内核内存来存放模块 ( 这个内存用 vmalloc 分配; 看第 8 章的 "vmalloc 和其友" ); 它接着拷贝模块的代码段到这块内存区, 借助内核符号表解决模块中的内核引用, 并且调用模块的初始化函数来启动所有东西.
9.平台依赖性
如果一个模块与一个给定内核工作, 它必须以与内核相同的对目标处理器的理解来建立.
10.预备知识
几乎所有模块代码都有下面内容:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
最重要的头文件中的一个. 这个文件包含很多驱动使用的内核 API 的定义, 包括睡眠函数和许多变量声明.
moudle.h 包含了大量加载模块需要的函数和符号的定义. 你需要 init.h 来指定你的初始化和清理函数, 如我们在上面的 "hello world" 例子里见到的, 这个我们在下一节中再讲.
大部分模块还包含 moudleparam.h, 使得可以在模块加载时传递参数给模块. 不是严格要求的, 但是你的模块确实应当指定它的代码使用哪个许可. 做到这一点只需包
含一行 MODULE_LICENSE: MODULE_LICENSE("GPL");一个内核代码中相对近期的惯例是把这些声明放在文件末尾.
11.初始化函数
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initialization_function);
初始化函数应当声明成静态的, 因为它们不会在特定文件之外可见;
声明中的 __init 标志,它是一个给内核的暗示, 给定的函数只是在初始化使用. 模块加载者在模块加载后会丢掉这个初始化函数, 使它的内存可做其他用途.
12.清理函数
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
在模块被去除之前返回所有资源给系统
清理函数没有返回值, 因此它被声明为 void. __exit 修饰符标识这个代码是只用于模块卸载( 通过使编译器把它放在特殊的 ELF 段). 如果你的模块直接建立在内核里, 或者如
果你的内核配置成不允许模块卸载, 标识为 __exit 的函数被简单地丢弃. 因为这个原因, 一个标识 __exit 的函数只在模块卸载或者系统停止时调用; 任何别的使用是错的. 再一
次, moudle_exit 声明对于使得内核能够找到你的清理函数是必要的. 如果你的模块没有定义一个清理函数, 内核不会允许它被卸载.
13.初始化中的错误处理
错误恢复有时用 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 */
}
在 Linux 内核里, 错误码是负数, 属于定义于 <linux/errno.h> 的集合.
显然, 模块清理函数必须撤销任何由初始化函数进行的注册, 并且惯例(但常常不是要求的)是按照注册时相反的顺序注销设施.
void __exit my_cleanup_function(void)
{
unregister_those(ptr3, "skull");
unregister_that(ptr2, "skull");
unregister_this(ptr1, "skull");
return;
}
14.,模块参数
insmod hellop howmany=10 whom="Mom"
一旦以那样的方式加载, hellop 会说 "hello, Mom" 10 次.
但是, 在 insmod 可以修改模块参数前, 模块必须使它们可用. 参数用 moudle_param 宏定义来声明, 它定义在 moduleparam.h. module_param 使用了 3 个参数: 变量名, 它的类型, 以及一个权限掩码用来做一个辅助的 sysfs 入口. 这个宏定义应当放在任何函数之外, 典型地是出现在源文件的前面. 因此 hellop 将声明它的参数, 并如下使得对 insmod 可用:
static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
所有的模块参数应当给定一个缺省值;
module_param_array(name,type,num,perm); 参数是数组
你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.
转载
1 代码是一个完整的 "hello world"模块:
#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);
这个模块定义了两个函数, 一个在模块加载到内核时被调用( hello_init )以及 一个在模块被去除时被调用( hello_exit ). moudle_init 和 module_exit 这几 行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核, 该模块带有一个自由的许可证; 没有这样 的说明, 在模块加载时内核会抱怨
2 核心模块与应用程序的基本区别
(1)驱动程序类似事件驱动编程,它初始化时候,注册一些设施,服务于系统调用
(2)应用程序运行在用户空间,驱动程序运行在内核空间
(3)应用程序可使用标准的库函数,而驱动程序连接到内核,只能使用内核导出的符号和函数
(4)应用程序错误,一般只会杀死进程,而驱动程序错误,会造成系统不稳定,或者崩溃
(5)应用程序拥有很大的栈空间,而内核栈很小,一般只有4KB
3 内核中并发的来源
(1)对称多处理器
(2)可延时机制timer、tasklet、workqueue
(3)大多数设备能够中断处理器,而中断处理程序异步运行
4 当前进程
(1)asm.current.h,current
(2)linux/sched.h,struct task_struct
5 一些细节
(1)具有前缀__的函数名称通常是接口的底层组件,应谨慎使用
(2)内核代码不能实现浮点数运算
6 编译和装载模块
(1)/proc/modules
(2)/sys/module
(3)编译模块
(4)Makefile
module-objs :=file1.o file2.o
obj-m := module.o
make -C /usr/src/kernels/2.6.10/ M=`pwd` modules
7 装载和卸载模块
(1)insmod和ld有些类似,将模块的代码和数据装入内核,然后使用内核的符号表解析模块中任何未解析的符号
(2)与链接器不同,内核不会修改模块的磁盘文件,而仅仅修改内存中的副本
(3)依赖定义在kernel/module.c中的一个系统调用
(4)sys_init_module给模块分配内核内存
(5)只有系统调用的名字前带有sys_前缀
(7)modprobe
(8)rmmod
(9)lsmod
(10)lsusb
8 版本依赖
(1)linux/versi(2)linux/module.h
UTS_RELEASE
LINUX_VERSION_CODE
KERNEL_VERSION(major,minor, release)
9 内核符号表
(1) Linux内核头文件提供了一个方便的方法来管理符号对模块外部的可见性,从而减少了可能造成的名字空间污染
(2)EXPORT_SYMBOL(name);
(3)EXPORT_SYMBOL_GPL(name);
10 其他知识
(1)头文件:函数、数据类型和变量的定义
(2)所有模块代码中都包含的代码
#include<linux/module.h>
#include<linux/init.h>
module.h包含有可装载模块需要的大量符号和函数的定义
init.h的目的是指定初始化和清除函数
内核能够识别的许可证
GPL
GPL v2
GPL and additional rights
Dual BSD/GPL
Dual MPL/GPL
Proprietary
如果一个模块没有地标记为上述内核可识别的许可证,则会被假定是专有的
(3) 描述性定义
MODULE_AUTHOR
MODULE_DESCRIPTION
MODULE_VERSION
MODULE_ALIAS
MODULE_DEVICE_TABLE
(4)模块参数
moduleparam.h
module_param
变量的名称、类型以及用于sysfs入口项的访问许可掩码
内核支持的模块参数类型
bool
invbool
charp
int
long
short
uint
ulong
ushort
module_param_array(name, type, num, perm);
代码是一个完整的 "hello world"模块:
#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);
这个模块定义了两个函数, 一个在模块加载到内核时被调用( hello_init )以及 一个在模块被去除时被调用( hello_exit ). moudle_init 和 module_exit 这几 行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核, 该模块带有一个自由的许可证; 没有这样 的说明, 在模块加载时内核会抱怨
2.内核模块和应用程序的区别
不同于大部分的小的和中型的应用程序从头至尾处理一个单个任务, 每个内核模块只注册自己以便来服务将来的请求, 并且它的初始化函数立刻终止. 换句话说, 模块初始化函数
的任务是为以后调用模块的函数做准备; 好像是模块说, " 我在这里, 这是我能做的."模块的退出函数( 例子里是 hello_exit )就在模块被卸载时调用. 它好像告诉内核, "我不再在那里了, 不要要求我做任何事了."这种编程的方法类似于事件驱动的编程, 但是虽然不是所有的应用程序都是事件驱动的, 每个内核模块都是. 另外一个主要的不同, 在事件驱动的应用程序和内核代码之间, 是退出函数: 一个终止的应用程序可以在释放资源方面懒惰, 或者完全不做清理工作, 但是模块的退出函数必须小心恢复每个由初始化函数建立的东西,
否则会保留一些东西直到系统重启.
3.用户空间和内核空间
一个模块在内核空间运行,而应用程序在用户空间运行,这个概念是操作系统的基础
在 Unix 下, 内核在最高级运行( 也称之为超级模式 ), 这里任何事情都允许, 而应用程序在最低级运行(所谓的用户模式), 这里处理器控制了对硬件的直接存取以及对内存的非法存取.
每个模式有它自己的内存映射 -- 它自己的地址
模块的角色是扩展内核的功能; 模块化的代码在内核空间运行. 经常地一个驱动进行之前提到的两种任务: 模块中一些的函数作为系统调用的一部分执行, 一些负责中断处理.
空间.
4.内核的并发
考虑并发来编写你的代码
5.当前进程
内核代码可以引用当前进程, 通过存取全局项 current, 它在 <asm/current.h> 中定义, 它产生一个指针指向结构 task_struct, 在 <linux/sched.h> 定义. current 指针指向当
前在运行的进程.
6.几个别的细节
应用程序存在于虚拟内存中, 有一个非常大的堆栈区. 堆栈, 当然, 是用来保存函数调用历史以及所有的由当前活跃的函数创建的自动变量. 内核, 相反, 有一个非常小的堆栈;
它可能小到一个, 4096 字节的页. 你的函数必须与这个内核空间调用链共享这个堆栈. 因此, 声明一个巨大的自动变量从来就不是一个好主意; 如果你需要大的结构, 你应当在调
用时间内动态分配.
当你查看内核 API 时, 你会遇到以双下划线(__)开始的函数名. 这样标志的函数名通常是一个低层的接口组件, 应当小心使用. 本质上讲, 双下划线告诉程序员:" 如果你调
用这个函数, 确信你知道你在做什么."
内核代码不能做浮点算术.
7.编译模块
有几个前提, 你必须在能建立内核模块前解决. 第一个是保证你有版本足够新的编译器, 模块工具, 以及其他必要工具. 在内核文档目录下的文件 Documentation/Changes 一直列
出了需要的工具版本; 你应当在向前走之前参考一下它. 试图建立一个内核(包括它的模块), 用错误的工具版本, 可能导致不尽的奇怪的难题. 注意, 偶尔地, 编译器的版本太新
可能会引起和太老的版本引起的一样的问题. 内核源码对于编译器做了很大的假设, 新的发行版本有时会一时地破坏东西.
8.加载和卸载模块
加载到内核insmod 为你完成这个工作、模块可以用 rmmod 工具从内核去除。lsmod 程序生成一个内核中当前加载的模块的列表
insmod: 它依赖一个在 kernel/module.c 中定义的系统调用. 函数 sys_init_module 分配内核内存来存放模块 ( 这个内存用 vmalloc 分配; 看第 8 章的 "vmalloc 和其友" ); 它接着拷贝模块的代码段到这块内存区, 借助内核符号表解决模块中的内核引用, 并且调用模块的初始化函数来启动所有东西.
9.平台依赖性
如果一个模块与一个给定内核工作, 它必须以与内核相同的对目标处理器的理解来建立.
10.预备知识
几乎所有模块代码都有下面内容:
#include <linux/module.h>
#include <linux/init.h>
#include <linux/sched.h>
最重要的头文件中的一个. 这个文件包含很多驱动使用的内核 API 的定义, 包括睡眠函数和许多变量声明.
moudle.h 包含了大量加载模块需要的函数和符号的定义. 你需要 init.h 来指定你的初始化和清理函数, 如我们在上面的 "hello world" 例子里见到的, 这个我们在下一节中再讲.
大部分模块还包含 moudleparam.h, 使得可以在模块加载时传递参数给模块. 不是严格要求的, 但是你的模块确实应当指定它的代码使用哪个许可. 做到这一点只需包
含一行 MODULE_LICENSE: MODULE_LICENSE("GPL");一个内核代码中相对近期的惯例是把这些声明放在文件末尾.
11.初始化函数
static int __init initialization_function(void)
{
/* Initialization code here */
}
module_init(initialization_function);
初始化函数应当声明成静态的, 因为它们不会在特定文件之外可见;
声明中的 __init 标志,它是一个给内核的暗示, 给定的函数只是在初始化使用. 模块加载者在模块加载后会丢掉这个初始化函数, 使它的内存可做其他用途.
12.清理函数
static void __exit cleanup_function(void)
{
/* Cleanup code here */
}
module_exit(cleanup_function);
在模块被去除之前返回所有资源给系统
清理函数没有返回值, 因此它被声明为 void. __exit 修饰符标识这个代码是只用于模块卸载( 通过使编译器把它放在特殊的 ELF 段). 如果你的模块直接建立在内核里, 或者如
果你的内核配置成不允许模块卸载, 标识为 __exit 的函数被简单地丢弃. 因为这个原因, 一个标识 __exit 的函数只在模块卸载或者系统停止时调用; 任何别的使用是错的. 再一
次, moudle_exit 声明对于使得内核能够找到你的清理函数是必要的. 如果你的模块没有定义一个清理函数, 内核不会允许它被卸载.
13.初始化中的错误处理
错误恢复有时用 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 */
}
在 Linux 内核里, 错误码是负数, 属于定义于 <linux/errno.h> 的集合.
显然, 模块清理函数必须撤销任何由初始化函数进行的注册, 并且惯例(但常常不是要求的)是按照注册时相反的顺序注销设施.
void __exit my_cleanup_function(void)
{
unregister_those(ptr3, "skull");
unregister_that(ptr2, "skull");
unregister_this(ptr1, "skull");
return;
}
14.,模块参数
insmod hellop howmany=10 whom="Mom"
一旦以那样的方式加载, hellop 会说 "hello, Mom" 10 次.
但是, 在 insmod 可以修改模块参数前, 模块必须使它们可用. 参数用 moudle_param 宏定义来声明, 它定义在 moduleparam.h. module_param 使用了 3 个参数: 变量名, 它的类型, 以及一个权限掩码用来做一个辅助的 sysfs 入口. 这个宏定义应当放在任何函数之外, 典型地是出现在源文件的前面. 因此 hellop 将声明它的参数, 并如下使得对 insmod 可用:
static char *whom = "world";
static int howmany = 1;
module_param(howmany, int, S_IRUGO);
module_param(whom, charp, S_IRUGO);
所有的模块参数应当给定一个缺省值;
module_param_array(name,type,num,perm); 参数是数组
你应当不要使模块参数可写, 除非你准备好检测这个改变并且因而作出反应.
转载
1 代码是一个完整的 "hello world"模块:
#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);
这个模块定义了两个函数, 一个在模块加载到内核时被调用( hello_init )以及 一个在模块被去除时被调用( hello_exit ). moudle_init 和 module_exit 这几 行使用了特别的内核宏来指出这两个函数的角色. 另一个特别的宏 (MODULE_LICENSE) 是用来告知内核, 该模块带有一个自由的许可证; 没有这样 的说明, 在模块加载时内核会抱怨
2 核心模块与应用程序的基本区别
(1)驱动程序类似事件驱动编程,它初始化时候,注册一些设施,服务于系统调用
(2)应用程序运行在用户空间,驱动程序运行在内核空间
(3)应用程序可使用标准的库函数,而驱动程序连接到内核,只能使用内核导出的符号和函数
(4)应用程序错误,一般只会杀死进程,而驱动程序错误,会造成系统不稳定,或者崩溃
(5)应用程序拥有很大的栈空间,而内核栈很小,一般只有4KB
3 内核中并发的来源
(1)对称多处理器
(2)可延时机制timer、tasklet、workqueue
(3)大多数设备能够中断处理器,而中断处理程序异步运行
4 当前进程
(1)asm.current.h,current
(2)linux/sched.h,struct task_struct
5 一些细节
(1)具有前缀__的函数名称通常是接口的底层组件,应谨慎使用
(2)内核代码不能实现浮点数运算
6 编译和装载模块
(1)/proc/modules
(2)/sys/module
(3)编译模块
(4)Makefile
module-objs :=file1.o file2.o
obj-m := module.o
make -C /usr/src/kernels/2.6.10/ M=`pwd` modules
7 装载和卸载模块
(1)insmod和ld有些类似,将模块的代码和数据装入内核,然后使用内核的符号表解析模块中任何未解析的符号
(2)与链接器不同,内核不会修改模块的磁盘文件,而仅仅修改内存中的副本
(3)依赖定义在kernel/module.c中的一个系统调用
(4)sys_init_module给模块分配内核内存
(5)只有系统调用的名字前带有sys_前缀
(7)modprobe
(8)rmmod
(9)lsmod
(10)lsusb
8 版本依赖
(1)linux/versi(2)linux/module.h
UTS_RELEASE
LINUX_VERSION_CODE
KERNEL_VERSION(major,minor, release)
9 内核符号表
(1) Linux内核头文件提供了一个方便的方法来管理符号对模块外部的可见性,从而减少了可能造成的名字空间污染
(2)EXPORT_SYMBOL(name);
(3)EXPORT_SYMBOL_GPL(name);
10 其他知识
(1)头文件:函数、数据类型和变量的定义
(2)所有模块代码中都包含的代码
#include<linux/module.h>
#include<linux/init.h>
module.h包含有可装载模块需要的大量符号和函数的定义
init.h的目的是指定初始化和清除函数
内核能够识别的许可证
GPL
GPL v2
GPL and additional rights
Dual BSD/GPL
Dual MPL/GPL
Proprietary
如果一个模块没有地标记为上述内核可识别的许可证,则会被假定是专有的
(3) 描述性定义
MODULE_AUTHOR
MODULE_DESCRIPTION
MODULE_VERSION
MODULE_ALIAS
MODULE_DEVICE_TABLE
(4)模块参数
moduleparam.h
module_param
变量的名称、类型以及用于sysfs入口项的访问许可掩码
内核支持的模块参数类型
bool
invbool
charp
int
long
short
uint
ulong
ushort
module_param_array(name, type, num, perm);
相关文章推荐
- 《Linux Device Drivers》第二章 构造和运行模块——note
- 第二章 构造和运行模块(1)
- 第二章 构造和运行模块
- 【Linux 驱动】第二章 构造和运行模块
- LDD3笔记:第二章 构造和运行模块
- 第二章:构造和运行模块
- 02 构造和运行模块
- ldd3学习之二:构造和运行模块
- 构造和运行模块
- 第二章:构造和允许模块(part1)
- linux设备驱动之构造和运行模块
- Linux设备驱动程序学习(2)-构造和运行模块
- LDD读书实验笔记——构造和运行模块
- linux设备驱动第二篇:构造和运行模块
- linux kernel 学习 ----构造和运行模块
- Linux设备驱动开发学习(3):构造和运行模块
- linux设备驱动第二篇:构造和运行模块
- 第二章:构造和允许模块(part2)
- linux 设备驱动程序 (2) —— 构造和运行模块
- 构造和运行模块