欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

linux驱动学习笔记(二. 模块)

程序员文章站 2022-04-08 21:41:35
一、什么是模块 在单进程或多线程应用程序中,我们通常会使用while loop 的方式从头到尾的去执行,在这个loop中,根据输入的外部事件状态进行不同响应,从而引入不同的if 分支跳转执行相应的业务逻辑。 而在一些基础SDK或framework中,为了抽象出公共逻辑和分离具体业务,大多会采用事件驱动的方式作为整个工程的核心框架,比如知名的MFC或者libuv都采用了这种思想。core 层会去处理复杂的核心事务逻辑以及底层的系统交互,而抛出一些接口或事件(event)供使用者调用和......

一、什么是模块

     在单进程或多线程应用程序中,我们通常会使用while loop 的方式从头到尾的去执行,在这个loop中,根据输入的外部事件状态进行不同响应,从而引入不同的if 分支跳转执行相应的业务逻辑。

      而在一些基础SDK或framework中,为了抽象出公共逻辑和分离具体业务,大多会采用事件驱动的方式作为整个工程的核心框架,比如知名的MFC或者libuv都采用了这种思想。core 层会去处理复杂的核心事务逻辑以及底层的系统交互,而抛出一些接口或事件(event)供使用者调用和注册;使用者只需实现相应的回调函数及事件注册,而何时触发调用,交给core层处理就行了。

      我们现在所说的模块,也是类似的实现方式。在模块的初始化过程中,会进行相应的系统注册,以实现某种服务绑定到系统中,之后就默默的等待着,等待着系统的呼叫。在系统呼叫的过程中该模块需要实现的功能早已经在初始化时注册好了,在执行完模块的回调函数(一般对应.ops)后系统执行后续的逻辑。当模块移除出系统时,相应的退出函数将会调用来执行解注册和清理工作。在这里,需要提下的是,中断处理并不是采用这种逻辑,为了处理突发的事务,模块会注册中断服务函数(ISR),在发生相应的中断事件时,相应的ISR会被调用,这个之后再表。

    了解了模块的运行方式,需要了解下模块的一些基本特点。

  • 模块运行于内核空间,这决定了其执行在CPU特权级别,也叫内核态,其可以执行所有的操作,包括硬件访问和内存空间的访问。相对的,如果模块运行错误,轻则杀死运行当前代码的内核进程,总则系统奔溃(panic);
  • 紧接上一条,模块链接于内核,因此其只能调用内核导出的那些函数,而不能使用任何我们所熟知或正在使用的函数库。
  • 因为可能存在着多个进程在调用我们的驱动程序,何时调用和如何交叉调用不得而知;同时异步中断随时到来打断我们当前执行的任务而做另外可能引入再次调用模块函数的可能,诸如此次的并发运行事实,要求驱动程序代码必须是可重入的(reentrant),对于共享数据的保护也是至关重要的
  • 因为模块函数是由内核函数调用执行的,所以其和其他整个调用链上的其他函数享有同一个栈。所以尽量使用动态分配的方式来取代在函数内声名较大的自动变量

二、模块组成

       程序员都喜欢看例子,通过例子我们能很快的把玩起来,同时也是快速的参考。以ldd3中的带参数的Hello World 版本为例,一步步拆解来了解模块的基本构成和一个写法习惯。

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

static char *mystr = "hello";
static int myint = 1;
static int myarr[3] = {0, 1, 2};


module_param(myint, int, S_IRUGO);
module_param(mystr, charp, S_IRUGO);
module_param_array(myarr, int,NULL, S_IWUSR|S_IRUSR);

MODULE_PARM_DESC(myint,"this is my int variable");
MODULE_PARM_DESC(mystr,"this is my char pointer variable");
MODULE_PARM_DESC(myarr,"this is my array of int");
MODULE_INFO(my_field_name, "What eeasy value");

static int __init hellowolrd_init(void) {
    pr_info("Hello world with parameters!\n");
    pr_info("The *mystr* parameter: %s\n", mystr);
    pr_info("The *myint* parameter: %d\n", myint);
    pr_info("The *myarr* parameter: %d, %d, %d\n", myarr[0], myarr[1], myarr[2]);
    return 0;
}

static void __exit hellowolrd_exit(void) {
    pr_info("End of the world\n");
}

module_init(hellowolrd_init);
module_exit(hellowolrd_exit);
MODULE_AUTHOR("John Madieu <john.madieu@gmail.com>");
MODULE_LICENSE("GPL");

