初探linux内核编程,参数传递以及模块间函数调用

一.前言                                 

我们一起从3个小例子来体验一下linux内核编程。如下:

1.内核编程之hello world

2.模块参数传递

3.模块间函数调用

二.准备工作                          

首先,在你的linux系统上面安装linux头文件,debian系列:

1 $:sudo apt-get install linux-headers-`uname -r`

安装后,在你的/lib/modules/目录下有你刚刚安装的头文件版本号对应的目录。头文件夹下面还有个build文件夹,里面的Makefile文件是等会要编译内核模块用的。如图,这是我机器上面的:

注意:安装的头文件版本一定要和你的系统版本一样,不然你自己编写的模块不能插入到本机的内核。如果你apt-get安装头文件时,没有对应的头文件,或者你的源里面放不稳定版本的源后,依然没有对应的头文件,你可以到这里搜索需要的deb包来安装。再或者下载跟本机对应的内核源码来构建环境。

三.内核编程之hello world    

我们先来了解下内核模块文件的入口和出口。它由2个宏来注册入口和出口,分别是:

1 module_init(x);
2 module_exit(x);

这2个宏在头文件目录的include/linux/module.h。宏里面的x代表注册到内核的入口和出口函数。通俗一点讲就是模块初始化和模块卸载时调用的函数。

初始化函数的形式:int my_init(void);

退出函数的形式:void my_exit(void);

另外还有一些宏:

MODULE_LICENSE(_license):模块的许可证。

MODULE_AUTHOR(_author):模块的作者。

MODULE_VERSION(_version):模块版本

MODULE_DESCRIPTION(_description):模块的描述。

还有一些就不一一举例了。

现在,我来看最简单的hello world例子:

文件名:kernel_hello.c

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4
 5 /* 以下4个宏分别是许可证,作者,模块描述,模块版本 */
 6 MODULE_LICENSE("Dual BSD/GPL");
 7 MODULE_AUTHOR("yuuyuu");
 8 MODULE_DESCRIPTION("kernel module hello");
 9 MODULE_VERSION("1.0");
10
11 /* 入口函数 */
12 static int hello_init(void)
13 {
14     printk(KERN_ALERT "hello_init() start\n");
15
16     return 0;
17 }
18
19 /* 退出函数 */
20 static void hello_exit(void)
21 {
22     printk(KERN_ALERT "hello_exit() start\n");
23 }
24
25 /* 注册到内核 */
26 module_init(hello_init);
27 module_exit(hello_exit);

上面的printk()函数时内核自己实现的输出函数,KERN_ALERT时输出信息的级别!

然后再写一个很简单的Makefile文件:

1 KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
2 PWD := $(shell pwd)
3
4 obj-m := kernel_hello.o
5
6 default:
7     $(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules

KERNAL_DIR:你刚刚安装头文件的目录

PWD:代表当前你编写模块文件的目录。

obj-m:模块的依赖目标文件。另外,内核的Makefile文件:obj-m代表将目标文件编译成模块,obj-y代表编译到内核,ojb-n代表不编译。

-C:执行make的时候,把工作目录切换到-C后面指定的参数目录,这里即头文件目录下的build目录。

M:这个M时内核Makefile里面的一个变量。作用时回到当前目录继续读取Makefile,这里的就是读完build目录下的Makefile之后再回到我们的这个目录,

读取我们刚刚编写的那个Makefile。

最后的modules是代表编译模块。

现在我们来编译下,在我们编写Makefile的目录下执行make,会看到生成了模块文件kernel_hello.ko

查看模块信息:sudo modinfo kernel_hello.ko

可以看到刚刚那几个宏插入的模块信息。

现在我们一口气执行4个动作:插入模块,查看内核已插入的模块,卸载模块,查看dmesg信息:

可以看到,模块在初始化和退出时都打印了函数里面的信息。

四.模块参数传递                    

模块的参数传递也是一个宏,在头文件目录的include/linux/moduleparam.h:

1 module_param(name, type, perm)

name:模块中的变量名,也是用户可指定参数名。

type:byte,short,ushot,int,uint,long,ulong,charp,bool这些

perm:模块的权限控制。跟linux文件权限控制一样的。

文件名:kernel_hello_param.c

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4
 5 /* 以下4个宏分别是许可证,作者,模块描述,模块版本 */
 6 MODULE_LICENSE("Dual BSD/GPL");
 7 MODULE_AUTHOR("yuuyuu");
 8 MODULE_DESCRIPTION("kernel module hello");
 9 MODULE_VERSION("1.0");
10
11 static char *msg;
12 module_param(msg, charp, 0644);
13
14 /* 入口函数 */
15 static int hello_init(void)
16 {
17     printk(KERN_ALERT "hello_init() start\n");
18     printk(KERN_ALERT "%s\n", msg);
19
20     return 0;
21 }
22
23 /* 退出函数 */
24 static void hello_exit(void)
25 {
26     printk(KERN_ALERT "hello_exit() start\n");
27 }
28
29 /* 注册到内核 */
30 module_init(hello_init);
31 module_exit(hello_exit);

比上一个文件,就增加了11,12,18行。注意第12行的charp,是内核的字符指针。

编译后,传参插入,dmesg查看信息:

插入的参数msg跟在模块后面即可。

五.模块间函数调用                

模块的函数导出到符号表才可以供其他函数使用,需要用到宏:

1 EXPORT_SYMBOL(sym)

该宏在include/linux/export.h里面。

既然模块间函数调用,我们要编写2个模块。

文件一:kernel_fun.h

1 #ifndef KERNEL_FUN_H
2 #define KERNEL_FUN_H
3
4 void fun(void);
5
6 #endif

文件二,要导出的模块文件:kernel_fun.c

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 #include <linux/export.h>
 5
 6 #include "kernel_fun.h"
 7
 8 /* 以下4个宏分别是许可证,作者,模块描述,模块版本 */
 9 MODULE_LICENSE("Dual BSD/GPL");
10 MODULE_AUTHOR("yuuyuu");
11 MODULE_DESCRIPTION("kernel module hello");
12 MODULE_VERSION("1.0");
13
14 /* 入口函数 */
15 static int fun_init(void)
16 {
17     printk(KERN_ALERT "fun_init() start\n");
18
19     return 0;
20 }
21
22 void fun()
23 {
24     printk(KERN_ALERT "fun() is called\n");
25 }
26
27 /* 退出函数 */
28 static void fun_exit(void)
29 {
30     printk(KERN_ALERT "fun_exit() start\n");
31 }
32
33 /* 注册到内核 */
34 module_init(fun_init);
35 module_exit(fun_exit);
36
37 /* 导出符号表 */
38 EXPORT_SYMBOL(fun);

最后一行就是导出到符号表。

文件三,要调用模块文件二的函数:kernel_mod.c

 1 #include <linux/init.h>
 2 #include <linux/module.h>
 3 #include <linux/kernel.h>
 4 #include <linux/export.h>
 5
 6 #include "kernel_fun.h"
 7
 8 /* 以下4个宏分别是许可证,作者,模块描述,模块版本 */
 9 MODULE_LICENSE("Dual BSD/GPL");
10 MODULE_AUTHOR("yuuyuu");
11 MODULE_DESCRIPTION("kernel module hello");
12 MODULE_VERSION("1.0");
13
14 /* 入口函数 */
15 static int mod_init(void)
16 {
17     printk(KERN_ALERT "mod_init() start\n");
18
19     /* 调用fun */
20     fun();
21     return 0;
22 }
23
24 /* 退出函数 */
25 static void mod_exit(void)
26 {
27     printk(KERN_ALERT "mod_exit() start\n");
28 }
29
30 /* 注册到内核 */
31 module_init(mod_init);
32 module_exit(mod_exit);

第20行即是调用其他模块的函数。

这里要编译2个模块,对应的Makefile文件:

1 KERNAL_DIR ?= /lib/modules/$(shell uname -r)/build
2 PWD := $(shell pwd)
3
4 obj-m := kernel_mod.o kernel_fun.o
5
6 default:
7     $(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules

编译好这2个模块后,我们现在来验证。注意,因为kernel_mod依赖kernel_fun,所以我要先插入kernel_fun模块。

卸载模块的时候,我们要先卸载kernel_mod,原因同上。

依次插入kernel_fun,查看它的符号表,然后插入kernel_mod,查看dmesg:

可以看到kernel_fun的fun()被kernle_mod调用了。

时间: 2024-10-29 19:12:25

初探linux内核编程,参数传递以及模块间函数调用的相关文章

【转】初探linux内核编程,参数传递以及模块间函数调用

http://www.cnblogs.com/yuuyuu/p/5119891.html ZC: 疑问,最后的 模块kernel_mod 调用 模块kernel_fun的函数fun,是成功的OK的.但是 模块kernel_mod 怎么就知道 它调用的就是 模块kernel_fun的fun函数?如果 又有一个 模块kernel_fun01它也导出了fun函数,此时 模块kernel_mod调用fun的话调用的是哪一个模块的fun函数? (ZC: 测试了一下,两个模块 有相同的导出函数的话,在 加载

解析Linux内核的基本的模块管理与时间管理操作---超时处理【转】

转自:http://www.jb51.net/article/79960.htm 这篇文章主要介绍了Linux内核的基本的模块管理与时间管理操作,包括模块加载卸载函数的使用和定时器的用法等知识,需要的朋友可以参考下 内核模块管理Linux设备驱动会以内核模块的形式出现,因此学会编写Linux内核模块编程是学习linux设备驱动的先决条件. Linux内核的整体结构非常庞大,其包含的组件非常多.我们把需要的功能都编译到linux内核,以模块方式扩展内核功能. 先来看下最简单的内核模块 ? 1 2

linux内核编程

这些天在学习linux内核编程,就在这里小小的show以下. 首先编写如下的linux代码.并命名为hello.c 这里你应该注意亮点: 第一.linux内核编程,不同于普通的用户态下的编程:有一个入口的main函数:这里的"main"函数是module_init();同时还有一个善后处理的函数:module_exit(). 第二.linux内核编程在编译的时候,不同于用户态下的编程:可以直接使用gcc编译器编译链接,就能够成为可执行的:而是需要编写一个Makefile文件,不是mak

Linux内核编程-0:来自内核的 HelloWorld

Linux内核编程一直是我很想掌握的一个技能.如果问我为什么,我也说不上来. 也许是希望有一天自己的ID也出现在内核开发组的邮件列表里?或是内核发行文件的CREDITS文件上? 也许是吧.其实更多的,可能是对于底层的崇拜,以及对于内核的求索精神. 想到操作系统的繁杂,想到软件系统之间的衔接,内心觉得精妙的同时,更是深深的迷恋. 所以从这篇文章开始,我要真正的走进Linux内核里了,让代码指引我,去奇妙的世界一探究竟. 在这篇文章中,一起来对内核说Hello World. 本次的编程环境: Cen

Linux内核编程:Linux2.6内核源码解析_进程遍历 &nbsp; &nbsp; &nbsp; &nbsp;

/*     *File    : test.c   *Author  : DavidLin        *Date    : 2014-12-07pm        *Email   : [email protected] or [email protected]        *world   : the city of SZ, in China        *Ver     : 000.000.001        *history :     editor      time    

Linux 内核编程 or 内核模块编程的文件读写与信号传输问题

Linux内核编程时,内核代码执行只能直接访问内存上的数据,硬盘上的文件系统必须通过间接的方式才能被内核读写.一般内核操作文件读写的方式有三种:1.通过/proc/文件作为桥梁完成硬盘文件系统与内核的交互:2.通过ioctl方式实现交互:3.直接利用虚拟文件系统的函数vfs_read().vfs_write()读写文件.三种方式的具体实现方法网上有很多详细教程,可以参考.这里对三种方法做出比较. proc机制是一种很老的文件读写方式,通用性好,实现也算成熟,使用时需要自己实现内核上层的读写函数,

Linux内核编程规范与代码风格

source: https://www.kernel.org/doc/html/latest/process/coding-style.html translated by trav, [email protected] 这是一篇阐述Linux内核编程代码风格的文档,译者以学习为目的进行翻译. 1 缩进 Tab的宽度是八个字符,因此缩进的宽度也是八个字符.有些异教徒想让缩进变成四个字符,甚至是两个字符的宽度,这些人和那些把 PI 定义为 3 的人是一个路子的. 注意:缩进的全部意义在于清晰地定义

linux内核编程入门--系统调用监控文件访问

参考的资料: hello world   https://www.cnblogs.com/bitor/p/9608725.html linux内核监控模块——系统调用的截获  https://www.cnblogs.com/lxw315/p/4773566.html 实现: 实验目的: 内核模块的编写:完成一个Linux/Windows内核/驱动模块的编写, 能够实现对文件访问的监控.或者对键盘设备.USB设备.网络设备. 蓝牙设备等的监控. 实验内容: 通过linux内核模块编程,写一个模块使

[linux内核]linux内核编程规范

1,__attrubte__关键字的作用 点击打开链接 __attrubte__ ((packed)) 的作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐.struct str_struct{        __u8    a;        __u8    b;        __u8    c;        __u16   d;} __attribute__ ((packed));以上结构体的大小为5/*  当用到typedef时,要特别注意__attribut