目录
一、驱动认知
1.1 为什么要学习写驱动
1.2 文件名与设备号
1.3 open函数打通上层到底层硬件的详细过程
二、基于框架编写驱动代码
2.1 编写上层应用代码
2.2 修改内核驱动框架代码
2.3 部分代码解读
2.3.1 static的作用
2.3.2 结构体成员变量赋值方式
2.3.3 结构体file_operations(最终加载到内核驱动链表)
2.3.4 手动生成设备
三、驱动代码编译和测试
3.1 驱动框架的模块编译并发送至树莓派
①Makefile内添加生成.o命令
②模块编译生成.ko文件
③把.ko文件发送至树莓派
3.2 上层代码交叉编译发送至树莓派
3.3 树莓派装载驱动并运行
①树莓派加载内核驱动(insmod)
②运行上层代码(无权限)
③增加访问权限再运行
④检查是否执行成功:demsg指令查看内核打印信息

一、驱动认知
1.1 为什么要学习写驱动

树莓派开发简单是因为有厂家提供的wiringPi库,实现超声波,实现继电器操作,做灯的点亮…都非常简单。
但未来做开发时,不一定都是用树莓派,则没有wiringPi库可以用。但只要能运行Linux,linux的标准C库一定有。
学会根据标准C库编写驱动,只要能拿到linux内核源码,拿到芯片手册,电路图…就能做开发。
用树莓派学习的目的不仅是为是体验其强大便捷的wiringPi库,更要通过树莓派学会linux内核开发,驱动编写等,做一个属于自己的库。
1.2 文件名与设备号
linux一切皆为文件,其设备管理同样是和文件系统紧密结合。在目录/dev下都能看到鼠标,键盘,屏幕,串口等设备文件,硬件要有相对应的驱动,那么open怎样区分这些硬件呢?
依靠文件名与设备号。在/dev下ls -l可以看到


设备号又分为:主设备号用于区别不同种类的设备;次设备号区别同种类型的多个设备。
内核中存在一个驱动链表,管理所有设备的驱动。 驱动开发无非以下两件事:

编写完驱动程序
加载到内核 用户空间open后,调用驱动程序(驱动程序就是操作寄存器来驱动IO口,单片机51,32就是这种操作)

驱动插入到链表的位置(顺序)由设备号检索。
1.3 open函数打通上层到底层硬件的详细过程
用户空间调用open(比如open("/dev/pin4",O_RDWR))产生一个软中断(中断号是0x80),进入内核空间调用sys_call,这个sys_call在内核里面是汇编的,用Source Insight搜索不到。

sys_calll真正调用的是sys_open(属于VFS层虚拟文件系统,因为磁盘的分区和引脚分区不一样,为了实现上层统一化),根据你的设备名比如pin4去到内核的驱动链表,根据其主设备号与次设备号找到相关驱动函数。

调用驱动函数里面的open,这个open就是对寄存器的操作,从而设置IO口引脚电平。这件事对于单片机来说特变容易,就两句话搞定:

sbit pin4 = P1^4;
pin4=1;

(对应下图的粉色笔迹)

二、基于框架编写驱动代码
2.1 编写上层应用代码

目的是用简单的例子展示从用户空间到内核空间的整套流程。

根据上面提到的驱动认知,有个大致的概念,以open为例子:
上层open→sys_call→sys_open→内核驱动链表节点→执行节点里的open
当然,没有装载驱动的话这个程序执行一定会报错。只有在内核装载了驱动并且在/dev下生成了“pin4”这样一个设备才能运行。

接下来介绍最简单的字符设备驱动框架。

2.2 修改内核驱动框架代码
所谓框架,就是在往驱动链表里面加驱动的时候要符合内核规则,它是定死的东西,基本的语句必须要有,少一个都不行。

虽然有这么多的代码,但核心运行的就两个printk。

