首页 > 系统相关 >Linux驱动开发:一文掌握 块设备VS字符设备开发流程全解!

Linux驱动开发:一文掌握 块设备VS字符设备开发流程全解!

时间:2025-01-17 12:31:05浏览次数:3  
标签:struct 驱动 VS Linux 全解 disk my example 设备

Linux驱动开发是嵌入式系统开发中的一个重要组成部分,它直接关系到硬件设备的功能实现和性能优化。在Linux系统中,驱动开发主要分为字符设备驱动、块设备驱动和网络设备驱动三大类。本文将重点介绍字符设备和块设备的基础知识,以及它们在驱动开发中的差异和开发流程。

一、字符设备与快设备的区别

1. 数据传输方式不同

字符设备:数据传输是以字符为单位进行的,通常不需要缓冲。例如键盘、鼠标等设备,它们的输入输出操作按字节顺序进行,适用于处理少量但频繁的I/O操作。

块设备:数据传输是以固定大小的数据块为单位进行的,这些数据块通常需要缓冲。例如硬盘、NAND Flash等存储设备,它们的读写操作以一定大小的数据块为单位,适用于处理大量数据的存储和传输。

2. 驱动程序结构不同

字符设备:由字符设备驱动程序控制,可以使用文件操作的接口进行操作。字符设备驱动的主要任务是实现设备的打开、关闭、读取和写入等基本功能。

块设备:由块设备驱动程序控制,同样可以使用文件操作的接口进行操作。块设备驱动的主要任务是实现对设备的读写请求管理、缓存管理和数据一致性保证等功能。

3. 典型应用场景

字符设备:适用于需要快速响应的设备,如串口通信设备、LED指示灯等。

块设备:适用于需要高效数据处理的设备,如磁盘驱动器、固态硬盘等。

 二、字符设备驱动的开发流程 

 字符设备驱动的开发流程通常包括以下几个步骤:

//包含必要的头文件:
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>

1. 定义设备号和类

每个字符设备都需要一个唯一的主设备号来标识。在内核中可以通过register_chrdev函数注册字符设备,并分配一个主设备号。

int ret = register_chrdev(0, "mydevice", &my_fops);
 
if (ret < 0) { 

    printk(KERN_ALERT "mydevice failed to register");
 
    return ret;
 
}
 
printk(KERN_INFO "mydevice registered ");

上述代码中,my_fops 是一个指向struct file_operations结构的指针,该结构定义了设备的各种操作函数,my_fops 结构体的定义 在下面的代码中。

2. 实现文件操作函数

字符设备驱动需要实现一系列的文件操作函数,包括但不限于打开、关闭、读取和写入等。这些函数通过填充 struct file_operations 结构体来实现。

struct file_operations  my_fops = {
 
    .owner = THIS_MODULE,
 
    .open = device_open,
 
    .release = device_release,
 
    .read = device_read,
 
    .write = device_write,
 
};

每个函数的具体实现根据设备的特性而定。例如,device_read函数可以这样实现:

ssize_t  device_read(struct file *filp, char *buffer, size_t count, loff_t *f_pos) {
    // 实现读取逻辑
    printk(KERN_INFO "Device read operation");
    return 0; // 返回读取的字节数
}

device_open 函数可以这样实现:

// 我们先定义一个结构体,open_count 用来记录 打开次数:

struct my_device {
    int  open_count;
};

int device_open(struct inode *inode, struct file *filp)
{
    struct my_device *dev;  //定义结构体指针 dev


     dev= kmalloc(sizeof(struct my_char_device), GFP_KERNEL);
    if (!dev) return -ENOMEM;
    /* 将私有数据设置到文件私有数据中 */
    filp->private_data = dev;
    /* 初始化设备 */
    if (device_init(dev)) {
        return -ENODEV;
    }
    /* 更新设备的打开次数 */
    atomic_inc(&dev->open_count);
 
    return 0;
}

参数说明:

  • inode:设备的索引节点,内核用它来表示一个打开的文件。

  • filp:文件结构体指针,内核用它来表示一个打开的文件描述符。

3. 编写模块初始化和退出函数

模块初始化函数用于注册设备和分配资源,而模块退出函数则负责注销设备和释放资源。这两个函数分别使用module_init和module_exit宏进行标记。

4.编译和加载驱动模块

完成上述步骤后,需要将驱动程序编译成内核模块。可以使用Makefile来自动化编译过程,假设我们的代码文件为 my_chardev.c ,执行下面的 Makefile 后 会生成 my_chardev.ko文件。

obj-m+ = my_chardev.o
 
all:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
 
clean:
    make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

然后,通过insmod命令加载驱动模块,并通过mknod命令创建设备节点。

sudo insmod my_chardev.ko
 
sudo mknod /dev/mychardriver c <major> <minor>
 
其中,<major>为主设备号,<minor>为次设备号。加载成功后,可以通过cat或echo命令测试设备的功能。

三、快设备驱动的开发流程

快设备的开发流程与字符设备类似,但在具体实现细节上有所不同。以下是快设备驱动开发的主要步骤:

//包含必要的头文件:

#include <linux/module.h>

#include <linux/init.h>

#include <linux/blkdev.h>

#include <linux/fs.h>

1. 定义设备结构和初始化函数

块设备驱动也需要定义一个设备结构体并初始化。

struct file_operations my_fops = {

.owner = THIS_MODULE,

.open = my_open,

.release = my_release,

.read = my_read,

.write = my_write,

.unlocked_ioctl = my_ioctl,

// 更多的文件操作函数按需添加

};

2. 实现文件操作函数

虽然快设备也是通过文件操作接口进行操作,但其内部实现涉及更多的数据管理和调度机制。例如,快设备的write函数可能需要处理写缓存和数据同步等问题。

// 这里是对应的操作函数实现
static int my_open(struct inode *inode, struct file *filp) {
    // 设备打开时的操作
    return 0;
}
 
static int my_release(struct inode *inode, struct file *filp) {
    // 设备关闭时的操作
    return 0;
}
 
static ssize_t my_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos) {
    // 设备读取操作
    return 0;
}
 
static ssize_t my_write(struct file *filp, const char __user *buf, size_t size, loff_t *ppos) {
    // 设备写入操作
    return 0;
}
 
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    // 设备控制操作
    return 0;
}
3. 编写模块初始化和退出函数

与字符设备类似,快设备驱动也需要编写模块初始化和退出函数,但需要额外处理设备的注册和注销,我们先定义 gendisk 结构体如下:

//gendisk 结构体在Linux块设备驱动中用于表示一个具体的磁盘设备或其分区.

static struct gendisk *example_disk;

 结构体中包含的主要成员变量如下:

  • int major:设备的主设备号。
  • int first_minor:起始次设备号。
  • int minors:次设备数量。
  • char disk_name:块设备名称。
  • struct hd_struct** part:分区表信息。
  • struct block_device_operations* fops:块设备操作函数。
  • struct request_queue* queue:请求队列,用于管理该设备的IO请求。
  • void* private_data:私有数据。
  • sector_t capacity:扇区数‌。

 编写模块初始化代码:

static int __init example_init(void) {

// 分配一个新的gendisk结构体

example_disk = alloc_disk(1);

if (!example_disk) {

printk(KERN_ERR "无法分配gendisk结构体\n");

return -ENOMEM;

}

// 设置gendisk的属性

snprintf(example_disk->disk_name, DISK_NAME_LEN, "example");

example_disk->major = 123; // 主设备号

example_disk->first_minor = 0;

example_disk->fops = &my_fops ; // 与设备关联的文件操作

set_capacity(example_disk, 1UL << 32); // 设置设备容量为2的32次方扇区

// 注册块设备

add_disk(example_disk);

return 0;

}

 编写模块注销代码:

static void __exit example_exit(void) {

del_gendisk(example_disk); // 注销gendisk

put_disk(example_disk); // 释放gendisk结构体

}

 模块初始化和退出函数

module_init(example_init);

module_exit(example_exit);

MODULE_LICENSE("GPL");  //声明 LICENSE 模式

4. 编译和加载驱动模块

快设备驱动的编译和加载过程与字符设备类似,上述代码文件命名为 myblkdriver.c,然后写 Makefile 文件,执行只Makefile 文件后 后生成 myblkdriver.ko 文件, 执行下面的命令进行挂载和创建设备节点。

sudo insmod myblkdriver.ko
 
sudo mknod /dev/myblkdriver b <major> <minor>

 加载成功后,可以通过相应的命令测试设备的功能。

四、总结

字符设备和快设备在Linux驱动开发中扮演着不同的角色,各自有其独特的特点和应用场景。字符设备适用于需要快速响应的小量数据传输场景,而快设备则适用于大量数据的高效存储和传输。无论是哪种类型的设备驱动开发,都需要遵循一定的流程和技术规范。通过本文的介绍,希望读者能够对Linux驱动开发有一个更深入的理解,并在实际操作中更加得心应手。

