Linux驱动开发 块设备

目录

序言

1.块设备结构

分区(gendisk)

请求(request)

请求队列

1. 多队列架构

2. 默认限制与扩展

bio

2.块设备的使用

头文件与宏定义

blk-mq 相关结构和操作

块设备操作函数

 模块初始化函数

 模块退出函数

3.总结


序言

块设备(如硬盘、虚拟盘)以固定大小的块(扇区)进行读写。块设备驱动的主要任务就是响应来自文件系统的 I/O 请求,并将数据正确地读写到设备对应的存储区域。

为了在多核系统中提高并发性能,最新内核采用了blk-mq(Block Multi-Queue)接口,它使用多个硬件队列来分发和处理 I/O 请求。每个请求包含数据传输的信息(如起始扇区、数据长度等),本篇文章将使用blk-mq而不再讲述单队列模式,另外如果有兴趣深入学习块设备和多队列模式可以阅读此链接。

1.块设备结构

分区(gendisk)

gendisk 是内核中描述一个块设备的结构体,可以理解为分区,它保存了设备的主设备号、次设备号、设备名称、容量、操作函数等信息。当驱动调用 add_disk() 后,系统会将设备显示在 /dev 下。

块设备中最小的可寻址单元是扇区,扇区大小一般是2的整数倍,最常见的大小是512字节。扇区的大小是设备的物理属性,扇区是所有块设备的基本单元,块设备 无法对比它还小的单元进行寻址和操作,不过许多块设备能够一次就传输多个扇区。虽然大多数块设备的扇区大小都是512字节,不过其它大小的扇区也很常见, 比如,很多CD-ROM盘的扇区都是2K大小。不管物理设备的真实扇区大小是多少,内核与块设备驱动交互的扇区都以512字节为单位。因此,set_capacity()函数也以512字节为单位。

请求(request)

request是描述I/O请求,包含数据传输的信息(如起始扇区、数据长度等)还有bio结构体。

下面只列出部分request结构体中的参数,具体的请查阅源文件或资料。

  • 扇区参数
   sector_t hard_sector; unsigned long hard_nr_sectors; unsigned int hard_cur_sectors;

上述 3 个成员标识还未完成的扇区,hard_sector 是第一个尚未传输的扇区,hard_nr_sectors 是尚待完成的扇区数,hard_cur_sectors 是当前 I/O 操作中待完成的扇区数。这些成员只用于内核块设备层,驱动不应当使用它们。
  在驱动程序中一般使用的是:

sector_t sector; 
unsigned long nr_sectors; 
unsigned int current_nr_sectors;


  这 3 个成员在内核和驱动交互中发挥着重大作用。它们以 512 字节大小为一个扇区,如果硬件的扇区大小不是 512 字节,则需要进行相应的调整。例如,如果硬件的扇区大小是 2048 字节,则在进行硬件操作之前,需要用 4 来除起始扇区号。
  注意:hard_sector 、 hard_nr_sectors 、 hard_cur_sectors 与 sector 、 nr_sectors 、
current_nr_sectors 之间可认为是“副本”关系。

  • struct bio *bio bio是这个请求中包含的 bio 结构体的链表,驱动中不宜直接存取这个成员,而应该使用后文将介绍的rq_for_each_bio()。
  • char *buffer 指向缓冲区的指针,数据应当被传送到或者来自这个缓冲区,这个指针是一个内核虚拟地址,可被驱动直接引用。

使用如下宏可以从 request 获得数据传送的方向

rq_data_dir(struct request *req);
0 返回值表示从设备中读,非 0 返回值表示向设备写。

请求队列

当外部设备或用户程序访问块设备时,会发起I/O请求,而我们的块设备有一个请求队列,我们这里是最新的blk-mq队列,其会为每一个CPU都分配一组软件队列和硬件队列,每个队列可以支持0-1023个I/O请求

1. 多队列架构
  • 软件队列(Software Queues):每个 CPU 核心分配一个队列,减少锁竞争。

  • 硬件派发队列(Hardware Dispatch Queues):根据设备硬件队列数量分配,映射到实际硬件通道。

  • 标签集(Tag Set):管理请求标签,实现请求与硬件的解耦。

2. 默认限制与扩展
  • 队列深度(Queue Depth):默认 1024,但需根据硬件能力调整。

  • 硬件队列数量:建议与 CPU 核心数或硬件通道数对齐。

struct bio { sector_t bi_sector;           /* 标识这个 bio 要传送的第一个(512 字节)扇区。 */ struct bio *bi_next;                           /* 下一个 bio */ struct block_device *bi_bdev; unsigned long bi_flags;       /* 一组描述 bio 的标志,如果这是一个写请求,最低有效位被置位,可以使用bio_data_dir(bio)宏来获得读写方向。 */ unsigned long bi_rw;             /* 低位表示 READ/WRITE,高位表示优先级*/  unsigned short bi_vcnt;           /* bio_vec 数量 */ unsigned short bi_idx;            /* 当前 bvl_vec 索引 */  /*不相邻的物理段的数目*/ unsigned short bi_phys_segments;  /*物理合并和 DMA remap 合并后不相邻的物理段的数目*/ unsigned short bi_hw_segments;  unsigned int bi_size;       /* 以字节为单位所需传输的数据大小,驱动中可以使用bio_sectors(bio)宏获得以扇区为单位的大小。 */ /* 为了明了最大的 hw 尺寸,我们考虑这个 bio 中第一个和最后一个 虚拟的可合并的段的尺寸 */ unsigned int bi_hw_front_size; unsigned int bi_hw_back_size;  unsigned int bi_max_vecs;               /* 我们能持有的最大 bvl_vecs 数 */  struct bio_vec *bi_io_vec;               /* bio_vec 结构体,bio 的核心*/ bio_end_io_t *bi_end_io; atomic_t bi_cnt; void *bi_private;  bio_destructor_t *bi_destructor; }; 

具体如何配置后面会讲到。

bio

I/O 请求的数据通常以 bio(block I/O)表示,一个请求中可能包含多个bio,而 bio中的每个数据段用 bio_vec 表示。一个 bio_vec 指向一段连续的内存页数据,在数据传输过程中我们需要将这些页映射到内核地址空间进行访问(通过 kmapkunmap

bio结构体:

struct bio { sector_t bi_sector;           /* 标识这个 bio 要传送的第一个(512 字节)扇区。 */ struct bio *bi_next;                           /* 下一个 bio */ struct block_device *bi_bdev; unsigned long bi_flags;       /* 一组描述 bio 的标志,如果这是一个写请求,最低有效位被置位,可以使用bio_data_dir(bio)宏来获得读写方向。 */ unsigned long bi_rw;             /* 低位表示 READ/WRITE,高位表示优先级*/  unsigned short bi_vcnt;           /* bio_vec 数量 */ unsigned short bi_idx;            /* 当前 bvl_vec 索引 */  /*不相邻的物理段的数目*/ unsigned short bi_phys_segments;  /*物理合并和 DMA remap 合并后不相邻的物理段的数目*/ unsigned short bi_hw_segments;  unsigned int bi_size;       /* 以字节为单位所需传输的数据大小,驱动中可以使用bio_sectors(bio)宏获得以扇区为单位的大小。 */ /* 为了明了最大的 hw 尺寸,我们考虑这个 bio 中第一个和最后一个 虚拟的可合并的段的尺寸 */ unsigned int bi_hw_front_size; unsigned int bi_hw_back_size;  unsigned int bi_max_vecs;               /* 我们能持有的最大 bvl_vecs 数 */  struct bio_vec *bi_io_vec;               /* bio_vec 结构体,bio 的核心*/ bio_end_io_t *bi_end_io; atomic_t bi_cnt; void *bi_private;  bio_destructor_t *bi_destructor; }; 

bio_vec结构体: 

struct bio_vec { struct page *bv_page;                   /* 页指针 */ unsigned int bv_len;                     /* 传输的字节数 */ unsigned int bv_offset;                   /* 偏移位置 */ }; 

2.块设备的使用

头文件与宏定义

包含内核模块、块设备、内存管理等所需的头文件,并定义设备名称、扇区大小(通常为 512 字节)和设备总大小(例如 16MB)。并且定义了一个自定义数据结构(例如 struct mydisk_device),用来保存设备的存储数据指针和设备大小。全局变量 device 保存了设备的实例。

#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/blkdev.h>
#include <linux/genhd.h>
#include <linux/vmalloc.h>
#include <linux/spinlock.h>
#include <linux/blk-mq.h>
#include <linux/bio.h>
#include <linux/highmem.h>   // 用于 kmap/kunmap#define DEVICE_NAME         "myblk"                // 设备名称
#define KERNEL_SECTOR_SIZE  512                    // 内核扇区大小,固定为 512 字节
#define MYDISK_SIZE         (16 * 1024 * 1024)     // 设备大小:16MB 分配内存基本单位为bytestruct mydisk_device {unsigned char *data;   // 存放设备数据的内存区size_t size;           // 设备的总大小(字节)
};static struct mydisk_device *device = NULL;