1.模块初始化

static int __init hellowolrd_init(void) {
    pr_info("Hello world with parameters!\n");
    pr_info("The *mystr* parameter: %s\n", mystr);
    pr_info("The *myint* parameter: %d\n", myint);
    pr_info("The *myarr* parameter: %d, %d, %d\n", myarr[0], myarr[1], myarr[2]);
    return 0;
}

static void __exit hellowolrd_exit(void) {
    pr_info("End of the world\n");
}

module_init(hellowolrd_init);

     初始化函数声名为static,即只在本文件内有效。如果需要使模块中的函数被内核其他部分可见,需要通过 如下的宏定义引出             EXPORT_SYMBOL(name)或  EXPORT_SYMBOL_GPL(name);

      函数定义中的__init 标识用来告诉内核该函数仅在初始化时期使用。在模块被转载之后,模块装载器会将初始化代码所占有的空间释放,从而节省系统开销。类比地,存在有__initdata ,__deinit,__devintdata等等。其具体实现见 五、补充

      module_int这个宏会指定整个模块的入口函数,其会在目标代码中增加一个特殊的段,以使初始化话函数能被正常的调用。module_init 宏定义见 五、补充

在初始化函数中,模块会调用相应的系统注册函数来进行注册,以代表模块实现了某种具体功能,比如设备文件等等。

     

三、模块如何编译

四、模块如何加载

五、补充

1.__init 标识说明

/* snip from `/include/linux/compiler_attributes.h` */
/* _______________________________分割线_______________________________________*/

/*
 *   gcc: https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html#index-section-function-attribute
 */
#define __section(S)                    __attribute__((__section__(#S)))


/* snip from `/include/linux/init.h` */
/* _______________________________分割线_______________________________________*/

/* These macros are used to mark some functions or 
 * initialized data (doesn't apply to uninitialized data)
 * as `initialization' functions. The kernel can take this
 * as hint that the function is used only during the initialization
 * phase and free up used memory resources after
 *
 * Usage:
 * For functions:
 * 
 * You should add __init immediately before the function name, like:
 *
 * static void __init initme(int x, int y)
 * {
 *    extern int z; z = x * y;
 * }
 *
 * If the function has a prototype somewhere, you can also add
 * __init between closing brace of the prototype and semicolon:
 *
 * extern int initialize_foobar_device(int, int, int) __init;
 *
 * For initialized data:
 * You should insert __initdata or __initconst between the variable name
 * and equal sign followed by value, e.g.:
 *
 * static int init_variable __initdata = 0;
 * static const char linux_logo[] __initconst = { 0x32, 0x36, ... };
 *
 * Don't forget to initialize data not at file scope, i.e. within a function,
 * as gcc otherwise puts the data into the bss section and not into the init
 * section.
 */

/* These are for everybody (although not all archs will actually
   discard it in modules) */
#define __init		__section(.init.text) __cold  __latent_entropy __noinitretpoline
#define __initdata	__section(.init.data)
#define __initconst	__section(.init.rodata)
#define __exitdata	__section(.exit.data)
#define __exit_call	__used __section(.exitcall.exit)

2.module_int 宏说明

/* snip from `/include/linux/module.h` */


/**
 * module_init() - driver initialization entry point
 * @x: function to be run at kernel boot time or module insertion
 *
 * module_init() will either be called during do_initcalls() (if
 * builtin) or at module insertion time (if a module).  There can only
 * be one per module.
 */
#ifndef MODULE

#define module_init(x)	__initcall(x);

#else /* MODULE */

