一个字符设备或者块设备都有一个主设备号和次设备号。主设备号和次设备号统称为设
备号。主设备号用来表示一个特定的驱动程序。次设备号用来表示使用该驱动程序的各
设备。
主设备号用来区分不同种类的设备(华为),而次设备号用来区分同一类型的多个设备(华为mate40)。

从用户态空间贯穿到底层驱动

Linux系统中对于文件的所有操作离不开三个通用的API函数:open、read、wirte,
例如使用函数open打开一个引脚4文件 pin4 到与底层硬件交互时,一共会经过以下三步:

  • 在用户态空间调用函数 OPEN 触发进程 open("/dev/pin4",O_RDWR),产生一个软中断(中断号
    0x80)进入内核空间;
  • 内核会调用函数 sys_call(System call interface),从sys_call到sys_open中间实际上经过一层虚拟文件系统VFS,做到了不同文件系统但有了统一的接口, 根据设备的文件名在内核的驱动链表中寻找设备的设备号(分为主设备号、次设备号);
  • 在寻找到设备号之后调用函数sys_open,再调用对应的驱动文件里的open,最终实现寄存器的电平操作。「在最终的硬件动作方面,单片机与Linux本质上是一样的

注意:

  1. 内核代码的编写与上层代码的编写有不同之处。内核里的文件代码是一个异常巨大的结构,若你希望在内核里的驱动链表里添加驱动文件,那就必须遵循链表的操作规则,即
    驱动框架。
  2. 上层与底层的代码实际上是一一对应的,上层opne("/dev/pin4",O_RDWR)底层一定有pin4_open,操作函数read、write同理。
  3. 对于其他底层的驱动文件也是类似的,open、read、write都是被放在大的结构体之后才加入到驱动链表中。
  4. 不同于系统的上层文件,底层的驱动框架文件中对于变量的声明有大量的static。之所以大量充斥着该函数,是因为整个内核文件数量庞大足足有上万个之多,容易与其他文件的函数命名冲突,函数static限定了变量的作用域仅在该文件中。

驱动

两个部分,第一部分为驱动的加载部分,第二部分为驱动加载完成后,驱动的使用时的调用过程。

驱动加载:

1、将驱动源代码编译后,生成ko文件,这是将要加载的驱动模块。2、调用命令insmod加载模块,首先会找代码里边固定的宏moudle_init()来找到驱动中的初始化函数这里是s5pv210_led_init()和退出函数s5pv210_led_exit()。

3、调用s5pv210_led_init()来进行设备号注册,和设备添加。MKDEV是一个宏,可以通过移位把主设备号和次设备号进行处理,生成一个32位的数据。调用register_chrdev_region()注册设备号,linux驱动根据散列hash表来建立设备描述cdev结构体的索引,当hash表的index冲突时,采用链表的方式避免冲突,这样可以通过设备号快速找到cdev结构体的地址。

4、调用cdev_init()来初始化结构体cdev,最重要的是将file_operations保存在cdev中,file_oerations里边有本地实现的open release ioctl等具体功能的函数指针,这样可以在使用驱动的时候找到相应的实现函数。

5、调用cdev_add()来添加设备结构体cdev到hash表中,根据参数设备号可以找到注册设备号时在hash表中的位置,然后将cdev结构体地址添加进去

6、映射io端口,即映射io端口的物理地址为虚拟地址,因为在系统运行时,外设的I/O内存资源的物理地址是已知的,由硬件的设计决定,可以在数据手册中找到。但是CPU通常并没有为这些已知的外设I/O内存资源的物理地址预定义虚拟地址范围,驱动程序并不能直接通过物理地址访问I/O内存资源,而必须将它们映射到核心虚地址空间内,然后才能根据映射所得到的核心虚地址范围,通过访问内存指令访问这些I/O内存资源。

7、使用命令mknod 添加/dev目录下设备描述文件,其实主要就是描述了我们输入的三个参数,首先c代表字符型设备,500代表主设备号,0代表次设备号。三个参数的用法在下边的流程描述。