自定义结构用于保存设备的内存数据区域和大小。在初始化阶段,我们会分配一块内存作为设备的“存储区”。

blk-mq 相关结构和操作

static struct blk_mq_tag_set tag_set;
static struct request_queue *queue = NULL;

tag_set:配置多队列(blk-mq)的参数,如硬件队列数、队列深度、NUMA 亲和性等。

queue:所有 I/O 请求都会被加入到这个请求队列中,blk-mq 会调用我们定义的处理函数来处理这些请求。

blk-mq 请求处理函数

这是驱动核心部分,用于处理每个 I/O 请求。函数中会根据请求的起始扇区和请求长度计算出设备内存的偏移,然后遍历请求中的所有 bio_vec 数据段,根据请求方向(读或写)完成数据拷贝,最后调用 blk_mq_end_request() 通知系统该请求处理完毕

static blk_status_t myblk_mq_fn(struct blk_mq_hw_ctx *hctx,const struct blk_mq_queue_data *bd)
{struct request *req = bd->rq;blk_status_t status = BLK_STS_OK;unsigned long offset;unsigned int total_len;struct req_iterator iter;struct bio_vec bvec;unsigned int copied = 0;/* 根据请求起始扇区计算设备内存偏移 */offset = blk_rq_pos(req) * KERNEL_SECTOR_SIZE;total_len = blk_rq_bytes(req);/* 检查请求是否超出设备范围 */if (offset + total_len > device->size) {printk(KERN_NOTICE "myblk: 请求超出设备范围: offset %lu, len %u\n", offset, total_len);status = BLK_STS_IOERR;goto done;}/* 遍历请求中的每个 bio_vec 数据段 */rq_for_each_segment(bvec, req, iter) {/* 将 bio_vec 中的页映射到内核地址空间 */char *buffer = kmap(bvec.bv_page) + bvec.bv_offset;unsigned int len = bvec.bv_len;if (copied + len > total_len)len = total_len - copied;/* 根据请求类型进行数据拷贝:* - 读请求:将设备数据复制到用户请求缓冲区* - 写请求:将用户数据写入设备内存*/if (rq_data_dir(req) == READ)memcpy(buffer, device->data + offset + copied, len);elsememcpy(device->data + offset + copied, buffer, len);copied += len;kunmap(bvec.bv_page);}done:/* 通知 blk-mq 请求处理完毕 */blk_mq_end_request(req, status);return status;
}
  • 请求参数解析

    • blk_rq_pos(req) 返回请求的起始扇区,乘以 512 得到字节偏移。

    • blk_rq_bytes(req) 返回请求需要传输的总字节数。

  • 边界检查
    检查请求的数据范围是否超过了设备分配的内存。如果超出,则返回错误状态。

  • 遍历 bio_vec 数据段
    使用 rq_for_each_segment() 遍历请求中每个数据段,每个数据段都对应一块内存页。

    • 通过 kmap 将页映射到内核虚拟地址空间,进行数据拷贝。

    • 根据请求方向(读或写)选择合适的 memcpy 操作。

    • 使用 kunmap 解除映射。

  • 结束请求
    调用 blk_mq_end_request() 告诉内核该请求已经完成,状态(成功或错误)作为参数传递。