/* Each module must use one module_init(). */
#define module_init(initfn)					\
	static inline initcall_t __maybe_unused __inittest(void)		\
	{ return initfn; }					\
	int init_module(void) __copy(initfn) __attribute__((alias(#initfn)));

#endif

依赖于模块是以build in的形式还是动态加载的方式运行, module_init 分为两种情况,


情形一 :build in


/* snip from `/include/linux/init.h` */

#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)

#define __initcall(fn) device_initcall(fn)    /*<-------------*/

#define __exitcall(fn)						\
	static exitcall_t __exitcall_##fn __exit_call = fn


/*-----------------------------------------------------------------------------*/
/* snip from `/include/linux/init.h` */

/*
 * initcalls are now grouped by functionality into separate
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 *
 * Initcalls are run by placing pointers in initcall sections that the
 * kernel iterates at runtime. The linker can do dead code / data elimination
 * and remove that completely, so the initcall sections have to be marked
 * as KEEP() in the linker script.
 */


#define ___define_initcall(fn, id, __sec) \               /*<----------*/
	static initcall_t __initcall_##fn##id __used \
		__attribute__((__section__(#__sec ".init"))) = fn;

#define __define_initcall(fn, id) ___define_initcall(fn, id, .initcall##id) /*<-------*/

/*-----------------------------------------------------------------------------*/
/*

将module_int(foo)展开(其中充斥着大量的字符拼接和字符串生成宏,见`link`),得到如下的形式:

static initcall_t __initcall_foo6 __used \
		__attribute__((__section__(".initcall6.init"))) = foo;

其中initcall_t 定义为 typedef int (*initcall_t)(void) 的函数指针,  
所以该语句可以解读为定义一个指向foo函数的函数指针__initcall_foo6并置于.initcall6.init段,
接下来找一下.initcall6.init相关的lds 控制与内核实际的初始化动作。

*/

/*------------------------------------------------------------------------------*/
/* snip from `/arch/arm/kernel/vmlinux.lds.S`*/

   	__init_begin = .;

	ARM_VECTORS
	INIT_TEXT_SECTION(8)
	
    /*...*/

	INIT_DATA_SECTION(16)  /*<-------------*/

/*------------------------------------------------------------------------------*/
/* snip from `/include/asm-generic/vmlinux.lds.h`*/

#define INIT_CALLS_LEVEL(level)						\
		__initcall##level##_start = .;				\
		KEEP(*(.initcall##level##.init))			\
		KEEP(*(.initcall##level##s.init))			\

#define INIT_CALLS							\
		__initcall_start = .;					\
		KEEP(*(.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)					\
		__initcall_end = .;

#define INIT_DATA_SECTION(initsetup_align)				\
	.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {		\
		INIT_DATA						\
		INIT_SETUP(initsetup_align)				\
		INIT_CALLS						\
		CON_INITCALL						\
		INIT_RAM_FS						\
	}
/*
  至此,我们找到了.initcall6.init 段的藏身之处,接下来就看内核是如何利用 
  __initcall##level##_start这些变量的.可以通过search __initcall6_start来查找

*/



/* snip from `/init/main.c` */

extern initcall_entry_t __initcall_start[];
extern initcall_entry_t __initcall0_start[];
/*...*/
extern initcall_entry_t __initcall6_start[];
extern initcall_entry_t __initcall7_start[];
extern initcall_entry_t __initcall_end[];

static initcall_entry_t *initcall_levels[] __initdata = {
	__initcall0_start,
	/*...*/
	__initcall6_start,
	__initcall7_start,
	__initcall_end,
};

static void __init do_initcall_level(int level, char *command_line)
{
	initcall_entry_t *fn;

	/* 启动参数解析 TBD */
    parse_args(initcall_level_names[level],
		   command_line, __start___param,
		   __stop___param - __start___param,
		   level, level,
		   NULL, ignore_unknown_bootoption);

	trace_initcall_level(initcall_level_names[level]);
	for (fn = initcall_levels[level]; fn < initcall_levels[level+1]; fn++)
		do_one_initcall(initcall_from_entry(fn)); /*<--------*/
}


static void __init do_initcalls(void)
{
	int level;
	size_t len = strlen(saved_command_line) + 1;
	char *command_line;

	command_line = kzalloc(len, GFP_KERNEL);
	if (!command_line)
		panic("%s: Failed to allocate %zu bytes\n", __func__, len);

	for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
		/* Parser modifies command_line, restore it each time */
		strcpy(command_line, saved_command_line);
		do_initcall_level(level, command_line); /*<---------*/
	}

	kfree(command_line);
}

/*
    暂时先追随到do_initcalls,该函数会根据level级别分别初始化各level的initcall_entry_t函数,从而完成build in 的module init函数 。
    至于do_initcalls由谁调用,do_initcall_level中的cmd parse待留到以后补充
*/

情形二、 动态加载


TBD

 

 

3.module_exit(x) 宏说明

/**
 * module_exit() - driver exit entry point
 * @x: function to be run when driver is removed
 *
 * module_exit() will wrap the driver clean-up code
 * with cleanup_module() when used with rmmod when
 * the driver is a module.  If the driver is statically
 * compiled into the kernel, module_exit() has no effect.
 * There can only be one per module.
 */
#define module_exit(x)	__exitcall(x);

      

     

 

本文地址:https://blog.csdn.net/liyucheng987/article/details/107191997