每个进程在启动的时候会做两件事 ,虚拟出来一个4G大小的内存空间,会打开三个标准的文件描述符。
内存空间分为两部分:
0-3G 用户空间 之前系统阶段所写的代码运行在用户空间。
3G-4G 内核空间 驱动阶段所写的代码运行在内核空间。
内核可以通过接口直接访问硬件,用户一般是不能直接操作硬件的,内核层代码是由一个新的框架的,这个框架就是我们今天介绍的。
一、模块化编程
1.内核层代码的框架
内核层的代码必须要包含的头文件
#include<linux/kernel.h>
#include<linux/module.h>
内核代码没有main函数的 内核代码的入口函数
加载函数
static int __init myled_init(void)
{
return 0;
}
卸载函数
static void __exit myled_exit(void)
{
}
有了头文件和加载卸载函数之后并不能马上使用
要想使用内核代码,我们还需要加上对应的声明
声明 加载函数
module_init(myled_init)
声明 卸载函数
module_exit(myled_exit)
声明 遵循开源协议
MODULE_LICENSE(“GPL”)
2.内核层代码示例
在内核层要想打印提示信息可以使用 printk 跟printf用法是一样
#include<linux/kernel.h>
#include<linux/module.h>
static int __init myled_init(void)
{
printk("this is my first driver code\n");
return 0;
}
static void __exit myled_exit(void)
{
printk("i am myled exit\n");
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
3.内核层代码的编译
要想编译内核的源码 我们需要了解如下两点
1.内核的源码运行在arm架构的开发板上
我们写代码时候用的是 x86架构,编译的时候就需要借助于交叉编译工具。
2.编译后的代码是运行在linux3.5的内核之下的
编译的时候就要借助于linux3.5的内核
3.了解makefile
obj-m += led.o #表示我们要新增的模块对应.o文件
KDIR:=/home/duan/work/20230620202/kernel/linux-3.5 #依赖的内核的路径
all:
make -C $(KDIR) M=$(PWD) modules #
clean:
rm -rf *.ko *.o *.symvers *.order *.mod.c
$(MAKE) -C $(KDIR) M=$(PWD) modules
1) -C $(KDIR)
表示在$(KERNELDIR)目录下执行make命令。
2) M=$(PWD)
表示包含$(PWD)下的Makefile文件。
3) modules
表示模块编译。
4.编译后的代码的使用
通过make的命令 最终生成了一个文件 led.ko,这个文件就是内核的模块,可以用来加载和卸载。加载的时候就会调用到加载函数,卸载的时候就会调用到卸载函数,模块的加载和卸载 就需要借助于两个命令。
insmod :加载模块
rmmod :卸载模块
例如:
insmod led.ko
rmmod led.ko
二、多模块编程
1.多个模块相互调用
被调用的函数所在的模块 要对被调用的函数进行一个声明,告诉内核 整个内核的所有模块都可以调用这个函数,被调用函数所在模块示例代码。以下代码仅用于测试、熟悉多模块间相互调用,熟悉框架,了解代码编写过程,并无实际应用。
led.c文件:
#include<linux/kernel.h>
#include<linux/module.h>
void myfunc(void)
{
printk("i am a led func\n");
}
static int __init myled_init(void)
{
printk("i am led init\n");
return 0;
}
static void __exit myled_exit(void)
{
printk("i am led exit\n");
}
EXPORT_SYMBOL(myfunc);
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
调用函数的代码示例
key.c文件
#include<linux/kernel.h>
#include<linux/module.h>
void myfunc(void);
static int __init mykey_init(void)
{
printk("i am key init\n");
myfunc();
return 0;
}
static void __exit mykey_exit(void)
{
printk("i am key exit\n");
}
module_init(mykey_init);
module_exit(mykey_exit);
MODULE_LICENSE("GPL");
加载模块的时候 要先加载被调用的函数所在的模块 例如 加载led.ko
卸载的时候 要先卸载调用其他模块的函数的模块 例如 先卸载key.ko
2.多个文件编译成一个模块
这种情况就类似于C语言里的一个入口函数调用别的文件里函数。
1.c:
#include<linux/kernel.h>
#include<linux/module.h>
void func(void);
void myfunc(void);
static int __init myled_init(void)
{
printk("i am my led init\n");
myfunc();
func();
return 0;
}
static void __exit myled_exit(void)
{
printk("i am my led exit\n");
}
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
2.c
#include<linux/kernel.h>
#include<linux/module.h>
void myfunc(void)
{
printk("i am my func\n");
}
3.c
#include<linux/kernel.h>
#include<linux/module.h>
void func(void)
{
printk("i am func\n");
}
Makefile:
obj-m += led.o
led-objs := 1.o 2.o 3.o
KDIR:=/home/duan/work/20230620202/kernel/linux-3.5 #依赖的内核的路径
all:
make -C $(KDIR) M=$(PWD) modules
clean:
rm -rf *.ko *.o *.symvers *.order *.mod.c
调用结果:
3.模块化传参
类似于C语言的主函数传参
led.c:
#include<linux/kernel.h>
#include<linux/module.h>
int tmp;
static int __init myled_init(void)
{
printk("tmp=%d\n",tmp);
return 0;
}
static void __exit myled_exit(void)
{
}
module_param(tmp,int,0744);
module_init(myled_init);
module_exit(myled_exit);
MODULE_LICENSE("GPL");
模块传参调用过程及结果: