首页 > 系统相关 >Linux--内核驱动框架(以字符设备驱动为例)

Linux--内核驱动框架(以字符设备驱动为例)

时间:2024-08-15 21:27:16浏览次数:16  
标签:文件 为例 -- 内核 模块 驱动 pin4 class 设备

下面将介绍一个简单的Linux内核驱动程序(部分),用于处理一个假设的字符设备(鼠标、键盘、串口、LED...),具体介绍如下:

1 头文件导入

#include <linux/fs.h>         // 用于声明 file_operations 结构体和其他文件系统相关函数。
#include <linux/module.h>     // 用于声明 module_init 和 module_exit 宏,并定义模块相关的功能。
#include <linux/init.h>       // 用于声明 __init 和 __exit 宏。
#include <linux/device.h>     // 用于声明 class 和 device 相关的结构体和函数。
#include <linux/uaccess.h>    // 用于处理用户空间和内核空间之间的数据传输(如 copy_from_user)。
#include <linux/types.h>      // 用于定义 dev_t 类型,它表示设备号。
#include <asm/io.h>           // 用于声明 ioremap 和 iounmap 函数。

这些头文件提供了内核编程所需的基础功能,例如设备文件操作、内核模块初始化和退出、与用户空间进行交互等。

2 全局变量和宏定义

1)'pin4'为假设的设备,首先创建设备类'pin4_class'和设备文件'pin4_class_dev',用于在'/dev'目录下创建设备节点。

static struct class *pin4_class;  
static struct device *pin4_class_dev;

2)定义设备号,包括主设备号(标识设备类型)及次设备号(标识设备实例),同时定义设备名称。

static dev_t devno;          // 设备号,用于标识内核中的设备。
static int major = 231;      // 主设备号231是为这个驱动手动分配的。
static int minor = 0;        // 次设备号。
static char *module_name = "pin4";  // 模块名称,用于标识和注册这个驱动模块。

3 设备操作函数

3.1 打开操作

// 打开设备文件时调用
static int pin4_open(struct inode *inode, struct file *file)
{
    printk("pin4_open\n");  // 使用内核日志系统打印消息,类似于用户态的 printf。
    return 0;
}

当用户空间程序通过'open()'调用打开设备文件时,内核会调用该函数。返回值0表示设备文件被正常打开,如果返回一个负值,则打开失败。

  • 'struct inode *inode':指向设备文件的inode结构体,inode是文件在文件系统中的抽象表示,包含文件的元数据(权限、类型、大小...)。对于字符设备来说,该参数可以用来获取设备号;
  • 'struct file *file':指向代表打开文件的结构体,包含文件的当前状态(文件指针、访问模式...)。

3.2 写入操作

// 写入设备文件时调用
static ssize_t pin4_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
    printk("pin4_write\n");  // 打印写操作的日志消息。
    return 0;
}

该函数在数据从用户空间传递到内核空间时被调用,通常用于处理写操作,将数据写入设备或存储在驱动程序中。

  • 'struct file *file':指向代表打开文件的结构体;
  • 'const char __user *buf':指向用户空间缓冲区的指针,缓冲区中包含了用户程序传递的数据;
  • 'size_t count':表示用户空间缓冲区'buf'的大小(以字节为单位),即写入的数据的字节数,驱动程序需要根据这个值来处理要写入的数据长度;
  • 'loff_t *ppos':指向文件位置指针的指针,通常用于标记文件当前的偏移量。

4 设备文件操作结构体

static struct file_operations pin4_fops = {
    .owner = THIS_MODULE,   
    .open  = pin4_open,     
    .write = pin4_write,    
};
  • THIS_MODULE:表示这个模块的所有者是当前模块,确保在模块被卸载前,不会被意外地引用或卸载;
  • pin4_open:将 pin4_open 函数指针赋值给 open 成员,用于处理设备文件的打开操作;
  • pin4_write:将 pin4_write 函数指针赋值给 write 成员,用于处理设备文件的写操作。

结构体'pin4_fops'包含了字符设备的各种操作,内核会通过这个结构体来调用相应的设备操作函数。

5 模块初始化函数

int __init pin4_drv_init(void)
{
    int ret;
    devno = MKDEV(major, minor);  // 生成设备号,使用主设备号和次设备号。
    ret = register_chrdev(major, module_name, &pin4_fops);  
    // 注册字符设备,将设备号和操作函数与内核关联起来,内核可以识别这个设备并知道如何处理它。

    pin4_class = class_create(THIS_MODULE, "myfirstdemo"); 
    // 创建一个类,在 `/sys/class` 目录下生成一个对应的设备类。

    pin4_class_dev = device_create(pin4_class, NULL, devno, NULL, module_name); 
    // 创建设备文件,实际在 `/dev` 目录下创建对应的设备节点。

    return 0;  // 返回 0 表示初始化成功。
}
  • 函数头部
int __init pin4_drv_init(void)

'_init'是一个内核宏,用于告诉编译器该函数仅在模块初始化时使用,'pin4_drv_init'是驱动的初始化函数名称。

  • 变量声明
int ret;

用于存储函数调用的返回值,以检查操作是否成功,这里主要用于检测register_chrdev函数的返回状态。

  • 生成设备号
devno = MKDEV(major, minor);

MKDEV(major, minor)是一个内核宏,用于生成一个设备号('dev_t'类型),设备号用于唯一标识内核中的某个设备。

  • 注册字符设备
ret = register_chrdev(major, module_name, &pin4_fops);

该函数用于注册字符设备驱动程序,告诉内核如何处理与该设备相关的操作(例如打开、关闭、读写等)。major为主设备号,设备名称module_name用于标识这个设备,&pin4_fops是指向file_operations结构体的指针,结构体中包含了操作该字符设备的函数指针(如open、read、write...),内核将通过这些函数指针来操作设备。

  • 创建设备类
pin4_class = class_create(THIS_MODULE, "myfirstdemo");

函数class_create用于创建一个设备类,在'/sys/class'目录下生成一个对应的设备类,这个类的主要作用是为设备文件创建一个逻辑分组,便于管理和分类。THIS_MODULE表示当前模块的指针,"myfirstdemo"为类的名称。

  • 创建设备文件
pin4_class_dev = device_create(pin4_class, NULL, devno, NULL, module_name);

函数device_create用于创建设备文件,在'/dev'目录下生成一个设备节点,用户可以通过这个节点实现与设备的交互。第一个NULL参数为指向父设备的指针(此处没有父设备),第二个NULL参数为私有数据指针。

6 模块清理函数

void __exit pin4_drv_exit(void)
{
    device_destroy(pin4_class, devno);  // 删除设备文件。
    class_destroy(pin4_class);          // 删除设备类。
    unregister_chrdev(major, module_name);  // 取消字符设备的注册。
}

其中unregister_chrdev为取消字符设备的注册,释放设备号,允许其他模块使用这个设备号。

7 模块入口和出口声明

module_init(pin4_drv_init);  
module_exit(pin4_drv_exit);  
MODULE_LICENSE("GPL v2");  
  • module_init定义模块的初始化入口,内核加载模块时会调用模块的初始化函数;
  • module_exit定义模块的清理出口,内核卸载模块时会调用模块的清理函数函数;
  • MODULE_LICENSE声明该模块使用 GPLv2 许可证,确保模块符合开源要求,并可以安全地被内核加载。

总结

该驱动程序是一个简单的字符设备驱动,主要用来展示如何在 Linux 内核中创建字符设备,并处理基本的文件操作(打开和写入)。程序在加载时会创建一个名为'/dev/pin4'的设备文件,用户可以通过文件操作访问这个设备。驱动程序还展示了如何注册和注销设备、创建和销毁设备文件,以及处理用户态和内核态之间的简单交互。

标签:文件,为例,--,内核,模块,驱动,pin4,class,设备
From: https://blog.csdn.net/2401_82904490/article/details/141184897

相关文章

  • BC1.2和PD 充电的区别
    USBBatteryChargingSpecification1.2(BC1.2)和USBPowerDelivery(USBPD)是两个不同的充电标准,它们在应用场景、充电能力、充电协议等方面有显著区别。1.标准简介BC1.2(BatteryChargingSpecification1.2)发布时间:2010年左右。设计目的:为了在标准USB端口上实现更......
  • C语言 ——— 结构体内存对齐
    目录发现问题 偏移量宏:offsetof()结构体内存的对齐规则小结 发现问题有以下两个结构体:结构体1:structS1{ charc1;//1字节 inti;//4字节 charc2;//1字节};结构体2:structS2{ charc1;//1字节 charc2;//1字节 inti;//4字节};通常情况下......
  • Kubernetes中Pod间通信的详细解析
    目录同一个节点中Pod通信原理网络拓扑结构通信过程不同节点上的Pod通信原理网络拓扑结构通信过程同一个节点中Pod通信原理网络拓扑结构Pod:每个Pod都有一个唯一的IP地址(例如,172.16.3.2和172.16.3.3)。Pod内部的网络接口(eth0)连接到一个虚拟网络设备(veth)。虚拟网络设备(vethp......
  • html table colgroup col 使用、功能测试
    代码<template><divclass="page-box"><!--colgroup使用方式1--><table><colgroup><colspan="2"style="width:100px"/><col......
  • 广度优先算法 BFS总结(算法详解+模板+例题)
    一.bfs是什么广度优先搜索(Breadth-FirstSearch,简称BFS),是一种图遍历算法。它从给定的起始节点开始,逐层地向外扩展,先访问起始节点的相邻节点,然后再访问相邻节点的相邻节点,以此类推,直到遍历完所有可达节点。二.基本思路1.一直往前走,直到到达终点。2.遇到分岔路口直接分出几条......
  • cuda环境配置剖析,不再傻傻分不清楚该怎么装环境
    深度学习的第一课,永远是配环境,而这涉及到了很多方面的零碎知识,对于新手来说是很头疼的。而CUDA,作为每个环境都绕不开的主题,在很多时候都会成为成功运行代码的阻碍。这里简单介绍了一下一些需要注意的概念,和如何用conda去配cuda,希望能够让大家配环境的时候能够稍微轻松点。Dri......
  • 函数(子程序)的常见、易混淆概念详解【对初学者有帮助】
    C语⾔中的函数也被称做子程序,意思就是⼀个完成某项特定的任务的⼀小段代码。C语⾔标准中提供了许多库函数,点击下面的链接可以查看c语言的库函数和头文件。C/C++官⽅的链接:https://zh.cppreference.com/w/c/header目录一、函数头与函数体二、实参与形参三、return的用法事......
  • 使用 Flask、Celery 和 Python 实现每月定时任务
    为了创建一个使用Flask、Celery和Python实现的每月定时任务,我们需要按照以下步骤进行:1.安装必要的库我们需要安装Flask、Celery和Redis(作为消息代理)。我们可以使用pip来安装它们:bash复制代码​pipinstallflaskceleryredis2.设置Flask和Celery首先,我们需......
  • 牛客周赛Round54(ABCDE)
     发布这些文章的目的很简单: 1.作者自己也是c++刚起手没多久的小白,深知自学一门学校不开设课程的语言的难处,(为了改错到处求人问路,看了好多没有真正帮助自己学习知识盲区,掌握新知识的教学视频,自己理解有偏差不能及时改正的困难)尽可能地帮助广大姐妹哥们儿们,我保证博文中的每......
  • 用栈访问最后若干元素——682、71、388
    682.棒球比赛(简单)你现在是一场采用特殊赛制棒球比赛的记录员。这场比赛由若干回合组成,过去几回合的得分可能会影响以后几回合的得分。比赛开始时,记录是空白的。你会得到一个记录操作的字符串列表 ops,其中 ops[i] 是你需要记录的第 i项操作,ops 遵循下述规则:整数 x ......