Linux 块设备开发学习

news/2024/9/28 12:50:41 标签: linux, 学习, 服务器, c++, 架构, arm开发, 驱动开发

Linux 块设备是指可以以固定大小的块(通常为 512 字节或 4KB)进行读写的存储设备。块设备通常用于实现文件系统和管理数据的存储与访问。以下是块设备的一些主要特点和组成部分:

1. 主要特点

  • 随机访问:块设备支持随机读写,可以直接访问任意位置的数据。
  • 块大小:数据以块为单位进行传输,块的大小通常为 512 字节或 4KB。
  • 设备类型:常见的块设备包括硬盘、固态硬盘(SSD)、USB 驱动器等。

2. 组成部分

  • 块设备驱动:负责管理块设备的读取、写入和控制等操作。驱动程序通过系统调用与内核进行交互。
  • 设备节点:在 /dev 目录下的特殊文件,用户空间程序通过这个节点与块设备进行交互。
  • 请求队列:块设备驱动使用请求队列来管理 I/O 请求,确保高效的读写操作。

3. 块设备的工作原理

  • I/O 调度:内核将读写请求排入请求队列,使用 I/O 调度算法(如 CFQ、Deadline 等)优化请求顺序,提高性能。
  • 数据传输:通过 DMA(直接内存访问)技术,块设备可以直接与内存进行数据传输,减轻 CPU 负担。

4. 常用操作

  • 读操作:从块设备读取指定块的数据到内存。
  • 写操作:将内存中的数据写入块设备的指定块。
  • 分区管理:使用工具(如 fdisk)对块设备进行分区,以便创建多个文件系统。

5. 文件系统

块设备通常用来创建文件系统,如 ext4、NTFS、FAT 等,文件系统提供了文件的组织、存储和访问方式。

6. 设备树

在某些平台上,块设备的信息可以通过设备树(Device Tree)来描述,以便在启动时告知内核如何初始化设备。

块设备开发中使用到API介绍:

1. 设备注册与注销

  • register_blkdev

    • 用途:注册一个块设备主设备号。
    • 使用
      int major;
      major = register_blkdev(0, "myblock");  // 0 表示动态分配设备号
      if (major < 0) {
          printk(KERN_ERR "Failed to register block device\n");
      }
      
  • unregister_blkdev

    • 用途:注销已经注册的块设备主设备号。
    • 使用
      unregister_blkdev(major, "myblock");
      

2. 分配和管理块设备结构

  • alloc_disk

    • 用途:分配 gendisk 结构体。
    • 使用
      struct gendisk *gd;
      gd = alloc_disk(16);  // 分配 16 个从设备号
      if (!gd) {
          printk(KERN_ERR "Failed to allocate gendisk\n");
      }
      
  • set_capacity

    • 用途:设置设备容量(单位为扇区)。
    • 使用
      set_capacity(gd, DEVICE_SIZE / SECTOR_SIZE);  // DEVICE_SIZE 是总字节数
      
  • add_disk

    • 用途:将 gendisk 结构体添加到系统中。
    • 使用
      add_disk(gd);
      
  • del_gendisk

    • 用途:从系统中删除 gendisk 结构体。
    • 使用
      del_gendisk(gd);
      

3. 设备操作结构

  • block_device_operations
    • 用途:包含与块设备相关的操作函数指针。
    • 使用
      static struct block_device_operations my_fops = {
          .owner = THIS_MODULE,
          .open = my_open,
          .release = my_release,
          .read = my_read,
          .write = my_write,
      };
      

4. 读写操作

  • submit_bio
    • 用途:提交一个块 IO 请求。
    • 使用
      struct bio *bio;
      bio = bio_alloc(GFP_KERNEL, 1);  // 分配一个 bio
      bio_set_dev(bio, bdget(dev));     // 设置设备
      bio_add_page(bio, page, size, offset); // 添加数据页
      submit_bio(READ, bio);            // 提交读请求
      

5. 内存管理

  • kmallockfree
    • 用途:动态分配和释放内存。
    • 使用
      device_buffer = kmalloc(DEVICE_SIZE, GFP_KERNEL);
      if (!device_buffer) {
          printk(KERN_ERR "Failed to allocate memory\n");
      }
      
      kfree(device_buffer);  // 释放内存
      

6. 设备节点管理

  • mknod
    • 用途:在 /dev 目录下创建设备节点,通常在用户空间执行。
    • 使用
      sudo mknod /dev/myblock b <major> 0  # <major> 是主设备号
      ``
      
      

