Linux驱动开发

本文最后更新于:2022年6月6日 晚上

主要内容:

  1. Linux 应用程序和底层驱动的关系
  2. 怎么编写Linux下的驱动

理解 驱动开发就是为了给应用程序提供操作底层设备的函数接口

Linux 驱动

Linux 设备驱动开发

  1. Linux 设备驱动框架(固定)
  2. 自己动手编写驱动
  3. 简单(C语言)

Linux 驱动 vs 裸机驱动

裸机驱动 (mpu6050 i2c)

直接操作控制器,让硬件工作,实现硬件功能和如何使用这些功能是在一起

Linux 驱动

因为操作系统中,同类硬件设备一般只有一个,但是想操作硬件设备的进程会很多。这就带来一个问题,应用层如何访问硬件设备以及多个人同时访问的时候,如何解决并发问题。

Linux 驱动本质是向应用层提供访问硬件设备的函数接口,也就是说驱动只是提供硬件的功能函数接口,而如何使用这些功能由应用层代码去做。

注意:Linux下的驱动提供函数接口,必须遵从设备驱动的框架,便于Linux操作系统对设备进行管理。

Linux 驱动 = Linux 驱动框架 + 裸机操作

Linux 设备分类

设备种类 举例
字符设备 键盘,鼠标, …
块设备 u盘,emmc,硬盘,….
网络设备 网卡

注意 :
(1)字符设备和块设备驱动在应用层存在设备文件和驱动对应。
(2)网络设备驱动在应用层存在是网络接口 (ifconfig命令可以查看)

模块编译的过程

在 Linux 内核中添加模块代码

按照 Linux 规定模块的形式添加代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <linux/init.h>
#include <linux/module.h>

MODULE_LICENSE("GPL v2");

static int hello_init(void) //入口函数
{
printk("Hello linux module init!\n");
return 0;
}

static void hello_exit(void)//出口函数
{
printk("Hello linux module exit!\n");
}

module_init(hello_init); //告诉linux模块入口函数,加载模块代码到操作系统的时候会调用
module_exit(hello_exit); //告诉linux模块出口函数,从操作系统中卸载模块代码的时候调用

模块 Makefile 编写

思想

Linux 内核源码的编译系统可以编译我们编写的模块代码

编译方式

  1. 第一种 (产品发布):

    将自己编写的代码,拷贝到Linux内核源码树下,然后配置编译,编译进内核

  2. 第二种 (驱动调试):

    自己编写Makefile,然后使用Linux内核的编译系统,编译自己的模块代码

    Q 1: Linux内核的编译系统在哪里?

    1. Linux内核源码下的Makefile

      注意 : 你的Linux内核源码必须已经根据自己所开发的平台进行了配置

      [1]修改了Makefile,指定了开发工具链
      [2]已经使用Linux内核默认配置文件进行了配置

    2. ubuntu系统自带的Linux内核编译系统 (pc机,x86)

      1
      /lib/modules/3.13.0-32-generic/build/Makefile

    Q 2:如何在自己编写的Makefile中使用Linux内核的编译系统 ?

    1
    make  -C  linux内核编译系统的路径  M=需要编译的模块代码路径  modules

    make 调用当前目录下的Makefile———–切换到————>linux内核编译系统的路径Makefile

    1. 使用 Linux 内核编译的 Makefile
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    # 标准模板
    ifeq ($(KERNELRELEASE),)

    KERNEL_BUILD=/lib/modules/$(shell uname -r)/build 记录Linux内核的编译系统路径
    MODULE_PATH =$(shell pwd) 记录模块代码的路径

    module:
    $(MAKE) -C $(KERNEL_BUILD) M=$(MODULE_PATH) modules
    # make -C /lib/modules/$(shell uname -r)/build M=记录模块代码的路径 modules

    clean:
    $(MAKE) -C $(KERNEL_BUILD) M=$(MODULE_PATH) clean

    else

    obj-m = hello_module.o

    endif
    1. 模块操作
    1
    2
    3
    4
    insmod  加载模块  ---->  模块入口函数
    lsmod 查看系统中的模块
    rmmod 卸载模块 ----> 模块出口函数
    modinfo led-driver.ko 查看模块包含的信息

    注意 :

    查看内核空间printk打印的信息: dmesg, 清除 :dmesg -c

Linux 应用程序调用底层驱动

核心思想 (牢记)

Linux 驱动本质是向应用层提供访问硬件设备的函数接口

应用程序 和 底层驱动

  1. 应用层的进程如何访问底层的驱动程序

    • 字符设备或块设备,我们可以通过设备文件(属性信息中包含的设备号)来找到底层驱动程序

    • 驱动的标识:设备号

      12bit(主设备号) + 20bit(次设备号) = 32bit

      主设备号:标识一类设备
      次设备号:为了区分同类型设备的不同设备

  2. Linux内核有那么多驱动程序,如何才能确定自己需要访问的驱动程序?

    通过设备文件中包含的设备号信息

  3. Linux内核中,如何描述文件?

    1. struct inode 描述文件属性信息

      • 文件类型,权限,大小,修改时间,设备号[设备文件]

      • 和设备文件共存亡,只分配一次

    2. struct file 描述一个打开的文件(打开的方式,文件偏移量,…)

      • 只要打开一次文件,就会分配一次
  4. 应用层访问底层字符设备驱动的过程?

    open —-> 设备文件

    ​ —->struct inode:设备号
    ​ —–>struct cdev
    ​ 它的一个成员记录操作硬件设备的函数接口
    ​ (struct file_operations)

    1
    2
    3
    寻找成功之后:
    struct inode --- 结构体记录struct cdev这个结构体首地址
    struct file --- 结构体记录struct file_operations这个结构体首地址
  5. 写字符驱动,需要做什么?

    1. 需要给自己设计的结构体分配空间

      1
      2
      3
      4
      5
      /* struct cdev:Linux 针对字符设备的通用描述 */
      struct led_device{
      struct cdev cdev;/* 通用的字符设备描述 */
      ...
      };
      1
      pled = kmalloc(sizeof(struct led_device), GFP_KERNEL);
    2. 提供硬件设备的操作函数接口

      1
      cdev_init(&pled->led_cdev,&led_fops);
    3. 申请一个空闲的设备号

      1
      2
      devno = MKDEV(LED_DEVICE_MAJOR,0); //返回设备号
      register_chrdev_region(devno, 1, "led-device"); //注册设备号
    4. 使用设备号,将 struct cdev 这个结构体添加到系统中去

      1
      cdev_add(&led_cdev, devno, 1);  //添加字符设备
    5. 创建设备文件

      1
      2
      3
      mknod  设备文件名  设备文件类型  主设备号  次设备号

      mknod /dev/led c 250 0

Linux 应用层和驱动层关系图


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!