当前位置: 首页 > news >正文

Linux驱动学习day1

通用的驱动框架

linux下一切皆文件

1、构造file_operations结构体

实现功能APP调用read函数,把驱动中保存的数据放回给APP

APP调用write函数,传入数据保存在驱动中

构造这个结构体,需要什么函数就在里面先写,然后到外面去创建该函数。

static struct file_operations hello_drv = {.owner       = THIS_MODULE,.open        = hello_drv_open,.read        = hello_drv_read,.wirte       = hello_drv_write,.release     = hello_drv_close,
}

2、实现对应的open/read/write函数

ssize_t (*read) (struct file* , char __user* , size_t , loft_t *);ssize_t (*write) (struct file* , const char __user* , size_t , loft_t *);
static ssize_t hello_drv_read (struct file* file, char __user* buf, size_t size, loft_t * offset)
{/*  __user表示其buf来自用户空间,不能直接访问 */printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);/* copy_to_user(void * to , const void * from , unsigned long n); */copy_to_user(buf , kernel_buf , MIN(size , 1024));return MIN(size , 1024);
}static ssize_t hello_drv_write (struct file* file, char __user* buf, size_t size, loft_t * offset)
{/*  __user表示其buf来自用户空间,不能直接访问 */printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);/* copy_from_user(kernel_buf , const void__user * from , unsigned long n); */copy_from_user(kernel_buf , buf , MIN(size , 1024));return MIN(size , 1024);
}static int hello_drv_open (struct inode* node , struct file* file)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);return 0;
}static int hello_drv_close (struct inode* node , struct file* file)
{printk("%s %s %d\n" , __FILE__ , __FUNCTION__ , __LINE__);return 0;
}

3、将file_operations结构体告诉内核(在入口函数)

/* static inline int register_chrdev() */
static int __init hello_init(void)
{major = register_chrdev(0 , "hello_drv" , &hello_drv);/* 这里参数1填0是让驱动找到chardrv数组(理解成数组)中空的地方,并且返回该地方的索引值为主设备号 */return 0;
}

4、unregister(在出口函数)

/* unregister_chrdev() */
static void __exit hello_exit(void)
{unregister_chrdev(major , "hello_drv");
}

hello_drv驱动代码

#include <linux/module.h>     // 最基本模块宏
#include <linux/kernel.h>     // printk
#include <linux/init.h>       // __init/__exit
#include <linux/fs.h>         // register_chrdev 等
#include <linux/uaccess.h>    // copy_to_user, copy_from_user
#include <linux/types.h>      // dev_t, bool 等类型static int major = 0;
static char kernel_buf[1024];
#define MIN(a,b)    (a < b ? a : b)/* funciton */static ssize_t hello_drv_read (struct file *file, char __user *buf , size_t size, loff_t * offset)
{printk(KERN_INFO "[hello_drv] %s:%d\n" , __FUNCTION__ , __LINE__);if(copy_to_user(buf, kernel_buf, MIN(1024 , size)) != 0){return -1;} return MIN(1024 , size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf , size_t size, loff_t * offset)
{printk(KERN_INFO "[hello_drv] %s:%d\n" , __FUNCTION__ , __LINE__);if(copy_from_user(kernel_buf, buf, MIN(1024,size)) != 0){return -1;}return MIN(1024 , size);
}static int hello_drv_open (struct inode *node, struct file *file)
{printk(KERN_INFO "[hello_drv] %s:%d\n" , __FUNCTION__ , __LINE__);return 0;
}static int hello_drv_close (struct inode *node, struct file *file)
{printk(KERN_INFO "[hello_drv] %s:%d\n" , __FUNCTION__ , __LINE__);return 0;
}/*  create struct file_operations */
static struct file_operations hello_drv = 
{.owner          = THIS_MODULE,.open           = hello_drv_open,.read           = hello_drv_read,.write          = hello_drv_write,.release        = hello_drv_close,
};/* register_chrdev */
static int __init hello_init(void)
{major = register_chrdev(0 , "hello_drv", &hello_drv);return 0;
}/* entry function */
static void __exit  hello_exit(void)
{unregister_chrdev(major, "hello_drv");return;
}module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");

编译驱动程序

这里我用了自己的makefile,我用了韦东山老师和原子哥的makefile均编译不了,有的原因是找不到交叉编译器等,或者某个头文件。我显示指定了交叉编译器等。

# 内核源码路径
KERN_DIR := /home/dd/RK3568/SDK/linux/rk3568_linux_sdk/kernel
CURRENT_PATH := $(shell pwd)# 驱动模块目标
obj-m := hello_drv.o# 交叉编译工具链
CROSS_COMPILE ?= /home/dd/RK3568/SDK/linux/rk3568_linux_sdk/prebuilts/gcc/linux-x86/aarch64/gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu/bin/aarch64-linux-gnu-
ARCH ?= arm64# 默认目标:编译模块 + 用户程序
build: kernel_modules hello_test# 编译内核模块
kernel_modules:$(MAKE) -C $(KERN_DIR) M=$(CURRENT_PATH) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) modules# 编译用户态测试程序
hello_test: hello_test.c$(CROSS_COMPILE)gcc -o hello_test hello_test.c# 清理所有编译产物
clean:$(MAKE) -C $(KERN_DIR) M=$(CURRENT_PATH) ARCH=$(ARCH) CROSS_COMPILE=$(CROSS_COMPILE) cleanrm -f hello_test