标签:struct,驱动,VS,Linux,全解,disk,my,example,设备
From: https://blog.csdn.net/m0_63998314/article/details/145198992

相关文章

  • Linux进程概念-进程状态
    在上一篇已经了解了在进程中的基本概念,现在我来了解一下进程的相关状态;对于进程的状态,在详细了解之前我们可以来一个粗略的理解:进程的状态可以理解为就是一个整数,用宏定义实现R,S等状态;也就是进程状态就是take_struck内的一个整数;如果不理解也没关系,下面来看看对进程状......
  • 消息队列实战指南:三大MQ 与 Kafka 适用场景全解析
    前言:在当今数字化时代,分布式系统和大数据处理变得愈发普遍,消息队列作为其中的关键组件,承担着系统解耦、异步通信、流量削峰等重要职责。ActiveMQ、RabbitMQ、RocketMQ和Kafka作为市场上极具代表性的消息队列产品,各自拥有独特的功能特性与适用场景。本博客旨在深入剖析这四种消......
  • Linux内存泄露案例分析和内存管理分享
    作者:京东科技李遵举一、问题近期我们运维同事接到线上LB(负载均衡)服务内存报警,运维同事反馈说LB集群有部分机器的内存使用率超过80%,有的甚至超过90%,而且内存使用率还再不停的增长。接到内存报警的消息,让整个团队都比较紧张,我们团队负责的LB服务是零售、物流、科技等业务服务的流......
  • 矩阵碰一碰发视频源码技术开发全解析,支持OEM
    在当今数字化内容传播迅速发展的时代,矩阵碰一碰发视频功能以其便捷、高效的特点,为用户分享视频提供了全新的体验。本文将深入探讨矩阵碰一碰发视频源码的技术开发过程,从原理到实现,为开发者提供全面的技术指引。一、技术原理矩阵碰一碰发视频功能主要基于近场通信技术,如NFC(N......
  • 工作中用到的linux的命令
    常用的命令汇总创建后台进程ping本机地址使用以下命令在后台执行ping本机地址:ping127.0.0.1&查找创建的后台进程并强制杀掉查看当前进程并杀掉指定的后台进程,可以使用以下命令:psaux|grepping#查找ping进程kill-9<PID>#使用进程ID强制......
  • Linux 找到占用磁盘最多的文件或目录,可以使用du和sort
    想要找到占用磁盘最多的文件或目录,可以使用du和sort命令: du-h/path/to/directory|sort-rh|head-n10其中:du-h/path/to/directory用于计算指定目录下的所有文件和子目录的大小,并以人类可读的格式显示。sort-rh用于按磁盘使用情况的大小(逆序)对输出进行排序。h......
  • Linux小知识
    linux命令使用方法,在网页中搜索命令用Ctrl+F查看命令用法可以在命令后加-h或--help,或者用man<command>查看详细用法常用命令nohup不挂断地执行命令烦恼于晚上跑的任务因为网络波动或是电脑自动重启挂了?用它就对了!nohup<command>[>logfile][2>&1][&]最常用的方......
  • Linux系统中 ping 的平均时间
    使用ping命令可以测试网络连接的质量,包括延迟和丢包率。在Linux系统中,计算ping命令的耗时可以通过以下方法进行:ping-c5域名|grep'rttmin/avg/max/mdev'|awk-F'[/]''{print$8,$NF}'ping-c5域名:向域名发送5次ICMP请求包(-c5表示发送5个包)。g......
  • 【Linux性能】如何在 Linux 中优雅地让 HTTP 请求超时?
    在Linux系统中,优化HTTP请求的性能和可靠性是每个系统管理员和开发者的必备技能之一。特别是当你在处理大量HTTP请求时,合理设置超时可以有效防止资源被长时间占用、提高系统性能,并避免潜在的安全风险。本篇文章将带你深入探讨如何在Linux中设置HTTP请求超时,逐步揭示各种......
  • Ubuntu等各类Linux系统安装配置Docker详细教程(全网最详细,步骤简洁,看完包懂)
    文章目录前言详细步骤1.安装相关依赖2.安装阿里云的DockerGPG密钥3.设置stable仓库4.安装Docker5.启动服务6.验证测试常见问题及对应解决方案Ubuntu22.04配置Docker-速通版前言Docker是一个非常常用的工具,但是由于其涉及到的知识点较多,所以网上的教程大部分非......