文章目录

  • 前言
  • 1、Hello驱动
    • 1.1、APP打开的文件在内核中如何表示?
    • 1.2、打开字符设备节点时,内核中也有对应的struct file
    • 1.3、如何编写驱动程序?
    • 1.4、驱动程序代码
    • 1.5、如何阅读驱动程序?
    • 1.6、测试程序(APP)
    • 1.7、Makefile的编写及测试
  • 参考文章

前言

韦东山嵌入式Linux驱动开发基础知识学习笔记
文章中大多内容来自韦东山老师的文档,还有部分个人根据自己需求补充的内容
视频教程地址:https://www.bilibili.com/video/BV14f4y1Q7ti

  在Linux系统中有一切皆是文件的说法,那么可以理解一个设备也就是一个文件,在应用开发中我们会打开设备文件再对这个设备文件进行一系列的操作,这个设备文件时如何生成的呢?上层的open、close、read、write又是如何通知到内核执行到对应的函数的呢?

1、Hello驱动

1.1、APP打开的文件在内核中如何表示?

APP打开文件时,可以得到一个整数,这个整数被称为文件句柄。对于APP的每一个文件句柄,在内核里面都有一个“struct file”与之对应。

▲struct file

来看一下open函数:
  int open(const char *pathname, int flags, mode_t mode);
不难猜测到其中的flags、mode等参数和内核中的struct file结构体里(f_flags、f_mode)具有一一对应的关系
去读写文件时,文件的当前偏移地址也会保存在struct file结构体的f_pos成员里。

可以说APP中使用函数传递的参数在内核中就保存在这样的结构体中,所以每一个设备必定要有这样一个结构体

1.2、打开字符设备节点时,内核中也有对应的struct file

APP也会使用write、read之类的函数来和内核进行数据交换进而达到和设备进行数据交换的目的,那么这些函数在内核中又是如何生效的呢?

可以从下图看见在struct file结构体中有一个成员是一个常类型结构体struct file_operations指针类型变量

▲struct file

结构体struct file_operations中包含了许多指针函数,正是这些函数对应了APP中所调用的open、close、read、write函数,对于驱动的开发者来说需要为设备文件编写对应的函数

▲struct file_operations

1.3、如何编写驱动程序?

① 确定主设备号,也可以让内核分配
② 定义自己的file_operations结构体
③ 实现对应的drv_open/drv_read/drv_write等函数,填入file_operations结构体
④ 把file_operations结构体告诉内核:register_chrdev
⑤ 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数
⑥ 有入口函数就应该有出口函数:卸载驱动程序时,出口函数调用unregister_chrdev
⑦ 其他完善:提供设备信息,自动创建设备节点:class_create, device_create

1.4、驱动程序代码

要实现的功能:
A. 驱动中实现open, read, write, release,APP调用这些函数时,都打印内核信息
B. APP调用write函数时,传入的数据保存在驱动中
C. APP调用read函数时,把驱动中保存的数据返回给APP

#include <linux/module.h>#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/miscdevice.h>
#include <linux/kernel.h>
#include <linux/major.h>
#include <linux/mutex.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/stat.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/tty.h>
#include <linux/kmod.h>
#include <linux/gfp.h>/* 1. 确定主设备号                                                                 */
static int major = 0;
static char kernel_buf[1024];
static struct class *hello_class;#define MIN(a, b) (a < b ? a : b)/* 3. 实现对应的open/read/write等函数,填入file_operations结构体                   */
static ssize_t hello_drv_read (struct file *file, char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s line %d\n", __FUNCTION__, __LINE__);err = copy_to_user(buf, kernel_buf, MIN(1024, size));/* 返回copy的数据长度 */return MIN(1024, size);
}static ssize_t hello_drv_write (struct file *file, const char __user *buf, size_t size, loff_t *offset)
{int err;printk("%s line %d\n", __FUNCTION__, __LINE__);err = copy_from_user(kernel_buf, buf, MIN(1024, size));/* 返回copy的数据长度 */return MIN(1024, size);
}static int hello_drv_open (struct inode *node, struct file *file)
{printk("%s line %d\n", __FUNCTION__, __LINE__);return 0;
}static int hello_drv_close (struct inode *node, struct file *file)
{printk("%s line %d\n", __FUNCTION__, __LINE__);return 0;
}/* 2. 定义自己的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,
};/* 4. 把file_operations结构体告诉内核:注册驱动程序                                */
/* 5. 谁来注册驱动程序啊?得有一个入口函数:安装驱动程序时,就会去调用这个入口函数 */
static int __init hello_init(void)
{int err;printk("%s line %d\n", __FUNCTION__, __LINE__);//返回主设备号将hello_drv注册为名为hello的设备major = register_chrdev(0, "hello", &hello_drv);  /* /dev/hello *//* 创建一个类hello_class */hello_class = class_create(THIS_MODULE, "hello_class");err = PTR_ERR(hello_class);if (IS_ERR(hello_class)) {printk("%s line %d\n", __FUNCTION__, __LINE__);unregister_chrdev(major, "hello");return -1;}/* 创建hello_class类下面 major为主设备号 0为次设备号且名称为hello的设备 */device_create(hello_class, NULL, MKDEV(major, 0), NULL, "hello"); /* /dev/hello */return 0;
}/* 6. 有入口函数就应该有出口函数:卸载驱动程序时,就会去调用这个出口函数           */
static void __exit hello_exit(void)
{printk("%s line %d\n", __FUNCTION__, __LINE__);device_destroy(hello_class, MKDEV(major, 0));class_destroy(hello_class);unregister_chrdev(major, "hello");
}/* 7. 其他完善:提供设备信息,自动创建设备节点                                     */module_init(hello_init);
module_exit(hello_exit);/* 这个驱动文件遵循GPL协议 */
MODULE_LICENSE("GPL");

