首页 > 系统相关 >Linux驱动编译方法

Linux驱动编译方法

时间:2023-08-20 16:56:47浏览次数:43  
标签:__ 编译 init 内核 Linux 驱动 hello

编译内核

为什么编译驱动前要编译内核?

编译驱动的内核要和开发板上的内核一致。因为开发板出厂时预烧录了一个内核,但自己在 ubuntu 编译是使用的是自己的内核,二者不一致时会导致导入驱动模块时出现问题(如内核污染提示)。

内核编译的步骤

下面记录内核编译步骤是对应 IMX6ULL PRO 开发板平台,相关文档资料有百问网提供。

# IMX6ULL PRO 平台对应的编译zImage的步骤
cd /home/book/100ask_imx6ull-sdk/Linux-4.9.88
make mrproper
make zImage -j4
make dtbs
cp arch/arm/boot/zImage ~/nfs_rootfscp arch/arm/boot/dts/100ask_imx6ull-14x14.dtb ~/nfs_rootfs

编译后,会在对应路径下生成 zImage 内核文件和 dts 设备树文件。

内核文件:arch/arm/boot/zImage

设备树文件:arch/arm/boot/dts/100ask_imx6ull-14x14.dtb

编译及安装内核模块

# 在Linux源码路径下进行
make modules
make ARCH=arm INSTALL_MOD_PATH=/home/book/nfs_rootfs modules_install

安装内核及模块到开发板

# 在IMX6ULL PRO开发板进行
mount -t nfs -o nolock,vers=3 192.168.5.11:/home/book/nfs_rootfs /mnt
cp /mnt/zImage /boot
cp /mnt/100ask_imx6ull-14x14.dtb /boot
cp /mnt/lib/modules /lib -rfd
sync

编译驱动

编译驱动首先要确保编译驱动的 Makefile 文件中的 KERN_DIR 设置正确。一份编译驱动的 Makefile 参考文件如下。

# 1. 使用不同的开发板内核时, 一定要修改KERN_DIR
# 2. KERN_DIR中的内核要事先配置、编译, 为了能编译内核, 要先设置下列环境变量:
# 2.1 ARCH,          比如: export ARCH=arm64
# 2.2 CROSS_COMPILE, 比如: export CROSS_COMPILE=aarch64-linux-gnu-
# 2.3 PATH,          比如: export PATH=$PATH:/home/book/100ask_imx6ull-sdk/ToolChain/arm-buildroot-linux-gnueabihf_sdk-buildroot/bin
# 注意: 不同的开发板不同的编译器上述3个环境变量不一定相同,
#       请参考各开发板的高级用户使用手册
KERN_DIR = /home/book/100ask_imx6ull-sdk/Linux-4.9.88

all:
	make -C $(KERN_DIR) M=`pwd` modules 
	$(CROSS_COMPILE)gcc -o hello_drv_test hello_drv_test.c 

clean:
	make -C $(KERN_DIR) M=`pwd` modules clean
	rm -rf modules.order
	rm -f hello_drv_test

obj-m	+= hello_drv.o

确保 Makefile 文件正确后,就可以执行 make all 命令编译驱动。编译后得到一个 hello_drv_test 测试文件、一个 hello_drv.o 模块文件。

安装驱动模块

驱动模块通过 insmod 命令来安装。

insmod  # 在开发板上安装驱动模块
lsmod  # 查看已安装的驱动模块
rmmod  # 删除已安装的模块
insmod -f [xxx.ko]  # 强制安装驱动模块

如果未按照前述要求去确保编译驱动的内核与开发板的内核相同,则执行 insmod 命令时会出现报错,提示内核已被污染。若要强制安装,可以使用上述强制安装命令。

驱动加载逻辑

驱动程序可以被直接编进内核中,也可以编译成 ko 文件后手动加载。这两种方式都会利用 module_init 和 module_exit 来初始化和注销驱动,但是两者的导入逻辑有所不同。

把驱动程序编进内核时,由于不会定义宏 MODULE,会选择与编译 ko 文件不同的分支来执行。这种情况下,会定义对应初始化和注销的两个函数指针,分别放在 .initcall6.init 段和 .exitcall6.exit 段中。内核启动时,就会去 .initcall6.init 段中取出指向该驱动的初始化函数的函数指针,执行这个函数指针指向的驱动的入口函数。若选择将驱动编进内核,那么驱动程序就只会被加载,而不会被卸载。所以,内核不会使用到 .exitcall6.exit 的段空间,这块空间会在内核启动后被释放。

编译为 ko 文件时,会使用宏 module_init(hello_init) 和 module_exit(hello_exit) 来将此驱动模块关联到模块相关命令。将这两个宏展开后,分别为 init_module 和 cleanup_module,分别在 insmod 和 rmmod 时被使用,通过调用 hello_init 和 hello_exit 来实现。

#ifndef MODULE

static initcall_t __initcall_hello_init6 __used \
__attribute__((__section__(".initcall6.init"))) = hello_init;
static exitcall_t __exitcall_hello_exit __used __section(.exitcall.exit) = hello_exi
t;

#else

static inline initcall_t __inittest(void) \
{ return hello_init; } \
int init_module(void) __attribute__((alias("hello_init")));

static inline exitcall_t __exittest(void) \
{ return hello_exit; } \
void cleanup_module(void) __attribute__((alias("hello_exit")));

#endif

