Linux 驱动开发之内核模块分析4

Linux 驱动开发之内核模块分析4(基于Linux6.6)---符号表导出分析

前言

Linux内核头文件提供了一个方便的方法用来管理符号的对模块外部的可见性,因此减少了命名空间的污染(命名空间的名称可能会与内核其他地方定义的名称冲突),并且适当信息隐藏。 如果你的模块需要输出符号给其他模块使用,应当使用下面的宏定义:

EXPORT_SYMBOL(name);

EXPORT_SYMBOL_GPL(name);   //只适用于包含GPL许可权的模块;

这两个宏均用于将给定的符号导出到模块外. _GPL版本的宏定义只能使符号对GPL许可的模块可用。 符号必须在模块文件的全局部分导出,不能在函数中导出,这是因为上述这两个宏将被扩展成一个特殊用途的声明,而该变量必须是全局的。这个变量存储于模块的一个特殊的可执行部分(一个"ELF段" ),在装载时,内核通过这个段来寻找模块导出的变量。

一、宏定义EXPORT_SYMBOL分析

2.1、__EXPORT_SYMBOL

 include/linux/export.h

/*
 * Allow symbol exports to be disabled completely so that C code may
 * be reused in other execution contexts such as the UEFI stub or the
 * decompressor.
 */
#define __EXPORT_SYMBOL(sym, sec, ns)

#elif defined(CONFIG_TRIM_UNUSED_KSYMS)

#include <generated/autoksyms.h>

/*
 * For fine grained build dependencies, we want to tell the build system
 * about each possible exported symbol even if they're not actually exported.
 * We use a symbol pattern __ksym_marker_<symbol> that the build system filters
 * from the $(NM) output (see scripts/gen_ksymdeps.sh). These symbols are
 * discarded in the final link stage.
 */
#define __ksym_marker(sym)	\
	static int __ksym_marker_##sym[0] __section(".discard.ksym") __used

#define __EXPORT_SYMBOL(sym, sec, ns)					\
	__ksym_marker(sym);						\
	__cond_export_sym(sym, sec, ns, __is_defined(__KSYM_##sym))
#define __cond_export_sym(sym, sec, ns, conf)				\
	___cond_export_sym(sym, sec, ns, conf)
#define ___cond_export_sym(sym, sec, ns, enabled)			\
	__cond_export_sym_##enabled(sym, sec, ns)
#define __cond_export_sym_1(sym, sec, ns) ___EXPORT_SYMBOL(sym, sec, ns)

#ifdef __GENKSYMS__
#define __cond_export_sym_0(sym, sec, ns) __GENKSYMS_EXPORT_SYMBOL(sym)
#else
#define __cond_export_sym_0(sym, sec, ns) /* nothing */
#endif

#else

#define __EXPORT_SYMBOL(sym, sec, ns)	___EXPORT_SYMBOL(sym, sec, ns)

分析:

1)#运算符,##运算符

     通常在宏定义中使用#来创建字符串 #abc就表示字符串”abc”等。

    ##运算符称为预处理器的粘合剂,用来替换粘合两个不同的符号,

如:#define xName (n)  x##n

则xName(4)  则变为x4

2)gcc的 __attribute__ 属性:

     __attribute__((section(“section_name”)))的作用是将指定的函数或变量放入到名为”section_name”的段中

    __attribute__属性添加可以在函数或变量定义的时候直接加入在定义语句中。

如:

int myvar__attribute__((section("mydata"))) = 0;

表示定义了整形变量myvar=0;并且将该变量存放到名为”mydata”的section中

关于gcc_attribute详解可以参考:http://blog.sina.com.cn/s/blog_661314940100qujt.html

2.2、EXPORT_SYMBOL的作用是什么?

作用和用途

  1. 符号导出

    • 在 Linux 内核中,模块通常是相互独立的,但有时模块之间需要共享代码或数据。EXPORT_SYMBOL 允许一个模块中的函数或变量可以被其他模块引用和调用。
    • 使用 EXPORT_SYMBOL 导出的符号可以在内核的其他部分或其他内核模块中访问。
  2. 内核模块间的相互调用

    • 当某个模块定义了一个函数或全局变量,并希望其他模块能够调用或访问它时,就需要使用 EXPORT_SYMBOL 来导出该符号。
    • 这对于内核模块间的协作非常重要,尤其是在驱动程序或其他核心功能中,不同模块之间的代码共享非常常见。

示例

假设你有一个内核模块 module_a.c,其中定义了一个函数 foo(),你希望其他模块也能调用它。你可以这样做:

module_a.c

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

void foo(void) {
    printk(KERN_INFO "foo function called\n");
}

