尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
嵌入式Linux驱动开发 01:基础开发与使用
文章目录目的基础说明驱动测试应用程序基础开发与使用驱动模块入口与出口驱动模块安装与卸载字符设备注册与注销设备开关与读写自动创建与销毁设备节点使用 VS Code 进行开发总结目的驱动开发是嵌入式Linux中工作比重比较大的一部分。这篇文章将记录下最基本的驱动开发过程。这篇文章中内容均在下面的开发板上进行测试《新唐NUC980使用记录自制开发板基于NUC980DK61YC》这篇文章主要是在下面文章基础上进行的《新唐NUC980使用记录访问以太网LAN8720A 启用SSH》基础说明对于驱动程序而言从不同角度来看对它的认知是不同的。从用户应用程序来看——Linux中一切皆文件系统中各种设备也都是文件通常/dev/目录下的就是各种设备文件。对于文件来说用户操作时无非就是open / close / read / write等方法。那么这些设备的驱动程序无非就是要实现这些方法提供给用户使用。驱动开发主要就是实现上面提到的各种文件操作的方法及其具体对硬件的操作。除了实现这些方法还需要用某种机制将这些方法和用户应用程序中各种文件操作方法关联起来通常就是注册下。Linux中驱动程序主要可以分为字符设备驱动、块设备驱动、网络设备驱动、复合设备驱动等这里主要以字符设备驱动展开说明。Linux中驱动程序可以直接编译到内核中也可以编译成*.ko形式的文件然后在使用时再安装。为了方便测试这里主要先以这种形式展开说明。驱动测试应用程序编写完驱动程序后最终需要进行测试这里先准备个测试程序。根据上一章节的内容这里的测试程序主要就是对根据驱动程序生成的设备文件进行 open / close / read / write 等操作。cd~/nuc980-sdk/mkdir-pdrivers/char_devcddrivers/char_dev/ gedit char_dev_test.c在文件中写入下面代码#includestring.h#includestdio.h#includesys/types.h#includesys/stat.h#includefcntl.h#includeunistd.h/* 该程序用于读写测试本文要编写的驱动程序对应生成的设备 */intmain(intargc,char**argv){intfd;charbuf[4096];intlen;/* 判断参数 */if((argc3)||(argc4)){printf(Usage: char_dev_test devpath -w string\n);// 写数据printf( char_dev_test devpath -r\n);// 读数据return-1;}/* 打开文件 */fdopen(argv[1],O_RDWR);if(fd0){printf(applog: can not open file %s\n,argv[1]);return-1;}/* 写数据或读数据 */if((0strcmp(argv[2],-w))(argc4)){lenstrlen(argv[3])1;write(fd,argv[3],len);}elseif((0strcmp(argv[2],-r))(argc3)){lenread(fd,buf,4096);buf[4095]\0;printf(applog: read - %s\n,buf);}else{close(fd);return-1;}close(fd);return0;}编译生成可执行文件# 配置编译工具链exportPATH$PATH:/home/nx/nuc980-sdk/arm_linux_4.8/bin# 编译生成开发板的可执行文件arm-linux-gcc-ochar_dev_test char_dev_test.c# 开发板启用了SSH的话可以使用SCP命令将程序通过网络拷贝到开发板中scpchar_dev_test root192.168.31.142:/root/基础开发与使用驱动模块入口与出口# cd ~/nuc980-sdk/drivers/char_dev/gedit char_dev.c在文件中写入下面代码#includelinux/module.hstaticint__initchar_dev_init(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);return0;}staticvoid__exitchar_dev_exit(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);}module_init(char_dev_init);// 模块入口module_exit(char_dev_exit);// 模块出口MODULE_LICENSE(GPL);// 模块许可上面代码中printk用在内核中用于打印内核日志。MODULE_LICENSE(GPL);只是个声明但不加的话驱动安装时会有个提示。创建Makefile文件gedit Makefile在文件中写入下面代码编译驱动模块需要用到内核源码下面需要指定正确的路径# 配置内核源码路径KERNEL_DIR :/home/nx/nuc980-sdk/NUC980-linux-4.4.y MODULE_DIR :$(shellpwd)obj-mchar_dev.o# 以模块方式编译驱动all:$(MAKE)-C$(KERNEL_DIR)M$(MODULE_DIR)modules clean:$(MAKE)-C$(KERNEL_DIR)M$(MODULE_DIR)modules clean编译生成驱动模块# export PATH$PATH:/home/nx/nuc980-sdk/arm_linux_4.8/binmake# 编译后生成的char_dev.ko就是驱动模块拷贝到开发板上scpchar_dev.ko root192.168.31.142:/root/驱动模块安装与卸载在开发板上使用insmod来安装驱动模块使用rmmod卸载驱动模块。使用lsmod查看已安装的驱动模块需要注意的是上面模块安装和卸载时出现的内核日志只有在串口终端中才会出现如果时通过SSH的终端那么需要使用dmesg命令来查看内核日志上面出现了多条日志信息前面一些是之前测试时产生的。字符设备注册与注销下面的字符设备驱动的编写方式是Linux 2.6 内核之前的旧方式现在字符设备主要使用cdrv方式。完成了最基本的模块和使用接下来开始准备字符设备驱动将驱动代码改成如下#includelinux/module.h#includelinux/fs.hstaticintmajor0;constchar*dev_namechar_dev;staticconststructfile_operationschar_dev_fops{.ownerTHIS_MODULE,};staticint__initchar_dev_init(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);majorregister_chrdev(0,dev_name,char_dev_fops);// 注册字符设备第一个参数0表示让内核自动分配主设备号return0;}staticvoid__exitchar_dev_exit(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);unregister_chrdev(major,dev_name);// 注销字符设备}module_init(char_dev_init);// 模块入口module_exit(char_dev_exit);// 模块出口MODULE_LICENSE(GPL);// 模块许可重新编译后在开发板上安装模块就可以在/proc/devices中看到字符类型设备驱动了设备开关与读写接下来继续完善驱动代码添加相应操作功能#includelinux/module.h#includelinux/fs.h#includelinux/uaccess.hstaticintmajor0;staticconstchar*dev_namechar_dev;staticchardev_buf[4096];#defineMIN(a,b)((a)(b)?(a):(b))staticintchar_dev_open(structinode*node,structfile*file){// printk(modlog: func %s, line %d.\n, __FUNCTION__, __LINE__);return0;}staticintchar_dev_close(structinode*node,structfile*file){// printk(modlog: func %s, line %d.\n, __FUNCTION__, __LINE__);return0;}staticssize_tchar_dev_read(structfile*file,char__user*buf,size_tsize,loff_t*offset){intret;retcopy_to_user(buf,dev_buf,MIN(size,4096));// 从内核空间拷贝数据到用户空间returnret;}staticssize_tchar_dev_write(structfile*file,constchar__user*buf,size_tsize,loff_t*offset){intret;retcopy_from_user(dev_buf,buf,MIN(size,4096));// 从用户空间拷贝数据到内核空间returnret;}staticconststructfile_operationschar_dev_fops{.ownerTHIS_MODULE,.openchar_dev_open,.releasechar_dev_close,.readchar_dev_read,.writechar_dev_write,};staticint__initchar_dev_init(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);majorregister_chrdev(0,dev_name,char_dev_fops);// 注册字符设备第一个参数0表示让内核自动分配主设备号return0;}staticvoid__exitchar_dev_exit(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);unregister_chrdev(major,dev_name);// 注销字符设备}module_init(char_dev_init);// 模块入口module_exit(char_dev_exit);// 模块出口MODULE_LICENSE(GPL);// 模块许可重新编译后在开发板上安装模块接着使用mknod来创建设备节点文件然后就可以使用前面的应用程序进行读写测试了卸载驱动后使用mknod创建的设备节点可以使用rm进行删除。自动创建与销毁设备节点上面测试中安装模块后还需要手动创建设备节点其实也可以在安装时自动生成设备节点在卸载时自动删除设备节点#includelinux/module.h#includelinux/fs.h#includelinux/uaccess.h#includelinux/device.hstaticintmajor0;staticconstchar*char_dev_namechar_dev;staticstructclass*char_dev_class;staticstructdevice*char_dev_device;staticchardev_buf[4096];#defineMIN(a,b)((a)(b)?(a):(b))staticintchar_dev_open(structinode*node,structfile*file){// printk(modlog: func %s, line %d.\n, __FUNCTION__, __LINE__);return0;}staticintchar_dev_close(structinode*node,structfile*file){// printk(modlog: func %s, line %d.\n, __FUNCTION__, __LINE__);return0;}staticssize_tchar_dev_read(structfile*file,char__user*buf,size_tsize,loff_t*offset){intret;retcopy_to_user(buf,dev_buf,MIN(size,4096));// 从内核空间拷贝数据到用户空间returnret;}staticssize_tchar_dev_write(structfile*file,constchar__user*buf,size_tsize,loff_t*offset){intret;retcopy_from_user(dev_buf,buf,MIN(size,4096));// 从用户空间拷贝数据到内核空间returnret;}staticconststructfile_operationschar_dev_fops{.ownerTHIS_MODULE,.openchar_dev_open,.releasechar_dev_close,.readchar_dev_read,.writechar_dev_write,};staticint__initchar_dev_init(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);majorregister_chrdev(0,char_dev_name,char_dev_fops);// 注册字符设备第一个参数0表示让内核自动分配主设备号char_dev_classclass_create(THIS_MODULE,char_dev_class);//if(IS_ERR(char_dev_class)){unregister_chrdev(major,char_dev_name);return-1;}char_dev_devicedevice_create(char_dev_class,NULL,MKDEV(major,0),NULL,char_dev_name);// 创建设备节点创建设备节点成功后就会出现/dev/char_dev_name的设备文件if(IS_ERR(char_dev_device)){device_destroy(char_dev_class,MKDEV(major,0));unregister_chrdev(major,char_dev_name);return-1;}return0;}staticvoid__exitchar_dev_exit(void){printk(modlog: func %s, line %d.\n,__FUNCTION__,__LINE__);device_destroy(char_dev_class,MKDEV(major,0));// 销毁设备节点销毁后/dev/下设备节点文件就会删除class_destroy(char_dev_class);unregister_chrdev(major,char_dev_name);// 注销字符设备}module_init(char_dev_init);// 模块入口module_exit(char_dev_exit);// 模块出口MODULE_LICENSE(GPL);// 模块许可重新编译后在开发板上进行测试使用 VS Code 进行开发驱动开发和内核中很多源码有交互使用 VS Code 等工具进行开发可以方便编写代码方便查阅源码一定程度上可以提升开发效率。Ubuntu中可以使用下面命令来安装VS Codesudosnapinstallcode--classic通常VS Code中安装了C/C支持后配置下内核源码位置即可.vscode下c_cpp_properties.json文件中内容如下其中重要的就是内核源码路径{configurations:[{name:Linux,includePath:[../../NUC980-linux-4.4.y/**],intelliSenseMode:linux-gcc-arm,compilerPath:/home/nx/nuc980-sdk/arm_linux_4.8/bin/arm-linux-gcc,cStandard:gnu89,cppStandard:gnu98}],version:4}总结本文内容只是嵌入式Linux中最基础的驱动开发过程实际去参考别的驱动的时候可能比上面要复杂些一方面因为实际功能的实现另一方面考虑兼容性和可移植性等从而对驱动和设备分层解耦等。虽然本文内容比较基础但是如果只是自己简单用用的话也是可以的毕竟简单。
RELATED

