嵌入式驱动程序设计

2022/8/2 14:22:45

本文主要是介绍嵌入式驱动程序设计,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

1.部分Linux命令

  Linux使用mknod命令创建设备节点

  Linux使用mkdir命令创建目录文件

  Linux使用mkfifo命令创建管道文件

  ☞ linux mknod命令解析_童安格粉丝的博客-CSDN博客_mknod

  ☞ mkfifo函数使用_superywf的博客-CSDN博客_mkfifo函数

 

2. Linux设备驱动

  Linux设备驱动主要分为:字符设备、块设备、网络设备

 ☞ 字符设备_百度百科 (baidu.com)

 ☞ 块设备_百度百科 (baidu.com)

 ☞ 网络设备(网络设备)_百度百科 (baidu.com)

 .  Linux驱动模块的后缀名为: .ko

 ☞ Linux中后缀名为ko、o、a、so、la的文件介绍_LINUX_操作系统

 

3. Linux系统调用

   Linux下常用系统调用名,系统调用实际也是VFS层提供的功能:

    read:读取;open:打开设备;close:关闭设备; lseek:偏移设备

    注意:fopen\fread为标准IO,不是系统调用

  ☞ Linux常用系统调用 - LaplaceDemon - 博客园 (cnblogs.com)

 

4. platform设备驱动

  内核platform总线设备是一种虚拟的设备驱动

  Linux发明了一种虚拟的总线,称为platform总线,相应的设备称为platform_device,而驱动称为platform_driver

  platform_driver和platform_device在总线中匹配成功后,执行probe接口

 ☞ Linux驱动之Platform device/driver - 知乎 (zhihu.com)

 

5. Linux实现物理地址和虚拟地址的映射的方式

   Linux通过ioremap函数实现物理地址和虚拟地址的映射

  ☞ Linux虚拟地址和物理地址的映射 - 流浪的Coder - 博客园 (cnblogs.com)

  ☞ ioremap_百度百科 (baidu.com)

 

6.设备树

  设备树是一种描述硬件结构的数据结构,采用设备树后,许多硬件的细节可以直接通过它传递给linux,而不再需要在内核中进行大量的冗余编码。

  设备树由一系列被命名的节点和属性(成对出现的名称和值)组成,可描述的信息包括:

   a.CPU数量和类别       b.内存基地址和大小

   c.总线和桥          d.外设连接

   e.中断控制器和中断使用情况  f.GPIO控制器和GPIO使用情况

   g.时钟控制器和时钟使用情况

  基本上就是画一颗电路板上CPU、总线、设备组成的树,Bootloader会将这棵树传递给内核,内核识别出这棵树,并根据它展开出Linux内核中的platform_device、i2C_client等设备,而这些设备用到的内存、IRQ等资源也被传递给了内核,内核会将这些资源给展开的相应的设备。

   设备树被内核识别后,会在/sys目录下为用户提供可视化操作接口

  /sys:sys是system的缩写,该目录也是一个虚拟目录,主要用于收集和存放系统核心设备信息,主要指各类系统核心设备及其驱动信息,例如块设备block、总线bus、内核kernel、电源power等等;

  设备树定义节点时,定义资源的属性名是reg

  reg:给出访问该device node的地址信息

  ☞ 设备树之节点和属性_麻辣小新的博客-CSDN博客_设备树label 

 

7.miscdevice

  miscdevice类型定义的是misc设备驱动

  linux的miscdevice的主设备号是10

  ☞ misc设备驱动_eurphan_y的博客-CSDN博客_misc驱动

  ☞ linux驱动开发 --miscdevice_Sunnie_ge的博客-CSDN博客_miscdevice

 

8.printk内核打印函数

  ☞ 内核打印函数printk_ptonlix的博客-CSDN博客_内核打印函数

  ☞ printk 内核打印 – 人人都懂物联网 (getiot.tech)

  在printk打印内核信息时,以信息级打印应设置KERN_INFO前缀

