一、准备工作

1、安装好相关环境(Ubuntu 18.04)

开发板:orangepi-zero2

交叉编译器:aarch64-none-linux-gnu-

2、安装交叉编译工具:

(1)下载并安装交叉编译工具,下载地址如下:Index of /armbian-releases/_toolchain/ | 清华大学开源软件镜像站 | Tsinghua Open Source Mirror

下载好压缩包,将压缩包放到Ubuntu里如下所示:

输入以下命令进行压缩包的解压:

tar -xf gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu.tar.xz

解压完后,会有一个文件夹,输入命令进入文件夹,可看到对应的交叉编译工具:

(2)设置环境变量临时有效

        1)输入以下命令,输出环境变量:

        echo $PATH

        2)pwd,打印当前路径:

        3)设置临时环境变量,用到以下命令:

        export PATH=$PATH:<路径>         

        4)我们再次输出环境变量,看看是否已经添加进来:

        但是,这个方法只适用于当前终端,一旦当前终端关闭或者在其他终端中则无效。它是一个临时的环境变量。

(3)设置永久有效

        修改工作目录的 .bashrc 是一个隐藏文件,用来配置命令终端(这里要注意每个人的工作目录可能不太一样,不要盲目复制命令,有些命令是需要改动的)。

vi /home/wsm/.bashrc

注意:上面路径中的wsm是我自己的工作目录,每个人的不一样,需要按照自己的工作目录修改。

在最后一行加上:

export PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/home/wsm/orangePiZero2/gcc-arm-9.2-2019.12-x86_64-aarch64-none-linux-gnu/bin

保存并退出,然后输入以下命令,使其生效:

source /home/wsm/.bashrc

(5)测试,验证是否成功:

在虚拟机编写一个hello.c的文件如下:

/*************************************************************************

> File Name: helloc.c

> Author:阿哈、小吴

> Mail: 1971363937@qq.com

> Created Time: Sun 05 Nov 2023 06:23:58 AM PST

************************************************************************/

#include

int main(void){

printf("hello!\n");

return 0;

}

用交叉编译工具编译为 test可执行文件:

aarch64-none-linux-gnu-gcc hello.c -o test

查看test文件是否是arm架构:

file test

如下:

        证明我们交叉编译工具已经安装好,你也可以将test文件拷贝到板子上运行看是否正常。

3、将Linux SDK 的源码准备好,并放在Ubuntu上,如下所示:

4、先将Linux 内核压缩包解压出来

unzip orangepi-build-main.zip

解压后,会多出解压好的文件夹:

二、编译 Linux 内核

1、先进入我们解压好的源码文件夹,如下所示:

2、运行 buid.sh 脚本,输入如下命令:

sudo ./build.sh

它会自动帮我们下载编译工具和源码。

3、运行上述脚本后,弹出下图,我们选择 Kernel package ,然后按回车:

4、紧接着,需要我们选择开发板的型号,这里我的开发板的型号是 OrangePiZero2,选择按回车即可:

5、接着选择型号分支,我的开发板是linux 4.9的,按回车即可:

(1)current 会去编译 linux 5.13

(2)legacy 会去编译 linux 4.9

6、然后会弹出 make menuconfig 配置内核的界面,此时,可以直接修改内核的配置,这里我没有配置,直接退出,退出后就会开始编译内核源码:

        (1)如果不需要修改内核的配置选项,在一开始运行 build.sh 脚本时,传入 KERNEL_CONFINGRE=no 就可以临时屏蔽弹出内核的配置界面了。

sudo ./build.sh KERNEL_CONFIIGURE=no

        (2)如果你想永久禁用内核配置界面的弹出,也可以设置 orangepi-buid-main/userpatches/config-default.conf 配置文件中的 KERNEL_CONFINGRE=no ,这样就可以永久禁用这个功能了:

        (3)编译内核的时候,如果提示错误信息为 Your display is too small to run MenuConfig! ,这是由于你的 Ubuntu PC 的终端界面太小,导致 MenuConfig 的界面无法显示,这时,只要重新把终端调大,然后重新运行 build.sh 脚本即可。