1.5、如何阅读驱动程序?

  阅读一个驱动程序,从它的入口函数开始,static int __init hello_init(void)就是入口函数。它的主要工作就是major = register_chrdev(0, "hello", &hello_drv); /* /dev/hello */,向内核注册一个file_operations结构体:hello_drv,这就是字符设备驱动程序的核心。
  file_operations结构体hello_drv在static struct file_operations hello_drv定义,里面提供了open/read/write/release成员,应用程序调用open/read/write/close时就会导致这些成员函数被调用。
  file_operations结构体hello_drv中的成员函数都比较简单,大多数只是打印而已。要注意的是,驱动程序和应用程序之间传递数据要使用copy_from_user/copy_to_user函数。

1.6、测试程序(APP)


#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>/** ./hello_drv_test -w abc* ./hello_drv_test -r*/
int main(int argc, char **argv)
{int fd;char buf[1024];int len;/* 1. 判断参数 */if (argc < 2) {printf("Usage: %s -w <string>\n", argv[0]);printf("       %s -r\n", argv[0]);return -1;}/* 2. 打开文件 */fd = open("/dev/hello", O_RDWR);if (fd == -1){printf("can not open file /dev/hello\n");return -1;}/* 3. 写文件或读文件 */if ((0 == strcmp(argv[1], "-w")) && (argc == 3)){len = strlen(argv[2]) + 1;len = len < 1024 ? len : 1024;write(fd, argv[2], len);}else{len = read(fd, buf, 1024);        buf[1023] = '\0';printf("APP read : %s\n", buf);}close(fd);return 0;
}

1.7、Makefile的编写及测试

这里要用module模块编译,可以通过下面的链接了解
简单实例讲解linux的module模块编译步骤

Makefile

KERN_DIR = /home/mi/100ask/100ask_stm32mp157_pro-sdk/Linux-5.4/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 cleanrm -rf modules.orderrm -f hello_drv_testobj-m    += hello_drv.o

其中的这一句需要分析一下

make -C $(KERN_DIR) M=`pwd` modules
#该命令是make modules命令的扩展,-C选项的作用是指将当前的工作目录转移到制定的 目录,即(KERN_DIR)目录,程序到(shell pwd)当前目录查找模块源码,将其编译,生成.ko文件。
#KERN_DIR表示内核源码目录,这种方式适用于嵌入式开发的交叉编译,KERN_DIR目录中包含了内核驱动模块所需要的各种头文件及依赖。
#M=$(shell pwd) | `pwd`选项让该Makefile在构造modules目标之前返回到模块源代码目录并在当前目录生成obj-m指定的xxx.o目标模块

参考文章:make -C $(KERN_DIR) M=pwd modules

编译生成文件中,开发板需要hello_drv.kohello_drv_test
ko文件可以通过insmod hello_drv.ko命令加载到内核,可以通过cat /proc/devices查看设备及其主设备号,这里的主设备号是241

▲加载ko和查看设备

也可以通过lsmod命令查看加载的ko

▲查看加载的ko

然后通过APP程序hello_drv_test来进行读写操作,在这里可能会遇到打印内核信息把APP信息冲刷掉的情况

▲APP 信息被内核信息冲刷掉

可以通过向/proc/sys/kernel/printk文件中写值的操作来关闭内核输出的一些信息

▲关闭printk信息输出至控制台

参考文章:linux下关闭printk打印信息以及通过网络查看方法

参考文章

简单实例讲解linux的module模块编译步骤
make -C $(KERN_DIR) M=pwd modules
linux下关闭printk打印信息以及通过网络查看方法