相关推荐

RAG企业知识库落地实战:从架构设计到政务级细节调优

RAG企业知识库落地实战:从架构设计到政务级细节调优

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/12 10:53:08
C++智能指针详解:unique_ptr、shared_ptr与weak_ptr实践指南

C++智能指针详解:unique_ptr、shared_ptr与weak_ptr实践指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/9/12 10:53:08
Kotlin 协程 - 热流 Channel、SharedFlow、StateFlow

Kotlin 协程 - 热流 Channel、SharedFlow、StateFlow

一、概念1.1 通信模式根据接收端节点数量以及接收方式,可分为三类。单播 Unicast一对一,对单个订阅者发送。组播 Multicast分配性(队列分摊消费):一对多,依次对单个订阅者发送。多个订阅者互斥,…

📅 2026/9/12 10:53:07
MORE NEWS

更多资讯

📰

AI SDK 应用内存基准测试:基于 Neovate Code 与 ephemeral VM 的进程级 RSS 测量指南

AI SDK 应用内存基准测试:基于 Neovate Code 与 ephemeral VM 的进程级 RSS 测量指南 【免费下载链接】ai The AI Toolkit for TypeScript. From the creators of Next.js, the AI SDK is a free open-source library for building AI-powered applications and age…

📰

使用 OpenTelemetry Collector Builder 定制 Jaeger 发行版:components/ 公共 API 与 ocb 装配实战