块设备操作函数

static int myblk_open(struct block_device *bdev, fmode_t mode)
{printk(KERN_INFO "myblk: 设备打开\n");return 0;
}static void myblk_release(struct gendisk *disk, fmode_t mode)
{printk(KERN_INFO "myblk: 设备关闭\n");
}static int myblk_getgeo(struct block_device *bdev, struct hd_geometry *geo)
{geo->heads     = 4;geo->sectors   = 32;geo->cylinders = (MYDISK_SIZE) / (4 * 32 * KERNEL_SECTOR_SIZE);geo->start     = 0;return 0;
}
  • open 与 release
    当用户程序通过 /dev/myblk 打开或关闭设备时,这两个函数会被调用。这里仅打印日志,实际应用中可能需要对设备进行状态维护或加锁操作。

  • getgeo
    该函数用于返回设备的几何信息(柱面、磁头、扇区数),部分老旧应用可能依赖这些信息,但对于虚拟设备来说,这个值通常只作兼容性返回。

定义块设备操作结构体:

static struct block_device_operations myblk_fops = {.owner  = THIS_MODULE,.open   = myblk_open,.release= myblk_release,.getgeo = myblk_getgeo,
};

 将前面定义的设备操作函数绑定到块设备操作结构体中,供系统调用。

 模块初始化函数

static int __init myblk_init(void)
{int ret;printk(KERN_INFO "myblk: 模块初始化\n");/* 分配设备结构 */device = kmalloc(sizeof(*device), GFP_KERNEL);if (!device) {ret = -ENOMEM;goto out;}device->size = MYDISK_SIZE;/* 分配设备存储内存 */device->data = vmalloc(device->size);if (!device->data) {ret = -ENOMEM;goto free_device;}/* 初始化 blk-mq tag_set */memset(&tag_set, 0, sizeof(tag_set));tag_set.ops = &mq_ops;             // 指定请求处理函数所在的操作结构tag_set.nr_hw_queues = 1;          // 设置硬件队列数量(这里只使用一个队列)tag_set.queue_depth = 128;         // 队列深度,根据实际需求调整tag_set.numa_node = NUMA_NO_NODE;tag_set.cmd_size = 0;ret = blk_mq_alloc_tag_set(&tag_set);if (ret)goto free_data;/* 初始化请求队列,采用 blk-mq 接口 */queue = blk_mq_init_queue(&tag_set);if (IS_ERR(queue)) {ret = PTR_ERR(queue);goto free_tag_set;}queue->queuedata = device;/* 注册块设备,动态分配主设备号 */major_num = register_blkdev(0, DEVICE_NAME);if (major_num <= 0) {ret = -EBUSY;goto cleanup_queue;}/* 分配并初始化 gendisk 结构 */mydisk = alloc_disk(1);  // 分区数量设为 1if (!mydisk) {ret = -ENOMEM;goto unregister_blk;}mydisk->major = major_num;mydisk->first_minor = 0;mydisk->fops = &myblk_fops;mydisk->private_data = device;snprintf(mydisk->disk_name, 32, DEVICE_NAME);set_capacity(mydisk, device->size / KERNEL_SECTOR_SIZE);mydisk->queue = queue;/* 将设备添加到系统中,使其在 /dev 下可见 */add_disk(mydisk);printk(KERN_INFO "myblk: 模块加载成功\n");return 0;unregister_blk:unregister_blkdev(major_num, DEVICE_NAME);
cleanup_queue:blk_cleanup_queue(queue);
free_tag_set:blk_mq_free_tag_set(&tag_set);
free_data:vfree(device->data);
free_device:kfree(device);
out:return ret;
}
  • 设备结构与内存分配
    使用 kmalloc 分配保存设备信息的结构,再用 vmalloc 分配一块连续的虚拟内存作为存储空间。

  • 初始化 blk-mq
    通过设置 tag_set 的各项参数(例如硬件队列数和队列深度),并调用 blk_mq_alloc_tag_setblk_mq_init_queue 来建立基于 blk-mq 的请求队列

  • 设备注册与 gendisk 初始化

    • 使用 register_blkdev() 动态获取一个主设备号;

    • 分配并设置 gendisk 结构,包括设备号、操作函数、设备容量(通过 set_capacity 将字节数转成扇区数)和请求队列;

    • 最后调用 add_disk() 注册设备,使其在系统中可见(如 /dev/myblk),这里填写的数字为分区数字,用来划分不同的区域。

 模块退出函数

