Linux操作系统:字符型驱动编写

简介: 字符型驱动编写

一 简介

 字符设备是 Linux 驱动中最基本的一类设备驱动,字符设备就是一个一个字节,按照字节流进行读写操作的设备,读写数据是分先后顺序的。比如我们最常见的 LED、按键、 IIC、SPI, LCD 等等都是字符设备,这些设备的驱动就叫做字符设备驱动。为了方便开发,做一个简单的字符型设备驱动模型供CV用(参考了原子开发文档)

 Linux 应用程序对驱动程序的调用如下图所示:

20210524095547817.png

 在 Linux 中一切皆为文件,驱动加载成功以后会在“ /dev”目录下生成一个相应的文件,应用程序通过对这个名为“ /dev/xxx” (xxx 是具体的驱动文件名字)的文件进行相应的操作即可实现对硬件的操作。比如现在有个叫做/dev/led 的驱动文件,此文件是 led 的驱动文件。应用程序使用 open 函数来打开文件/dev/led,使用完成以后使用 close 函数关闭/dev/led 这个文件。 open 和 close 就是打开和关闭 led 驱动的函数,如果要点亮或关闭led,那么就使用 write 函数来操作,也就是向此驱动写入数据,这个数据就是要关闭还是要打开 led 的控制参数。如果要获取 led 灯的状态,就用 read 函数从驱动中读取相应的状态。

 应用程序运行在用户空间,而 Linux 驱动属于内核的一部分,因此驱动运行于内核空间。当我们在用户空间想要实现对内核的操作,比如使用 open 函数打开/dev/led 这个驱动,因为用户空间不能直接对内核进行操作,因此必须使用一个叫做“系统调用”的方法来实现从用户空间陷入到内核空间,这样才能实现对底层驱动的操作。 open、 close、 write 和read 等这些函数是有 C 库提供的,在 Linux 系统中,系统调用作为 C 库的一部分。当我们调用 open 函数的时候流程如下图所示:

20210524095839806.png

二 开发流程

 我们在学习裸机或者 STM32 的时候关于驱动的开发就是初始化相应的外设寄存器,在Linux 驱动开发中肯定也是要初始化相应的外设寄存器,这个是毫无疑问的。只是在 Linux驱动开发中我们需要按照其规定的框架来编写驱动,所以说学 Linux 驱动开发重点是学习其驱动框架。

驱动模块的加载和卸载

字符设备注册与注销

实现设备的具体操作函数

1、 能够对 chrtest 进行打开和关闭操作

2、对 chrtest 进行读写操作

添加 LICENSE 和作者信息

三 设备号

 为了方便管理, Linux 中每个设备都有一个设备号,设备号由主设备号和次设备号两部分组成,主设备号表示某一个具体的驱动,次设备号表示使用这个驱动的各个设备。

 Linux 系统中主设备号范围为 0~4095,所以大在选择主设备号的时候一定不要超过这个范围。在文件 include/linux/kdev_t.h 中提供了几个关于设备号的操作函数(本质是宏),如下所示:

#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))

第 1 行,宏 MINORBITS 表示次设备号位数,一共是 20 位。

第 2 行,宏 MINORMASK 表示次设备号掩码。

第 3 行,宏 MAJOR 用于从 dev_t 中获取主设备号,将 dev_t 右移 20 位即可。

第 4 行,宏 MINOR 用于从 dev_t 中获取次设备号,取 dev_t 的低 20 位的值即可。

第 5 行,宏 MKDEV 用于将给定的主设备号和次设备号的值组合成 dev_t 类型的设备号。

有静态分配和动态分配两种方式

实例如下