使用 OpenTelemetry Collector Builder 定制 Jaeger 发行版:components/ 公共 API 与 ocb 装配实战 【免费下载链接】jaeger CNCF Jaeger, a Distributed Tracing Platform 项目地址: https://gitcode.com/GitHub_Trending/ja/jaeger Jaeger V2 已全面基于 O…

📰

从MCP到Skill:AI Agent能力范式的演进与实践

1. 从MCP到Skill:AI Agent能力范式的代际更替当Claude平台文档中首次出现"Agent Skills"这个术语时,业内开发者们立即意识到:一个旧时代正在落幕。MCP(Modular Capability Protocol)作为上一代AI能力封装标准…

📰

美团闪购即时零售战略解析与亿级品牌培育路径

1. 美团闪购的三十亿目标:即时零售的战略野心2023年夏季,美团闪购高调宣布"三年培育30个亿级连锁品牌"的战略目标,这不仅是数字层面的增长承诺,更是即时零售赛道进入规模化竞争阶段的标志性事件。作为美团在本地生活服务…

📰

2026年AI生成内容检测技术与降AI率工具全解析

1. AI检测与学术写作的现状分析 2026年的学术圈正面临一个前所未有的挑战:AI生成内容检测技术的快速普及。各大高校和期刊出版社纷纷部署了先进的AI检测系统,如GPTZero、Turnitin AI Detection等,这些系统能准确识别出论文中由ChatGPT、Claud…

📰

数据安全服务核心技术解析与实践指南

1. 数据安全服务的行业现状与核心挑战数据安全服务已经从单纯的合规需求演变为企业核心竞争力的重要组成部分。过去三年间,全球数据泄露事件造成的平均损失增长了42%,这使得企业对数据安全服务的需求呈现出爆发式增长。但与此同时,服务提供商…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