使用make命令即可生成.ko驱动文件。

hello_test测试文件编写

这里就是之前常使用的标准库的调用,但是打开的是之前创建好的驱动文件。

#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>int main(argc , char*argv[])
{int fd = 0;int len = 0;char buf[1024] = {0};if(argc < 2){    printf("Usage:\n");/* <dev> 表示参数必须含有这个设备文件名  , [string] 表示可有也可没有 */printf("%s <dev> [string]\n" , argv[0]);return -1;}/*open*/fd = open(argv[1] , O_RDWR);if(fd < 0){printf("can not open file\n");return -1;}if(argc == 3){    /*write*/len = write(fd , argv[2] , strlen(argv[2]));if(len < 0){printf("write error");return -1;}printf("write len:%d\n" , len);}else{    /*read*/len = read(fd , buf , strlen(buf) - 1);if(len < 0){    printf("read error\n");}printf("read buf:%s\n" , buf);}/*close*/close(fd);return 0;
}

导入板子

使用的是nfs方法导入,首先先创建共享目录 , 然后在Linux 主机端添加环境变量,设置前先要看板子上是否能ping通主机IP

sudo exportfs -ra
/home/dd/nfs_share xx.xx.xx.xxx(rw,wdelay,no_root_squash,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)

xx.xx.xx.xxx是Linux开发板的IP地址 , 前面那个目录是共享目录。 

添加变量之后使用下面命令可查看刚刚添加的变量

sudo exportfs -v

 我的机子需要关闭防火墙,不然板子上的命令一直被PC端拒绝。

sudo ufw disable

设置好上述环境,在板子上输入(开发板要先建好共享目录文件夹)

mount -t nfs -o vers=3,nolock xx.xx.xx.xxx:/home/dd/nfs_share /mnt/driver_project/

执行上述命令,切换到共享目录执行ls即可看到共享文件。

在共享文件中用insmod hello_drv.ko(手动加载内核模块)命令,使用cat /proc/devices 即可看到生成的驱动hello_drv。 236就是主设备号。

然后创建结点mknod /dev/hello c 236 0 。之后就可以运行测试脚本了,读写均没有问题。

总结

阅读驱动源码的工具好难调(一开始时间都比较多花费在配置环境等上面),用vscode+clangd确实是很方便(配置好环境)。驱动学习加油!

http://www.lqws.cn/news/170803.html

相关文章:

  • NodeJS Koa 后端用户会话管理,JWT, Session,长短Token,本文一次性讲明白
  • tpc udp http
  • ONLYOFFICE协作空间3.1.1 企业版 介绍及部署说明:家庭云计算专家
  • Playwright 测试框架 - .NET
  • C# WPF 左右布局实现学习笔记(1)
  • Spring Boot 类加载机制深度解析
  • 媒体新闻发稿:选择适合自己的媒体
  • Java持久层技术对比:Hibernate、MyBatis与JPA的选择与应用
  • 终极数据结构详解:从理论到实践
  • html css js网页制作成品——HTML+CSS榴莲商城网页设计(4页)附源码
  • 令牌桶 滑动窗口->限流 分布式信号量->限并发的原理 lua脚本分析介绍
  • Maven相关问题:jna版本与ES冲突 + aop失效
  • Redis——1、服务端高并发分布式结构演进之路
  • OpenAI对抗法庭命令:捍卫ChatGPT用户隐私之战
  • 分布式Session处理的五大主流方案解析
  • k8s下离线搭建elasticsearch
  • 408第一季 - 数据结构 - 线性表II
  • uniapp 集成腾讯云 IM 消息搜索功能
  • 大量企业系统超龄服役!R²AIN SUITE 一体化企业提效解决方案重构零售数智化基因
  • 如何更改默认 Crontab 编辑器 ?
  • Unity基于GraphView的可视化关卡编辑器开发指南
  • 使用VuePress2.X构建个人知识博客,并且用个人域名部署到GitHub Pages中
  • Podman 和 Docker
  • 每日算法刷题Day24 6.6:leetcode二分答案2道题,用时1h(下次计时20min没写出来直接看题解,节省时间)
  • 微信小程序- 用canvas生成排行榜
  • 本地部署Qwen3
  • ComfyUI 中如何使用 Depth ControlNet SD1.5
  • 基于uniapp+WebSocket实现聊天对话、消息监听、消息推送、聊天室等功能,多端兼容
  • 27.【新型数据架构】-数据共享架构
  • 如何让其他品牌更难转化走我们的用户?