标签:hello data- resize 1.0 change btree bool cal event
有没有一种机制使得编译出的内核本身并不须要包括全部功能,而在这些功能须要被使用的时候,其相应的代码被动态地载入到内核中呢?
Linux提供了这样的一种机制,这样的机制被称为模块(Module)。模块具有这样的特点。
代码清单4.1 一个最简单的Linux内核模块
01 /* 02 * a simple kernel module: hello 03 * 04 * Copyright (C) 2014 Barry Song (baohua@kernel.org) 05 * 06 * Licensed under GPLv2 or later. 07 */ 08 09 #include <linux/init.h> 10 #include <linux/module.h> 11 12 static int __init hello_init(void) 13 { 14 printk(KERN_INFO "Hello World enter\n"); 15 return 0; 16 } 17 module_init(hello_init); 18 19 static void __exit hello_exit(void) 20 { 21 printk(KERN_INFO "Hello World exit\n "); 22 } 23 module_exit(hello_exit); 24 25 MODULE_AUTHOR("Barry Song <21cnbao@gmail.com>"); 26 MODULE_LICENSE("GPL v2"); 27 MODULE_DESCRIPTION("A simple Hello World Module"); 28 MODULE_ALIAS("a simplest module");
内核模块中用于输出的函数是内核空间的printk()而非用户空间的printf()。printk()的使用方法和printf()基本类似,但前者可定义输出级别。printk()可作为一种最主要的内核调试手段,在Linux驱动的调试章节中将具体解说这个函数。
在Linux中。使用lsmod命令能够获得系统中载入了的全部模块以及模块间的依赖关系,比如:
Module Size Used by hello 9 472 0 nls_iso8859_1 12 032 1 nls_cp437 13 696 1 vfat 18 816 1 fat 57 376 1 vfat ...
$ cat /proc/modules hello 12393 0 - Live 0xe67a2000 (OF) nls_utf8 12493 1 - Live 0xe678e000 isofs 39596 1 - Live 0xe677f000 vboxsf 42561 2 - Live 0xe6767000 (OF) ...内核中已载入模块的信息也存在于/sys/module文件夹下。载入hello.ko后,内核中将包括/sys/module/hello文件夹。该文件夹下又包括一个refcnt文件和一个sections文件夹,在/sys/module/hello文件夹下执行“tree –a”得到例如以下文件夹树:
root@barry-VirtualBox:/sys/module/hello# tree -a . ├── coresize ├── holders ├── initsize ├── initstate ├── notes │ └── .note.gnu.build-id ├── refcnt ├── sections │ ├── .exit.text │ ├── .gnu.linkonce.this_module │ ├── .init.text │ ├── .note.gnu.build-id │ ├── .rodata.str1.1 │ ├── .strtab │ └── .symtab ├── srcversion ├── taint └── uevent 3 directories, 15 files
使用modprobe命令载入的模块若以“modprobe -r filename”的方式卸载将同一时候卸载其依赖的模块。模块之间的依赖关系上存放在根文件系统的/lib/modules/<kernel-version>/modules.dep文件里,实际上是在总体编译内核的时候由depmod工具生成的,它的格式很easy:
kernel/lib/cpu-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko kernel/lib/pm-notifier-error-inject.ko: kernel/lib/notifier-error-inject.ko kernel/lib/lru_cache.ko: kernel/lib/cordic.ko: kernel/lib/rbtree_test.ko: kernel/lib/interval_tree_test.ko: updates/dkms/vboxvideo.ko: kernel/drivers/gpu/drm/drm.ko
# modinfo hello.ko filename: hello.ko alias: a simplest module description: A simple Hello World Module license: GPL v2 author: Barry Song <21cnbao@gmail.com> srcversion: 081230411494509792BD4A3 depends: vermagic: 3.8.0-39-generic SMP mod_unload modversions 686
Linux内核模块最常见的是以MODULE_LICENSE( "GPL v2" )语句声明模块採用GPL v2。
(4)模块參数(可选)。
模块參数是模块被载入的时候能够被传递给它的值,它本身相应模块内部的全局变量。
(5)模块导出符号(可选)。
内核模块能够导出符号(symbol。相应于函数或变量),这样其它模块能够使用本模块中的变量或函数。
(6)模块作者等信息声明(可选)。
代码清单4.2 内核模块载入函数
1 static int _ _init initialization_function(void) 2 { 3 /* 初始化代码 */ 4 } 5 module_init(initialization_function);
在Linux内核里,错误编码是一个接近于0的负值,在<linux/errno.h>中定义。包括-ENODEV、-ENOMEM之类的符号值。
总是返回相应的错误编码是种很好的习惯。由于仅仅有这样,用户程序才干够利用perror等方法把它们转换成有意义的错误信息字符串。
在Linux内核中,能够使用request_module(const char *fmt, …)函数载入内核模块,驱动开发者能够通过调用
request_module(module_name);
这样的灵活的方式载入其它内核模块。
在Linux中,全部标识为_ _init的函数假设直接编译进入内核,成为内核镜像的一部分,在连接的时候都放在.init.text这个区段内。
#define _ _init _ _attribute_ _ ((_ _section_ _ (".init.text")))
全部的_ _init函数在区段.initcall.init中还保存了一份函数指针,在初始化时内核会通过这些函数指针调用这些_ _init函数,并在初始化完毕后,释放init区段(包括.init.text、.initcall.init等)的内存。
除了函数以外,数据也能够被定义为_ _initdata,对于仅仅是初始化阶段须要的数据,内核在初始化完后,也能够释放它们占用的内存。比如,以下的代码中将hello_data定义为__initdata。
static int hello_data __initdata = 1; static int __init hello_init(void) { printk(KERN_INFO "Hello, world %d\n", hello_data); return 0; } module_init(hello_init); static void __exit hello_exit(void) { printk(KERN_INFO "Goodbye, world\n"); } module_exit(hello_exit);
1 static void _ _exit cleanup_function(void) 2 { 3 /* 释放代码 */ 4 } 5 module_exit(cleanup_function);
我们用__exit来修饰模块卸载函数,能够告诉内核假设相关的模块被直接编译进内核(即built-in),则cleanup_function() 函数会被省略直接不连接进最后的镜像。既然模块被built-in了。就不可能卸载它了。卸载函数也就没有存在的必要了。除了函数以外,仅仅是退出阶段採用的数据也能够用__exitdata来形容。
static char *book_name = "dissecting Linux Device Driver"; module_param(book_name, charp, S_IRUGO); static int book_num = 4000; module_param(book_num, int, S_IRUGO);
代码清单4.4 带參数的内核模块
01 #include <linux/init.h> 02 #include <linux/module.h> 03 04 static char *book_name = "dissecting Linux Device Driver"; 05 module_param(book_name, charp, S_IRUGO); 06 07 static int book_num = 4000; 08 module_param(book_num, int, S_IRUGO); 09 10 static int __init book_init(void) 11 { 12 printk(KERN_INFO "book name:%s\n", book_name); 13 printk(KERN_INFO "book num:%d\n", book_num); 14 return 0; 15 } 16 module_init(book_init); 17 18 static void __exit book_exit(void) 19 { 20 printk(KERN_INFO "book module exit\n "); 21 } 22 module_exit(book_exit); 23 24 MODULE_AUTHOR("Barry Song <baohua@kernel.org>"); 25 MODULE_LICENSE("GPL v2"); 26 MODULE_DESCRIPTION("A simple Module for testing module params"); 27 MODULE_VERSION("V1.0");
# tail -n 2 /var/log/messages Jul 2 01:03:10 localhost kernel: <6> book name:dissecting Linux Device Driver Jul 2 01:03:10 localhost kernel: book num:4000
# tail -n 2 /var/log/messages Jul 2 01:06:21 localhost kernel: <6> book name:GoodBook Jul 2 01:06:21 localhost kernel: book num:5000 Jul 2 01:06:21 localhost kernel: book num:5000另外,在/sys文件夹下,也能够看到book模块的參数:
barry@barry-VirtualBox:/sys/module/book/parameters$ tree . ├── book_name └── book_num
01 #include <linux/init.h> 02 #include <linux/module.h> 03 04 int add_integar(int a, int b) 05 { 06 return a + b; 07 } 08 EXPORT_SYMBOL_GPL(add_integar); 09 10 int sub_integar(int a, int b) 11 { 12 return a - b; 13 } 14 EXPORT_SYMBOL_GPL(sub_integar); 15 16 MODULE_LICENSE("GPL v2");
# grep integar /proc/kallsyms e679402c r __ksymtab_sub_integar [export_symb] e679403c r __kstrtab_sub_integar [export_symb] e6794038 r __kcrctab_sub_integar [export_symb] e6794024 r __ksymtab_add_integar [export_symb] e6794048 r __kstrtab_add_integar [export_symb] e6794034 r __kcrctab_add_integar [export_symb] e6793000 t add_integar [export_symb] e6793010 t sub_integar [export_symb]
MODULE_AUTHOR(author); MODULE_DESCRIPTION(description); MODULE_VERSION(version_string); MODULE_DEVICE_TABLE(table_info); MODULE_ALIAS(alternate_name);
1 /* 相应此驱动的设备表 */ 2 static struct usb_device_id skel_table [] = { 3 { USB_DEVICE(USB_SKEL_VENDOR_ID, 4 USB_SKEL_PRODUCT_ID) }, 5 { } /* 表结束 */ 6 }; 7 8 MODULE_DEVICE_TABLE (usb, skel_table);
模块的使用计数一般不必由模块自身管理。并且模块计数管理还考虑了SMP与PREEMPT机制的影响。
int try_module_get(struct module *module);
该函数用于添加模块使用计数。若返回为0。表示调用失败,希望使用的模块没有被载入或正在被卸载中。
void module_put(struct module *module);
该函数用于降低模块使用计数。
try_module_get ()与module_put()的引入与使用与Linux 2.6以后的内核下的设备模型密切相关。
Linux 2.6以后的内核为不同类型的设备定义了struct module *owner域,用来指向管理此设备的模块。当開始使用某个设备时,内核使用try_module_get(dev->owner)去添加管理此设备的owner模块的使用计数;当不再使用此设备时。内核使用module_put(dev->owner)降低对管理此设备的owner模块的使用计数。
这样,当设备在使用时,管理此设备的模块将不能被卸载。仅仅有当设备不再被使用时,模块才同意被卸载。
在Linux 2.6以后的内核下,对于设备驱动而言,很少须要亲自调用try_module_get()与module_put()。由于此时开发者所写的驱动通常为支持某具体设备的owner模块,对此设备owner模块的计数管理由内核里更底层的代码如总线驱动或是此类设备共用的核心模块来实现。从而简化了设备驱动开发。
我们能够为代码清单4.1的模板编写一个简单的Makefile:
KVERS = $(shell uname -r) # Kernel modules obj-m += hello.o # Specify flags for the module compilation. #EXTRA_CFLAGS=-g -O0 build: kernel_modules kernel_modules: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) modules clean: make -C /lib/modules/$(KVERS)/build M=$(CURDIR) clean
因此,这样的做法可能构成“蓄意侵权(willful infringement)”。
第二种做法是写一个wrapper内核模块(这个模块遵循GPL)。把EXPORT_SYMBOL_GPL()导出的符号封装一次再次以EXPORT_SYMBOL()形式导出,而其它的模块不直接调用内核而是调用wrapper函数。如图4.1所看到的。这样的做法也具有争议。
图4.1将EXPORT_SYMBOL_GPL又一次以EXPORT_SYMBOL导出
一般觉得。保守的做法是Linux内核不能使用非GPL许可权。
Linux内核模块编程与内核模块LICENSE -《具体解释(第3版)》预读
标签:hello data- resize 1.0 change btree bool cal event
原文地址:http://www.cnblogs.com/brucemengbm/p/7220244.html