8、调用rmmod命令后,卸载驱动,找到驱动中moudle_exit()宏来找到卸载驱动的退出函数,在这个里边调用cdev_del()和unregister_chrdev_region()删除设备并且去掉设备号的注册,相反的过程,不用赘言。

驱动使用:

1、在驱动测试文件中,首先打开了/dev目录下的设备文件,但是这个文件只是设备基本信息的描述,没有实质的动作,具体的作用可以看作为设备的索引,通过打开文件可以找到设备驱动的位置。这里分析我们输入的三个参数,c表示字符型设备open系统调用中拿到c就知道要去找字符型设备的结构体。主设备号和次设备号用来索引hash散列表,找到cdev结构体的地址,而cdev中保存有file_operations的地址,就可以找到驱动的具体实现函数,就是驱动加载的逆过程。而open执行完之后,返回一个文件描述符,这个描述符中就带有找到的cdev地址。

2、Open函数根据得到的cdev找到file_operations 中的.open对应的的函数指针,调用这个函数来初始化驱动,这里可以做io端口控制寄存器的设置,将相关的端口设置为输出。

3、调用ioctl通信,ioctl是io管道管理函数,是linux系统封装用来给驱动用的通信函数,方便用户使用,不用关心通信的实现方式,也不用考虑通信是否跨线程或者跨进程,可以看作是一个通道,在使用时塞入数据,在驱动里边写好拿出数据并做相应的处理及可以,感觉非常像socket套接字。

4、测试文件中调用ioctl传入数据,ioctl根据传入的文件描述符参数中的cdev找到file_operations 中的. unlocked_ioctl对应的的函数指针,在这个函数里边调用copy_from_user(),可以取出传入的参数,根据参数做驱动对应的动作。

5、退出驱动后,跟2-4同样的道理找到.release执行,释放驱动。

学习链接:https://blog.csdn.net/weixin_46959681/article/details/117962761

上层空间到底层内核驱动的逻辑调用

修改引脚驱动文件 pin4Driver.c(GPIO)
演示代码: raspberryGPIO.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"; //模块名//函数volatil确保指令不被编译器优化,且要求每次直接读取寄存器里的器。
volatile unsigned int* GPFSEL0 = NULL;
volatile unsigned int* GPSET0  = NULL;
volatile unsigned int* GPCLR0  = NULL;//pin4_open函数
static int pin4_open(struct inode *inode,struct file *file)
{printk("pin4_open\n");*GPFSEL0 &= ~(0x6 << 12);//将寄存器12-14位置001 *GPFSEL0 |= (0x1 << 12);//将寄存器12位置1return 0;
}//函数pin4_write
static ssize_t pin4_write(struct file *file1,const char __user *buf,size_t count, loff_t *ppos)
{int cmd;printk("pin4_write\n");//函数copy_from_user —— 获取上层应用空间的数据。copy_from_user(&cmd,buf,count);if(cmd == 1){printk("set 1.\n");*GPSET0 |= 0x1 << 4;//将引脚4设置为1}else if(cmd == 0){printk("set 0.\n");*GPCLR0 |= 0x1 << 4;//将引脚4设置为0}else{printk("error.\n");}printk("%d\n",cmd);return 0;
}static struct file_operations pin4_fops = {.owner = THIS_MODULE,.open  = pin4_open,.write = pin4_write,
};int __init pin4_drv_init(void) //真实驱动入口
{int ret;printk("insmod driver pi4 success.\n");//1.创建主设备、次设备号。devno = MKDEV(major,minor);  printk("drive init succeed\n");//2.注册驱动,告诉内核,把这个驱动加入到驱动链表。ret = register_chrdev(major,module_name,&pin4_fops);  //3.加载驱动代码文件时自动操作路径“/root/dev”下生成驱动设备。pin4_class = class_create(THIS_MODULE,"myfirstdriver");//4.创建设备文件pin4_class_dev = device_create(pin4_class,NULL,devno,NULL,module_name);  //函数 ioremap —— 将IO的物理地址映射内存的虚拟地址。GPFSEL0 = (volatile unsigned int *)ioremap(0x3f200000,4);GPSET0  = (volatile unsigned int *)ioremap(0x3f20001C,4);GPCLR0  = (volatile unsigned int *)ioremap(0x3f200028,4);return 0;
}void __exit pin4_drv_exit(void)
{   //函数iounmap —— 与装载驱动的IO映射相对应,卸载驱动时必须解除映射。iounmap(GPFSEL0);iounmap(GPSET0);iounmap(GPCLR0);//1.销毁设备device_destroy(pin4_class,devno);//2.销毁类文件class_destroy(pin4_class);//3.卸载驱动unregister_chrdev(major, module_name);
}module_init(pin4_drv_init);
module_exit(pin4_drv_exit);
MODULE_LICENSE("GPL v2");