static void __exit myblk_exit(void)
{del_gendisk(mydisk);put_disk(mydisk);unregister_blkdev(major_num, DEVICE_NAME);blk_cleanup_queue(queue);blk_mq_free_tag_set(&tag_set);vfree(device->data);kfree(device);printk(KERN_INFO "myblk: 模块卸载\n");
}

清理顺序
模块卸载时要反向释放在初始化时分配的所有资源:

  • 先通过 del_gendiskput_disk 移除并释放 gendisk 结构;

  • 注销块设备主设备号;

  • 清理请求队列和释放 blk-mq 的 tag_set;

  • 最后释放分配的内存区域。

3.总结

通过对块设备的深入研究,我们对其工作原理、数据传输方式以及在多核系统中的并发性能有了更清晰的认识。​块设备以固定大小的扇区为单位进行数据读写,块设备驱动程序负责响应文件系统的I/O请求,将数据准确地读写到设备的存储区域。​在多核系统中,采用blk-mq(Block Multi-Queue)接口可以提高并发性能,利用多个硬件队列来分发和处理I/O请求。​

在块设备的实现过程中,gendisk结构体用于描述设备的基本信息,如主次设备号、设备名称和容量等。​request结构体则描述具体的I/O请求,包含数据传输的起始扇区、数据长度等信息。​bio(block I/O)结构体用于表示I/O请求的数据,可能包含多个bio_vec,每个bio_vec指向一段连续的内存数据。​

最后再聊聊块设备,其实我自己也迷糊了一会,块设备,到底是干嘛的?

 硬件的块设备就是SSD,HHD这类数据存储设备

软件的块设备其实就是我们的虚拟磁盘,当操作系统操作数据需要与块设备进行I/O操作,而块设备负责管理这些数据,它将数据划分成大小相等的扇区(例如每个扇区为 512B),每个都有对应的标识符,当操作系统或者用户程序需要访问时,就需要通过块设备去进行存取(就是这么简单,可惜之前傻傻分不清还以为是I/O通道,又一阵子以为是u盘这类存储设备)总的来说就是可以读写磁盘的一个设备。

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

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

相关文章

ResNet改进(14):添加 EMA注意力机制提升跨空间学习效率

本专栏代码均经过测试,可以直接替换项目中的模型,一键运行! 采用最新的即插即用模块,有效涨点!! 1.EMA注意力机制 EMA(Efficient Multi-scale Attention)注意力机制是一种创新的注意力设计,能够有效提升模型在跨空间学习任务中的表现。以下是对该机制的详细解析: EM…

