当前位置: 首页 > news >正文

[OS] EXPORT_SYMBOL()

在 Linux 内核中,EXPORT_SYMBOL() 用于将模块中的函数或变量导出,使得其他内核模块能够使用这些导出的符号。这对于模块之间共享功能或数据非常有用。给出的代码示例展示了如何使用 EXPORT_SYMBOL() 将变量和函数导出供其他模块使用。

/* ... */
int GLOBAL_VARIABLE = 1000;
EXPORT_SYMBOL(GLOBAL_VARIABLE);
// Function to print hello for num times.
void print_hello(int num)
{
while (num--) {
printk(KERN_INFO "Hello Friend!!!\n");
}
}
EXPORT_SYMBOL(print_hello);
// Function to add two passed number.
void add_two_numbers(int a, int b)
{
printk(KERN_INFO "Sum of the numbers %d", a + b);
}
EXPORT_SYMBOL(add_two_numbers);
static int __init my_init(void)
{
printk(KERN_INFO "Hello from Export Symbol 1 module.");
return 0;
}
static void __exit my_exit(void)
{
printk(KERN_INFO "Bye from Export Symbol 1 module.");
}
module_init(my_init);
module_exit(my_exit);
/* ... */

 

int GLOBAL_VARIABLE = 1000;
EXPORT_SYMBOL(GLOBAL_VARIABLE);
  • GLOBAL_VARIABLE 是一个全局变量,初始值为 1000。通过 EXPORT_SYMBOL(GLOBAL_VARIABLE),该变量被导出,使得其他模块可以通过它访问或修改这个变量。
  • 用途: 这在多个模块需要共享同一个全局变量时非常有用。例如,如果多个模块需要共享一个状态变量,它们可以通过导出这个全局变量实现。
2. 导出函数 print_hello()
void print_hello(int num) {while (num--) {printk(KERN_INFO "Hello Friend!!!\n");}
}
EXPORT_SYMBOL(print_hello);

 

  • print_hello() 函数用于打印指定次数的 "Hello Friend!!!" 消息。
  • 通过 EXPORT_SYMBOL(print_hello),该函数也被导出,使得其他模块可以调用 print_hello() 函数。
  • 用途: 这在需要其他模块执行类似任务时很有用。例如,一个通用的日志输出功能可以通过导出函数供多个模块使用。
3. 导出函数 add_two_numbers()
void add_two_numbers(int a, int b) {printk(KERN_INFO "Sum of the numbers %d", a + b);
}
EXPORT_SYMBOL(add_two_numbers);
  • add_two_numbers() 函数用于打印传入的两个整数的和。
  • 通过 EXPORT_SYMBOL(add_two_numbers),该函数也被导出,使得其他模块可以调用它来计算两个数字的和并输出结果。
  • 用途: 当多个模块需要类似的简单计算时,这样的功能可以被复用。
4. 模块的初始化和退出函数

  • my_init():这是模块加载时执行的初始化函数。它在加载时输出一条消息,表明模块已被加载。
  • my_exit():这是模块卸载时执行的清理函数。它在模块卸载时输出一条消息,表明模块已被卸载。
  • module_init()module_exit() 用来注册模块的初始化和退出函数。

比喻:

可以把 EXPORT_SYMBOL() 想象成把某些工具放在一个“共享工具箱”中,供其他模块(类似于工程师)使用。每个工程师(模块)都可以从工具箱里取出这些工具(导出的函数或变量)来完成任务,而不需要自己重新造轮子。

导出的 API 在另一个模块中的使用:

假设我们有另一个模块 myModule2,这个模块想要使用 myModule1 中导出的 GLOBAL_VARIABLEprint_hello()

myModule2 示例代码:
#include <linux/init.h>
#include <linux/module.h>extern int GLOBAL_VARIABLE;  // 声明外部导出的全局变量
extern void print_hello(int num);  // 声明外部导出的函数static int __init my_module_init(void) {printk(KERN_INFO "Hello from myModule2.\n");// 使用导出的变量和函数printk(KERN_INFO "GLOBAL_VARIABLE is: %d\n", GLOBAL_VARIABLE);print_hello(5);  // 打印5次 "Hello Friend!!!"return 0;
}static void __exit my_module_exit(void) {printk(KERN_INFO "Bye from myModule2.\n");
}module_init(my_module_init);
module_exit(my_module_exit);MODULE_LICENSE("GPL");
  • extern 关键字:

    • 使用 extern 关键字来声明在 myModule1 中导出的变量和函数。这告诉内核这个符号已经在其他模块中定义,可以直接引用。
  • 使用导出的变量和函数:

    • 通过 GLOBAL_VARIABLEmyModule2 可以访问 myModule1 中的全局变量。
    • 通过调用 print_hello(5)myModule2 可以调用 myModule1 中的函数,并打印 5 次 "Hello Friend!!!"。

 

