【嵌入式Linux学习笔记】Linux驱动开发

news/2024/11/18 14:40:11/

在这里插入图片描述

Linux系统构建完成后,就可以基于该环境方便地进行开发了,相关的开发流程与MCU类似,但是引入了设备树的概念,编写应用代码要相对复杂一点。但是省去了很多配置工作。

学习视频地址:【正点原子】STM32MP157开发板

字符设备驱动开发

驱动流程
在这里插入图片描述

在 Linux 中一切皆为文件,驱动加载成功以后会在“/dev”目录下生成一个相应的文件,应用程序通过对这个名为“/dev/xxx”(xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。
比如现在有个叫做/dev/led 的驱动文件,此文件是 led 灯的驱动文件。

  1. open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。 open和 close 就是打开和关闭 led 驱动的函数
  2. 点亮或关闭 led,那么就使用 write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数
  3. 如果要获取led 灯的状态,就用 read 函数从驱动中读取相应的状态。

内核驱动函数及变量
owner:拥有该结构体的模块的指针变量, 一般设置为 THIS_MODULE
llseek:修改文件当前的读写位置
read:读取设备文件
write:向设备文件写入数据
poll:轮询函数,用于查询设备是否可以进行非阻塞的读写。
unlocked_ioctl:提供对于设备的控制功能,与应用程序中的 ioctl 函数对应
compat_ioctl:函数与 unlocked_ioctl 函数功能一样,区别在于在 64 位系统上,
32 位的应用程序调用将会使用此函数。在 32 位的系统上运行 32 位的应用程序调用的是unlocked_ioctl。
mmap:用于将将设备的内存映射到进程空间中(也就是用户空间),一般帧
缓冲设备会使用此函数,比如 LCD 驱动的显存,将帧缓冲(LCD 显存)映射到用户空间中以后应用程序就可以直接操作显存了,这样就不用在用户空间和内核空间之间来回复制。
open:打开设备文件
release:用于释放(关闭)设备文件,与应用程序中的 close 函数对应
fasync:用于刷新待处理的数据,用于将缓冲区中的数据刷新到磁盘中

驱动开发步骤

1. 驱动模块的加载和卸载

  1. 将驱动编译进Linux内核中,内核启动时就会自动运行驱动程序
  2. 将驱动编译为模块(Linux 下模块扩展名为.ko),Linux内核启动后采用modprobe指令加载模块。
module_init(xxx_init); //注册模块加载函数
module_exit(xxx_exit); //注册模块卸载函数

2. 字符设备的注册与注销

字符设备的注册与注销放在模块的加载和卸载函数中。

# 只指定主设备号,会造成资源浪费
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops)
static inline void unregister_chrdev(unsigned int major,const char *name)# 用多少申请多少,比较合理
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name)
int register_chrdev_region(dev_t from, unsigned count, const char *name)
void unregister_chrdev_region(dev_t from, unsigned count)

3. 实例代码

字符设备驱动

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>#define CHRDEVBASE_MAJOR	100				/* 主设备号 */
#define CHRDEVBASE_NAME		"devbase" 	    /* 设备名   */static char readbuf[100];		/* 读缓冲区 */
static char writebuf[100];		/* 写缓冲区 */
static char kerneldata[] = {"kernel data!"};/** @description		: 打开设备* @param - inode 	: 传递给驱动的inode* @param - filp 	: 设备文件,file结构体有个叫做private_data的成员变量* 					  一般在open的时候将private_data指向设备结构体。* @return 			: 0 成功;其他 失败*/
static int chrdevbase_open(struct inode *inode, struct file *filp)
{printk("devbase open! \r\n");return 0;
}/** @description		: 从设备读取数据 * @param - filp 	: 要打开的设备文件(文件描述符)* @param - buf 	: 返回给用户空间的数据缓冲区* @param - cnt 	: 要读取的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 读取的字节数,如果为负值,表示读取失败*/
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 向用户空间发送数据 */memcpy(readbuf, kerneldata, sizeof(kerneldata));retvalue = copy_to_user(buf, readbuf, cnt);if(retvalue == 0){printk("kernel senddata ok!\r\n");}else{printk("kernel senddata failed!\r\n");}return 0;
}/** @description		: 向设备写数据 * @param - filp 	: 设备文件,表示打开的文件描述符* @param - buf 	: 要写给设备写入的数据* @param - cnt 	: 要写入的数据长度* @param - offt 	: 相对于文件首地址的偏移* @return 			: 写入的字节数,如果为负值,表示写入失败*/
static ssize_t chrdevbase_write(struct file *filp, const char __user *buf, size_t cnt, loff_t *offt)
{int retvalue = 0;/* 接收用户空间传递给内核的数据并且打印出来 */retvalue = copy_from_user(writebuf, buf, cnt);if(retvalue == 0){printk("kernel recevdata:%s\r\n", writebuf);}else{printk("kernel recevdata failed!\r\n");}return 0;
}/** @description		: 关闭/释放设备* @param - filp 	: 要关闭的设备文件(文件描述符)* @return 			: 0 成功;其他 失败*/
static int chrdevbase_release(struct inode *inode, struct file *filp)
{printk("devbase release! \r\n");return 0;
}/** 设备操作函数结构体*/
static struct file_operations devbase_fops = {.owner = THIS_MODULE,	.open = chrdevbase_open,.read = chrdevbase_read,.write = chrdevbase_write,.release = chrdevbase_release,
};/** @description	: 驱动入口函数 * @param 		: 无* @return 		: 0 成功;其他 失败*/
static int __init chrdevbase_init(void)
{int retvalue = 0;/* 注册字符设备驱动 */retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &devbase_fops);if(retvalue < 0){printk("chrdevbase driver register failed\r\n");}printk("chrdevbase init!\r\n");return 0;
}/** @description	: 驱动出口函数* @param 		: 无* @return 		: 无*/
static void __exit chrdevbase_exit(void)
{/* 注销字符设备驱动 */unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);printk("chrdevbase exit!\r\n");
}/* * 将上面两个函数指定为驱动的入口和出口函数 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);/* * LICENSE和作者信息*/
MODULE_LICENSE("GPL");
MODULE_AUTHOR("JozenLee");