计算机硬件——CPU 主要参数

什么是 CPU &#xff1f; CPU 的英文全称是 Central Processing Unit&#xff0c;即中央处理器。CPU 的内部结构可分为控制单元、逻辑单元和存储单元三大部分。CPU 的性能大致上反映出了它所配置的微机的性能&#xff0c;因此 CPU 的性能指标十分重要。 CPU 的主要参数 CPU …

针对 Python 3.7.0,以下是 Selenium 版本的兼容性建议和安装步骤

1. Selenium 版本推荐 最高兼容版本&#xff1a; Selenium 4.11.2&#xff08;官方明确支持 Python 3.7&#xff0c;但需注意部分新功能可能受限&#xff09;。 稳定兼容版本&#xff1a; Selenium 3.141.0&#xff08;经典版本&#xff0c;完全兼容 Python 3.7&#xff0c;适…

stm32 主频216MHz,写个ms延时函数,us延时函数

在 STM32 微控制器中&#xff0c;实现精确的 ms&#xff08;毫秒&#xff09;和 us&#xff08;微秒&#xff09;延时函数通常依赖于系统时钟&#xff08;SysTick&#xff09;或定时器。以下是基于主频为 216 MHz 的实现方法&#xff1a; 1. 使用 SysTick 实现延时函数 SysTic…

modus开源程序是一个由 WebAssembly 提供支持的构建代理流的框架

一、软件介绍 文末提供程序和源码下载 Modus 是一个开源的无服务器框架&#xff0c;用于在 Go 和 AssemblyScript 中构建代理系统和 AI 应用程序。 它简化了模型、上下文和数据的集成。我们将继续添加其他功能&#xff0c;以更好地支持工具的构建和调用。 You write a functi…

从零构建大语言模型全栈开发指南:第四部分:工程实践与部署-4.3.2知识库增强与外部API集成(代码示例:HTTP节点与检索增强生成)

👉 点击关注不迷路 👉 点击关注不迷路 👉 点击关注不迷路 文章大纲 知识库增强与外部API集成:HTTP节点与检索增强生成实战4.3.2 知识库增强与外部API集成(代码示例:HTTP节点与检索增强生成)1. 核心挑战与优化目标1.1 技术瓶颈分析1.2 设计目标2. 关键技术方案2.1 知识…

蓝桥杯Java B组省赛真题高频考点近6年统计分类

基础考点 考点高频难度模拟9基础枚举5基础思维4基础动态规划3基础规律2基础单位换算2基础搜索 1基础双指针1基础数学1基础哈希表1基础暴力1基础Dijkstra1基础 二分1基础 中等考点 考点高频难度动态规划6中等数学5中等枚举4中等模拟3中等思维3中等贪心3中等前缀和3中等二分2中…

Rancher2.8.5架构

大多数 Rancher 2.x 软件均运行在 Rancher Server 上。Rancher Server 包括用于管理整个 Rancher 部署的所有软件组件。 下图展示了 Rancher 2.x 的上层架构。下图中&#xff0c;Rancher Server 管理两个下游 Kubernetes 集群&#xff0c;其中一个由 RKE 创建&#xff0c;另一…

Java Lambda 表达式提升效率

lambda 表达式的应用场景 Stream 的应用场景 Lambda/Stream 的进一步封装 自定义函数式接口&#xff08;用 jdk 自带的函数式接口也可以&#xff09; https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaexpressions.html import java.io.Serializable;/*** 可序…

keep-alive缓存

#keep-alive缓存动态路由的使用指南# 代码如下图 &#xff1a; <router-view v-slot"{ Component }"> <keep-alive :include"[Hot, Notifications, User, Setting, Test]"> <component :is"Component" …

使用 PyTorch 的 `GradualWarmupScheduler` 实现学习率预热

