声明
以下都是我刚开始看驱动视频的个人强行解读,如果有误请指出,共同进步。
本节目标
- 了解什么是模块
- 加载、卸载模块的时候打印一段话
- 简单的Makefile编译
简述
对于剪裁linux内核,其实就是选择是否把某一部分代码编译进内核,编译进去了,我们在启动linux的时候,就可以直接用。那么如果为了减小编译出来的linux内核,不编译进去,想用的时候怎么办?
linux的做法是:模块。
我们要用,就把模块插入linux内核,不用了,就把他卸载掉。linux把必要的编译进去,没必要的可以编译成模块,然后我们要用就插入模块。方便。
下面开始说编译一个最简单的模块。
对于编写模块的C程序,不是像学C语言一样,上来先int main()。我们从头文件开始一步一步看。
一、头文件
所有的头文件都在迅为的linux源码下,相对路径:Android4.0/iTop4412_Kernel_3.0/include
直接先上必要的头文件
// linux要加载、卸载模块,需要包含此头文件
#include <linux/init.h>
// 加墨、卸载模块相关的宏定义、库函数需要包含此头文件
#include <linux/module.h>
在此我试图查看源码看看是如何实现的,打开看了一下我决定关掉先学怎么用…
linux加载、卸载模块要分别调用两个来自module.h的宏定义(是什么后面讲),才能完成加载和卸载的过程。
二、GPL协议
Linux是开源的,所以我们要遵循开源协议,在模块的开头附上这些信息,如果不写会无法编译(写了才表示你统一遵循协议)
// 声明GPL许可证,总之免费使用,但是要开源
MODULE_LICENSE("Dual BSD/GPL");
// 这里是作者
MODULE_AUTHOR("MrYang");
以上写法表示我们遵循BSD和GPL协议,至于代表什么意义,可以自己去查…作者就填自己。
三、模块的init和exit
在讲头文件的时候我们有说,当我们把模块插入内核的时候,会调用一个宏定义,卸载也是一样,我们先上这两个宏定义。
// 函数名很清楚了...
module_init(加载函数);
module_exit(卸载函数);
此处我没有上源码,而是直接写的函数,一方面是打开源码看确实看不懂,二是刚学也没必要深究,先学会咋用把。
名字其实已经很清楚了,加载模块的时候调用module_init(),这是一个宏定义,参数是一个自定义的int类型的函数,linux调用了module_init(),然后就找到了我们定义的函数进行执行。卸载的参数则是一个void类型的函数。
四、定义init和exit需要调用的函数
// 用于init时调用的函数
static int mryang_init(void)
{
printk(KERN_EMERG "HELLO WORLD enter!\n");
return 0;
}
// 用于exit时调用的函数
static void mryang_exit(void)
{
printk(KERN_EMERG "HELLO WORLD exit!\n");
}
首先注意到输出函数并非C语言常用的printf,而是printk,k应该是指kernel,输出内容前的KERN_EMERG是指控制内核log输出级别,高级别的输出到屏幕,低级别的保存为日志文件,我们需要打印到屏幕,就给了最高的权限。
我们自定义了两个函数,用了放入module_init()和module_exit()里当作参数。这样,linux加载就会调用module_init()宏定义,然后根据参数调用我们自定义的mryang_init()函数,卸载同理
五、贴出模块内的所有程序(mini_linux_module.c)
#include <linux/init.h>
#include <linux/module.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("MrYang");
static int mryang_init(void)
{
printk(KERN_EMERG "Hello MrYang!\n");
return 0;
}
static void mryang_exit(void)
{
printk(KERN_EMERG "Bye MrYang!\n");
}
module_init(mryang_init);
module_exit(mryang_exit);
编写完了程序,一个问题来了,我们如何编译他?对于模块,才用的是利用Makefile自动化编译来编译他
为什么要用Makefile?假如有很多很多程序的时候,我们难不成一个一个编译吗?所以我们编写一个Makefile,来进行编译。
六、写一个Makefile对程序进行自动化编译
先不管Makefile怎么一步一步写,我们直接上代码
#!/bin/bash
#通知编译器我们要编译模块的哪些源码
#这里是编译itop4412_hello.c这个文件编译成中间文件itop4412_hello.o
obj-m += mini_linux_module.o
#源码目录变量,这里用户需要根据实际情况选择路径
#作者是将Linux的源码拷贝到目录/home/topeet/android4.0下并解压的
KDIR := /home/topeet/android4.0/iTop4412_Kernel_3.0
#当前目录变量
PWD ?= $(shell pwd)
#make命名默认寻找第一个目标
#make -C就是指调用执行的路径
#$(KDIR)Linux源码目录,作者这里指的是/home/topeet/android4.0/iTop4412_Kernel_3.0
#$(PWD)当前目录变量
#modules要执行的操作
all:
make -C $(KDIR) M=$(PWD) modules
#make clean执行的操作是删除后缀为o的文件
clean:
rm -rf *.o
Makefile显然不是我写的!
迅为的说法是,我们没必要抱着Makefile去啃,有些东西大致看看就懂了,真要用了再去研究。
所以这其实可以当作一个模板,因为东西很少,所以看起来比较直观。
obj-m表示编译成模块,后面跟着的是后缀为.o的文件名,你把.c放这跟他同级,Makefile自动帮你编译成.o文件。KDIR就是源码目录,PWD是当前目录,所以你的.c和Makefile放在同一级的文件夹里。
大致看看就行,我们要改的就两个地方,一个是文件叫啥名字,一个是源码的路径
七、编译,加载、查看、卸载等等…
最后俩文件 mini_linux_module.c 和 Makefile 放在同一个文件夹下,然后控制台输入
make
报错会有ERROR,不报错,生成mini_linux_module.ko文件就是最后驱动了。
我们把生成的驱动放到U盘然后插入板子,输入命令挂载U盘到/mnt/disk目录
mount /dev/sda1 /mnt/disk
然后我们进入/mnt/disk目录就可以用命令加载、卸载驱动了!
加载驱动:
insmod mini_linux_module.ko
查看驱动:
lsmod
卸载驱动:
rmmod mini_linux_module
注意 卸载不带.ko
卸载的时候我出现了点小问题,发现卸载失败,后来百度搜的方法是:
- 控制台uname -r 查看内核版本,返回:3.0.15
- 新建文件夹 mkdir /lib/modules/3.0.15,再卸载 就OK了
补充1:原来迅为的pdf里有写,所以说还是要认真看迅为给的资料…
=======================================
不用板子用虚拟机如何完成?
Makefile文件里源码路径要修改为虚拟机的linux内核源码路径
KDIR := /lib/modules/$(shell uname -r)/build
但运行起来发现printk无输出,可以先安装sudo apt install busybox-syslogd,再用命令sudo cat /proc/kmsg,就可以完整显示内核打印的消息了,若要后台命令后面就加个&。
你可以插入模块之后,再用命令查看内核输出的信息,网上的说法是只有串口才能输出printk信息,用SSH无法输出,只能手动查看,网上的解决方法试了几个没啥用,懒得试了…