设备应用

#include "stdio.h"
#include "unistd.h"
#include "sys/types.h"
#include "sys/stat.h"
#include "fcntl.h"
#include "stdlib.h"
#include "string.h"/* 用户数据, 用于写入设备测试 */
static char usrdata[] = {"usr data!"};int main(int argc, char *argv[])
{int fd, retvalue;char *filename;char readbuf[100], writebuf[100];if(argc != 3){printf("Error Usage!\r\n");return -1;}filename = argv[1];/* 打开驱动文件 */fd  = open(filename, O_RDWR);if(fd < 0){printf("Can't open file %s\r\n", filename);return -1;}/* 从驱动文件读取数据测试 */if(atoi(argv[2]) == 1){ retvalue = read(fd, readbuf, 50);if(retvalue < 0){printf("read file %s failed!\r\n", filename);}else{printf("read data:%s\r\n",readbuf);}}/* 向设备驱动写数据测试 */if(atoi(argv[2]) == 2){memcpy(writebuf, usrdata, sizeof(usrdata));retvalue = write(fd, writebuf, 50);if(retvalue < 0){printf("write file %s failed!\r\n", filename);}}/* 关闭设备 */retvalue = close(fd);if(retvalue < 0){printf("Can't close file %s\r\n", filename);return -1;}return 0;
}

http://www.ppmy.cn/news/32285.html

相关文章

【YOLOv8/YOLOv7/YOLOv5/YOLOv4/Faster-rcnn系列算法改进NO.59】引入ASPP模块

前言作为当前先进的深度学习目标检测算法YOLOv8&#xff0c;已经集合了大量的trick&#xff0c;但是还是有提高和改进的空间&#xff0c;针对具体应用场景下的检测难点&#xff0c;可以不同的改进方法。此后的系列文章&#xff0c;将重点对YOLOv8的如何改进进行详细的介绍&…

springboot健身房管理系统

springboot健身房管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取项目下载方式&#x1f345; 一、项目背景介绍&#xf…

python 模拟鼠标,键盘点击

信息爆炸 消息轰炸模拟鼠标和键盘敲击import time from pynput.keyboard import Controller as key_col from pynput.mouse import Button,Controller def keyboard_input(insertword):keyboardkey_col()keyboard.type(insertword)def mouth():mouseController()mouse.press(…

百因必有果,你的伯乐就是我

疫情虽去&#xff0c;可这三年&#xff0c;扑倒了生活&#xff0c;削弱了经济 但擎创一直奋斗在第一线 三年里人数不减反增&#xff0c;人数规模已达400 2023或许经济有所好转&#xff0c;但逃不出下行的趋势 各位千里马还是要找一个靠谱强劲的伯乐滴&#xff01; 下面关于…

Java代码瘦身,巧用 @Valid,@Validated 的分组校验和嵌套检验,实现高阶参数校验操作

导读 在 JavaEE 项目中&#xff0c; RestFull 层接收参数首先要对一些字段的格式进行校验&#xff0c;以防止所有查询都落到数据库&#xff0c;这也是一种合理的限流手段。以前基本上都是用 if...else...&#xff0c;这样的代码太啰嗦&#xff0c;除了使用策略模式进行优化&…

机械革命极光Pro开机错误重启无法进入桌面怎么办?

机械革命极光Pro开机错误重启无法进入桌面怎么办&#xff1f;有用户正常使用机械革命极光Pro电脑开机是&#xff0c;出现了无法开机进入系统桌面的情况&#xff0c;重新启动电脑依然无法解决问题。那么这个情况怎么去进行问题的解决呢&#xff1f;一起来看看以下的解决方法分享…

华为OD机试(20222023)考点分类

字符串,数组,集合操作 题库分值序号题目考点 or 实现

docker 形态构建redis 哨兵模式集群

主从模式介绍 哨兵是 Redis 的一种运行模式&#xff0c;它专注于对 Redis 实例&#xff08;主节点、从节点&#xff09;运行状态的监控&#xff0c;并能够在主节点发生故障时通过一系列的机制实现选主及主从切换&#xff0c;实现故障转移&#xff0c;确保整个 Redis 系统的可用…