目录
一、向内核添加新功能
1.1 静态加载法:
1.2 动态加载法:
a、新功能源码与Linux内核源码在同一目录结构下时
b、新功能源码与Linux内核源码不在同一目录结构下时
c、主机ubuntu下使用ko文件
d、开发板Linux下使用ko文件
二、内核模块基础代码解析
Linux内核的插件机制——内核模块
三、内核模块的多源文件编程
四、 内核模块信息宏
一、向内核添加新功能
1.1 静态加载法:
即新功能源码与内核其它代码一起编译进uImage文件内
1. 新功能源码与Linux内核源码在同一目录结构下
在linux-3.14/driver/char/目录下编写myhello.c,文件内容如下:
#include <linux/module.h>
#include <linux/kernel.h>
int __init myhello_init(void)
{
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("myhello is running\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
return 0;
}
void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
MODULE_LICENSE("GPL");
module_init(myhello_init);
module_exit(myhello_exit);
2. 给新功能代码配置Kconfig
cd ~/fs4412/linux-3.14/drivers/char
vim Kconfig
(就是给make menuconfig中添加一个菜单项)
#39行处添加如下内容:
config MY_HELLO
tristate "This is a hello test"
help
This is a test for kernel new function
3. 给新功能代码改写Makefile
#进入myhello.c的同级目录
cd ~/fs4412/linux-3.14/drivers/char
vim Makefile
#拷贝18行,粘贴在下一行,修改成:
obj-$(CONFIG_MY_HELLO) += myhello.o
4. make menuconfig 界面里将新功能对应的那项选择成<*>
cd ~/fs4412/linux-3.14
make menuconfig
#make menuconfig如果出错,一般是两个原因:
#1. libncurses5-dev没安装
#2. 命令行界面太小(太矮或太窄或字体太大了)
选成和内核一起编译
5. make uImage
6. cp arch/arm/boot/uImage ~/tftpboot
7. 启动开发板观察串口终端中的打印信息
1.2 动态加载法:
即新功能源码与内核其它源码不一起编译,而是独立编译成内核的插件(被称为内核模块)文件.ko
a、新功能源码与Linux内核源码在同一目录结构下时
1. 给新功能代码配置Kconfig
2. 给新功能代码改写Makefile
3. make menuconfig 界面里将新功能对应的那项选择成<M>
4. make uImage
5. cp arch/arm/boot/uImage /tftpboot
这个时候启动就没有那些打印了
6. make modules
make modules会在新功能源码的同级目录下生成相应的同名.ko文件
(生成的ko文件只适用于开发板linux)
注意此命令执行前,开发板的内核源码已被编译
b、新功能源码与Linux内核源码不在同一目录结构下时
1. cd ~/Linux_4412
2. mkdir mydrivercode
3. cd mydrivercode
4. cp ../linux-3.14/drivers/char/myhello.c .
5. vim Makefile
添加这个makefile文件
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= /home/book/Linux_4412/kernel/linux-3.14
ROOTFS ?= /home/book/nfs_rootfs
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modulesmodules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules INSTALL_MOD_PATH=$(ROOTFS) modules_installclean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versionselse
obj-m += myhello.o
endif
6. make (生成的ko文件适用于主机ubuntu linux)
/lib/modules/$(shell uname -r)/build
这是ubuntu自己的内核编译体系
7. make ARCH=arm
(生成的ko文件适用于开发板linux,注意此命令执行前,开发板的内核源码已被编译)
c、主机ubuntu下使用ko文件
这里报错了因为我最后一次是用arm编译的
这次就没问题了 ,这个命令是给内核安装模块
lsmod #查看已被插入的内核模块有哪些,显示的是插入内核后的模块名
这样太多了,不好找。可以用下面的命令
这样就能看安装成功与否
这个名字叫模块名和我们的KO文件名是两个。
dmesg #查看内核的打印信息
内核打印和应用打印是分开的不然屏幕太乱了。内核打印信息在一个文件了。运行这个命令就能查看。
sudo rmmod ??? #,此处为插入内核后的模块名,此时将已被插入的内核模块从内核中移除 ----- 相当于卸载插件
一定要加sudo
sudo dmesg -C #清除内核已打印的信息
现在打印信息就是空的了。
d、开发板Linux下使用ko文件
#在串口终端界面开发板Linux命令行下执行
insmod ./???.ko #将内核模块插入正在执行的内核中运行 ----- 相当于安装插件
lsmod #查看已被插入的内核模块有哪些
rmmod ??? #将已被插入的内核模块从内核中移除 ----- 相当于卸载插件
内核随时打印信息,我们可以在串口终端界面随时看到打印信息,不需要dmesg命令查看打印信息
在rm时提示我们需要创建一个文件夹。创建完了以后就能直接删除了。同样删除时会打印我们写的那句话。
二、内核模块基础代码解析
Linux内核的插件机制——内核模块
类似于浏览器、eclipse这些软件的插件开发,Linux提供了一种可以向正在运行的内核中插入新的代码段、在代码段不需要继续运行时也可以从内核中移除的机制,这个可以被插入、移除的代码段被称为内核模块。
主要解决:
1. 单内核扩展性差的缺点
2. 减小内核镜像文件体积,一定程度上节省内存资源
3. 提高开发效率
4. 不能彻底解决稳定性低的缺点:内核模块代码出错可能会导致整个系统崩溃
内核模块的本质:一段隶属于内核的“动态”代码,与其它内核代码是同一个运行实体,共用同一套运行资源,只是存在形式上是独立的。
```c
#include <linux/module.h> //包含内核编程最常用的函数声明,如printk
#include <linux/kernel.h> //包含模块编程相关的宏定义,如:MODULE_LICENSE
/*该函数在模块被插入进内核时调用,主要作用为新功能做好预备工作
被称为模块的入口函数
__init的作用 :
1. 一个宏,展开后为:__attribute__ ((__section__ (".init.text"))) 实际是gcc的一个特殊链接标记
2. 指示链接器将该函数放置在 .init.text区段
3. 在模块插入时方便内核从ko文件指定位置读取入口函数的指令到特定内存位置
*/
int __init myhello_init(void)
{
/*内核是裸机程序,不可以调用C库中printf函数来打印程序信息,
Linux内核源码自身实现了一个用法与printf差不多的函数,命名为printk (k-kernel)
printk不支持浮点数打印*/
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("myhello is running\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
return 0;
}
/*该函数在模块从内核中被移除时调用,主要作用做些init函数的反操作
被称为模块的出口函数
__exit的作用:
1.一个宏,展开后为:__attribute__ ((__section__ (".exit.text"))) 实际也是gcc的一个特殊链接标记
2.指示链接器将该函数放置在 .exit.text区段
3.在模块插入时方便内核从ko文件指定位置读取出口函数的指令到另一个特定内存位置
*/
void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
/*
MODULE_LICENSE(字符串常量);
字符串常量内容为源码的许可证协议 可以是"GPL" "GPL v2" "GPL and additional rights" "Dual BSD/GPL" "Dual MIT/GPL" "Dual MPL/GPL"等, "GPL"最常用
其本质也是一个宏,宏体也是一个特殊链接标记,指示链接器在ko文件指定位置说明本模块源码遵循的许可证
在模块插入到内核时,内核会检查新模块的许可证是不是也遵循GPL协议,如果发现不遵循GPL,则在插入模块时打印抱怨信息:
myhello:module license 'unspecified' taints kernel
Disabling lock debugging due to kernel taint
也会导致新模块没法使用一些内核其它模块提供的高级功能
*/
MODULE_LICENSE("GPL");
/*
module_init 宏
1. 用法:module_init(模块入口函数名)
2. 动态加载模块,对应函数被调用
3. 静态加载模块,内核启动过程中对应函数被调用
4. 对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.initcall段),方便系统初始化统一调用。
5. 对于动态加载的模块,由于内核模块的默认入口函数名是init_module,用该宏可以给对应模块入口函数起别名
*/
module_init(myhello_init);
/*
module_exit宏
1.用法:module_exit(模块出口函数名)
2.动态加载的模块在卸载时,对应函数被调用
3.静态加载的模块可以认为在系统退出时,对应函数被调用,实际上对应函数被忽略
4.对于静态加载的模块其本质是定义一个全局函数指针,并将其赋值为指定函数,链接时将地址放到特殊区段(.exitcall段),方便系统必要时统一调用,实际上该宏在静态加载时没有意义,因为静态编译的驱动无法卸载。
5.对于动态加载的模块,由于内核模块的默认出口函数名是cleanup_module,用该宏可以给对应模块出口函数起别名
*/
module_exit(myhello_exit);
```
__init
c——i——s——o——ko
程序从c到ko要经历四个步骤这个宏就是为了链接。
在使用insmod命令插入时内核会将被标记的这部分拿过去放到指定的内存空间里方便后续进行调用。
__exit
一般就是和__init相反的操作我们叫做出口函数
模块三要素:入口函数 出口函数 MODULE__LICENSE
百度百科-验证
三、内核模块的多源文件编程
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?= 目标板linux内核源码顶层目录的绝对路径
ROOTFS ?= 目标板根文件系统顶层目录的绝对路径
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
endif
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
else
obj-m += hello.o
endif
Makefile中:
obj-m用来指定模块名,注意模块名加.o而不是.ko
可以用 模块名-objs 变量来指定编译到ko中的所有.o文件名
(每个同名的.c文件对应的.o目标文件)
hello-obj = f1.o f2.o f3.o
一个目录下的Makefile可以编译多个模块:
添加:obj-m += 下一个模块名.o
上面是hello模块下面是xyz模块。多个模块可以共用一个makefile
+= 追加
四、 内核模块信息宏
MODULE_AUTHOR(字符串常量); //字符串常量内容为模块作者说明
MODULE_DESCRIPTION(字符串常量); //字符串常量内容为模块功能说明
MODULE_ALIAS(字符串常量); //字符串常量内容为模块别名
这些宏用来描述一些当前模块的信息,可选宏
这些宏的本质是定义static字符数组用于存放指定字符串内容,这些字符串内容链接时存放在.modinfo字段,可以用modinfo命令来查看这些模块信息,用法:
modinfo 模块文件名
#include <linux/module.h>
#include <linux/kernel.h>
int __init myhello_init(void)
{
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("#####################################################\n");
printk("myhello is running\n");
printk("#####################################################\n");
printk("#####################################################\n");
return 0;
}
void __exit myhello_exit(void)
{
printk("myhello will exit\n");
}
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Tianyu Xin");
MODULE_DESCRIPTION("This is a simple test");
MODULE_ALIAS("Hi");
module_init(myhello_init);
module_exit(myhello_exit);
文章来源:https://www.toymoban.com/news/detail-494092.html
企业做项目时用。自己玩的时候加不加都行。
文章来源地址https://www.toymoban.com/news/detail-494092.html
到了这里,关于内核模块(编译方法)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!