00. 目录
01. 概述
内核模块作为一个可拓展的动态模块,为Linux内核提供了灵活性,但是有时我们需要根据不同的应用场景给内核传递不同的参数, 例如在程序中开启调试模式、设置详细输出模式以及制定与具体模块相关的选项,都可以通过参数的形式来改变模块的行为。
02. module_param
Linux内核提供一个宏来实现模块的参数传递
module_param函数 (内核源码/include/linux/moduleparam.h)
/**
* module_param - typesafe helper for a module/cmdline parameter
* @value: the variable to alter, and exposed parameter name.
* @type: the type of the parameter
* @perm: visibility in sysfs.
*
* @value becomes the module parameter, or (prefixed by KBUILD_MODNAME and a
* ".") the kernel commandline parameter. Note that - is changed to _, so
* the user can use "foo-bar=1" even for variable "foo_bar".
*
* @perm is 0 if the the variable is not to appear in sysfs, or 0444
* for world-readable, 0644 for root-writable, etc. Note that if it
* is writable, you may need to use kernel_param_lock() around
* accesses (esp. charp, which can be kfreed when it changes).
*
* The @type is simply pasted to refer to a param_ops_##type and a
* param_check_##type: for convenience many standard types are provided but
* you can create your own by defining those variables.
*
* Standard types are:
* byte, short, ushort, int, uint, long, ulong
* charp: a character pointer
* bool: a bool, values 0/1, y/n, Y/N.
* invbool: the above, only sense-reversed (N = true).
*/
#define module_param(name, type, perm) \
module_param_named(name, name, type, perm)
/**
* module_param_array - a parameter which is an array of some type
* @name: the name of the array variable
* @type: the type, as per module_param()
* @nump: optional pointer filled in with the number written
* @perm: visibility in sysfs
*
* Input and output are as comma-separated values. Commas inside values
* don't work properly (eg. an array of charp).
*
* ARRAY_SIZE(@name) is used to determine the number of elements in the
* array, so the definition must be visible.
*/
#define module_param_array(name, type, nump, perm) \
module_param_array_named(name, name, type, nump, perm)
以上代码中的module_param函数需要传入三个参数:
- name: 我们定义的变量名;
- type: 参数的类型,目前内核支持的参数类型有byte,short,ushort,int,uint,long,ulong,charp,bool,invbool。其中charp表示的是字符指针,bool是布尔类型,其值只能为0或者是1;invbool是反布尔类型,其值也是只能取0或者是1,但是true值表示0,false表示1。变量是char类型时,传参只能是byte,char * 时只能是charp。
- perm: 表示的是该文件的权限,具体参数值见下表。
用户组 | 标志位 | 解释 |
---|---|---|
当前用户 | S_IRUSR | 用户拥有读权限 |
S_IWUSR | 用户拥有写权限 | |
当前用户组 | S_IRGRP | 当前用户组的其他用户拥有读权限 |
S_IWUSR | 当前用户组的其他用户拥有写权限 | |
其他用户 | S_IROTH | 其他用户拥有读权限 |
S_IWOTH | 其他用户拥有写权限 |
上述文件权限唯独没有关于可执行权限的设置,请注意, 该文件不允许它具有可执行权限。如果强行给该参数赋予表示可执行权限的参数值S_IXUGO, 那么最终生成的内核模块在加载时会提示错误。
程序示例
static int itype = 0;
module_param(itype, int, 0);
static bool btype = 0;
module_param(btype, bool, 0644);
static char ctype = 0;
module_param(ctype, byte, 0);
static char *stype = 0;
module_param(stype, charp, 0644);
static int __init param_init(void)
{
printk(KERN_ALERT "param init!\n");
printk(KERN_ALERT "itype=%d\n",itype);
printk(KERN_ALERT "btype=%d\n",btype);
printk(KERN_ALERT "ctype=%d\n",ctype);
printk(KERN_ALERT "stype=%s\n",stype);
return 0;
}
- 第1-11行:定义了四个常见变量然后使用module_param宏来声明这四个参数
- 第13-21行:并在param_init中输出上面声明的四个参数。
03. 符号共享代码
在前面已经详细的分析了关于导出符号的内核源码,符号指的就是在内核模块中导出函数和变量, 在加载模块时被记录在公共内核符号表中,以供其他模块调用。 这个机制,允许我们使用分层的思想解决一些复杂的模块设计。我们在编写一个驱动的时候, 可以把驱动按照功能分成几个内核模块,借助符号共享去实现模块与模块之间的接口调用,变量共享。
/* For every exported symbol, place a struct in the __ksymtab section */
#define __EXPORT_SYMBOL(sym, sec) \
extern typeof(sym) sym; \
__CRC_SYMBOL(sym, sec) \
static const char __kstrtab_##sym[] \
__attribute__((section("__ksymtab_strings"), aligned(1))) \
= VMLINUX_SYMBOL_STR(sym); \
extern const struct kernel_symbol __ksymtab_##sym; \
__visible const struct kernel_symbol __ksymtab_##sym \
__used \
__attribute__((section("___ksymtab" sec "+" #sym), unused)) \
= {
(unsigned long)&sym, __kstrtab_##sym }
#define EXPORT_SYMBOL(sym) \
__EXPORT_SYMBOL(sym, "")
EXPORT_SYMBOL宏用于向内核导出符号,这样的话,其他模块也可以使用我们导出的符号了。 下面通过一段代码,介绍如何使用某个模块导出符号。
扫描二维码关注公众号,回复:
16673408 查看本文章
程序示例
static int itype = 0;
module_param(itype, int, 0);
EXPORT_SYMBOL(itype);
int my_add(int a, int b)
{
return a + b;
}
EXPORT_SYMBOL(my_add);
int my_sub(int a, int b)
{
return a - b;
}
EXPORT_SYMBOL(my_sub);
- 第2-3行:定义了参数itype,并通过EXPORT_SYMBOL宏导出
- 第7-12行:和my_add,并通过EXPORT_SYMBOL宏导出
- 第14-21行:my_sub函数,并通过EXPORT_SYMBOL宏导出
04. 程序示例
add.h
#ifndef __ADD_H__
#define __ADD_H__
extern int itype;
int my_add(int a, int b);
int my_sub(int a, int b);
#endif
insmod test.ko itype=123 btype=1 ctype=200 stype=abc
add.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include "add.h"
static int __init cal_init(void)
{
printk(KERN_ALERT "cal init!\n");
printk(KERN_ALERT "itype+1 = %d, itype-1 = %d\n", my_add(itype, 1), my_sub(itype, 1));
return 0;
}
static void __exit cal_exit(void)
{
printk(KERN_ALERT "cal exit!\n");
}
module_init(cal_init);
module_exit(cal_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("uplooking");
MODULE_DESCRIPTION("cal module");
MODULE_ALIAS("cal_module");
test.c
#include <linux/module.h>
#include <linux/init.h>
#include <linux/kernel.h>
static int itype = 0;
module_param(itype, int, 0);
static bool btype = 0;
module_param(btype, bool, 0700);
static char ctype = 0;
module_param(ctype, byte, 0);
static char *stype = 0;
module_param(stype, charp, 0644);
static int __init param_init(void)
{
printk(KERN_ALERT "param init!\n");
printk(KERN_ALERT "itype=%d\n",itype);
printk(KERN_ALERT "btype=%d\n",btype);
printk(KERN_ALERT "ctype=%d\n",ctype);
printk(KERN_ALERT "stype=%s\n",stype);
return 0;
}
static void __exit param_exit(void)
{
printk(KERN_ALERT "module exit!\n");
}
EXPORT_SYMBOL(itype);
int my_add(int a, int b)
{
return a+b;
}
EXPORT_SYMBOL(my_add);
int my_sub(int a, int b)
{
return a-b;
}
EXPORT_SYMBOL(my_sub);
module_init(param_init);
module_exit(param_exit);
MODULE_LICENSE("GPL v2");
MODULE_AUTHOR("uplooking");
MODULE_DESCRIPTION("module_param");
MODULE_ALIAS("module_param");
Makefile
CROSS_COMPILE=aarch64-linux-gnu-gcc
obj-m := test.o add.o
all:
$(MAKE) -C $(KERNEL_DIR) M=`pwd` modules
.PHONE:clean
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(PWD) clean