修改上层应用的测试代码 pin4Test2.c
上层测试代码: pin4Test2.c

#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>int main()
{int fd;int cmd;fd = open("/dev/pin4",O_RDWR);if(fd < 0){perror("Open failed:");}else{printf("Open success.\n");}printf("Please input number 0/1, 0 - Low Level, 1 - high Level.\n");scanf("%d\n",&cmd);if(cmd == 0){printf("pin4 is low level.\n");}if(cmd == 1){printf("pin4 is high level.\n");}write(fd,&cmd,4);return 0;
}

编译两个文件

  1. 引脚驱动文件编译。 将引脚的驱动文件放在系统的字符驱动文件目录下home/xxx/linux-rpi-4.14.y/drivers/char,直接 vi 修改引导配置文件Makefile,在文件中直接插入字段 obj-m += pin4Driver.o(前者代表编译的方式,后者代表编译生成的文件。)
  2. 模块化编译。 在第一步的前提下跳转回到系统源码目录下 home/xxx/linux-rpi-4.14.y/,输入指令进行交叉编译:ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf-KERNEL=kernel7 make -j4 modules。等待编译完成后再次打开字符设备驱动文件,编译成功则目录底下会生成新的文件pin4Driver.ko。“j4”表示使用4个核进行编译工作,“modules”表示生成的驱动模块
  3. 上层应用测试代码编译。上层的文件处理比较简单,直接对其进行交叉编译处理即可,输入指令:arm-linux-gnueabihf-gcc pin4Test.c -o pin4Test

在树莓派内加载驱动

  1. 驱动加载
    输入指令:sudo insmod pin4Driver.ko。查看驱动文件是否加载成功有两个方面:
    输入指令:ls /dev/pin4 -l ,查看设备文件下是否有设备文件;
    输入指令:lsmod ,查看内核是否挂载设备驱动文件 pin4Driver.ko
  2. 设备权限设置
    输入指令:sudo chmod 666 /dev/pin4
    【666 ,表示所有人都可以访问该设备。】
  3. 测试
    在树莓派主目录 pi@home 下运行文件 ./pin4Test。注意,此时上层界面是没有任何显示的,真正的运行现场是在内核空间。输入指令dmesg | grep pin4 ,可以查看内核中调用函数 printk() 成功,完成了从最上层到最底层的逻辑调用。
    树莓派gpio readall查看引脚4的模式,电平变化。

