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");
相关文章
|
7月前
|
存储 Linux API
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
在计算机系统的底层架构中,操作系统肩负着资源管理与任务调度的重任。当我们启动各类应用程序时,其背后复杂的运作机制便悄然展开。程序,作为静态的指令集合,如何在系统中实现动态执行?本文带你一探究竟!
【Linux进程概念】—— 操作系统中的“生命体”,计算机里的“多线程”
|
6月前
|
存储 Linux iOS开发
【Linux】冯诺依曼体系与操作系统理解
本文深入浅出地讲解了计算机体系的两大核心概念:冯诺依曼体系结构与操作系统。冯诺依曼体系作为现代计算机的基础架构,通过中央处理器、存储器和输入输出设备协同工作,解决了硬件性能瓶颈问题。操作系统则是连接硬件与用户的桥梁,管理软硬件资源,提供运行环境。文章还详细解析了操作系统的分类、意义及管理方式,并重点阐述了系统调用的作用,为学习Linux系统编程打下坚实基础。适合希望深入了解计算机原理和技术内幕的读者。
153 1
|
2月前
|
监控 Linux 开发者
理解Linux操作系统内核中物理设备驱动(phy driver)的功能。
综合来看,物理设备驱动在Linux系统中的作用是至关重要的,它通过与硬件设备的紧密配合,为上层应用提供稳定可靠的通信基础设施。开发一款优秀的物理设备驱动需要开发者具备深厚的硬件知识、熟练的编程技能以及对Linux内核架构的深入理解,以确保驱动程序能在不同的硬件平台和网络条件下都能提供最优的性能。
126 0
|
4月前
|
Java 关系型数据库 MySQL
在Linux操作系统上设置JDK、Tomcat、MySQL以及J2EE后端接口的部署步骤
让我们总结一下,给你的Linux操作系统装备上最强的军队,需要先后装备好JDK的弓箭,布置好Tomcat的阵地,再把MySQL的物资原料准备好,最后部署好J2EE攻城车,那就准备好进军吧,你的Linux军团,无人可挡!
113 18
|
4月前
|
开发框架 关系型数据库 Java
Linux操作系统中JDK、Tomcat、MySQL的完整安装流程以及J2EE后端接口的部署
然后Tomcat会自动将其解压成一个名为ROOT的文件夹。重启Tomcat,让新“植物”适应新环境。访问http://localhost:8080/yourproject看到你的项目页面,说明“植物”种植成功。
125 10
|
5月前
|
网络协议 Linux 网络安全
Palo Alto PAN-OS 11.2.5 for KVM - ML 驱动的 NGFW
Palo Alto PAN-OS 11.2.5 for KVM - ML 驱动的 NGFW
205 10
Palo Alto PAN-OS 11.2.5 for KVM - ML 驱动的 NGFW
|
7月前
|
Linux
Linux 操作系统
在 Linux 中,UID(用户 ID)是标识用户身份的重要概念。UID 唯一标识每个用户,通过 UID 可区分不同用户类别:UID 0 为超级用户,1-999 为系统用户,1000 及以上为普通用户。因此,正确选项为:UID 标识用户、可区分用户类别、普通用户 UID 大于 1000。
|
7月前
|
NoSQL Unix Linux
Linux 操作系统的诞生与发展历程
步探索与准备: 1991年初,林纳斯·托瓦兹开始在一台386sx兼容微机上学习minix操作系统。通过学习,他逐渐不能满足于minix系统的现有性能,并开始酝酿开发一个新的免费操作系统。
221 8
Linux 操作系统的诞生与发展历程
|
7月前
|
运维 自然语言处理 Ubuntu
OS Copilot-操作系统智能助手-Linux新手小白的福音
OS Copilot 是阿里云推出的一款操作系统智能助手,专为Linux新手设计,支持自然语言问答、辅助命令执行和系统运维调优等功能。通过简单的命令行操作,用户可以快速获取所需信息并执行任务,极大提升了Linux系统的使用效率。安装步骤简单,只需在阿里云服务器上运行几条命令即可完成部署。使用过程中,OS Copilot不仅能帮助查找命令,还能处理文件和复杂场景,显著节省了查找资料的时间。体验中发现,部分输出格式和偶尔出现的英文提示有待优化,但整体非常实用,特别适合Linux初学者。
371 10
|
8月前
|
弹性计算 运维 Ubuntu
os-copilot在Alibaba Cloud Linux镜像下的安装与功能测试
我顺利使用了OS Copilot的 -t -f 功能,我的疑惑是在换行的时候就直接进行提问了,每次只能写一个问题,没法连续换行更有逻辑的输入问题。 我认为 -t 管道 功能有用 ,能解决环境问题的连续性操作。 我认为 -f 管道 功能有用 ,可以单独创建可连续性提问的task问题。 我认为 | 对文件直接理解在新的服务器理解有很大的帮助。 此外,我还有建议 可以在非 co 的环境下也能进行连续性的提问。
187 7

热门文章

最新文章