企业网站的域名是该企业的,深圳网站建设公司信任湖南岚鸿信 赖,网站建设商城,嵌入式网站开发学习文章目录 开发驱动准备工作1.正常运行的Linux系统的开发板2.内核源码树3.nfs挂载的rootfs4.得心趁手的IDE 第一个Hello world 驱动程序常见模块的操作命令模块的初始化和清理模块的版本信息模块中的各种宏 示例Hello World代码printk函数解析 使用MakeFile编译驱动模块使用insm… 文章目录 开发驱动准备工作1.正常运行的Linux系统的开发板2.内核源码树3.nfs挂载的rootfs4.得心趁手的IDE 第一个Hello world 驱动程序常见模块的操作命令模块的初始化和清理模块的版本信息模块中的各种宏 示例Hello World代码printk函数解析 使用MakeFile编译驱动模块使用insmod加载模块 开发驱动准备工作
1.正常运行的Linux系统的开发板
开发板中的Linux的Image必须是自己编译的不能是别人编译的。笔者用的是IMX8QM的开发板任何开发板都是一样的流程。
2.内核源码树
其实就是一个经过配置编译之后的内核源码。编译驱动的过程中就要用到内核源码树。编写代码的时候需要用到系统内核的头文件因此一定要有内核源码
3.nfs挂载的rootfs
尽管内核是linux的核心但文件却是**用户与操作系统交互所采用的主要工具。**这对linux来说尤其如此这是因为在UNIX传统中它使用文件I/O机制管理硬件设备和数据文件。根文件系统首先是一种文件系统该文件系统不仅具有普通文件系统的存储数据文件的功能但是相对于普通的文件系统它的特殊之处在于它是内核启动时所挂载mount的第一个文件系统内核代码的映像文件保存在根文件系统中系统引导启动程序会在根文件系统挂载之后从中把一些初始化脚本如rcS,inittab和服务加载到内存中去运行。我们要明白文件系统和内核是完全独立的两个部分。在嵌入式中移植的内核下载到开发板上是没有办法真正的启动Linux操作系统的会出现无法加载文件系统的错误。一套linux体系只有内核本身是不能工作的必须要rootfs上的etc目录下的配置文件、/bin /sbin等目录下的shell命令还有/lib目录下的库文件等···相配合才能工作。Linux启动时第一个必须挂载的是根文件系统若系统不能从指定设备上挂载根文件系统则系统会出错而退出启动。成功之后可以自动或手动挂载其他的文件系统。因此一个系统中可以同时存在不同的文件系统。在 Linux 中将一个文件系统与一个存储设备关联起来的过程称为挂载mount。使用 mount 命令将一个文件系统附着到当前文件系统层次结构中根。在执行挂装时要提供文件系统类型、文件系统和一个挂装点。根文件系统被挂载到根目录下“/”上后在根目录下就有根文件系统的各个目录文件/bin /sbin /mnt等再将其他分区挂接到/mnt目录上/mnt目录下就有这个分区的各个目录和文件。
4.得心趁手的IDE
这里使用的是Vscode配置过程如下
在Ubuntu桌面新建文件夹
mkdir 8qm_driver用Vscode打开新建的文件夹可以使用在新建的文件夹内使用终端命令
code .配置头文件目录
使用快捷键 CtrlShiftP 选择编辑配置 输入以下内容
{configurations: [{name: Linux,includePath: [/home/marxist/8qm源码/imx_4.14.98_2.0.0_ga/include,${workspaceFolder}/**],defines: [__GNUC__,__KERNEL__],compilerPath: /usr/bin/gcc,cStandard: c17,cppStandard: gnu14,intelliSenseMode: linux-gcc-arm64}],version: 4
}注意在includePath 块中换成你的Linux内核源码的路径一定要是和你的目标机的源码一致
比如我的目标机的Linux版本为 4.14.98 那么编译的时候内核源码也为4.14.98
当年初学驱动的时候就因为版本不一致dmesg一直报错提示vermagic不一致查了一天资料才发现这个问题┭┮﹏┭┮
第一个Hello world 驱动程序
常见模块的操作命令
在Linux系统中管理内核模块的常见命令包括lsmod、insmod、rmmod、modinfo等。这些命令用于列出、加载、卸载和查看内核模块的详细信息。 lsmod 作用列出当前加载到内核中的所有模块。用法直接运行lsmod命令无需参数。输出模块名称、使用次数及相关模块。 lsmodinsmod 作用加载已编译的内核模块。用法insmod module_file.ko注意加载时需要提供模块的完整路径。常用于开发阶段手动加载模块测试。 sudo insmod my_driver.komodinfo 作用显示模块的详细信息包括依赖、版本信息、许可证等。用法modinfo module_file.ko输出示例 depends显示模块依赖的其他模块。vermagic显示模块编译时的内核版本和相关信息。 modinfo my_driver.kormmod 作用卸载已加载的模块。用法rmmod module_name注意模块名称不带文件扩展名.ko卸载时只需提供模块名。 sudo rmmod my_driver模块的初始化和清理
模块源代码通常使用module_init宏声明初始化函数。这个宏指定的函数在模块加载时使用insmod命令自动调用例如
static int __init chrdev_init(void)
{printk(KERN_INFO Initializing character device\n);return 0;
}module_init(chrdev_init);module_init的作用是将chrdev_init函数与insmod命令绑定。当运行insmod时这个函数被调用。初始化函数中的输出通常使用printk打印可以通过dmesg命令查看内核消息。
模块的版本信息
内核模块的版本信息由vermagic字段表示。vermagic包含了内核版本号、编译器版本等信息。加载模块时vermagic必须与当前运行的内核版本一致否则insmod会失败。这是为了确保模块与内核的兼容性和系统的稳定性。
模块中的各种宏 MODULE_LICENSE 作用声明模块的许可证类型。常见的值包括GPL开源和Proprietary私有。示例MODULE_LICENSE(GPL);注意使用GPL许可证表示模块遵循开源协议加载时不会受到限制。 MODULE_AUTHOR 作用声明模块的作者信息。示例MODULE_AUTHOR(Author Name); module_init 和 module_exit 作用module_init宏用于指定模块的初始化函数module_exit宏用于指定模块的清理函数。 示例 module_init(chrdev_init);
module_exit(chrdev_exit);__init 和 __exit 作用这些宏用于标记初始化和清理函数。__init标记的函数在模块加载后被释放以节省内存。__exit标记的函数在模块卸载时调用。 示例 static int __init chrdev_init(void)
{printk(KERN_INFO Initializing character device\n);return 0;
}static void __exit chrdev_exit(void)
{printk(KERN_INFO Exiting character device\n);
}示例Hello World代码
新建一个kernel_test.c
#include linux/init.h
#include linux/module.h//在模块加载到内核程序中被执行一次
int __init test_init(void){printk(Hello World\n);return 0;
}
//在模块从内核驱动中卸载时执行一次
void __exit test_exit(void)
{printk(Goodbye World\n);}
module_init(test_init); //注册此模块加载的回调函数
module_exit(test_exit); //注册此模块卸载的回调函数
MODULE_LICENSE(GPL); //声明遵循协议printk函数解析
(1)printk在内核源码中用来打印信息的函数用法和printf非常相似。
(2)printk和printf最大的差别printf是C库函数是在应用层编程中使用的不能在linux内核源代码中使用printk是linux内核源代码中自己封装出来的一个打印函数是内核源码中的一个普通函数只能在内核源码范围内使用不能在应用编程中使用。
(3)printk相比printf来说还多了个打印级别的设置。printk的打印级别是用来控制printk打印的这条信息是否在终端上显示的。应用程序中的调试信息要么全部打开要么全部关闭一般用条件编译来实现DEBUG宏但是在内核中因为内核非常庞大打印信息非常多有时候整体调试内核时打印信息要么太多找不到想要的要么一个没有没法调试。所以才有了打印级别这个概念。
(4)操作系统的命令行中也有一个打印信息级别属性值为0-7。当前操作系统中执行printk的时候会去对比printk中的打印级别和我的命令行中设置的打印级别小于我的命令行设置级别的信息会被放行打印出来大于的就被拦截的。譬如我的ubuntu中的打印级别默认是4那么printk中设置的级别比4小的就能打印出来比4大的就不能打印出来。
(5)ubuntu中这个printk的打印级别控制没法实践ubuntu中不管你把级别怎么设置都不能直接打印出来必须dmesg命令去查看
使用MakeFile编译驱动模块
在源码同级别目录新建一个MakeFile文件内容如下
obj-m kernel_test.o
all:make -C /home/marxist/8qm源码/imx_4.14.98_2.0.0_ga M$(PWD) modules
clean:make -C /home/marxist/8qm源码/imx_4.14.98_2.0.0_ga M$(PWD) clean(1)KERN_DIR变量的值就是我们用来编译这个模块的内核源码树的目录
(2)obj-m kernel_test.o这一行就表示我们要将kernel_test.c文件编译成一个模块
(3)make -C $(KERN_DIR) Mpwd modules 这个命令用来实际编译模块工作原理就是利用make -C进入到我们指定的内核源码树目录下然后在源码目录树下借用内核源码中定义的模块编译规则去编译这个模块编译完成后把生成的文件还拷贝到当前目录下完成编译。
(4)make clean 用来清除编译痕迹
模块的makefile非常简单本身并不能完成模块的编译而是通过make -C进入到内核源码树下借用内核源码的体系来完成模块的编译链接的这个Makefile本身是非常模式化的3和4 部分是永远不用动的只有1和2需要动。1是内核源码树的目录必须根据自己的编译环境来调整
保存文件之后在MakeFile的同级目录直接执行 make命令即可
成功之后出现很多的文件只有ko文件才是最终能够被加载进内核的模块其余的都是中间编译连接生成的文件 使用insmod加载模块
将编译好的模块传输到目标机器上 使用insmod加载成功之后使用dmesg命令可以查看打印的信息 使用rmmod命令可以卸载模块同时执行exit函数 使用modinfo 查看模块信息 至此第一个Hello world 驱动就编写成功了