EXPORT_SYMBOL(foo);  // 导出 foo 函数,使其他模块可以调用

module_b.c

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

extern void foo(void);  // 声明外部函数

static int __init mod_b_init(void) {
    printk(KERN_INFO "module_b loaded\n");
    foo();  // 调用 module_a 中的 foo 函数
    return 0;
}

static void __exit mod_b_exit(void) {
    printk(KERN_INFO "module_b unloaded\n");
}

module_init(mod_b_init);
module_exit(mod_b_exit);
MODULE_LICENSE("GPL");

module_b.c 中,你通过 extern 声明了 foo() 函数,后续可以调用 module_a.c 中导出的 foo() 函数。EXPORT_SYMBOLmodule_a.c 中将 foo() 导出,允许其他模块访问它。

EXPORT_SYMBOLEXPORT_SYMBOL_GPL

  • EXPORT_SYMBOL: 将符号导出为可以被所有模块访问的符号,适用于任何模块,包括闭源模块。

  • EXPORT_SYMBOL_GPL: 这个宏的作用和 EXPORT_SYMBOL 类似,但它有一个附加的限制——仅允许 GPL 许可的模块访问该符号。也就是说,只有那些以 GPL 或兼容许可发布的模块,才可以访问该符号。

    使用 EXPORT_SYMBOL_GPL 是为了在内核中确保只有符合 GPL 许可的代码可以访问特定的内核符号,从而避免闭源模块直接使用内核中的 GPL 代码。

二、 EXPORT_SYMBOL使用方法

EXPORT_SYMBOL 是 Linux 内核模块开发中的一个非常重要的宏,用于将符号(函数、变量等)从一个内核模块导出,使得其他模块能够访问这些符号。下面将详细介绍 EXPORT_SYMBOL 的使用方法。

1. 基本语法

EXPORT_SYMBOL(symbol);

symbol 是你希望导出的符号(通常是函数或变量)。通过此宏,符号将会被公开,其他模块就能访问这个符号。

2. 使用场景

  • 函数导出:如果你在一个内核模块中实现了一个函数,想让其他模块也能调用这个函数,就需要使用 EXPORT_SYMBOL 将其导出。
  • 变量导出:如果你有一个全局变量,其他模块可能需要访问该变量,那么就需要将它导出。

3. 示例:导出函数

假设我们有两个模块:module_amodule_bmodule_a 中有一个函数 foo,我们希望 module_b 能够调用 foo

module_a.c

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

void foo(void) {
    printk(KERN_INFO "foo function called\n");
}

EXPORT_SYMBOL(foo);  // 导出 foo 函数,使得其他模块可以调用

module_a.c 中,我们定义了一个函数 foo,并通过 EXPORT_SYMBOL(foo) 将其导出。这意味着其他模块可以调用 foo() 函数。

module_b.c

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

extern void foo(void);  // 声明 foo 函数

static int __init mod_b_init(void) {
    printk(KERN_INFO "module_b loaded\n");
    foo();  // 调用 module_a 中的 foo 函数
    return 0;
}

static void __exit mod_b_exit(void) {
    printk(KERN_INFO "module_b unloaded\n");
}

module_init(mod_b_init);
module_exit(mod_b_exit);
MODULE_LICENSE("GPL");

module_b.c 中,首先通过 extern 声明了外部函数 foo,然后在初始化函数 mod_b_init 中调用了 foo()。由于 foomodule_a.c 中通过 EXPORT_SYMBOL 导出,因此 module_b 可以成功调用。

4. 使用 EXPORT_SYMBOL_GPL

EXPORT_SYMBOL_GPL 是一个与 EXPORT_SYMBOL 类似的宏,但它有一个附加限制:只有在 GPL 许可下的模块才能访问该符号。

  • EXPORT_SYMBOL 可以被任何模块(无论是否是 GPL 许可的模块)访问。
  • EXPORT_SYMBOL_GPL 仅允许 GPL 许可的模块访问该符号。这对于确保闭源模块不会直接使用内核中 GPL 代码的部分有帮助。

使用 EXPORT_SYMBOL_GPL 示例

假设你希望导出的符号仅允许 GPL 模块使用,你可以使用 EXPORT_SYMBOL_GPL

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

void foo(void) {
    printk(KERN_INFO "foo function called\n");
}

EXPORT_SYMBOL_GPL(foo);  // 仅允许 GPL 模块访问 foo

5. 符号的访问权限

  • 使用 EXPORT_SYMBOL 可以让任何内核模块访问被导出的符号。
  • 使用 EXPORT_SYMBOL_GPL 只能让 GPL 许可的内核模块访问该符号。如果其他非 GPL 模块尝试访问这个符号,编译器会给出错误。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值