内核使用 char_device_struct 来描述一种驱动程序,通过 chrdevs[256] 的指针数组来管理这些驱动程序,chrdevs 数组的每个成员都是一个链表头结点。实际上,chardevs 就是一个链表形式的哈希表。内核提供了 register_chrdev_region 函数来申请注册新的驱动程序(实际上注册函数还有 alloc_chrdev_region 和 register_chrdev)。注册驱动程序时,通过用户传递的主设备号和次设备号,查询对应区域是否被其他设备占用。若未被占用,则生成一个 char_device_struct 对象挂到 chrdevs 中对应的链表里。这就为字符驱动产生了一类新的驱动程序。

struct char_device_struct {
	struct char_device_struct *next;
	unsigned int major;
	unsigned int baseminor;
	int minorct;
	char name[64];
	struct cdev *cdev; /* will die */
}

内核使用 cdev 结构来描述具体的一个驱动设备,内核提供了 cdev_add 函数来加入新的驱动设备。在此函数中,cedv 将会被加入到 cdev_map 中,可以使用 dev (设备号)到 dev+count-1 来索引这个设备。

APP 打开一个字符设备节点时,则是根据该设备节点的主次设备号的计算结果,从 cdev_map 找到对应的 cdev,再从 cdev 中得到 file_operations 结构体,进行文件操作时则会调用 file_operations 下的各个函数接口去执行对应的操作。

标签:__,编译,init,内核,Linux,驱动,hello
From: https://www.cnblogs.com/zenonx/p/17644242.html

相关文章

  • Windows安装Archlinux(WSL2)
    导言对于在Windows中使用Linux系统,有很多种实现方法。最常见的方法就是使用VM虚拟机软件。于是微软推出了WSL(WindowsSubSystemForLinux),初代的WSL1,总体上是在运行时将LinuxSystemCall翻译为NTAPI调用,从而在NT内核基础之上模拟实现Linux内核。也许是因为WSL1靠翻译SystemCa......
  • linux 安装jdk
    (1)下载安装包JDK安装包下载地址:链接:https://pan.baidu.com/s/1Vt0nqNBOVl7Chuap4Gj3tg提取码:9fy2(2)SSH上传工具SSH安装包下载地址:链接:https://pan.baidu.com/s/1TiFqxSJbmxupcjSHmcLLvQ提取码:tbo9(3)安装SSH成功可直接右键,点击【Upload】上传到/usr/local/java目录......
  • 软件测试|Linux三剑客之awk命令详解
    简介awk是一种强大的文本处理工具,在Unix和类Unix系统中广泛使用。它允许您在文本文件中进行复杂的数据处理和格式化输出。awk的名字是根据它的三位创始人Aho、Weinberger和Kernighan姓氏的首字母命名的。本文将详细介绍awk命令的基本用法和一些常见的用例。awk基本语法aw......
  • 软件测试|Linux 基础教程:创建和删除目录
    简介在Linux系统中,创建和删除目录是非常常见的操作。目录是用于组织文件和其他目录的一种结构,它们是组织文件系统的重要组成部分。本文将介绍如何在Linux系统中创建和删除目录。创建目录在Linux中,可以使用mkdir命令来创建一个新的目录。mkdir是MakeDirectory(创建目录)的......
  • 虚拟机linux无法实现与原机windows之间的复制和拖拽文件--已解决
    在虚拟机(我用的是Ubuntu)桌面右键打开终端,输入第一行sudoaptinstallopen-vm-tools中间全部yes,然后关闭终端然后再次在桌面打开终端,输入sudoaptinstallopen-vm-tools-desktop中间全部yes完成......
  • Linux笔记(银河麒麟V10)
    Linux下切换Python版本$whereispython$rm/usr/bin/python$ln-s/usr/bin/python3.6/usr/bin/python测试:$python--versionPython3.8.2安装Node.js-v18$curlhttps://nodejs.org/dist/v18.17.0/node-v18.17.0-linux-x64.tar.xz--outputnodejs18.tar.xz#......
  • 常见的Linux问题和故障排除方法
    以下是一些常见的Linux问题和故障排除方法:1.**无法启动系统**:-检查硬件连接是否正常,例如硬盘、内存等。-使用恢复模式或救援系统进行修复。-查看启动日志以找出问题所在。2.**网络连接问题**:-检查网络连接,确保网线或Wi-Fi正常。-使用`ping`命令测试网络连......
  • linux shell 条件语句和循环结构
    条件语句和循环结构在Shell脚本中用于控制程序的流程,让你可以根据条件决定执行不同的代码块,或者重复执行某些代码。下面是条件语句和循环结构的一些示例和说明:**条件语句:**1.**if语句**:使用`if`语句根据条件执行不同的代码块。if["$age"-gt18];thenecho"成年"el......
  • linux shell 函数与脚本调试
    **函数:**1.**函数定义**:使用`function`关键字或者直接写函数名来定义函数。functionmy_function(){echo"函数内容"}my_function(){echo"函数内容"}2.**函数调用**:直接使用函数名来调用函数。my_function3.**函数参数**:可以在函数中使用参数来传递值。......
  • Linux驱动开发详解——学习笔记
    Linux设备驱动概述计算机系统的运转需要软件和硬件共同参与,硬件是底层基础,软件则实现了具体的应用。硬件和软件之间则通过设备驱动来联系。在没有操作系统的情况下,工程师可以根据硬件设备的特点自行定义接口。而在有操作系统的情况下,驱动的架构则由相应的操作系统来定义。驱动存......