/* ... */
extern void print_hello(int);
extern void add_two_numbers(int, int);
extern int GLOBAL_VARIABLE;
/*
* Call functions which are in other module.
*/
static int __init my_init(void)
{
printk(KERN_INFO "Hello from Hello Module");
print_hello(2);
add_two_numbers(5, 6);
printk(KERN_INFO "Value of GLOBAL_VARIABLE %d", GLOBAL_VARIABLE);
return 0;
}
static void __exit my_exit(void)
{
printk(KERN_INFO "Bye from Hello Module");
}
module_init(my_init);
module_exit(my_exit);
/* ... */

在 Linux 内核中,模块之间的加载顺序非常重要,尤其当一个模块依赖另一个模块导出的符号时。如果依赖的模块没有先加载,依赖模块将无法找到其需要的符号,导致加载错误。

为什么会出错:

在你给出的场景中:

  • myModule1.ko 导出了全局变量和函数(如 GLOBAL_VARIABLEprint_hello())。
  • myModule2.ko 依赖 myModule1.ko 中导出的符号,并通过 extern 引用它们。

如果你尝试先加载 myModule2.ko,会发生错误,因为在加载 myModule2.ko 时,内核找不到 GLOBAL_VARIABLEprint_hello() 等符号——这些符号还没有被 myModule1.ko 导出。

内核模块加载的过程类似于以下步骤:

  1. 内核会首先检查模块的依赖项,并查看该模块是否需要使用其他模块导出的符号。
  2. 如果依赖的符号没有找到(即所需的模块尚未加载),内核会报错,并拒绝加载该模块。