#define pr_info(fmt,arg...)  \
printk(KERN_INFO fmt,##arg)

 

9.内核空间拷贝到用户空间

  实现内核空间拷贝到用户空间的接口名:copy_to_user

  ☞ linux驱动开发--copy_to_user 、copy_from_user函数实现内核空间数据与用户空间数据的相互访问 (aliyun.com)

 

10.cdev结构体

  内核提供了cdev结构体描述字符设备,cdev_add是向内核添加,cdev_del是从内核中移除

  ☞ Linux 字符设备驱动结构(一)—— cdev 结构体、设备号相关知识解析

 

11.内核模块

  11-1:内核模块源码编译方式

    内核模块源码可以配置为编译进内核和以模块编译的方式

    objs-y的目标被编译进内核镜像中,objs-m 以模块方式编,其余的跟内核没关系。

   

  11-2:内核模块入口

    内核模块的入口需要通过module_init进行注册到内核里

  

  11-3:内核模块加载

    内核模块通过insmod命令实现用户空间向内核空间加载

    ☞ Linux 系统设置 : insmod 命令详解_HarkerYX的博客-CSDN博客_insmod

 

12.linux主设备号

  Linux字符设备和块设备独占主设备号

  Linux设备号中主设备号为12位

 

13.kmalloc和malloc

  使用kmalloc函数在内核空间申请内存,使用malloc函数从用户空间申请内存

  ☞ kmalloc_百度百科 (baidu.com)

 

14.sysfs文件系统

  sysfs是一个虚拟的文件系统,它可以产生一个包括所有系统硬件的层级视图,它的一个目的就是战士设备驱动模型中各组件的层次关系。

  内核通过sysfs文件系统导出内核设备模型

  ☞ sysfs 文件系统 - 简书 (jianshu.com)

  

15.linux内核的虚拟内存

  ☞  linux虚拟内存与物理内存,内核态与用户态_selfsongs的博客-CSDN博客

  在linux系统中,进程的4GB内存空间被分成2个部分,用户空间与内核空间。用户空间的地址一般分布为0~3GB,这样,剩下的3~4G为内核空间。

  linux内核的虚拟内存是3G-4G空间

 

16.wait_event接口

  使用wait_event接口将当前访问进程设置为等待状态

  ☞ linux内核中的wait_event_interruptible_timeout接口解析_weixin_30263277的博客-CSDN博客

 

17.驱动的Makefile框架

BASE_KERNEL ?= 内核的源码目录
obj-m += 驱动源文件名.o
all:
    make -C $(BASE_KERNEL) M=$(PWD) modules
clean:
    make -C $(BASE_KERNEL) M=$(PWD) clean  

 eg.驱动源文件为abc.c和hello.c

# 若是虚拟机自身内核,可以定义变量为: /lib/modules/$(shell uname -r)/build
# 定义内核源码根目录
BASE_KERNEL ?= /lib/modules/$(shell uname -r)/build
# 定义目标名
TARGET := hello

obj-m := abc.o
obj-m += hello.o

all:
    make -C $(BASE_KERNEL) M=$(PWD) modules

clean:
    make -C $(BASE_KERNEL) M=$(PWD) clean

 

18.linux驱动模块组成

  1)模块加载函数,通过insmod命令加载内核,模块的加载函数会自动被内核执行,完成本模块的相关初始化工作。
  2)模块卸载函数,通过rmmod命令卸载某模块,被调用执行。
  3)模块许可证声明,使用MODULE_LICENSE配置GPL的声明。
  4)可选模块参数、模块导出符号表、模块作者等信息声明。

  ☞ linux驱动模块组成及其它_天天向上_好好学习的博客-CSDN博客_linux驱动模块的组成

  GPL:GNU 通用公共许可协议

module_init(hello_init);//模块加载
module_exit(hello_exit);//模块卸载
MODULE_LICENSE("GPL");//模块许可证声明
MODULE_AUTHOR("wit@zhaixue.cc");//模块作者等信息声明

   insmod 命令用法:

sudo insmod hello.ko #加载模块到内核中

  rmmod 命令用法:

sudo rmmod hello.ko #卸载模块

 

19.编写global_memory的open函数

  globalmem意味着“全局内存”,在globalmem字符设备驱动中会分配一片大小为GLOBALMEM_SIZE(4KB)的内存空间,并在驱动中提供针对该片内存的读写、控制和定位函数,以供用户空间的进程能通过Linux系统调用获取或设置这片内存的内容。

  对于globalmem驱动而言,私有数据的设置是在global_open()中完成的。

  open在驱动中,主要做面向对象的解包。想办法从基类找子类,把子类绑到所有的文件描述符里面,便于read()、write()、ioctl()、llseek()函数进行访问。思路是先通过inode节点,想办法找到它的子类,映射给虚拟的文件。

static int mem_open(struct inode *node, struct file *fp) {
    struct mem_dev *dev; //定义一把子类的钥匙
    dev = container_of(node->i_cdev, struct mem_dev, cdev);//container_of 通过基类找子类
    fp->private_data = dev; //与虚拟的文件系统进行绑定 (private_data:地址空间的私有成员)
    return 0; //代表成功
}
container_of(ptr,type,member)//其中ptr、type、member分别代表指针、结构体类型、成员

  ☞ container of()函数简介_叨陪鲤的博客-CSDN博客_container_of函数

eg.

struct mem_dev{
     struct cdev abc;
     unsigned char mem[GLOBALMEM_SIZE];
     //...      
}

static int mem_open(struct inode *node, struct file *fp) {
    struct mem_dev *dev;
    dev = container_of(node->i_cdev, struct mem_dev, abc);
    fp->private_data = dev;
    return 0;
}

 

20.最简单的驱动程序“Hello World”编写

#include <linux/init.h>
#include <linux/module.h>

static __init int hello_init(void) {
    printk("Hello world!\n");
    return 0;
}
static __exit void hello_exit(void) {
    printk("Goodbye!\n");
}

module_init(hello_init)
module_exit(hello_exit)
MODULE_LICENSE("GPL");

 

运行结果:

 模块加载之后的结果:

 

 模块卸载之后的结果:

  

 食用方法☞:Linux内核驱动学习---编写最简单Linux内核模块HelloWorld_九阈的博客-CSDN博客_添加最简单的linux内核模块

 

21.从设备树获取资源

  从设备树获取资源一般由 platform_get_resource 函数来实现

  ☞ platform_get_resource_铁头小哥的博客-CSDN博客_platform_get_resource

static int drv_probe(struct platform_device *pdev) {
    const char *value;
    struct resource *res;

    device_property_read_string(&pdev->dev, "xxx_value", &value);
    printk("the value is %s\n", value);

    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    printk("the start: %x, the end: %x\n",res->start, res->end);
    return 0;
}

 

platform的OF匹配表,使驱动与.dfs中描述的设备节点进行匹配,从而使驱动的probe()函数执行:

static struct of_device_id matchs[] = {
    {.compatible = "swpu,embed,led", },
    {}
};

static struct platform_driver drv_05025_driver = {
    .probe = drv_probe,
    .remove = drv_remove,
    .driver = {
        .name    = "drv",
        .of_match_table = matchs,
        },
 };

module_platform_driver(drv_05025_driver);
MODULE_LICENSE("GPL");

 



这篇关于嵌入式驱动程序设计的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程