7、编译内核源码时,提示的部分信息说明如下(了解)

        (1)Linux 内核源码的版本信息:

        [ o.k. ]Compiling legacy Kernel [ 4.9.170 ]

        (2)使用的交叉编译工具链的版本:

         [ o.k. ]Compiler version [ aarch64-none-linux-gnu-gcc 9.2.1 ]

        (3)内核默认使用的配置文件以及它存放的路径:

         [ o.k. ]Using kernel config file [ config/kernel/linux-sun50iw9-legacy.config ]

        (4)如果 KERNEL_CONFINGRE=yes ,内核最终使用的配置文件 .config 会复制到 output/config 中,如果没有对内核配置进行修改,最终的配置文件和默认的配置文件是一致的:

        [ o.k. ]Exporting new kernel config [ output/config/linux-sun50iw9-legacy.config ]

        (5)编译生成的内核相关的 deb 包的路径:

        [ o.k. ]Target directory [ output/debs ]

        (6)编译生成的内核镜像 deb 包的包名:

        [ o.k. ]File name [ linux-image-legacy-sun50iw9_2.2.0_arm64.deb ]

        (7)编译使用的时间:

        [ o.k. ]Runtime [ 5 min ]

        (8)最后会显示重复编译上一次选择的内核的编译命令,使用下面的命令无需通过图形界面选择,可以直接开始编译内核源码:  

8、编译好后,生成的目录如下:

以上文件释义如下所示:

a. build.sh: 编译启动脚本

b. external: 包含编译镜像需要用的配置文件、特定功能的脚本以及部分程序的源码,编译镜像过程中缓存的 rootfs 压缩包也存放在 external 中

c. kernel: 存放 linux 内核的源码,里面名为 orange-pi-4.9-sun50iw9 的文件夹存 放 的 就 是 H616 系 列 开 发 板 legacy 分 支 的 内 核 源 码 , 里 面 名 为orange-pi-5.13-sunxi64 的文件夹存放的就是 H616 开发板 current 分支的内核源码(如果只编译了 legacy 分支的 linux 镜像,那么则只能看到 legacy 分支的内核源码;如果只编译了 current 分支的 linux 镜像那么则只能看到current 分支的内核源码),内核源码的文件夹的名字请不要手动修改,如果修改了,编译系统运行时会重新下载内核源码

d. LICENSE: GPL 2 许可证文件

e. README.md: orangepi-build 说明文件

f. output: 存放编译生成的 u-boot、linux 等 deb 包、编译日志以及编译生成的镜像等文件

g. scripts: 编译 linux 镜像的通用脚本

h. toolchains: 存放交叉编译工具链

i. u-boot: 存放 u-boot 的源码,里面名为 v2018.05-sun50iw9 的文件夹存放的就是 H616 系列开发板 legacy 分支的 u-boot 源码,里面名为 v2021.07-sunxi的文件夹存放的就是 H616 开发板 current 分支的 u-boot 源码(如果只编译了current 分支的 linux 镜像,那么则只能看到 current 分支的 u-boot源码),u-boot 源码的文件夹的名字请不要手动修改,如果修改了,编译系统运行时会重新下载 u-boot 源码

j. userpatches: 存放编译脚本需要用到的配置文件

编译好的内核文件就在 kernel 下。

三、内核移植

1、查看我们编译生成的内核相关的 deb 包:

我生成的如下所示: 

2、将编译好的 deb 上传至 开发板的 /root 目录下:

可以通过SSH连接,使用scp命令上传

scp linux-image-legacy-sun50iw9_2.2.2_arm64.deb root@orangepi:/root

3、查看已经安装了的 deb 文件:

dpkg -l | grep linux-image

4、卸载开发板中的 deb 文件:

apt purge -y linux-image-legacy-sun50iw9

5、安装上传的 deb 文件:

dpkg -i linux-image-legacy-sun50iw9_2.2.2_arm64.deb

6、重启开发板

sudo reboot.

7、再次查看安装的 deb 文件:

可见,已经安装好了最新的 deb 文件。

四、驱动编译

1、写一个示例代码(放在orangepi-build-main/kernel/orange-pi-4.9-sun50iw9/drivers/char 下):

新建一个 pin4driver.c 文件:

文件写入代码如下:

#include //file_operations声明

#include //module_init module_exit声明

#include //__init __exit 宏定义声明

#include //class devise声明

#include //copy_from_user 的头文件

#include //设备号 dev_t 类型声明

#include //ioremap iounmap的头文件

// 将该文件放在 orangepi-build-main/kernel/orange-pi-4.9-sun50iw9/drivers/char 下

static struct class *pin4_class;

static struct device *pin4_class_dev;

static dev_t devno; //设备号

static int major =231; //主设备号

static int minor =0; //次设备号

static char *module_name="pin4"; //模块名

//led_read函数

static ssize_t pin4_read(struct file *file1,char __user *buf,size_t count, loff_t *ppos)

{

printk("pin4_read\n"); //内核的打印函数和printf类似

return 0;

}

//led_open函数

static int pin4_open(struct inode *inode,struct file *file)

{

printk("pin4_open\n"); //内核的打印函数和printf类似

return 0;

}

//led_write函数

static ssize_t pin4_write(struct file *file,const char __user *buf,size_t count, loff_t *ppos)

{

printk("pin4_write\n");

return 0;

}

