当前位置: 首页 > news >正文

如何编写您的第一个 Linux 设备驱动程序(一)

大家好!我是大聪明-PLUS

本文旨在以一个简单的字符驱动程序为例,演示 Linux 系统中设备驱动程序的实现。

对我来说,主要目标是总结并为未来编写内核模块奠定基础,并积累向公众展示技术文献的经验。

附言:

由于篇幅过长,我决定将文章分为三部分:

第一部分 - 内核模块简介、初始化和清除。
第二部分 - open、read、write 和 trim 函数。
第三部分 - 编写 Makefile 并测试设备。

在开始之前,我想先说明一下,本节将介绍基础知识;更详细的信息将在本文的第二部分和最后一部分提供。

那么,让我们开始吧。

准备工作



字符驱动程序是指与字符设备配合使用的驱动程序。
字符设备是指可以作为字节流访问的设备。
例如 /dev/ttyS0 和 /dev/tty1 就是字符设备。

更新。

关于内核版本:

~$ uname -r
4.4.0-93-generic


驱动程序用 scull_dev 结构表示每个字符设备,并且还向内核提供 cdev 接口。

struct scull_dev {struct scull_qset *data;  int quantum;		  int qset;		  unsigned long size;	  struct semaphore sem;     struct cdev cdev;	  
};struct scull_dev *scull_device;


该设备将是一个指针链接列表,每个指针指向一个 scull_qset 结构。

struct scull_qset {void **data;struct scull_qset *next;
};


为了更清晰地理解,请看图。

图像



要注册设备,您需要指定一些特殊的编号:

MAJOR – 主设备号(在系统中唯一);
MINOR – 次设备号(在系统中不唯一)。

内核提供了一种机制,允许您手动注册专用编号,但这种方法并不可取;最好礼貌地请求内核为您动态分配这些编号。示例代码如下。

定义好设备的编号后,我们需要将这些编号与驱动程序操作关联起来。这可以使用 file_operations 结构体来完成。

struct file_operations scull_fops = {.owner = THIS_MODULE,.read = scull_read,.write = scull_write,.open = scull_open,.release = scull_release,
};


内核包含特殊的宏 module_init/module_exit,它们指定模块初始化/删除函数的路径。如果没有这些定义,初始化/删除函数将永远不会被调用。

module_init(scull_init_module);
module_exit(scull_cleanup_module);


我们将在这里存储有关设备的基本信息。

int scull_major = 0;		
int scull_minor = 0;		
int scull_nr_devs = 1;		
int scull_quantum = 4000;	
int scull_qset = 1000;		


准备工作的最后一步是包含头文件。
下面给出了一个简短的描述。

#include <linux/module.h> 
#include <linux/init.h>  
#include <linux/fs.h>    
#include <linux/cdev.h>  
#include <linux/slab.h>  
#include <asm/uaccess.h> 

初始化


现在我们看一下设备初始化函数。

static int scull_init_module(void)
{int rv, i;dev_t dev;rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");	if (rv) {printk(KERN_WARNING "scull: can't get major %d\n", scull_major);return rv;}scull_major = MAJOR(dev);scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);if (!scull_device) {rv = -ENOMEM;goto fail;}memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev));for (i = 0; i < scull_nr_devs; i++) {scull_device[i].quantum = scull_quantum;scull_device[i].qset = scull_qset;sema_init(&scull_device[i].sem, 1);scull_setup_cdev(&scull_device[i], i);}dev = MKDEV(scull_major, scull_minor + scull_nr_devs);	return 0;fail:scull_cleanup_module();return rv;
}


首先,通过调用 alloc_chrdev_region ,我们注册了一系列设备符号编号并指定了设备名称。然后,通过调用 MAJOR(dev) ,我们获取主设备号。
接下来,检查返回值;如果返回的是错误代码,则退出该函数。值得注意的是,在开发实际的设备驱动程序时,应该始终检查返回值以及指向任何元素的指针(例如 NULL)。

rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull");	if (rv) {printk(KERN_WARNING "scull: can't get major %d\n", scull_major);return rv;
}scull_major = MAJOR(dev);


如果返回值不是错误代码,我们继续初始化。

