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.pswp.cn/pingmian/75497.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/75497.shtml
英文地址,請注明出處:http://en.pswp.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;并可以通過…