2021-07-28嵌入式学习---驱动相关推荐

  1. 2021.07.28

    WPF(Windows Presentation Foundation)是微软推出的基于Windows 的用户界面框架,属于.NET Framework 3.0的一部分. WPF的特点:程序人员与美工 ...

  2. 2021/9/28 python学习笔记:np.argmax、np.delete

    1.np.argmain,np.argmax函数:找最大最小值索引,可按轴查找 import numpy as npc = np.array([[4,7,0,9],[3,6,2,4],[9,8,0,3 ...

  3. 新手如何理解一个Web应用的构建(2021.07.29更新)

    Web应用构建 ==前言== 我为什么写这篇文章? 适合什么样的人看? ==摘要== 步骤汇总 (1)网站定位与功能设定 (2)信息架构 (3)UI设计 (4)应用架构 (5)开发 (6)部署 (7) ...

  4. 哔哩哔哩“2021.07.13 我们是这样崩的”报告的学习-1

    哔哩哔哩"2021.07.13 我们是这样崩的"报告的学习-1 这份报告是我学计算机两年来第一次真实看到大厂的员工到底在干什么.出现了很多专有名词,以及当前最先进的互联网企业的应用 ...

  5. 嵌入式Linux驱动笔记(五)------学习platform设备驱动

    你好!这里是风筝的博客, 欢迎和我一起交流. 设备是设备,驱动是驱动. 如果把两个糅合写一起,当设备发生变化时,势必要改写整个文件,这是非常愚蠢的做法.如果把他们分开来,当设备发生变化时,只要改写设备 ...

  6. IMX6ULL嵌入式Linux驱动学习笔记(二)

    IMX6ULL嵌入式Linux驱动学习 一.字符设备驱动 二.驱动模块的加载与卸载 三.字符设备的注册与注销 四.设备号 五.file_operations的具体实现 六.字符设备驱动框架 七.编写应 ...

  7. 嵌入式学习之Linux驱动篇-迅为视频更新了

    想学习Linux驱动但是无从下手的同学,学习Linux驱动但是一直不能入门的同学,学习了很多视频和资料还是很懵的同学快来学习拉 https://www.bilibili.com/video/BV1Vy ...

  8. 嵌入式Linux驱动开发【学习小结】

    文章目录 前言 一.嵌入式Linux驱动程序和单片机裸奔有啥区别? 二.为什么需要嵌入式Linux驱动开发 三.驱动程序框架大致演变过程 总结 前言 随着去嵌入式设备资源不断丰富,主频不断升高,搭载操 ...

  9. PCL点云库安装及学习(2021.7.28)

    PCL点云库学习 2021.7.28 1.PCL简介 2.Win10系统下PCL环境配置 2.1 前提环境(Win10 64位+Visual Studio 2015) 2.2 方式一:源码编译(过程繁 ...

最新文章

  1. Alisql源码编译安装(详细篇)
  2. Java序列化技术与Protobuff
  3. squid cache_peer 参数详解
  4. Java-J2SE专题复习
  5. 在一表中设置组合主键(两个字段组合成一个主键)
  6. 这人说的有意思,哈 哈
  7. Request_获取ServletContext
  8. pcre安装_Nginx | Nginx的介绍和安装
  9. python之路-08-集合
  10. CentOS查看硬件情况
  11. 最新!泰晤士2021亚洲大学排名发布:91所中国大陆高校上榜!
  12. java static 初始化顺序_java static 初始化顺序语法并不正确
  13. 自动类型转化的鲜为人知的陷阱
  14. 基于MHSS的ARAIM算法的详细分析解释
  15. 本地Blast2GO安装
  16. B站哔哩哔哩视频一键下载,这个视频下载工具太给力了
  17. 引爆5G市场,场景为王?
  18. 调用系统相机和相册出现闪退报错No Activity found to handle Intent
  19. 计算机基本办公用法哪里学,使用电脑办公必须学会的七大办公技巧!
  20. 终于有懂哥能把云计算、大数据和人工智能讲得明明白白了

热门文章

  1. 3D月光宝盒游戏机模拟器方案源码项目解析(1)
  2. centos7系统nginx优化
  3. 互联网日报 | 2月6日 星期六 | 快手上市首日市值超万亿港元;瑞幸咖啡在美申请破产保护;虾米音乐正式宣布关停...
  4. Mysql数据库每天定时备份
  5. 第11讲:2.指数平滑模型
  6. 番茄花园Windows7 32位64位 旗舰装机版 v2022【全驱动】
  7. 起始页引导页+Fragment+viewLaout的一些简单使用
  8. 爬虫之requests+BeautifulSoup详解
  9. PP-LCNet 一个轻量级的CPU卷积神经网络
  10. Python -- 定义一个函数,判断输入的数是不是质数