Linux驱动—内核模块基本使用

Linux驱动—内核模块基本使用

  • fs4412内核模块加载
  • 怎样编写makefile文件
  • 内核模块工具(加载insmod,卸载rmod,查看信息modinfo)
    • 加载模块insmod
    • 模块信息 modinfo
    • 模块卸载

fs4412内核模块加载

这里我用的是华清远见的教材,所以用的内核是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中的交叉编译的位置
Linux驱动—内核模块基本使用_第1张图片
我这里是

CROSS_COMPILE ?= /usr/local/arm/gcc-4.6.4/bin/arm-none-linux-gnueabi-

然后make可以编译了
在这里插入图片描述
你看这里就生成了hello.o

如果吗make出错的话,显示init不存在
那么可以把上面的
#include
改成
#include
因为我看其他内核编码的头文件是这样的

这个ko文件就是咱们要的模块文件
在这里插入图片描述
Linux驱动—内核模块基本使用_第2张图片

那么如果改makefile改成 obj-y
编译到内核
那么它会生成一个zImage
hello.c进入到这里面
在这里插入图片描述
file drivers/char/hello.ko
在这里插入图片描述
这样可以看到这个模块文件的信息
它是个ARM的模块,32位 ELF等
需要在arm开发板上编译

把模块拷贝到source/rootfs目录下
在这里插入图片描述

source/rootfs是开发板的文件系统

已经开发板连接串口
这里用的是三星的exy4412
Linux驱动—内核模块基本使用_第3张图片
Linux驱动—内核模块基本使用_第4张图片

它不是个可执行文件
在这里插入图片描述
加入到内核中运行

插入模块insmod

insmod hello.ko

在这里插入图片描述
前二行不是咱们写的
最后一行才是咱们的结果

这里出现这样是因为加载了vser模块而导致内核被污染,并且因此禁止了锁的调试功能。

GPL协议:获取修改linux内核后需要发布

那么在代码中我们可以这样接收协议:

MODULE_LICENSE("GPL");

MODULE_LICENSE是一个宏,里面的参数是一个字符串,代表的是许可证协议
可以是GPL 、GPL V2 等
详情可见module.h头文件

如果要调用卸载模块
需要输入命令

rmod hello.ko

怎样编写makefile文件

比如上面的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试试

Linux驱动—内核模块基本使用_第5张图片
第一次解释该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_PATHlib/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 

编译完成后文件如下
Linux驱动—内核模块基本使用_第6张图片
编译完成后,在当前目录下会生成一个.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 是内核源码的版本,视内核源码版本的不同而不同)。

内核模块工具(加载insmod,卸载rmod,查看信息modinfo)

加载模块insmod

加载指定目录下的一个.ko文件到内核。

这里先说明下linux的内核在lib/modules
Linux驱动—内核模块基本使用_第7张图片

还是上面的那个例子:

insmod vser.ko
insmod /lib/modules/4.15.0-46-generic/extra/vser.ko 

Linux驱动—内核模块基本使用_第8张图片

模块加载成功后,使用dmesg命令
Linux驱动—内核模块基本使用_第9张图片
这样就调用了模块的初始化函数
就是module_init 函数
打印出吗modoule init字符

除了insmod外,还有个更智能的modprobe:自动加载模块到内核

推荐使用这个命令,但前提条件是模块要执行安装操作,在运行该命令前最好执行一次depmod来更新模块的依赖信息
(注意:modprobe不指定路径及后缀)

depmod
modprobe vser

跟上面结果一样

模块信息 modinfo

查看模块信息

在模块安装了并运行了depmod命令更新后,可以不指定路径和后缀,来查看某一特定的.ko的模块信息

实例:
Linux驱动—内核模块基本使用_第10张图片
它的路径filename等等信息

模块卸载

其中cleanup module 是卸载模块时候定义的卸载函数

rmod vser
dmesg

有时候不是rmod是rmmod
Linux驱动—内核模块基本使用_第11张图片
执行dmesg执行模块内核,
Linux驱动—内核模块基本使用_第12张图片

你可能感兴趣的:(linux驱动,linux驱动)