因此,必须先加载导出符号的模块(myModule1.ko,然后再加载依赖模块(myModule2.ko)。

如何解决:

为了解决模块加载顺序问题,确保在插入内核模块时遵循正确的依赖顺序:

  1. 先插入 myModule1.ko

    • 运行 insmod myModule1.ko 或者 modprobe myModule1,首先将导出符号的模块加载到内核中。
    • 这样,内核会将 myModule1.ko 中的符号(GLOBAL_VARIABLEprint_hello() 等)导出并使其在整个内核中可用。
  2. 再插入 myModule2.ko

    • myModule1.ko 成功加载后,再运行 insmod myModule2.komodprobe myModule2,这时内核可以找到 myModule1.ko 导出的符号,myModule2.ko 将能够正确加载。

错误示例:

如果你反过来加载模块,先加载 myModule2.ko,会看到类似以下的错误:

insmod: error inserting 'myModule2.ko': -1 Unknown symbol in module

 这个错误通常表示模块中有未解析的符号,原因是这些符号(GLOBAL_VARIABLEprint_hello())还没有被内核注册,因为 myModule1.ko 尚未加载。

//Insert myModule1.ko then myModule2.ko and you can see the following:
$ sudo insmod myModule1.ko
$ sudo insmod myModule2.ko
[15606.692155] Hello from Export Symbol 1 module.
[15612.175760] Hello from Hello Module
[15612.175764] Hello Friend!!!
[15612.175766] Hello Friend!!!
[15612.175780] Sum of the numbers 11
[15612.175782] Value of GLOBAL_VARIABLE 1000

5. EXPORT_SYMBOL — Linux Kernel Workbook 1.0 documentation (lkw.readthedocs.io)

Linux World: Exporting symbols from module (tuxthink.blogspot.com)

c - How to prevent "error: 'symbol' undeclared here" despite EXPORT_SYMBOL in a Linux kernel module? - Stack Overflow

c - How to call exported kernel module functions from another module? - Stack Overflow 

 How to define a function in one linux kernel module and use it in another? - Stack Overflow

How to create a working thread 

In our case, the working function is my_fork(), so my_fork() needs to do all the job.

How to create a working process

在 Linux 内核中,kernel_clone() 函数是用来创建一个新进程或线程的底层系统调用。这类似于用户空间的 fork()clone(),但在内核中允许更细粒度的控制。提供的代码片段展示了如何使用 kernel_clone() 来创建一个新进程,并让该进程执行指定的函数(如 hello())。

struct kernel_clone_args clone_args = {.flags = SIGCHLD,.pidfd = NULL,.child_tid = NULL,.parent_tid = NULL,.exit_signal = SIGCHLD,.stack = (unsigned long) &hello,.stack_size = 0,.tls = 0
};
pid_t pid = kernel_clone(&clone_args);

字段解释:

  1. flags:

    • 作用: 用来控制新进程或线程的行为。
    • 在这个例子中,flags = SIGCHLD 表示在子进程终止时会发送 SIGCHLD 信号给父进程。这是常见的用于通知父进程子进程终止的机制。
    • 其他可能的标志
      • CLONE_VM: 子进程共享父进程的地址空间。
      • CLONE_FS: 子进程共享父进程的文件系统信息。
  2. pidfd:

    • 作用: 如果不为 NULL,则存储一个文件描述符(PID file descriptor),该文件描述符指向子进程的 PID。这在进程控制中很有用。
    • 在你的例子中,pidfd = NULL,表示不使用 PID 文件描述符。
  3. parent_tidchild_tid:

    • 作用: 用于线程(而不是进程)的同步。如果不为 NULL,则将父/子线程的 TID(线程 ID)存储到指定的地址中。
    • 在你的例子中,这两个字段都被设置为 NULL,表示不使用线程 ID。
  4. exit_signal:

    • 作用: 指定子进程终止时父进程接收到的信号。在这个例子中,exit_signal = SIGCHLD 表示当子进程终止时,父进程会接收到 SIGCHLD 信号。
    • 这与 fork() 系统调用的默认行为一致。
  5. stack:

    • 作用: 该字段指定新进程的栈起始地址。
    • 在这个例子中,stack = (unsigned long) &hello,即将 hello 函数的地址作为栈地址传递。这意味着当新进程启动时,它将执行 hello() 函数。
    • 注意:在一般情况下,stack 通常用于指定用户态进程的栈地址。而在这里,hello() 函数将被用作新进程的执行入口。
  6. stack_size:

    • 作用: 用于指定栈的大小。
    • 在这个例子中,stack_size = 0,表示默认的栈大小。这意味着内核会使用系统默认的栈大小。
  7. tls:

    • 作用: 这是线程局部存储(Thread Local Storage)的指针,用于在多线程环境中给每个线程分配独立的存储区域。
    • 在这个例子中,tls = 0,表示不使用线程局部存储。
原文地址:https://blog.csdn.net/m0_74331272/article/details/142748741
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mrgr.cn/news/45021.html

相关文章:

  • 前端推荐书单
  • 【电路基础 · 4】电路的图;KCL、KVL巩固;支路电流法
  • 『网络游戏』自适应制作登录UI【01】
  • 基于开源大型lmm模型生成标签对InternVL2-1B等轻量lmm模型进行微调
  • ArcGIS中分区统计栅格值前需要进行投影吗(在投影坐标系下进行吗),为什么?
  • 基于SSM+小程序的教育培训管理系统(教育3)
  • 速盾:高防服务器是如何防御CC攻击的?
  • Robot Operating System——单个组件或节点的诊断状态
  • 2024最新 Navicat Premium 17 简体中文版安装图文详细教程
  • 万字长文详解Linux并发与竞争 - 原子操作、自旋锁、信号量、互斥体
  • 第二十二天|回溯算法| 理论基础,77. 组合(剪枝),216. 组合总和III,17. 电话号码的字母组合
  • 【C++11】可变模板参数
  • redis过期策略和内存淘汰机制
  • 基于SpringBoot“花开富贵”花园管理系统【附源码】
  • 基于SpringBoot博物馆游客预约系统【附源码】
  • vue2和vue3中的组件间通信知识点总结
  • 深入理解 JavaScript 中的表达式、运算符、语句和声明概念
  • 二进制求和
  • Bianchi模型、python计算及ns3验证_关于E[P*]的补充
  • Kubernetes 洞察:DaemonSet 全解析