我们通过调用 kmalloc 函数分配内存,并确保检查指针是否为 NULL。

更新版

值得一提的是,您无需调用两个函数 kmalloc 和 memset,而是可以使用一个调用 kzalloc,它将分配一个内存区域并将其初始化为零。

scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);if (!scull_device) {rv = -ENOMEM;goto fail;
}memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev));


我们继续初始化。这里的关键函数是 scull_setup_cdev,我们将在下面讨论它。MKDEV 用于存储主设备号和次设备号。

for (i = 0; i < scull_nr_devs; i++) {scull_device[i].quantum = scull_quantum;scull_device[i].qset = scull_qset;sema_init(&scull_device[i].sem, 1);scull_setup_cdev(&scull_device[i], i);}dev = MKDEV(scull_major, scull_minor + scull_nr_devs);


我们返回值或处理错误并删除设备。

return 0;fail:scull_cleanup_module();return rv;
}


上面我们介绍了 scull_dev 和 cdev 结构体,它们实现了设备与内核之间的接口。 scull_setup_cdev 函数初始化该结构体并将其添加到系统中。

static void scull_setup_cdev(struct scull_dev *dev, int index)
{int err, devno = MKDEV(scull_major, scull_minor + index);cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE;dev->cdev.ops = &scull_fops;err = cdev_add(&dev->cdev, devno, 1);if (err)printk(KERN_NOTICE "Error %d adding scull  %d", err, index);
}

移动


当设备模块从内核中移除时,会调用 scull_cleanup_module 函数。
该函数反转初始化过程,删除设备结构、释放内存,并移除内核分配的主设备号和次设备号。

void scull_cleanup_module(void)
{int i;dev_t devno = MKDEV(scull_major, scull_minor);if (scull_device) {for (i = 0; i < scull_nr_devs; i++) {scull_trim(scull_device + i);cdev_del(&scull_device[i].cdev);	}kfree(scull_device);}unregister_chrdev_region(devno, scull_nr_devs); 
}

完整代码


我欢迎建设性的批评,并期待您的反馈。

如果您发现任何错误,或者我的材料呈现方式不正确,请告知我。
为了更快地收到回复,请给我发送私信。

谢谢!

http://www.dtcms.com/a/499518.html

相关文章:

  • 做更好的自己 网站客户又找不到你
  • Spring MVC 封装全局统一异常处理
  • 海尔建设网站的内容wordpress设置教程
  • Flutter---EQ均衡器
  • 响应式食品企业网站网站的外链是什么
  • 【Protobuf】proto3语法详解1
  • 网站备案要做家居网站设计
  • VS2022+DirectX9之创建DirectX9设备
  • unordered_map和unordered_set的封装与简单测试
  • (Kotlin协程十六)try/catch 可以捕获子协程的异常吗?为什么?
  • 网站移动端怎么做的做外国网站怎么买空间
  • 图像的脉冲噪声和中值滤波
  • 3.4特殊矩阵的压缩存储
  • SpringAI+DeepSeek大模型应用开发
  • 递归-24.两两交换链表中的节点-力扣(LeetCode)
  • 【Java零基础·第12章】Lambda与Stream API
  • Qemu-NUC980(八):GPIO Controller
  • 外贸型企业网站建设开源商城源码
  • JS逆向-安全辅助项目Yakit热加载魔术方法模版插件语法JSRpc进阶调用接口联动
  • 使用IOT-Tree接入各种数据转BACnet模拟设备输出
  • 网站搭建说明北京海淀区是几环
  • 基于多模态AI技术的传统行业智能化升级路径研究——以开源AI大模型、AI智能名片与S2B2C商城小程序为例
  • 【C语言进阶】指针进阶_数组指针的使用,数组参数和指针参数
  • PySide6 控件插入日期时间(QDateTime)
  • 网站建设 jsp php垂直网站建设
  • 招商网站大全企业官方网站建设的流程
  • 征程 6 | 工具链如何支持 Matmul/Conv 双 int16 输入量化?
  • 【案例实战】鸿蒙分布式调度:跨设备协同实战
  • 中英文网站设计网站开发投标文件
  • Langgraph译文1:让AI自主决策的代理架构