zd@ubuntu:~/SYSTEM/linux-rpi-4.14.y/drivers/char$ vi pin4driver2.c
#include <linux/fs.h>       //file_operations声明
#include <linux/module.h>    //module_init  module_exit声明
#include <linux/init.h>      //__init  __exit 宏定义声明
#include <linux/device.h>  //class  devise声明
#include <linux/uaccess.h>   //copy_from_user 的头文件
#include <linux/types.h>     //设备号  dev_t 类型声明
#include <asm/io.h>          //ioremap iounmap的头文件static struct class *pin4_class;
static struct device *pin4_class_dev;static dev_t devno;                //设备号
static int major =231;            //主设备号
static int minor =0;              //次设备号
static char *module_name="pin4";   //模块名 上层的名字//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n");  //内核的打印函数,和printf类似return 0;
}//pin4_write函数  因为上层需要open和write这两个函数
//            如果上层需要调用read等其他函数,可用SourceInsight去内核源码搜索,照着格式修改即可使用 在file_operations结构体里面
static ssize_t pin4_write(struct file *file1,const char __user *buf,size_t count, loff_t *ppos)
{printk("pin4_write\n");return 0;
}static struct file_operations pin4_fops = {//内核定义好的结构体 内核源码里有//就是驱动的结构体 要加载到内核驱动链表.owner = THIS_MODULE, .open  = pin4_open,  //上层有读 底层就要有open的支持.write = pin4_write, //上层有写 底层就要有write的支持
};int __init pin4_drv_init(void)   //驱动的真正入口
{int ret;devno = MKDEV(major,minor);//创建设备号 //********************注册驱动 加载到内核驱动链表***********//主设备号231 模块名pin4 上面的结构体ret   = register_chrdev(major, module_name,&pin4_fops); //注册驱动 告诉内核,把这个驱动加入到内核驱动的链表中pin4_class=class_create(THIS_MODULE,"myfirstdemo");  //由代码在/dev下自动生成设备  也可以手动生成设备pin4_class_dev =device_create(pin4_class,NULL,devno,NULL,module_name);  //创建设备文件return 0;
}void __exit pin4_drv_exit(void)
{device_destroy(pin4_class,devno);       //删除设备  /dev底下的 上面也是创建了设备和类class_destroy(pin4_class);              //删除类unregister_chrdev(major, module_name);  //卸载驱动 就是删除链表节点的驱动}module_init(pin4_drv_init);  //入口:内核加载驱动的时候,这个宏(module_init它不是个函数)会被调用,而真正的驱动入口是它里面调用的函数
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

2.3 部分代码解读
2.3.1 static的作用

内核代码数量庞大,为了防止与其他的文件有变量命名冲突,static限定变量的作用域仅仅只在这个文件。内核源码里面运用了大量的static,因为内核源码众多,一万五千多个C文件,很容易造成代码命名的冲突。

2.3.2 结构体成员变量赋值方式

static struct file_operations pin4_fops = {.owner = THIS_MODULE,.open  = pin4_open,.write = pin4_write,
};

这是内核代码中常见的对结构体的操作方式,单独给指定结构体某些元素赋值。

注意:在keil的编译工具环境中不允许这样写,linux可以。

2.3.3 结构体file_operations(最终加载到内核驱动链表)
在SourceInsight中查看结构体file_operations,可以发现很多的函数指针(指向函数的指针,函数内进行一些程序的执行),这些函数名跟系统上层对文件的操作差不多。(read,write,llseek)(在课程视频9:36)

如果上层想要实现read,就复制过来,按照格式改一改就能使用。
上层对应底层,上层想要用read,底层就要有read的支持。

2.3.4 手动生成设备
框架中有自动生成设备的代码,那么手动生成设备是怎么样的呢?(一般不这样干,麻烦,仅作为演示)

进入/dev目录,查看帮助可知道创建规则
sudo mknod 设备名称 设备类型 主设备号 次设备号

使用如下命令创建名称为zhu,主设备号为8,次设备号为1的字符设备。

sudo mknod zhu c 8 1

用 ls -l可以看到已经创建成功

三、驱动代码编译和测试
3.1 驱动框架的模块编译并发送至树莓派

在ubuntu中,进入Linux内核源码(前一章节编译好的)字符设备驱动目录linux-rpi-4.14.y/drivers/char(IO口属于字符设备驱动)。进入源码目录下的原因是,写驱动必须要链接到源码(源码定义好了结构体等等),必须要有源码。
拷贝上文分析过的驱动框架代码,拿到这个文件夹下 ,并创建成名字为pin4drive.c的文件


①Makefile内添加生成.o命令
进行配置,使得工程编译时可以编译这个文件
当然不一定要放在/char下。但注意:放在哪个文件夹下,就修改那个文件夹的Makefile即可。


模仿这些文件的编译方式,以编译成模块的形式(还有一个方式为编译进内核)编译pin4drive.c
在Makefile里面添加:

obj-m                           += pin4drive2.o

m就是模块的形式
②模块编译生成.ko文件
之前编译内核镜像的时候用的是这个命令:

现在只需进行模块编译,不需要生成zImage,dtbs文件;
回到源码目录/linux-rpi-4.14.y再执行下面指令

注:如果说编译中途提示出错,照着错误提示去修改.c文件即可,和上层编译类似。
编译完成生成的一些文件如下:

③把.ko文件发送至树莓派

scp pin4drive.ko pi@192.168.101.19:/home/pi(别人视频的地址)

之前犯的一个小错误是树莓派和ubuntu的ip地址一样,导致连接不上,修改树莓派的ip地址即可

3.2 上层代码交叉编译发送至树莓派
拷贝上文分析的上层代码到ubuntu中,此处我命名为pin4test.c

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>int main()
{int fd;fd = open("/dev/pin4",O_RDWR);if(fd < 0){printf("open failed\n");perror("reson");}else{printf("open success\n");}fd = write(fd,'1',1);//写一个字符'1',写一个字节return 0;
}

使用交叉编译工具在Linux进行编译

arm-linux-gnueabihf-gcc pin4drivertest.c -o pin4test

发送至树莓派

scp pin4test pi@192.168.101.19:/home/pi

3.3 树莓派装载驱动并运行
①树莓派加载内核驱动(insmod)

sudo insmod pin4drive2.ko

查看是否已经成功添加驱动

可以去设备下查看

ls /dev/pin4 -l

看到驱动添加成功,主设备号231,次设备号0,和内核里面的代码对应上。

或者lsmod查看内核挂载的驱动

如果需要卸载驱动,就sudo rmmod pin4drive

②运行上层代码(无权限)

./pin4test

发现没有对设备pin4的访问权限

crw是超级用户所拥有的权限,而框中两类用户则无读写的权限(下面有详细说明)

③增加访问权限再运行
解决方法1:加超级用户

sudo ./pin4test

解决方法2:增加“所有用户都可以访问的权限”(建议)

sudo chmod 666 /dev/pin4

运行成功:

拓展 >> chmod 命令用于更改文件/文件夹的属性(读,写,执行)

permission to:  user(u)   group(g)   other(o)     /¯¯¯\      /¯¯¯\      /¯¯¯\
octal:            6          6          6
binary:         1 1 0      1 1 0      1 1 0
what to permit: r w x      r w x      r w xwhat to permit - r: read, w: write, x: executepermission to  - user: the owner that create the file/foldergroup: the users from group that owner is memberother: all other users

EG: chmod 744 仅允许用户(所有者)执行所有操作,而组和其他用户只允许读。

④检查是否执行成功:demsg指令查看内核打印信息
用dmesg命令显示内核缓冲区信息,并通过管道筛选与pin4相关信息

dmesg | grep pin4

第三阶段:43-47.树莓派基于Linux内核驱动开发相关推荐

  1. 树莓派基于Linux内核驱动开发详解

    一.驱动认知 首先理解Linux内核框图 文件系统认知,Linux内核框图 1.什么是驱动 linux内核驱动.软件层面上的驱动 广义上是指:这一段代码操作了硬件去动,所以这一段代码就叫硬件的驱动程序 ...

  2. 树莓派基于Linux内核驱动开发

    一.驱动认知 1.1 为什么要学习写驱动 树莓派开发简单是因为有厂家提供的wiringPi库,实现超声波,实现继电器操作,做灯的点亮-都非常简单. 但未来做开发时,不一定都是用树莓派,则没有wirin ...

  3. linux内核下网络驱动流程,基于Linux内核驱动的网络带宽测速方法与流程

    本发明涉及一种测速方法,尤其是一种网络带宽测速方法. 背景技术: :电信运营商为客户提供一定带宽的Internet接入:为了检验带宽是否达标,一般均由客户使用个人电脑在网页上直接测速.但是随着智能网关 ...

  4. 嵌入式 Linux 内核驱动开发【The first day: 36093万字】

    嵌入式 Linux 内核驱动开发[1] 嵌入式 Linux 内核驱动开发前言 第1章 Linux 内核裁剪和定制 [1]Linux 内核开发简介 [2] Linux 源码阅读工具 [1.2.1]Sou ...

  5. Linux内核驱动开发的EXPORT_SYMBOL

    前言 很抱歉各位粉丝啊,博主好久没有更新原创文章,从今天起开始恢复写文章的时光.前段时间各种不顺利,到时心情低落.现在恢复状态了. 简介 本文主要来讲讲Linux内核驱动中,EXPORT_SYMBOL ...

  6. linux 内核驱动开发

    一.为什么要学习内核? 有些人要学习内核,而有些人则可以不学习它.你如果以后要从事系统研发或驱动开发的话,就要学习内核. 刚刚接触内核,主要学习内核的接口函数.不要深入的去读内核,因为你读也读不懂,内 ...

  7. linux内核驱动开发 培训,嵌入式Linux驱动开发培训 - 华清远见教育集团官网

    9.LINUX下USB驱动开发基础 9.1 USB规范介绍 9.2 USB主机控制器 9.3 USB HUB 9.4 USB设备状态 9.5 USB描述符 9.6 USB请求 9.7 USB通讯数据格 ...

  8. Linux内核驱动开发-USB热插拔信息调取

    前言: 前段时间上科大嵌入式安卓开发溜了个作业,开发一个驱动,可以实现读取USB热插拔信息,程序调用显示USB设备名称和插拔时间.代码已经放在了我的Github上,供大家参考. 思路: USB热插拔的 ...

  9. Linux 内核驱动开发基础

    1.裸板驱动和linux驱动的异同点 裸板驱动:uart驱动程序:uart_inituart_putsuart_getsi2c控制器驱动:i2c_starti2c_stopi2c_txi2c_rxg- ...

最新文章

  1. BZOJ4766: 文艺计算姬
  2. 操作系统,看这一篇足够了!
  3. 用keil仿真程序,出现 EVALUATION MODE Running with Code Size Limit:2K
  4. python最高版本-python最新版
  5. 微信小程序打开PDF
  6. vue 后台返回的文件流进行预览_vue项目-pdf预览和下载,后台返回文件流形式
  7. 高级流程图_数据收集方法之流程图
  8. 【Linux】一步一步学Linux——dig命令(160)
  9. vue中waiting for update signal from wds_10个vue快捷开发技巧助你成为中级前端工程师!(二)...
  10. 原生App切图的那些事儿
  11. 使用C语言编程求解: 1 - 1/2 + 1/3 - 1/4 + 1/5 - ... + 1/99 - 1/100 的值。
  12. [论文笔记]FusionNet: Fusing via Fully-Aware Attention with Application to Machine Comprehension
  13. Linux 如何配置 SFTP 来代替单一ftp应用
  14. 【CDAS峰会】吴喜之:数据科学的未来发展
  15. 日常工作记录---在虚拟机中进行slam建图
  16. 音乐迷(无损音乐下载器)
  17. java qq邮箱登录_SpringBoot实现QQ邮箱注册和登录
  18. BUCTOJ周赛(5)问题 E: 数学(线性DP+LCS)
  19. 用PayPal在eBay上撸货加哪种卡可以长期用?
  20. 2022-7-8 Leetcode 904.水果成篮

热门文章

  1. 距2022高考还有10天,让我们一起为届高考生加油吧
  2. 上计算机课的日记,上电脑课日记150字.doc
  3. 2021年全球与中国汽车夏季轮胎行业市场规模及销售渠道分析
  4. android 揭示动画,Android进阶设计 | 使用揭露动画(Reveal Effect)做一个丝滑的Activity转场动画...
  5. windows cmd 自定义关机,重启,休眠
  6. emeditor正则表达式_掌握EmEditor正则表达式语法,大大提升工作效率
  7. jh锂电保护电路_干货 | 分享几款常用的保护电路
  8. 关于网页中的文本选择以及统计选中文本长度
  9. 解决网页上的文字不能复制
  10. 什么是AOP?AOP面向切面编程