块设备开发流程

  1. 环境准备

    • 确保你的 Linux 系统上安装了开发工具,例如 gccmakekernel headersbuild-essential
  2. 创建块设备驱动文件

    • 创建一个新的 C 文件,例如 myblock.c,用于实现块设备驱动。
  3. 编写 Makefile

    • 创建一个 Makefile 以便编译驱动程序。
  4. 实现驱动功能

    • 编写驱动的主要功能,如打开、关闭、读写等。
  5. 编译驱动

    • 使用 make 命令编译驱动,生成模块文件(.ko)。
  6. 加载模块

    • 使用 insmod 命令加载块设备驱动模块。
  7. 创建设备节点

    • 使用 mknod 命令在 /dev 目录下创建设备文件。
  8. 测试驱动

    • 使用工具(如 ddfdiskmount)进行读写测试。
  9. 卸载模块

    • 使用 rmmod 卸载驱动。

示例代码

以下是一个简单的块设备驱动示例,包含基本的功能:

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/bio.h>
#include <linux/blkdev.h>
#include <linux/slab.h>

#define DEVICE_NAME "myblock"
#define SECTOR_SIZE 512
#define DEVICE_SIZE (SECTOR_SIZE * 1024) // 512KB

static int major;
static char *device_buffer;

static int my_open(struct block_device *bdev, fmode_t mode) {
    return 0;
}

static void my_release(struct gendisk *gd, fmode_t mode) {
}

static int my_read(struct block_device *bdev, sector_t sector, unsigned long nsectors, char *buffer) {
    memcpy(buffer, device_buffer + sector * SECTOR_SIZE, nsectors * SECTOR_SIZE);
    return nsectors;
}

static int my_write(struct block_device *bdev, sector_t sector, unsigned long nsectors, const char *buffer) {
    memcpy(device_buffer + sector * SECTOR_SIZE, buffer, nsectors * SECTOR_SIZE);
    return nsectors;
}

static struct block_device_operations my_fops = {
    .owner = THIS_MODULE,
    .open = my_open,
    .release = my_release,
};

static int __init mymodule_init(void) {
    major = register_blkdev(0, DEVICE_NAME);
    if (major < 0) return major;

    device_buffer = kmalloc(DEVICE_SIZE, GFP_KERNEL);
    if (!device_buffer) {
        unregister_blkdev(major, DEVICE_NAME);
        return -ENOMEM;
    }
    memset(device_buffer, 0, DEVICE_SIZE);

    struct gendisk *gd = alloc_disk(16);
    if (!gd) {
        kfree(device_buffer);
        unregister_blkdev(major, DEVICE_NAME);
        return -ENOMEM;
    }

    gd->major = major;
    gd->first_minor = 0;
    gd->fops = &my_fops;
    sprintf(gd->disk_name, DEVICE_NAME);
    set_capacity(gd, DEVICE_SIZE / SECTOR_SIZE);
    add_disk(gd);
    
    return 0;
}

static void __exit mymodule_exit(void) {
    del_gendisk(gd);
    unregister_blkdev(major, DEVICE_NAME);
    kfree(device_buffer);
}

module_init(mymodule_init);
module_exit(mymodule_exit);
MODULE_LICENSE("GPL");

Makefile 示例

obj-m += myblock.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

测试步骤

  1. 编译驱动

    make
    
  2. 加载模块

    sudo insmod myblock.ko
    
  3. 创建设备节点

    sudo mknod /dev/myblock b <major> 0  # <major> 是驱动的主设备号
    
  4. 测试读写

    echo "Hello, World!" | sudo dd of=/dev/myblock bs=512 count=1
    sudo dd if=/dev/myblock of=testfile bs=512 count=1
    
  5. 卸载模块

    sudo rmmod myblock
    

下面是一个简单的测试应用程序,用于测试 Linux 块设备驱动的读写功能,以及预期的输出结果。

测试应用程序

创建一个名为 test_myblock.c 的文件,包含以下代码:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

#define DEVICE "/dev/myblock"
#define BUFFER_SIZE 512

int main() {
    int fd;
    char write_buffer[BUFFER_SIZE] = "Hello, Linux Block Device!";
    char read_buffer[BUFFER_SIZE];

    // 打开设备文件
    fd = open(DEVICE, O_RDWR);
    if (fd < 0) {
        perror("Failed to open device");
        return -1;
    }

    // 写入数据
    if (write(fd, write_buffer, BUFFER_SIZE) < 0) {
        perror("Failed to write to device");
        close(fd);
        return -1;
    }

    // 读取数据
    lseek(fd, 0, SEEK_SET);  // 重置文件指针
    if (read(fd, read_buffer, BUFFER_SIZE) < 0) {
        perror("Failed to read from device");
        close(fd);
        return -1;
    }

    // 输出读取的数据
    printf("Read from device: %s\n", read_buffer);

    // 关闭设备
    close(fd);
    return 0;
}