#include "stdio.h"
#include "string.h"
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>
// static struct file_operations test_fops; //就是设备的操作函数集合,只是此时还没有初始化 test_fops 中的 open、 release 等这些成员变量,所以这个操作函数集合还是空的
/*cat /proc/devices可以查看设备号*/
#define CHRDEVBASE_MAJOR 200 // 主设备号
#define CHRDEVBASE_NAME "chrdevbase" // 设备名
static char readbuf[100]; // 读缓冲区
static char writebuf[100]; // 写缓冲区
static char kerneldata[] = {"kernel data!"};
 /*
   * @description : 打开设备
   * @param – inode : 传递给驱动的 inode
   * @param - filp : 设备文件, file 结构体有个叫做 private_data 的成员变量
   * 一般在 open 的时候将 private_data 指向设备结构体。
   * * @return : 0 成功;其他 失败
   */
 static int chrtest_open(struct inode *inode, struct file *filp)
 {
    /*实现具体功能*/
      //printk("chrdevbase open!\r\n");
     return 0;
 }
 /*
   * @description : 从设备读取数据
   * @param - filp : 要打开的设备文件(文件描述符)
   * @param - buf : 返回给用户空间的数据缓冲区
   * @param - cnt : 要读取的数据长度
   * @param - offt : 相对于文件首地址的偏移
   * @return : 读取的字节数,如果为负值,表示读取失败
   */
 static ssize_t chrtest_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
 {
   /*实现具体功能*/
     int retvalue = 0;
   /* 向用户空间发送数据 */
     memcpy(readbuf, kerneldata, sizeof(kerneldata));
     retvalue = copy_to_user(buf, readbuf, cnt);//因为内核空间不能直接操作用户空间的内存,因此需要借助 copy_to_user 函数来完成内核空间的数据到用户空间的复制
     if(retvalue == 0)
     {
     printk("kernel senddata ok!\r\n");
     }
     else
     {
     printk("kernel senddata failed!\r\n");
     }
   //printk("chrdevbase read!\r\n");
   return 0;
 }
 /*
   * @description : 向设备写数据
   * @param - filp : 设备文件,表示打开的文件描述符
   * @param - buf : 要写给设备写入的数据
   * @param - cnt : 要写入的数据长度
   * @param - offt : 相对于文件首地址的偏移
   * @return : 写入的字节数,如果为负值,表示写入失败
   */
 static ssize_t chrtest_write(struct file *filp,const char __user *buf,size_t cnt, loff_t *offt)
{
      int retvalue = 0;
    /* 接收用户空间传递给内核的数据并且打印出来 */
     retvalue = copy_from_user(writebuf, buf, cnt);
     if(retvalue == 0)
     {
     printk("kernel recevdata:%s\r\n", writebuf);
     }
     else
     {
     printk("kernel recevdata failed!\r\n");
     }
     //printk("chrdevbase write!\r\n");
   return 0;
}
  /*
   * @description : 关闭/释放设备
   * @param - filp : 要关闭的设备文件(文件描述符)
   * @return : 0 成功;其他 失败
   */
 static int chrtest_release(struct inode *inode, struct file *filp)
 {
   /*实现具体功能*/
   //printk("chrdevbase release! \r\n");
   return 0;
 }
  /*
   * 设备操作函数结构体
   */
 static struct file_operations test_fops = {
              .owner = THIS_MODULE,
              .open = chrtest_open,
              .read = chrtest_read,
              .write = chrtest_write,
              .release = chrtest_release,
   };
    /*
    * @description : 驱动入口函数
    * @param : 无
    * @return : 0 成功;其他 失败
    */
    static int __init chrdevbase_init(void)
   {
       /* 入口函数具体内容 */
       int retvalue = 0;
      /* 注册字符设备驱动 */
       retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);//调用函数 register_chrdev 注册字符设备,主设备号为 200,设备名为“ chrtest”
       if(retvalue < 0)
       {
         printk("chrdevbase driver register failed\r\n");
      /* 字符设备注册失败,自行处理 */
       }
       printk("chrdevbase_init()\r\n");
      return 0;
 }
 /* 驱动出口函数 */
  static void __exit xxx_exit(void)
   {
       /* 注销字符设备驱动 */
       unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);//调用函数 unregister_chrdev 注销主设备号为 200 的这个设备。
       printk("chrdevbase_exit()\r\n");
   }
 /* 将上面两个函数指定为驱动的入口和出口函数 */
 module_init(xxx_init);
 module_exit(xxx_exit);
 MODULE_LICENSE("GPL");
 MODULE_AUTHOR("xin.han");
相关文章
|
16天前
|
监控 Unix Linux
Linux操作系统调优相关工具(四)查看Network运行状态 和系统整体运行状态
Linux操作系统调优相关工具(四)查看Network运行状态 和系统整体运行状态
30 0
|
17天前
|
Linux 编译器 开发者
Linux设备树解析:桥接硬件与操作系统的关键架构
在探索Linux的庞大和复杂世界时🌌,我们经常会遇到许多关键概念和工具🛠️,它们使得Linux成为了一个强大和灵活的操作系统💪。其中,"设备树"(Device Tree)是一个不可或缺的部分🌲,尤其是在嵌入式系统🖥️和多平台硬件支持方面🔌。让我们深入了解Linux设备树是什么,它的起源,以及为什么Linux需要它🌳。
Linux设备树解析:桥接硬件与操作系统的关键架构
|
1月前
|
Linux 数据安全/隐私保护 虚拟化
Linux技术基础(1)——操作系统的安装
本文是龙蜥操作系统(Anolis OS) 8.4 的安装指南,用户可以从[龙蜥社区下载页面](https://openanolis.cn/download)获取ISO镜像。安装方法包括物理机的光驱和USB闪存方式,以及虚拟机中的VMware Workstation Pro设置。安装过程涉及选择语言、配置安装目标、选择软件集合和内核,设置Root密码及创建新用户。安装完成后,可通过文本模式或图形化界面验证系统版本,如Anolis OS 8.4,标志着安装成功。
|
1月前
|
Linux API 调度
Linux系统驱动跟裸机驱动的区别
Linux系统驱动跟裸机驱动的区别
29 0
|
1月前
|
存储 缓存 算法
Linux--系统结构与操作系统
Linux--系统结构与操作系统
|
1月前
|
存储 缓存 Linux
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
【Shell 命令集合 磁盘维护 】Linux 设置和查看硬盘驱动器参数 hdparm命令使用教程
35 0
|
16天前
|
Linux
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
Linux操作系统调优相关工具(三)查看IO运行状态相关工具 查看哪个磁盘或分区最繁忙?
21 0
|
10天前
|
Linux Go
Linux命令Top 100驱动人生! 面试必备
探索Linux命令不再迷茫!本文分10部分详解20个基础命令,带你由浅入深掌握文件、目录管理和文本处理。 [1]: <https://cloud.tencent.com/developer/article/2396114> [2]: <https://pan.quark.cn/s/865a0bbd5720> [3]: <https://yv4kfv1n3j.feishu.cn/docx/MRyxdaqz8ow5RjxyL1ucrvOYnnH>
64 0
|
23天前
|
Linux
Linux驱动运行灯 Heartbeat
Linux驱动运行灯 Heartbeat
10 0
|
1月前
|
存储 Shell Linux
【Shell 命令集合 网络通讯 】⭐Linux 显示当前系统的主机名和操作系统类型 uuname命令 使用教程
【Shell 命令集合 网络通讯 】⭐Linux 显示当前系统的主机名和操作系统类型 uuname命令 使用教程
29 0