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");
相关实践学习
CentOS 7迁移Anolis OS 7
龙蜥操作系统Anolis OS的体验。Anolis OS 7生态上和依赖管理上保持跟CentOS 7.x兼容,一键式迁移脚本centos2anolis.py。本文为您介绍如何通过AOMS迁移工具实现CentOS 7.x到Anolis OS 7的迁移。
相关文章
|
25天前
|
安全 Linux 数据安全/隐私保护
Vanilla OS:下一代安全 Linux 发行版
【10月更文挑战第30天】
44 0
Vanilla OS:下一代安全 Linux 发行版
|
2月前
|
人工智能 分布式计算 大数据
Linux操作系统:开源力量的崛起与影响###
一场技术革命的火种,如何燎原? 本文将带您深入探索Linux操作系统的诞生背景、核心特性及其对现代科技世界的深远影响。从1991年芬兰学生Linus Torvalds的一个小众项目,到如今成为支撑全球无数服务器、超级计算机及物联网设备的基石,Linux的发展既是一部技术创新史,也是开源文化胜利的见证。通过剖析其设计哲学、安全性、灵活性等关键优势,结合实例展示Linux在云计算、大数据处理等领域的广泛应用,本文旨在揭示Linux为何能在众多操作系统中脱颖而出,以及它如何塑造了我们今天的数字生活。 ###
|
2月前
|
安全 Linux 编译器
探索Linux内核的奥秘:从零构建操作系统####
本文旨在通过深入浅出的方式,带领读者踏上一段从零开始构建简化版Linux操作系统的旅程。我们将避开复杂的技术细节,以通俗易懂的语言,逐步揭开Linux内核的神秘面纱,探讨其工作原理、核心组件及如何通过实践加深理解。这既是一次对操作系统原理的深刻洞察,也是一场激发创新思维与实践能力的冒险。 ####
|
5天前
|
人工智能 安全 Linux
Linux操作系统的演变与未来趋势###
本文深入探讨了Linux操作系统从诞生至今的发展历程,分析了其开源模式对技术创新和IT行业的影响,并展望了Linux在未来技术生态中的角色。通过历史回顾、现状分析和未来预测,本文旨在为读者提供一个关于Linux操作系统全面而深入的视角。 ###
|
5天前
|
缓存 网络协议 Linux
深入探索Linux操作系统的内核优化策略####
本文旨在探讨Linux操作系统内核的优化方法,通过分析当前主流的几种内核优化技术,结合具体案例,阐述如何有效提升系统性能与稳定性。文章首先概述了Linux内核的基本结构,随后详细解析了内核优化的必要性及常用手段,包括编译优化、内核参数调整、内存管理优化等,最后通过实例展示了这些优化技巧在实际场景中的应用效果,为读者提供了一套实用的Linux内核优化指南。 ####
20 1
|
18天前
|
缓存 资源调度 安全
深入探索Linux操作系统的心脏——内核配置与优化####
本文作为一篇技术性深度解析文章,旨在引领读者踏上一场揭秘Linux内核配置与优化的奇妙之旅。不同于传统的摘要概述,本文将以实战为导向,直接跳入核心内容,探讨如何通过精细调整内核参数来提升系统性能、增强安全性及实现资源高效利用。从基础概念到高级技巧,逐步揭示那些隐藏在命令行背后的强大功能,为系统管理员和高级用户打开一扇通往极致性能与定制化体验的大门。 --- ###
48 9
|
18天前
|
缓存 运维 网络协议
深入Linux内核架构:操作系统的核心奥秘
深入Linux内核架构:操作系统的核心奥秘
36 2
|
22天前
|
缓存 网络协议 Linux
Linux操作系统内核
Linux操作系统内核 1、进程管理: 进程调度 进程创建与销毁 进程间通信 2、内存管理: 内存分配与回收 虚拟内存管理 缓存管理 3、驱动管理: 设备驱动程序接口 硬件抽象层 中断处理 4、文件和网络管理: 文件系统管理 网络协议栈 网络安全及防火墙管理
38 4
|
21天前
|
安全 网络协议 Linux
Linux操作系统的内核升级与优化策略####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统内核升级的重要性,并详细阐述了一系列优化策略,旨在帮助系统管理员和高级用户提升系统的稳定性、安全性和性能。通过实际案例分析,我们展示了如何安全有效地进行内核升级,以及如何利用调优技术充分发挥Linux系统的潜力。 ####
44 1
|
24天前
|
物联网 Linux 云计算
Linux操作系统的演变与未来趋势####
【10月更文挑战第29天】 本文深入探讨了Linux操作系统从诞生至今的发展历程,分析了其在服务器、桌面及嵌入式系统领域的应用现状,并展望了云计算、物联网时代下Linux的未来趋势。通过回顾历史、剖析现状、预测未来,本文旨在为读者提供一个全面而深入的视角,以理解Linux在当今技术生态中的重要地位及其发展潜力。 ####