这里我用的是华清远见的教材,所以用的内核是fs4412的内核
linux-3.14-fs4412下载
视频地址
这里先进入/drivers/char
写个hello.c的程序
#include
#include
/*内核驱动函数没有主函数*/
//模块加载函数
int hello_init(void)
{
printk("Hello,Kernel!\n");
return 0; //0表示成功,!0表示失败
}
//模块卸载函数
void hello_exit(void)
{
printk("Goodbye,Kernel!\n");
}
//告诉内核哪个是加载,哪个是卸载函数
//声明一下
module_init(hello_init); //声明模块加载函数
module_exit(hello_exit); //声明模块卸载函数
//module_init是二个宏,是为了声明二个函数
但是现在不能直接用gcc编译。
它需要放在内核makefile中编译
打开Makefile
obj-y 编译到内核
obj-m 编译成模块
这里编译个模块
obj-m +=hello.o
=是赋值,这样会把之前的模块都覆盖了
所以是附加,
以后一般都是+=或者-=
这样就可以编译模块了
这里的Makefile不是完整的
我们要回到fs4412根目录的Makefile进行编译make、
这里如果出现什么arm-none没有,或者编号127的错误之类的
或许是你交叉编译环境没配置好
配置Ubuntu交叉编译环境
arm-none-linux-gnueabi-gcc 未找到命令
还需要修改Makefile中的交叉编译的位置
我这里是
CROSS_COMPILE ?= /usr/local/arm/gcc-4.6.4/bin/arm-none-linux-gnueabi-
如果吗make出错的话,显示init不存在
那么可以把上面的
#include
改成
#include
因为我看其他内核编码的头文件是这样的
那么如果改makefile改成 obj-y
编译到内核
那么它会生成一个zImage
hello.c进入到这里面
file drivers/char/hello.ko
这样可以看到这个模块文件的信息
它是个ARM的模块,32位 ELF等
需要在arm开发板上编译
把模块拷贝到source/rootfs目录下
source/rootfs是开发板的文件系统
插入模块insmod
insmod hello.ko
这里出现这样是因为加载了vser模块而导致内核被污染,并且因此禁止了锁的调试功能。
GPL协议:获取修改linux内核后需要发布
那么在代码中我们可以这样接收协议:
MODULE_LICENSE("GPL");
MODULE_LICENSE是
一个宏,里面的参数是一个字符串,代表的是许可证协议
可以是GPL 、GPL V2 等
详情可见module.h头文件
如果要调用卸载模块
需要输入命令
rmod hello.ko
比如上面的hello.c需要自己编写个makefile文件也可以
#Makefile
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?=/home/lyx/chen/linux/linux-3.14-fs4412
ROOTFS ?=/nfs/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
代码最外层的ifeq...else..endif
语句分为了二部分
代码1·16是在KERNELRELEASE
变量值为空的情况下执行的代码,
else
代码17·21则相反
KERNELRELEASE
是一个内核源码树中顶层Makefile文件中定义的一个变量
并对其赋值为Linux内核源码的版本,会通过export导出在子Makefile使用
obj-m编译的是模块,生成ko文件
接下来make试试
第一次解释该Makefile时候,代码第一行的变量KERNELRELEASE
没有被定义,也没有被赋值。所以ifeq=空那个成立
#Makefile
ifeq ($(KERNELRELEASE),)
ifeq ($(ARCH),arm)
KERNELDIR ?=/home/lyx/chen/linux/linux-3.14-fs4412
ROOTFS ?=/nfs/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
这部分包含了内核源码目录KERNELDIR
变量的定义,并且根据编译平台ARM下的驱动是 ARM还是PC上运行的驱动
对该变量进行了不同的赋值
这样就可以在命令行中使用ARCH
进行赋值选择编译哪个平台下的驱动
其中,/home/lyx/chen/linux/linux-3.14-fs4412是内核源码目录
如果是编译的ARM平台下的驱动,则还需要对ROOTFS进行赋值和定义。
ROOTFS ?=/nfs/rootfs
接下来是对当前模块所在目录的变量PWD
定义
endif
PWD := $(shell pwd)
Makefile文件中的第一个目标modules
为默认目标
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD)
$(MAKE) -》执行make命令
这句话是进入到内核源码目录(由-C $(KERNELDIR)指定),编译在内核源码树外的一个目录,即M=(PWD) 指定的模块(由最后的modules指定)
相当于make 位置 模块
如果再次进入目录,再次make,这时候KERNELRELEASE
有值,执行else
else
obj-m := hello.o
这样说明这个是编译成模块
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) INSTALL_MOD_PATH=$(ROOTFS) modules_install
modules-install:
表示把编译之后的模块安装到指定目录,
安装的目录是INSTALL_MOD_PATH
即lib/modules/$(KERNELRELEASE)
目录下.
关于更详细的用make -help 命令查看
clean:
clean:
rm -rf *.o *.ko .*.cmd *.mod.* modules.order Module.symvers .tmp_versions
clean目标用于清除make生成的中间文件 .0 .ko .cmd .mod .tmp等
模块的源文件和Makefile文件编写完成后,执行make进行编译和安装。
如果权限不够用root
make
make modules_install
如果要编译ARM目标上的驱动,则使用下面命令:
make ARCH=arm
make ARCH=arm modules_install
编译完成后文件如下
编译完成后,在当前目录下会生成一个.ko的文件。安装成功后,如果是PC平台,则会把生成的.ko文件复制到/lib/modules/3.13.0-32-generic/extra
目录下(3.13.0-32-generic)
是内核源码的版本,视内核源码版本的不同而不同);
如果是目标板平台,则会把生成的.ko文件复制到nfs/rootfs/lib/modules/3.14.25/extra
目录下(3.14.25 是内核源码的版本,视内核源码版本的不同而不同)。
加载指定目录下的一个.ko文件到内核。
还是上面的那个例子:
insmod vser.ko
insmod /lib/modules/4.15.0-46-generic/extra/vser.ko
模块加载成功后,使用dmesg
命令
这样就调用了模块的初始化函数
就是module_init 函数
打印出吗modoule init字符
除了insmod外,还有个更智能的modprobe:自动加载模块到内核
推荐使用这个命令,但前提条件是模块要执行安装操作
,在运行该命令前最好执行一次depmod
来更新模块的依赖信息
(注意:modprobe不指定路径及后缀)
depmod
modprobe vser
跟上面结果一样
查看模块信息
在模块安装了并运行了depmod命令更新后,可以不指定路径和后缀,来查看某一特定的.ko的模块信息
其中cleanup module 是卸载模块时候定义的卸载函数
rmod vser
dmesg