【嵌入式Linux】嵌入式Linux驱动开发基础知识之第一个驱动相关推荐

  1. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之总线设备驱动模型

    文章目录 前言 1.驱动编写的三种方法 1.1.传统写法 1.2.总线驱动模型 1.3.设备树驱动模型 2.Linux实现分离:Bus/Dev/Drv模型 2.1.Bus/Dev/Drv模型 2.2. ...

  2. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之设备树模型

    文章目录 前言 1.设备树的作用 2.设备树的语法 2.1.设备树的逻辑图和dts文件.dtb文件 2.1.1.1Devicetree格式 1DTS文件的格式 node的格式 properties的格 ...

  3. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之Pinctrl子系统和GPIO子系统的使用

    文章目录 前言 1.Pinctrl子系统 1.1.为什么有Pinctrl子系统 1.2.重要的概念 1.3.代码中怎么引用pinctrl 2.GPIO子系统 2.1.为什么有GPIO子系统 2.2.在 ...

  4. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之按键驱动框架

    文章目录 前言 1.APP怎么读取按键值 1.1.查询方式 1.2.休眠-唤醒方式 1.3.poll方式 1.3.异步通知方式 1.5. 驱动程序提供能力,不提供策略 2.按键驱动程序框架--查询方式 ...

  5. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED模板驱动程序的改造:设备树

    文章目录 前言 1.驱动的三种编写方法 2.怎么使用设备树写驱动程序 2.1.设备树节点要与platform_driver能匹配 2.2.修改platform_driver的源码 3.实验和调试技巧 ...

  6. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之驱动设计的思想:面向对象/分层/分离

    文章目录 前言 1.分离设计 驱动程序分析---程序分层 通用驱动程序---面向对象 个性化驱动程序---分离 APP 程序分析 前言 韦东山嵌入式Linux驱动开发基础知识学习笔记 文章中大多内容来 ...

  7. 【嵌入式Linux】嵌入式Linux驱动开发基础知识之LED驱动框架--面向对象、分层设计思想

    文章目录 前言 1.LED驱动程序框架 1.1.对于LED驱动,我们想要什么样的接口? 1.2.LED驱动要怎么写,才能支持多个板子?分层写 1.3.程序分析 驱动程序 应用程序 Makefile 1 ...

  8. linux课程_【课程完结】嵌入式Linux应用/驱动开发基础知识两大篇章已全部录制完毕 共72集...

    完结撒花 <第四篇嵌入式Linux应用开发基础知识> <第五篇嵌入式Linux驱动开发基础知识> 两大篇章已全部录制完毕 共计 72 集 01 嵌入式Linux应用开发基础知识 ...

  9. 驱动开发基础知识必修-(一)嵌入式开发板的启动过程

    驱动开发必修-嵌入式开发板的启动过程 简介 从打印日志入手 熟悉启动过程 1.执行u-boot程序 2.加载linux内核 3.挂载rootfs 4.加载完后 进入终端(命令输入行) UBOOT 1. ...

最新文章

  1. c+和python先学哪个比较好-C和Python我该先学什么?
  2. 解决activiti中由模板转换的流程图连线名称缺失问题
  3. mysql数据库的分离_数据库分离和附加 (SQL Server)
  4. c语言计算器括号怎么解决,C语言计算器,该如何解决
  5. 走自己的路,让国际米兰连胜去吧!(写给米兰球迷)
  6. linux ftp lcd 命令,Linux FTP命令使用实例
  7. 一个炒鸡好用的 indicator 开源指示器
  8. --go_out: protoc-gen-go: plugins are not supported问题处理
  9. 三维场景中创建镜面反射效果(three.js实战9)
  10. 将数字编号翻译为英文编号(python)实现
  11. 【示波器专题】示波器探头的负载效应
  12. 解决Direct local .aar file dependencies are not supported when building an AAR. The resulting AAR woul
  13. 已安装内存:16.0GB(3.93GB可用):拆开机箱拔掉内存条重新安装试试。
  14. Codeforces 298B Sail 题解
  15. 避免这些坑,让你快速找到好工作
  16. Windows使用任务计划执行批处理定时重启IIS部署的网站
  17. Camtasia Recorder
  18. 使用共享内存进行亲缘间通信
  19. 【身份证识别】基于形态学实现二代身份证号码识别系统matlab源码含GUI
  20. 会计相关计算机知识点,会计电算化知识点总结

热门文章

  1. 计算机科学与技术 物联网工,北京科技大学计算机与通信工程学院-王睿
  2. macos支持exfat吗_在Windows上使用VMware Workstation虚拟机安装macOS
  3. android手动创建数据表,Android开发—数据库应用—手动创建(SQLite)数据库--手动创建数据表(table)...
  4. 软件测试计划和测试报告
  5. 第十四篇 元类编程(二)
  6. Selenium +Java自动化环境安装
  7. 【P1714】切蛋糕(单调队列)
  8. Vue(八)发送跨域请求
  9. [BZOJ2879] [Noi2012] 美食节 (费用流 动态加边)
  10. 学习Coding-iOS开源项目日志(二)