Linux 块设备开发学习

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 块设备开发的总结:

块设备开发总结

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值