这篇文章主要介绍“怎么使用Linux内核模块”,在日常操作中,相信很多人在怎么使用Linux内核模块问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”怎么使用Linux内核模块”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
Linux内核使用xx_initcall_xx(fn)
的宏来定义内核模块。
宏定义文件:include/linux/init.h
,定义如下:
/* * Early initcalls run before initializing SMP. * * Only for built-in code, not modules. */#define early_initcall(fn) __define_initcall(fn, early)/* * A "pure" initcall has no dependencies on anything else, and purely * initializes variables that couldn't be statically initialized. * * This only exists for built-in code, not for modules. * Keep main.c:initcall_level_names[] in sync. */#define pure_initcall(fn) __define_initcall(fn, 0)#define core_initcall(fn) __define_initcall(fn, 1)#define core_initcall_sync(fn) __define_initcall(fn, 1s)#define postcore_initcall(fn) __define_initcall(fn, 2)#define postcore_initcall_sync(fn) __define_initcall(fn, 2s)#define arch_initcall(fn) __define_initcall(fn, 3)#define arch_initcall_sync(fn) __define_initcall(fn, 3s)#define subsys_initcall(fn) __define_initcall(fn, 4)#define subsys_initcall_sync(fn) __define_initcall(fn, 4s)#define fs_initcall(fn) __define_initcall(fn, 5)#define fs_initcall_sync(fn) __define_initcall(fn, 5s)#define rootfs_initcall(fn) __define_initcall(fn, rootfs)#define device_initcall(fn) __define_initcall(fn, 6)#define device_initcall_sync(fn) __define_initcall(fn, 6s)#define late_initcall(fn) __define_initcall(fn, 7)#define late_initcall_sync(fn) __define_initcall(fn, 7s)
其中:early_initcall(fn)
只针对内核的核心代码,不能描述模块。
从上面代码可以看出,每个宏的实现都是__define_initcall()
,其定义如下:
#define __define_initcall(fn, id) \ static initcall_t __initcall_##fn##id __used \ __attribute__((__section__(".initcall" #id ".init"))) = fn; \ LTO_REFERENCE_INITCALL(__initcall_##fn##id) typedef int (*initcall_t)(void);
对于上面定义,需要关注以下几点:
1、initcall_t
:是一个函数指针类型,定义__initcall_##fn##id
,指向fn
。
2、__used
:在文件include/linux/compiler-gcc.h
中定义为:# define __used __attribute__((__used__))
,通知编译器在目标文件中保留一个静态函数,即使该函数未被使用。
3、__attribute__((__section__(".initcall" #id ".init")))
:定义的函数指针位于.initcall*.init
段中。
在文件include/asm-generic/vmlinux.lds.h
中,定义了宏INIT_CALLS
,定义如下:
#define INIT_CALLS_LEVEL(level) \ VMLINUX_SYMBOL(__initcall##level##_start) = .; \ *(.initcall##level##.init) \ *(.initcall##level##s.init) \#define INIT_CALLS \ VMLINUX_SYMBOL(__initcall_start) = .; \ *(.initcallearly.init) \ INIT_CALLS_LEVEL(0) \ INIT_CALLS_LEVEL(1) \ INIT_CALLS_LEVEL(2) \ INIT_CALLS_LEVEL(3) \ INIT_CALLS_LEVEL(4) \ INIT_CALLS_LEVEL(5) \ INIT_CALLS_LEVEL(rootfs) \ INIT_CALLS_LEVEL(6) \ INIT_CALLS_LEVEL(7) \ VMLINUX_SYMBOL(__initcall_end) = .;
在文件arch/arm64/kernel/vmlinux.lds.S
中,设置了宏INIT_CALLS
在.init.data
段中的分布位置,内容如下:
.init.data : { INIT_DATA INIT_SETUP(16) INIT_CALLS CON_INITCALL SECURITY_INITCALL INIT_RAM_FS }
在Linux内核编译链接后,会生成文件arch/arm64/kernel/vmlinux.lds
。在该文件中将展开宏INIT_CALLS
,并分配到.init.data
段中,内容如下:
.init.data : {... __initcall_start = .; *(.initcallearly.init) __initcall0_start = .; *(.initcall0.init) *(.initcall0s.init) __initcall1_start = .; *(.initcall1.init) *(.initcall1s.init) __initcall2_start = .; *(.initcall2.init) *(.initcall2s.init) __initcall3_start = .; *(.initcall3.init) *(.initcall3s.init) __initcall4_start = .; *(.initcall4.init) *(.initcall4s.init) __initcall5_start = .; *(.initcall5.init) *(.initcall5s.init) __initcallrootfs_start = .; *(.initcallrootfs.init) *(.initcallrootfss.init) __initcall6_start = .; *(.initcall6.init) *(.initcall6s.init) __initcall7_start = .; *(.initcall7.init) *(.initcall7s.init) __initcall_end = .;... }
注:在__initcall_start
和__initcall_end
之间是按照从0到7的顺序进行排列,对应pure_initcall(fn)
到late_initcall_sync(fn)
。
Linux内核模块可以直接编译到内核映像,在内核启动时加载;也可以在系统启动后,通过insmod
命令加载到内核。
Linux内核启动时,kernel_init
线程会实现静态编译的内核模块加载。
1、程序调用流程
## kernel/init/main.cstart_kernel()-> rest_init()-> kernel_thread(kernel_init, NULL, CLONE_FS) ## 1.创建内核线程kernel_init()-> kernel_init_freeable()-> do_basic_setup()-> do_initcalls()-> ## 2.模块加载过程 do_initcall_level()-> do_one_initcall()
2、do_initcalls()
函数
函数功能:按顺序扫描.init.data
段中的每个等级,即:从__initcall0_start
到__initcall_end
。
## __initdata中的定义在static initcall_t *initcall_levels[] __initdata = { __initcall0_start, __initcall1_start, __initcall2_start, __initcall3_start, __initcall4_start, __initcall5_start, __initcall6_start, __initcall7_start, __initcall_end,};static void __init do_initcalls(void){ int level; for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) do_initcall_level(level);}
3、do_initcall_level()
函数
函数功能:在同一个等级中,按顺序扫描.initcall*.init
到.initcall*s.init
static void __init do_initcall_level(int level){ ... for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++) do_one_initcall(*fn);}
4、do_one_initcall()
函数
函数功能:调用每个定义的模块函数。
int __init_or_module do_one_initcall(initcall_t fn){ ... if (initcall_debug) ret = do_one_initcall_debug(fn); else ret = fn(); ##调用某个定义的initcall函数 ... return ret;}
注:每个fn()
对应第一部分模块定义中的fn
,例:device_initcall(fn)
。
从上面函数的执行流程可以看出内核模块加载优先级如下:
early_initcall(fn) ## 优先级最高,后续优先级依次降低pure_initcall(fn)core_initcall(fn)core_initcall_sync(fn)postcore_initcall(fn)postcore_initcall_sync(fn)arch_initcall(fn)arch_initcall_sync(fn)subsys_initcall(fn)subsys_initcall_sync(fn)fs_initcall(fn)fs_initcall_sync(fn)rootfs_initcall(fn)device_initcall(fn)device_initcall_sync(fn)late_initcall(fn)late_initcall_sync(fn) ## 优先级最低
Linux内核模块的优先级决定了模块的加载顺序,在驱动开发时,需要关注有启动顺序要求的模块定义。
到此,关于“怎么使用Linux内核模块”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注亿速云网站,小编会继续努力为大家带来更多实用的文章!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。