环境介绍
最近在学习ARM Linux驱动开发,由于手头的ARM开发板版本太老,和教程里用的驱动内核相差太多,于式打算用树莓派3B来进行学习。 要用树莓派开发内核,就必须先让树莓派跑上自己编译的内核,否则insmod会执行失败教程介绍了如何交叉编译树莓派3B内核并运行ko模块,本教程不再使用虚拟机,而是使用win10的WSL搭配vscode进行开发 硬件:WIN10电脑 、树莓派
WSL安装
参考:win10安装WSL 注意:我安装的是Ubuntu18.4,使用的是WSL1,WSL1完全够用。当然WSL2也是可以的。
安装VSCode
很简单,下载安装就行了,然后安装Remote WSL插件,喜欢折腾的朋友可以再装几个插件。 主要是配置方面需要介绍,文章后面会进行补充。
连接树莓派
连接树莓的方法多种多样,有树莓派的朋友应该都会吧。我采用的是ssh的方式,树莓派通过wifi连接路由器,电脑也是通过wifi连接同一个路由器。
- 树莓派系统烧录: 资源下载
- 连接SSH 树莓派默认是关闭SSH的,打开方法:烧录好后在boot目录下新建一个SSH文件
为树莓派安装sz、rz
开发过程中需要频繁地在PC和树莓派间传输文件,传输方法也很多,这里使用sz和rz树莓派安装sz,rz 使用方法:
#这些命令都在树莓派执行
sz ~/test.txt #将树莓派的文件发送到PC
rz -y #将PC的文件拷贝到当前目录,-y表示覆盖已存在的文件
下载内核源码与工具
- 查看当前树莓派内核版本uanme -a
- 下载对应版本的内核,比如我的是4.19.97内核源码下载地址 注意:这里有很多版本,一定要选对版本,否则编译后可能开不了机
- 如何找到自己版本内核呢?可以看树莓派官网的raspberrypi OS的release note 点这里打开 比如我的是4.19.79,可以看出日期是2020-02-13和2020-02-05
- 下载对应的代码即可
- 下载前可以看下Makefile的log是不是对的
- 下载交叉编译工具 下载地址
配置交叉编译环境
- 打开wsl,把内核源码和工具拷贝到wsl中
#WSL可以直接访问windows的磁盘,/mnt/d 代表D盘
cp /mnt/d/Download/linux-raspberrypi-kernel_1.20200205-1.zip ./
cp /mnt/d/Download/infinitystar-raspberrypi-tools-master.zip ./
#解压
mkdir linux-4.19
unzip -d linux-4.19 linux-raspberrypi-kernel_1.20200205-1.zip
mkdir tools
unzip -d tools infinitystar-raspberrypi-tools-master.zip
- 配置交叉编译环境~/tools/raspberrypi-tools/arm-bcm2708目录下有几个交叉编译工具,我们使用的WSL是64位的,所以使用gcc-linaro-arm-linux-gnueabihf-raspbian-x64
vi ~/.bashrc
#再最后一行添加如下内容
export PATH=$PATH:/root/tools/raspberrypi-tools/arm-bcm2708/gcc-linaro-arm-linux-gnueabihf-raspbian-x64/bin
#重新运行一遍.bashrc,使配置生效
source ~/.bashrc
#查看是否配置成功
arm-linux-gnueabihf-gcc -v
- 下载一些依赖( 这是我从其它教程里抄的,不一定全,大家编译出错以后百度一下,一般很容易搜到少了哪个依赖 )
sudo apt install git bc bison flex libssl-dev make libc6-dev libncurses5-dev
编译内核
- 进入内核源码目录,修改Makefile
这一步的作用:我们在make后需要带上两个参数 如:make ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- ,这两个参数最后传递给Makefile,如果我们在Makefile中直接写死,就可以直接输入make就行了。
2. 从树莓派获取config文件
内核编译执行make menuconfig时会默认读取.config文件,里面配置了内核会编译哪些模块,不编译哪些模块
#在树莓派上执行
sudo modprobe configs
zcat /proc/config.gz > board.config
sz board.config
#在WSL上执行
cd ~/linux-4.19/linux-raspberrypi-kernel_1.20200205-1/
cp /mnt/d/xxx/board.config ./
cp board.config .config
- 执行make menuconfig并重新保存.config
虽然已经有了.config文件,但最好还是执行下make menuconfig,然后重新保存一下(原因我也不清楚。。。)
- 这一步和下一步一般第一次编译都会出错,原因是系统有些东西没有安装,百度很容易找到答案
- 编译内核
KERNEL=kernel7
make -j4
- 拷贝内核到树莓派
#生成树莓派可用的kernel镜像
./scripts/mkknlimg arch/arm/boot/zImage kernel7.img
#把kernel7.img拷贝到树莓派的/boot下即可
- 树莓派重启后uname -a看下日期,如果没问题的话应该已经是自己编译的内核了
用VSCode编译内核驱动
- 先写个驱动小demo
- Makefile文件
KERNELDIR := /root/linux-4.19/linux-raspberrypi-kernel_1.20200205-1
CURRENT_PATH := $(shell pwd)
obj-m := led.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
- led.c文件
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/cdev.h>
#define MYDEV_NAME "LedTest"
struct led_dev
{
struct cdev dev;
struct class *class;
struct device *device;
dev_t devid;
int major;
int minor;
};
static int led_open(struct inode *inode, struct file *file)
{
return 0;
}
static int led_release(struct inode *inode, struct file *filp)
{
return 0;
}
ssize_t led_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
ssize_t led_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
return 0;
}
static const struct file_operations led_fops = {
.read = led_read,
.write = led_write,
.open = led_open,
.release = led_release,
.owner = THIS_MODULE,
};
struct led_dev my_dev;
static int __init led_init(void)
{
int ret = 0;
printk("led_init\n");
/* 设备号分配 */
if(my_dev.major){ //给定主设备号
my_dev.devid = MKDEV(my_dev.major,0);
ret = register_chrdev_region(my_dev.devid,1,MYDEV_NAME);
}else{ //没有指定主设备号
ret = alloc_chrdev_region(&my_dev.devid,0,1,MYDEV_NAME);
}
if(ret < 0){
printk("register char dev failed!\n");
return -1;
}
my_dev.major = MAJOR(my_dev.devid);
my_dev.minor = MINOR(my_dev.devid);
printk("register chardev major:%d minor:%d\n", my_dev.major, my_dev.minor);
/* 创建字符设备 */
cdev_init(&my_dev.dev, &led_fops);
ret = cdev_add(&my_dev.dev, my_dev.devid, 1);
if(ret){
unregister_chrdev_region(my_dev.devid, 1);
}
/* 创建设备节点 */
my_dev.class = class_create(THIS_MODULE, MYDEV_NAME);
if(IS_ERR(my_dev.class)){
return PTR_ERR(my_dev.class);
}
my_dev.device = device_create(my_dev.class,NULL,my_dev.devid,NULL,MYDEV_NAME);
if(IS_ERR(my_dev.device)){
return PTR_ERR(my_dev.device);
}
return 0;
}
static void __exit led_exit(void)
{
printk("led_exit\n");
cdev_del(&my_dev.dev);
unregister_chrdev_region(my_dev.devid, 1);
device_destroy(my_dev.class,my_dev.devid);
class_destroy(my_dev.class);
return;
}
module_init(led_init);
module_exit(led_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("LIUJIANHUA");
- 打开VSCode,点击左下角图标,选择WSL
- 用VSCode打开自己驱动的文件夹 WSL系统路径:C:\Users\liujh\AppData\Local\Packages\CanonicalGroupLimited.UbuntuonWindows_79rhkp1fndgsc\LocalState\rootfs\root
- VSCode配置 打开后会显示很多头文件找不到,编写代码的时候也不会有补全,使用下面的配置方法可以解决大部分问题。 文件:驱动代码路径/.vscode/Settings.json
{
"editor.insertSpaces": false,
"editor.renderWhitespace": "all",
"C_Cpp.default.includePath": [
"/root/linux-4.19/linux-raspberrypi-kernel_1.20200205-1/include",
"/root/linux-4.19/linux-raspberrypi-kernel_1.20200205-1/include/uapi",
"/root/linux-4.19/linux-raspberrypi-kernel_1.20200205-1/arch/arm/include",
"/root/linux-4.19/linux-raspberrypi-kernel_1.20200205-1/arch/arm/include/generated",
],
"C_Cpp.default.defines": [
"__KERNEL__",
]
}
另外还可以直接再VSCODE中打开一个WSL终端,非常方便。