static struct file_operations pin4_fops = {

.owner = THIS_MODULE,

.open = pin4_open,

.write = pin4_write,

.read = pin4_read,

};

int __init pin4_drv_init(void) //真实入口

{

int ret;

devno = MKDEV(major,minor); //创建设备号

ret = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中

pin4_class=class_create(THIS_MODULE,"myfirstdemo");//由代码在Dev下自动生成设备

pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name); //创建设备文件

return 0;

}

void __exit pin4_drv_exit(void)

{

device_destroy(pin4_class,devno);

class_destroy(pin4_class);

unregister_chrdev(major, module_name); //卸载驱动

}

module_init(pin4_drv_init); //入口 内核加载该驱动的时候,这个宏会被调用

module_exit(pin4_drv_exit); //出口,卸载模块函数

MODULE_LICENSE("GPL v2");

2、怎么样才能编译到这个驱动代码呢?需要我们去修改 Makefile,如下所示:

注意:我们修改的是 orangepi-build-main/kernel/orange-pi-4.9-sun50iw9/drivers/char 这个路径下的 Makefile 文件!!!

我们需要编译成模块的方式,obj-m 就是模块的编译方式,我们需要在Makefile中添加:

obj-m += pin4driver.o

如下所示:

3、然后我们需要回到内核的源码目录下,进行模块的编译,即 orangepi-build-main/kernel/orange-pi-4.9-sun50iw9 这个路径下:

(1)方法一:输入以下命令进行编译,这条命令是将所有内容都编译了,需要蛮久的:

make ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu- -j4

编译好后,可以看到编译生成的 .ko 文件所在的路径:

(2)方法二:输入以下命令,只是编译模块,较快:

make modules ARCH=arm64 CROSS_COMPILE=aarch64-none-linux-gnu-

编译如下所示:

4、将编译好的 pin4driver.ko 模块,拷贝到我们的开发板(可以用scp指令拷贝,也可用U盘拷贝)

这里讲一下用U盘拷贝的方法:

(1)首先先将 pin4driver.ko 模块 拷贝到 共享文件夹中:

cp pin4driver.ko /mnt/hgfs/share/

(2)查看共享文件夹中的文件:

ls /mnt/hgfs/share/

(3)从 windows 上的共享文件夹 将 pin4driver.ko 模块 拷贝到U盘上

(4)将U盘插到板子上, 用 dmesg 命令查看识别到的是哪个设备:

可以看到内核识别到的设备是 sda1。

(5)输入指令挂载U盘,挂载到 /mnt 目录下:

sudo mount /dev/sda1 /mnt/

挂载好后,cd 进入 /mnt 目录,再 ls,即可看到U盘下的文件:

(6)用cp命令将 pin4driver.ko 模块 拷贝出来即可

(7)拷贝完后,需要取消U盘的挂载:

(8)拔出U盘即可

5、在开发板中加载驱动模块:

(1)输入以下命令即可:

sudo insmod pin4driver.ko

(2)我们看一下是否生成了pin4这个设备驱动:

模块名、主设备号与次设备号都与我们的驱动代码对上了:

6、接下来就是测试

我们需要写一个应用程序来去调用我们的驱动程序:

写一个test.c程序如下(这是用户空间的代码):

#include

#include

#include

#include

int main()

{

int fd;

fd = open("/dev/pin4",O_RDWR);

if(fd<0){

printf("open failed!\n");

perror("res");

}else{

printf("open success!\n");

}

fd = write(fd,'1',1);

}

编译运行:

为什么无法打开呢?这是因为,我们加载的驱动的权限问题,我们需要给它可读可写的权限:

修改权限,再次执行即可:

7、查看驱动层的输出语句:

输入dmesg查看:

这样子,就完成了简单的字符设备驱动开发。

五、总结

        (1)通过insmod将模块,加载到内核中去,它会去 module_init 函数 加载驱动模块,再会去调用我们写的 pin4_drv_init 驱动入口函数,而这个函数又会去通过 register_chardev 函数去注册驱动,把这个驱动(整个file_operations结构体变量)加入到内核链表中。

        (2)当驱动装载成功后,会在 /dev/ 文件下生成 一个驱动(如pin4),但是要注意,我们需要给这个驱动一个所有用户均可读可写的权限。

        (3)(用户空间)应用程序中的 open 函数会去通过 系统调用sys_call(软中断,中断号是0x80) “陷入” 到内核空间(sys_call会调用sys_open),然后根据文件名找到相关的设备号,根据设备号从驱动链表里面找出驱动,如果找到了驱动就返回一个 fd 文件句柄。 

        (4)因为它会去调用驱动里面的 open 函数,于是,我们就会在内核里看到打印了相关的信息。

相关文章

评论可见,请评论后查看内容,谢谢!!!评论后请刷新页面。