使用 PyTorch 的 GradualWarmupScheduler 实现学习率预热 在深度学习中,学习率(Learning Rate, LR)是影响模型训练效果的关键超参数之一。为了提升模型的收敛速度和稳定性,学习率调度策略变得尤为重要。其中,学习率预热(Learning Rate Warmup) 是一种常用的策略,它通过…

【DLI】Generative AI with Diffusion Models通关秘籍

Generative AI with Diffusion Models&#xff0c;加载时间在20分钟左右&#xff0c;耐心等待。 6.2TODO 这里是在设置扩散模型的参数&#xff0c;代码里的FIXME部分需要根据上下文进行替换。以下是各个FIXME的替换说明&#xff1a; 1.a_bar 是 a 的累积乘积&#xff0c;在 …

如何在本地部署魔搭上千问Qwen2.5-VL-32B-Instruct-AWQ模型在显卡1上面运行推理,并开启api服务

环境: 云服务器Ubuntu NVIDIA H20 96GB Qwen2.5-VL-32B Qwen2.5-VL-72B 问题描述: 如何在本地部署魔搭上千问Qwen2.5-VL-32B-Instruct-AWQ模型在显卡1上面运行推理,并开启api服务 解决方案: 1.环境准备 硬件要求 显卡1(显存需≥48GB,推荐≥64GB)CUDA 11.7或更高…

基于方法分类的无监督图像去雾论文

在之前的博客中&#xff0c;我从研究动机的角度对无监督图像去雾论文进行了分类&#xff0c;而现在我打算根据论文中提出的方法进行新的分类。 1. 基于对比学习的方法 2022年 论文《UCL-Dehaze: Towards Real-world Image Dehazing via Unsupervised Contrastive Learning》&a…

4月3号.

JDK7前时间相关类: 时间的相关知识: Data时间类: //1.创建对象表示一个时间 Date d1 new Date(); //System.out.println(d1);//2.创建对象表示一个指定的时间 Date d2 new Date(0L); System.out.println(d2);//3.setTime修改时间 //1000毫秒1秒 d2.setTime(1000L); System.o…

数据结构与算法:子数组最大累加和问题及扩展

前言 子数组最大累加和问题看似简单,但能延伸出的题目非常多,千题千面,而且会和其他算法结合出现。 一、最大子数组和 class Solution { public:int maxSubArray(vector<int>& nums) {int n=nums.size();vector<int>dp(n);//i位置往左能延伸出的最大累加…

MIT6.828 Lab3-2 Print a page table (easy)

实验内容 实现一个函数来打印页表的内容&#xff0c;帮助我们更好地理解 xv6 的三级页表结构。 修改内容 kernel/defs.h中添加函数声明&#xff0c;方便其它函数调用 void vmprint(pagetable_t);// lab3-2 Print a page tablekernel/vm.c中添加函数具体定义 采用…

2025高频面试设计模型总结篇

文章目录 设计模型概念单例模式工厂模式策略模式责任链模式 设计模型概念 设计模式是前人总结的软件设计经验和解决问题的最佳方案&#xff0c;它们为我们提供了一套可复用、易维护、可扩展的设计思路。 &#xff08;1&#xff09;定义&#xff1a; 设计模式是一套经过验证的…

Java基础:面向对象进阶(二)

01-static static修饰成员方法 static注意事项&#xff08;3种&#xff09; static应用知识&#xff1a;代码块 static应用知识&#xff1a;单列模式 02-面向对象三大特征之二&#xff1a;继承 什么是继承&#xff1f; 使用继承有啥好处? 权限修饰符 单继承、Object类 方法重…

Spring框架如何做EhCache缓存?

在Spring框架中&#xff0c;缓存是一种常见的优化手段&#xff0c;用于减少对数据库或其他资源的访问次数&#xff0c;从而提高应用性能。Spring提供了强大的缓存抽象&#xff0c;支持多种缓存实现&#xff08;如EhCache、Redis、Caffeine等&#xff09;&#xff0c;并可以通过…