编译测试应用程序

在终端中运行以下命令编译测试程序:

gcc test_myblock.c -o test_myblock

测试步骤

  1. 编译并加载块设备驱动
    确保块设备驱动已经编译并加载到内核中。

  2. 创建设备节点

    sudo mknod /dev/myblock b <major> 0  # <major> 是块设备的主设备号
    
  3. 编译测试应用程序

    gcc test_myblock.c -o test_myblock
    
  4. 运行测试程序

    sudo ./test_myblock
    

预期输出结果

如果块设备驱动正常工作,测试程序将输出:

Read from device: Hello, Linux Block Device!
以下是关于 Linux 块设备开发的总结:

块设备开发总结

块设备开发是一个复杂但重要的任务,涉及到系统的存储管理。掌握相关知识和技术,可以有效地实现和优化块设备驱动,提高系统的性能和稳定性。


http://www.niftyadmin.cn/n/5681138.html

相关文章

计算机毕业设计党建学习网站查看发布党建评论留言搜索部署安装/springboot/javaWEB/J2EE/MYSQL数据库/vue前后分离小程序

目录 ‌开发背景‌&#xff1a; ‌开发意义‌&#xff1a; ‌开发目标‌&#xff1a; 部署安装 主要功能 功能图 界面介绍 技术介绍 需求分析 1. 用户角色分析 2. 功能需求分析 3. 性能需求分析 4. 界面设计需求 5. 其他需求 ‌党建学习网站的开发背景、意义与目…

MySQL进阶:深入理解数据约束与优化查询

MySQL管理数据库 创建数据库 CREATE DATABASE IF NOT EXISTS 数据库名; 删除数据库 DROP DATABASE 数据库名; 表的管理 查看所有表 use 数据库名; 选中一个数据库show tables; 创建表:student(整数id,字符串name,整数age) CREATE TABLE sutdent (id int, name varchar(…

0. Pixel3 在Ubuntu22下Android12源码拉取 + 编译

0. Pixel3 在Ubuntu22下Android12源码拉取 编译 原文地址: http://www.androidcrack.com/index.php/archives/3/ 1. 前言 这是一个非常悲伤的故事, 因为一个意外, 不小心把之前镜像的源码搞坏了. 也没做版本管理,恢复不了了. 那么只能说是重新做一次. 再者以前的镜像太老旧…

Qt --- Qt窗口

一、前言 前面学习的所有代码&#xff0c;都是基于QWidget控件。QWidget更多的是作为别的窗口的一个部分。 Qt中的QMainWindow就是窗口的完全体 Menu Bar菜单栏 Tool Bar Area 工具栏&#xff0c;类似于菜单栏&#xff0c;工具栏本质上就是把菜单中的一些比较常用的选项&…

Midjourney 使用教程——入门篇

目录标题 一、前提二、Midjourney 使用文档三、如何注册使用Midjourney四、结合GPT快速生成Midjourney 构图指令五、其他 一、前提 先连接国外代理服务器。没有的可以退下了。 二、Midjourney 使用文档 Discord 快速入门 注意&#xff1a;如图所示&#xff0c;需要10美刀一…

另外知识与网络总结

一、重谈NAT&#xff08;工作在网络层&#xff09; 为什么会有NAT 为了解决ipv4地址太少问题&#xff0c;到了公网的末端就会有运营商路由器来构建私网&#xff0c;在不同私网中私有IP可以重复&#xff0c;这就可以缓解IP地址太少问题&#xff0c;但是这就导致私有IP是重复的…

JMeter源码解析之JMeter命令行新增命令

JMeter源码解析之JMeter命令行新增命令 需求描述 需要新增一条命令&#xff0c;能够在JMeter命令行中能够展示输入对应的JMeter命令&#xff0c;能够展示对应的命令信息 查看命令效果如下&#xff1a; apache-jmeter-5.1\bin>jmeter --? Copyright © 1999-2024 The …

洞悉go.dev

本篇内容是根据2020年1月份Grokking Go.dev音频录制内容的整理与翻译, Carmen、Mat 和 Jon 与 Steve Francia 和 Julie Qiu 一起讨论了新的 Go.dev 网站。它背后的动机是什么&#xff1f;它使用了什么技术来构建它&#xff1f;他们如何努力使包发现变得更好&#xff1f;有哪些…