Linux内核缓存管理技术全景图谱:从网络到存储的十八般武艺
前言:缓存管理——Linux内核的"隐形骨架"
inux内核的缓存管理技术——内核的"隐形骨架",支撑着文件系统、块设备、DMA传输、内存分配等各个子系统的高效运转。
本文将基于Linux内核主线源码(v6.x),系统性地梳理所有缓存管理技术,构建一个完整的技术图谱。每个技术都将包含:设计思想、核心数据结构、初始化流程、申请/回收机制、适用场景。
第一部分:缓存管理技术全景树形图
┌─────────────────────────────────────────────────────────────────────────────┐ │ Linux内核缓存管理技术全景图 │ │ (基于内核v6.x) │ ├─────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 1. 通用内存分配器层 (基础依赖) │ │ │ │ ├── SLAB/SLUB/SLOB - 通用对象缓存 │ │ │ │ ├── kmalloc - 通用内存分配 │ │ │ │ ├── vmalloc - 虚拟连续内存 │ │ │ │ └── CMA (Contiguous Memory Allocator) - 连续内存分配器 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 2. 网络子系统专用缓存 │ │ │ │ ├── sk_buff - 网络数据包缓冲区 (已分析) │ │ │ │ ├── sock_alloc_send_skb - socket发送缓存 │ │ │ │ ├── TCP recv_buf - TCP接收缓冲区 │ │ │ │ ├── netdev_alloc_skb - 设备层接收缓存 │ │ │ │ ├── napi_alloc_skb - NAPI高性能缓存 │ │ │ │ └── page_frag_cache - 页分片缓存 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 3. 存储/文件系统专用缓存 │ │ │ │ ├── buffer_head - 块缓冲区头 │ │ │ │ ├── bio (Block I/O) - 块I/O请求 │ │ │ │ ├── page cache - 页高速缓存 (核心) │ │ │ │ ├── inode/dentry cache - 文件系统元数据缓存 │ │ │ │ ├── fscache - 文件系统缓存层 │ │ │ │ ├── bcache - 块设备缓存 │ │ │ │ └── dm-cache - 设备映射器缓存 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 4. DMA/IO专用缓存 │ │ │ │ ├── dma_pool / dma_pool_alloc - DMA池 │ │ │ │ ├── dma_buf - DMA缓冲区共享 │ │ │ │ ├── ion - 内存分配器(Android) │ │ │ │ ├── dmabuf_heap - DMA-BUF堆 │ │ │ │ ├── videobuf2 - 视频缓冲区 (已分析) │ │ │ │ └── dmabuf_sync - DMA缓存同步 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 5. 多媒体专用缓存 │ │ │ │ ├── vb2_v4l2_buffer - V4L2缓冲区 │ │ │ │ ├── alsa_buffer - 音频环形缓冲区 │ │ │ │ ├── snd_pcm_ringbuf - PCM环形缓冲 │ │ │ │ ├── drm_gem_object - DRM GEM对象 │ │ │ │ ├── dma_buf_imp - DMA-BUF导入/导出 │ │ │ │ └── cma_alloc - 连续内存分配 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 6. 高性能计算/加速器专用缓存 │ │ │ │ ├── dmabuf_system_heap - 系统堆 │ │ │ │ ├── dmabuf_cma_heap - CMA堆 │ │ │ │ ├── tee_shm - TEE共享内存 │ │ │ │ ├── vfio_dma - VFIO DMA区域 │ │ │ │ └── uio_dma - UIO DMA缓冲区 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────────────────────────────────────────────────────────────┐ │ │ │ 7. 内核内部专用缓存 │ │ │ │ ├── kmem_cache - SLAB缓存 (通用) │ │ │ │ ├── percpu_alloc - per-CPU分配器 │ │ │ │ ├── mempool - 内存池 (紧急备用) │ │ │ │ ├── kfifo - 无锁环形队列 │ │ │ │ ├── ring_buffer - 跟踪环形缓冲区 │ │ │ │ ├── relayfs - 中继文件系统 │ │ │ │ └── debugfs_buffer - 调试缓冲区 │ │ │ └──────────────────────────────────────────────────────────────────────┘ │ │ │ └─────────────────────────────────────────────────────────────────────────────┘
第二部分:通用内存分配器层(基础依赖)
2.1 SLAB/SLUB分配器
/**
* struct kmem_cache - SLAB缓存的核心结构
* @brief 管理特定大小对象的缓存池
*
* @note 源码位置:include/linux/slab.h
* @see mm/slub.c
*/
struct kmem_cache {
/* per-CPU数据(无锁访问) */
struct kmem_cache_cpu __percpu *cpu_slab;
/* 节点数据(NUMA感知) */
struct kmem_cache_node *node[MAX_NUMNODES];
unsigned long size; /*!< 对象大小(含对齐)*/
unsigned long object_size; /*!< 原始对象大小 */
int offset; /*!< 空闲指针偏移 */
/* 分配标志 */
slab_flags_t flags;
unsigned int useroffset; /*!< 用户空间可访问偏移 */
unsigned int usersize; /*!< 用户空间可访问大小 */
/* 构造函数 */
void (*ctor)(void *);
/* 统计信息 */
int refcount; /*!< 引用计数 */
int objs_per_slab; /*!< 每个slab的对象数 */
};
/**
* @brief 创建SLAB缓存
* @param name 缓存名称(在/proc/slabinfo中可见)
* @param size 对象大小
* @param align 对齐要求
* @param flags 标志
* @param ctor 构造函数
*/
struct kmem_cache *
kmem_cache_create(const char *name, unsigned int size,
unsigned int align, slab_flags_t flags,
void (*ctor)(void *))
{
struct kmem_cache *s;
int err;
/* 1. 从全局缓存池分配kmem_cache结构 */
s = kmem_cache_zalloc(kmem_cache, GFP_KERNEL);
if (!s)
return ERR_PTR(-ENOMEM);
/* 2. 设置基本属性 */
s->name = name;
s->size = size;
s->align = align;
s->flags = flags;
s->ctor = ctor;
/* 3. 计算每个slab的对象数 */
calculate_slab_order(s);
/* 4. 初始化per-CPU缓存 */
init_freelist_randomization(s);
init_tracking(s);
/* 5. 加入全局链表 */
list_add(&s->list, &slab_caches);
return s;
}
EXPORT_SYMBOL(kmem_cache_create);
适用场景:所有需要频繁分配固定大小对象的子系统,如inode、dentry、sk_buff等。
2.2 CMA(连续内存分配器)
/**
* struct cma - 连续内存分配器
* @brief 管理一大块物理连续内存,用于DMA/多媒体
*
* @note 源码位置:include/linux/cma.h
* @see mm/cma.c
*/
struct cma {
unsigned long base_pfn; /*!< 起始页帧号 */
unsigned long count; /*!< 页面数量 */
unsigned long *bitmap; /*!< 位图(管理分配状态)*/
unsigned int order_per_bit; /*!< 每个bit代表的页阶 */
struct mutex lock;
const char *name;
};
/**
* @brief 预留CMA区域(设备树或内核参数)
*
* 设备树示例:
* reserved-memory {
* #address-cells = <2>;
* #size-cells = <2>;
* ranges;
*
* linux,cma {
* compatible = "shared-dma-pool";
* reusable;
* size = <0 0x10000000>; // 256MB
* linux,cma-default;
* };
* };
*/
/**
* @brief 从CMA分配连续内存
* @param cma CMA区域
* @param count 页面数量
* @param align 对齐要求
* @param no_warn 是否告警
*/
struct page *cma_alloc(struct cma *cma, unsigned long count,
unsigned int align, bool no_warn)
{
unsigned long mask, offset;
unsigned long pfn = -1;
unsigned long start = 0;
if (!cma || !cma->count)
return NULL;
mask = (1 << align) - 1;
mutex_lock(&cma->lock);
/* 1. 在位图中查找连续区域 */
for (;;) {
unsigned long bitmap_max, bitmap_bit;
size_t bitmap_count;
unsigned long start_bit;
start_bit = ALIGN(start, 1 << cma->order_per_bit);
bitmap_max = cma->count >> cma->order_per_bit;
bitmap_bit = find_next_zero_bit(cma->bitmap, bitmap_max, start_bit);
if (bitmap_bit >= bitmap_max)
break;
/* 2. 计算物理页帧 */
pfn = cma->base_pfn + (bitmap_bit << cma->order_per_bit);
start = bitmap_bit + 1;
/* 3. 标记为已使用 */
bitmap_set(cma->bitmap, bitmap_bit, bitmap_count);
break;
}
mutex_unlock(&cma->lock);
if (pfn == -1)
return NULL;
return pfn_to_page(pfn);
}
EXPORT_SYMBOL_GPL(cma_alloc);
适用场景:摄像头传感器缓冲区、GPU内存、显示缓冲区等需要物理连续内存的设备。
第三部分:网络子系统专用缓存
3.1 sock_alloc_send_skb - socket发送缓存
/**
* sock_alloc_send_skb() - 为socket分配发送skb
* @sk: socket
* @size: 数据大小
* @force: 强制分配标志
* @err: 错误码输出
*
* @note 源码位置:net/core/sock.c
*
* @details 这个函数比普通alloc_skb多了:
* 1. 遵守socket内存限制(sk_sndbuf)
* 2. 支持阻塞等待内存可用
* 3. 自动处理分片
*/
struct sk_buff *sock_alloc_send_skb(struct sock *sk, unsigned long size,
int force, int *err)
{
struct sk_buff *skb;
int npages;
int i;
/* 1. 检查内存限制 */
if (atomic_read(&sk->sk_wmem_alloc) >= sk->sk_sndbuf) {
if (force)
goto alloc;
/* 等待内存可用 */
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
*err = -EAGAIN;
return NULL;
}
alloc:
/* 2. 计算所需页数(用于分片)*/
size = SKB_DATA_ALIGN(size);
npages = (size + PAGE_SIZE - 1) >> PAGE_SHIFT;
if (npages <= MAX_SKB_FRAGS) {
/* 使用分片模式 */
skb = alloc_skb(0, sk->sk_allocation);
if (!skb)
goto failure;
for (i = 0; i < npages; i++) {
struct page *page = alloc_pages(sk->sk_allocation, 0);
if (!page)
goto failure;
skb_fill_page_desc(skb, i, page, 0, PAGE_SIZE);
}
} else {
/* 使用线性区 */
skb = alloc_skb(size, sk->sk_allocation);
if (!skb)
goto failure;
}
/* 3. 更新socket内存统计 */
skb_set_owner_w(skb, sk);
return skb;
failure:
*err = -ENOBUFS;
return NULL;
}
EXPORT_SYMBOL(sock_alloc_send_skb);
3.2 TCP接收缓冲区
/**
* struct tcp_sock - TCP套接字状态
* @brief 包含TCP特有的接收缓冲区管理
*
* @note 源码位置:include/linux/tcp.h
*/
struct tcp_sock {
/* 接收缓冲区 */
struct sk_buff_head recv_queue; /*!< 已接收但未读取的数据 */
struct sk_buff_head out_of_order_queue; /*!< 乱序包队列 */
u32 rcv_nxt; /*!< 下一个期望接收的序号 */
u32 copied_seq; /*!< 用户已拷贝的序号 */
u32 rcv_wnd; /*!< 接收窗口大小 */
u32 rcv_ssthresh; /*!< 接收窗口慢启动阈值 */
u32 rcv_tstamp; /*!< 接收时间戳 */
};
/**
* tcp_queue_rcv() - 将skb放入TCP接收队列
* @sk: socket
* @skb: 接收的数据包
* @fragstolen: 是否窃取了分片
*/
static void tcp_queue_rcv(struct sock *sk, struct sk_buff *skb,
int *fragstolen)
{
struct tcp_sock *tp = tcp_sk(sk);
/* 1. 如果当前接收队列为空,直接添加 */
if (skb_queue_empty(&sk->sk_receive_queue)) {
skb_queue_head(&sk->sk_receive_queue, skb);
return;
}
/* 2. 按顺序插入(TCP保证有序)*/
__skb_queue_tail(&sk->sk_receive_queue, skb);
/* 3. 更新接收窗口 */
tcp_rcv_space_adjust(sk);
}
/**
* tcp_rcv_established() - TCP连接建立状态的数据接收
*/
void tcp_rcv_established(struct sock *sk, struct sk_buff *skb)
{
struct tcp_sock *tp = tcp_sk(sk);
/* 1. 检查序号是否预期 */
if (TCP_SKB_CB(skb)->seq != tp->rcv_nxt)
goto out_of_order;
/* 2. 处理数据 */
tcp_queue_rcv(sk, skb, &fragstolen);
/* 3. 更新rcv_nxt */
tp->rcv_nxt += skb->len;
/* 4. 如果有空间,发送ACK */
if (tcp_ack_scheduled(tp))
tcp_send_ack(sk);
return;
out_of_order:
/* 乱序包处理 */
tcp_data_queue_ofo(sk, skb);
}
3.3 page_frag_cache - 页分片缓存
/**
* struct page_frag_cache - per-CPU页分片缓存
* @brief 用于高效分配小块内存(从页中切分)
*
* @note 源码位置:include/linux/gfp.h
*/
struct page_frag_cache {
void *va; /*!< 虚拟地址 */
__u16 offset; /*!< 当前偏移 */
__u16 size; /*!< 剩余大小 */
__u8 pfmemalloc; /*!< 是否使用紧急内存池 */
struct page *page; /*!< 当前页 */
};
/**
* page_frag_alloc() - 从页分片缓存分配内存
* @nc: per-CPU缓存
* @sz: 需要的大小
* @gfp: GFP标志
*
* @return: 分配的虚拟地址
*/
void *page_frag_alloc(struct page_frag_cache *nc,
unsigned int sz, gfp_t gfp_mask)
{
unsigned int size = PAGE_SIZE;
struct page *page;
int offset;
/* 1. 对齐大小 */
sz = ALIGN(sz, SMP_CACHE_BYTES);
/* 2. 如果当前页有足够空间 */
if (nc->page && nc->offset + sz <= size) {
offset = nc->offset;
nc->offset += sz;
return nc->va + offset;
}
/* 3. 需要分配新页 */
page = alloc_pages_node(NUMA_NO_NODE, gfp_mask, 0);
if (!page)
return NULL;
/* 4. 更新缓存 */
if (nc->page)
put_page(nc->page);
nc->page = page;
nc->va = page_address(page);
nc->offset = sz;
nc->size = size;
return nc->va;
}
EXPORT_SYMBOL(page_frag_alloc);
适用场景:NAPI接收路径、小块DMA缓冲区分配。
第四部分:存储/文件系统专用缓存
4.1 buffer_head - 块缓冲区头
/**
* struct buffer_head - 块I/O缓冲区头
* @brief 代表磁盘上的一个块在内存中的缓冲区
*
* @note 源码位置:include/linux/buffer_head.h
*/
struct buffer_head {
unsigned long b_state; /*!< 缓冲区状态 */
struct buffer_head *b_this_page; /*!< 同一页上的循环链表 */
struct page *b_page; /*!< 所属页 */
sector_t b_blocknr; /*!< 块号 */
size_t b_size; /*!< 块大小 */
char *b_data; /*!< 数据指针 */
struct block_device *b_bdev; /*!< 所属块设备 */
bh_end_io_t *b_end_io; /*!< I/O完成回调 */
void *b_private; /*!< 回调私有数据 */
atomic_t b_count; /*!< 引用计数 */
};
/**
* @brief 块缓冲区状态标志
*/
enum bh_state_bits {
BH_Uptodate, /*!< 数据有效 */
BH_Dirty, /*!< 数据脏(需要写回)*/
BH_Lock, /*!< 正在I/O */
BH_Req, /*!< 已有I/O请求 */
BH_Mapped, /*!< 已映射到磁盘 */
BH_New, /*!< 新分配的块 */
BH_Async_Read, /*!< 异步读进行中 */
BH_Async_Write, /*!< 异步写进行中 */
BH_Delay, /*!< 延迟分配 */
BH_Unwritten, /*!< 预分配但未写入 */
};
/**
* alloc_buffer_head() - 分配buffer_head
* @gfp_flags: GFP标志
*
* @note 从bh_cache slab分配
*/
struct buffer_head *alloc_buffer_head(gfp_t gfp_flags)
{
struct buffer_head *bh;
/* 从slab缓存分配 */
bh = kmem_cache_zalloc(bh_cachep, gfp_flags);
if (bh) {
INIT_LIST_HEAD(&bh->b_assoc_buffers);
spin_lock_init(&bh->b_uptodate_lock);
atomic_set(&bh->b_count, 0);
}
return bh;
}
EXPORT_SYMBOL(alloc_buffer_head);
适用场景:ext4、XFS等传统文件系统的块I/O操作。
4.2 bio(Block I/O) - 现代块I/O
/**
* struct bio - 块I/O请求的现代表示
* @brief 代表一个I/O请求,支持向量化I/O
*
* @note 源码位置:include/linux/blk_types.h
*/
struct bio {
struct bio *bi_next; /*!< 队列中的下一个bio */
struct block_device *bi_bdev; /*!< 目标块设备 */
blk_status_t bi_status; /*!< I/O状态 */
unsigned int bi_opf; /*!< 操作码和标志 */
unsigned short bi_flags; /*!< 状态标志 */
unsigned short bi_ioprio; /*!< I/O优先级 */
struct bvec_iter bi_iter; /*!< 当前迭代位置 */
/* 完成回调 */
bio_end_io_t *bi_end_io;
void *bi_private;
/* I/O统计 */
atomic_t __bi_remaining; /*!< 剩余引用 */
atomic_t __bi_cnt; /*!< 引用计数 */
/* 向量数组(最多BIO_MAX_VECS个)*/
struct bio_vec *bi_io_vec; /*!< 实际向量数组 */
struct bio_vec bi_inline_vecs[]; /*!< 内联向量(小请求)*/
};
/**
* struct bio_vec - bio向量(页+偏移+长度)
*/
struct bio_vec {
struct page *bv_page; /*!< 页 */
unsigned int bv_len; /*!< 长度 */
unsigned int bv_offset; /*!< 页内偏移 */
};
/**
* bio_alloc() - 分配新的bio
* @gfp_mask: GFP标志
* @nr_iovecs: 预分配的向量数量
* @bs: bio集(可选)
*/
struct bio *bio_alloc(gfp_t gfp_mask, unsigned short nr_iovecs,
struct bio_set *bs)
{
struct bio *bio;
void *p;
/* 1. 如果指定了bio_set,从池分配 */
if (bs) {
p = mempool_alloc(&bs->bio_pool, gfp_mask);
bio = p + bs->front_pad;
bio->bi_pool = bs;
} else {
/* 2. 否则直接分配(小请求使用内联向量)*/
if (nr_iovecs <= BIO_INLINE_VECS) {
bio = kmalloc(sizeof(struct bio) +
nr_iovecs * sizeof(struct bio_vec),
gfp_mask);
} else {
bio = kmalloc(sizeof(struct bio), gfp_mask);
}
}
if (!bio)
return NULL;
/* 3. 初始化 */
bio->bi_flags = 0;
bio->bi_ioprio = 0;
atomic_set(&bio->__bi_cnt, 1);
atomic_set(&bio->__bi_remaining, 1);
if (nr_iovecs) {
if (nr_iovecs <= BIO_INLINE_VECS) {
bio->bi_io_vec = bio->bi_inline_vecs;
} else {
bio->bi_io_vec = bio_alloc_bvec(nr_iovecs, gfp_mask);
if (!bio->bi_io_vec)
goto err_out;
}
}
return bio;
}
EXPORT_SYMBOL(bio_alloc);
适用场景:现代存储栈(NVMe、多队列块层)、支持向量I/O的场景。
4.3 page cache - 页高速缓存
/**
* struct address_space - 页缓存的核心结构
* @brief 管理一个文件或设备的页缓存
*
* @note 源码位置:include/linux/fs.h
*/
struct address_space {
struct inode *host; /*!< 所属inode */
struct xarray i_pages; /*!< 页缓存树(XArray)*/
atomic_t i_mmap_writable; /*!< 可写映射计数 */
struct rb_root_cached i_mmap; /*!< 共享内存映射树 */
unsigned long nrpages; /*!< 总页数 */
unsigned long nrexceptional; /*!< 异常条目数 */
struct address_space_operations *a_ops; /*!< 操作函数表 */
unsigned long flags; /*!< 标志 */
spinlock_t private_lock; /*!< 私有锁 */
struct list_head private_list; /*!< 私有链表 */
};
/**
* struct address_space_operations - 页缓存操作
*/
struct address_space_operations {
/* 读取页 */
int (*readpage)(struct file *, struct page *);
int (*readpages)(struct file *, struct address_space *,
struct list_head *, unsigned int);
/* 写入页 */
int (*writepage)(struct page *, struct writeback_control *);
int (*writepages)(struct address_space *, struct writeback_control *);
/* 脏页跟踪 */
void (*set_page_dirty)(struct page *);
/* 释放页 */
int (*releasepage)(struct page *, gfp_t);
void (*freepage)(struct page *);
/* 直接I/O */
ssize_t (*direct_IO)(struct kiocb *, struct iov_iter *);
};
/**
* find_get_page() - 在页缓存中查找页
* @mapping: address_space
* @offset: 页偏移
*
* @note 使用XArray快速查找
*/
struct page *find_get_page(struct address_space *mapping, pgoff_t offset)
{
struct page *page;
rcu_read_lock();
page = xa_load(&mapping->i_pages, offset);
if (page && !page_cache_get_speculative(page))
page = NULL;
rcu_read_unlock();
return page;
}
EXPORT_SYMBOL(find_get_page);
适用场景:所有文件系统的数据缓存、内存映射文件、共享库等。
4.4 fscache - 文件系统缓存层
/**
* struct fscache_cache - 缓存后端
* @brief 代表一个缓存后端(如cachefiles)
*/
struct fscache_cache {
const struct fscache_cache_ops *ops; /*!< 操作函数 */
struct list_head cache_link; /*!< 缓存链表 */
void *cache_priv; /*!< 私有数据 */
refcount_t ref; /*!< 引用计数 */
unsigned int flags; /*!< 标志 */
char name[16]; /*!< 名称 */
};
/**
* struct fscache_cookie - 缓存对象标识
*/
struct fscache_cookie {
struct hlist_bl_node hash_link; /*!< 哈希链表 */
struct fscache_cache *cache; /*!< 关联的缓存 */
struct fscache_object *object; /*!< 缓存对象 */
unsigned long flags; /*!< 标志 */
refcount_t ref; /*!< 引用计数 */
};
/**
* fscache_read_or_alloc_page() - 从缓存读取或分配页
*/
int fscache_read_or_alloc_page(struct fscache_cookie *cookie,
struct page *page,
fscache_rw_complete_t end_io_func,
void *context,
gfp_t gfp)
{
struct fscache_cache *cache = cookie->cache;
if (!cache || !cache->ops->read_or_alloc_page)
return -ENOBUFS;
return cache->ops->read_or_alloc_page(cookie, page,
end_io_func, context, gfp);
}
EXPORT_SYMBOL(fscache_read_or_alloc_page);
适用场景:网络文件系统(NFS、AFS)的本地缓存、离线访问支持。
第五部分:DMA/IO专用缓存
5.1 dma_pool - DMA池
/**
* struct dma_pool - DMA内存池
* @brief 管理小尺寸DMA缓冲区的分配
*
* @note 源码位置:include/linux/dmapool.h
* @see drivers/base/dmapool.c
*/
struct dma_pool {
struct list_head page_list; /*!< DMA页链表 */
spinlock_t lock; /*!< 保护页链表 */
size_t size; /*!< 对象大小 */
size_t allocation; /*!< 每次分配的页大小 */
size_t boundary; /*!< 边界对齐 */
char name[32]; /*!< 池名称 */
struct device *dev; /*!< DMA设备 */
unsigned int flags; /*!< 标志 */
};
/**
* struct dma_page - DMA池中的一页
*/
struct dma_page {
struct list_head page_list; /*!< 池中的链表 */
void *vaddr; /*!< 虚拟地址 */
dma_addr_t dma; /*!< DMA地址 */
unsigned int in_use; /*!< 使用计数 */
unsigned long bitmap[0]; /*!< 分配位图 */
};
/**
* dma_pool_create() - 创建DMA池
* @name: 池名称
* @dev: DMA设备
* @size: 对象大小
* @align: 对齐要求
* @boundary: 边界(0表示无边界)
*/
struct dma_pool *dma_pool_create(const char *name, struct device *dev,
size_t size, size_t align, size_t boundary)
{
struct dma_pool *retval;
if (align == 0)
align = 1;
/* 1. 分配池结构 */
retval = kmalloc(sizeof(*retval), GFP_KERNEL);
if (!retval)
return retval;
/* 2. 初始化 */
strlcpy(retval->name, name, sizeof(retval->name));
retval->dev = dev;
retval->size = size;
retval->allocation = PAGE_SIZE;
retval->boundary = boundary;
retval->flags = 0;
INIT_LIST_HEAD(&retval->page_list);
spin_lock_init(&retval->lock);
return retval;
}
EXPORT_SYMBOL(dma_pool_create);
/**
* dma_pool_alloc() - 从DMA池分配对象
* @pool: DMA池
* @mem_flags: GFP标志
* @handle: 输出DMA地址
*/
void *dma_pool_alloc(struct dma_pool *pool, gfp_t mem_flags,
dma_addr_t *handle)
{
unsigned long flags;
struct dma_page *page;
size_t offset;
void *retval;
spin_lock_irqsave(&pool->lock, flags);
/* 1. 查找有空闲空间的页 */
list_for_each_entry(page, &pool->page_list, page_list) {
int i = find_first_zero_bit(page->bitmap,
pool->allocation / pool->size);
if (i < pool->allocation / pool->size) {
/* 2. 找到空闲位置 */
set_bit(i, page->bitmap);
page->in_use++;
offset = i * pool->size;
*handle = page->dma + offset;
retval = page->vaddr + offset;
spin_unlock_irqrestore(&pool->lock, flags);
return retval;
}
}
/* 3. 没有空闲页,分配新页 */
page = pool_alloc_page(pool, mem_flags);
if (!page) {
spin_unlock_irqrestore(&pool->lock, flags);
return NULL;
}
/* 4. 使用新页的第一个对象 */
set_bit(0, page->bitmap);
page->in_use = 1;
*handle = page->dma;
retval = page->vaddr;
list_add(&page->page_list, &pool->page_list);
spin_unlock_irqrestore(&pool->lock, flags);
return retval;
}
EXPORT_SYMBOL(dma_pool_alloc);
适用场景:网卡描述符环、USB控制传输缓冲区、小尺寸DMA缓冲区。
5.2 dma_buf - DMA缓冲区共享
/**
* struct dma_buf - 共享DMA缓冲区
* @brief 在不同设备驱动之间共享DMA缓冲区
*
* @note 源码位置:include/linux/dma-buf.h
* @see drivers/dma-buf/dma-buf.c
*/
struct dma_buf {
size_t size; /*!< 缓冲区大小 */
struct file *file; /*!< 关联文件(用于fd)*/
struct list_head attachments; /*!< 附件列表 */
const struct dma_buf_ops *ops; /*!< 操作函数表 */
struct mutex lock;
unsigned vmapping_counter; /*!< vmap引用计数 */
void *vmap_ptr; /*!< vmap地址 */
const char *exp_name; /*!< 导出者名称 */
struct module *owner;
void *priv; /*!< 私有数据 */
};
/**
* struct dma_buf_attachment - DMA缓冲区附件
* @brief 代表一个设备对dma_buf的使用
*/
struct dma_buf_attachment {
struct dma_buf *dmabuf; /*!< 关联的dma_buf */
struct device *dev; /*!< 附加设备 */
struct list_head node; /*!< 附件链表节点 */
unsigned int dma_map_count; /*!< DMA映射计数 */
enum dma_data_direction dir; /*!< DMA方向 */
};
/**
* dma_buf_export() - 导出DMA缓冲区
* @exp_info: 导出信息
* @return: dma_buf指针
*/
struct dma_buf *dma_buf_export(const struct dma_buf_export_info *exp_info)
{
struct dma_buf *dmabuf;
struct file *file;
int ret;
/* 1. 分配dma_buf结构 */
dmabuf = kzalloc(sizeof(*dmabuf), GFP_KERNEL);
if (!dmabuf)
return ERR_PTR(-ENOMEM);
/* 2. 初始化 */
dmabuf->ops = exp_info->ops;
dmabuf->size = exp_info->size;
dmabuf->exp_name = exp_info->exp_name;
dmabuf->owner = exp_info->owner;
dmabuf->priv = exp_info->priv;
INIT_LIST_HEAD(&dmabuf->attachments);
mutex_init(&dmabuf->lock);
/* 3. 创建匿名文件(用于fd传递)*/
file = anon_inode_getfile("dmabuf", &dma_buf_fops, dmabuf,
exp_info->flags);
if (IS_ERR(file)) {
kfree(dmabuf);
return ERR_CAST(file);
}
dmabuf->file = file;
return dmabuf;
}
EXPORT_SYMBOL_GPL(dma_buf_export);
/**
* dma_buf_attach() - 附加设备到dma_buf
* @dmabuf: dma_buf
* @dev: 设备
*/
struct dma_buf_attachment *dma_buf_attach(struct dma_buf *dmabuf,
struct device *dev)
{
struct dma_buf_attachment *attach;
/* 1. 分配附件结构 */
attach = kzalloc(sizeof(*attach), GFP_KERNEL);
if (!attach)
return ERR_PTR(-ENOMEM);
/* 2. 调用驱动attach回调 */
if (dmabuf->ops->attach) {
int ret = dmabuf->ops->attach(dmabuf, dev, attach);
if (ret) {
kfree(attach);
return ERR_PTR(ret);
}
}
/* 3. 初始化并加入列表 */
attach->dev = dev;
attach->dmabuf = dmabuf;
attach->dma_map_count = 0;
mutex_lock(&dmabuf->lock);
list_add(&attach->node, &dmabuf->attachments);
mutex_unlock(&dmabuf->lock);
return attach;
}
EXPORT_SYMBOL_GPL(dma_buf_attach);
适用场景:图形缓冲区共享(Wayland、DRM)、相机预览、视频编码器输入、跨设备DMA。
5.3 ION - Android内存分配器
/**
* struct ion_device - ION核心设备
* @brief Android平台的内存分配器,现已被dmabuf heap取代
*
* @note 源码位置:drivers/staging/android/ion/ion.h
*/
struct ion_device {
struct miscdevice dev; /*!< 杂项设备 */
struct rb_root buffers; /*!< 缓冲区红黑树 */
struct mutex buffer_lock;
struct idr heap_idr; /*!< 堆ID管理器 */
struct list_head heaps; /*!< 堆列表 */
struct rw_semaphore heap_sem;
};
/**
* struct ion_heap - ION堆
*/
struct ion_heap {
enum ion_heap_type type; /*!< 堆类型 */
struct ion_device *dev; /*!< 所属设备 */
void *priv; /*!< 私有数据 */
struct list_head node; /*!< 堆链表节点 */
const char *name; /*!< 堆名称 */
};
/**
* struct ion_buffer - ION缓冲区
*/
struct ion_buffer {
struct kref ref; /*!< 引用计数 */
struct list_head attachments; /*!< 附件列表 */
struct device *dev; /*!< 设备 */
size_t size; /*!< 大小 */
unsigned int flags; /*!< 标志 */
struct sg_table *sg_table; /*!< 散列表 */
void *vaddr; /*!< 虚拟地址 */
dma_addr_t dma_addr; /*!< DMA地址 */
};
/**
* ion_alloc() - 分配ION缓冲区
* @client: ION客户端
* @len: 长度
* @align: 对齐
* @heap_id_mask: 堆ID掩码
* @flags: 标志
*/
struct ion_buffer *ion_alloc(struct ion_client *client, size_t len,
unsigned int align, unsigned int heap_id_mask,
unsigned int flags)
{
struct ion_device *dev = client->dev;
struct ion_heap *heap;
struct ion_buffer *buffer = NULL;
int ret;
/* 1. 选择合适的堆 */
heap = ion_find_heap(dev, heap_id_mask);
if (!heap)
return ERR_PTR(-ENODEV);
/* 2. 从堆分配 */
buffer = heap->ops->allocate(heap, len, align, flags);
if (IS_ERR(buffer))
return buffer;
/* 3. 初始化缓冲区 */
buffer->dev = dev;
buffer->size = len;
buffer->flags = flags;
INIT_LIST_HEAD(&buffer->attachments);
kref_init(&buffer->ref);
return buffer;
}
适用场景:Android图形栈、Camera HAL、Codec(现已被dmabuf heap取代)。
第六部分:高性能计算/加速器专用缓存
6.1 dmabuf_heap - DMA-BUF堆
/**
* struct dma_heap - DMA-BUF堆
* @brief dmabuf heaps是ION的继任者
*
* @note 源码位置:drivers/dma-buf/dma-heap.c
*/
struct dma_heap {
const char *name; /*!< 堆名称 */
const struct dma_heap_ops *ops; /*!< 操作函数 */
void *priv; /*!< 私有数据 */
struct device *dev; /*!< 设备 */
struct list_head list; /*!< 堆链表 */
int heap_devt; /*!< 设备号 */
struct cdev heap_cdev; /*!< 字符设备 */
};
/**
* struct dma_heap_ops - 堆操作函数
*/
struct dma_heap_ops {
/**
* @brief 分配缓冲区
* @param heap 堆
* @param len 长度
* @param fd_flags 文件描述符标志
* @param heap_flags 堆标志
* @return dma_buf指针
*/
struct dma_buf *(*allocate)(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags);
};
/**
* struct system_heap_buffer - 系统堆缓冲区
*/
struct system_heap_buffer {
struct dma_heap *heap; /*!< 所属堆 */
struct list_head attachments; /*!< 附件列表 */
struct mutex lock;
struct sg_table sg_table; /*!< 散列表 */
int vmap_cnt; /*!< vmap计数 */
void *vaddr; /*!< 虚拟地址 */
unsigned long len; /*!< 长度 */
};
/**
* dma_heap_buffer_alloc() - 系统堆分配实现
*/
static struct dma_buf *system_heap_allocate(struct dma_heap *heap,
unsigned long len,
unsigned long fd_flags,
unsigned long heap_flags)
{
struct system_heap_buffer *buffer;
struct sg_table *sg_table;
struct page **pages;
int nr_pages = PAGE_ALIGN(len) / PAGE_SIZE;
int i, ret;
/* 1. 分配缓冲区结构 */
buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
if (!buffer)
return ERR_PTR(-ENOMEM);
buffer->heap = heap;
buffer->len = len;
mutex_init(&buffer->lock);
INIT_LIST_HEAD(&buffer->attachments);
/* 2. 分配页面数组 */
pages = kmalloc_array(nr_pages, sizeof(*pages), GFP_KERNEL);
if (!pages) {
kfree(buffer);
return ERR_PTR(-ENOMEM);
}
/* 3. 分配物理页 */
for (i = 0; i < nr_pages; i++) {
pages[i] = alloc_page(GFP_KERNEL | __GFP_ZERO);
if (!pages[i]) {
while (--i >= 0)
__free_page(pages[i]);
kfree(pages);
kfree(buffer);
return ERR_PTR(-ENOMEM);
}
}
/* 4. 创建散列表 */
sg_table = &buffer->sg_table;
ret = sg_alloc_table_from_pages(sg_table, pages, nr_pages,
0, len, GFP_KERNEL);
if (ret) {
for (i = 0; i < nr_pages; i++)
__free_page(pages[i]);
kfree(pages);
kfree(buffer);
return ERR_PTR(ret);
}
kfree(pages);
/* 5. 导出dma_buf */
return dma_buf_export(buffer, &system_heap_buf_ops, len, fd_flags);
}
适用场景:现代Android图形栈、多媒体框架、需要跨设备共享大块内存的场景。
6.2 vfio_dma - VFIO DMA区域
/**
* struct vfio_dma - VFIO DMA映射区域
* @brief 管理分配给用户的DMA区域
*
* @note 源码位置:drivers/vfio/vfio_iommu_type1.c
*/
struct vfio_dma {
struct rb_node node; /*!< 红黑树节点 */
dma_addr_t iova; /*!< I/O虚拟地址 */
unsigned long vaddr; /*!< 用户虚拟地址 */
size_t size; /*!< 大小 */
int prot; /*!< 保护标志 */
bool iommu_mapped; /*!< 是否已映射到IOMMU */
struct list_head vaddr_list; /*!< 虚拟地址列表 */
unsigned int vaddr_max; /*!< 最大虚拟地址数 */
struct task_struct *task; /*!< 所属任务 */
struct mm_struct *mm; /*!< 内存描述符 */
};
/**
* vfio_dma_alloc() - 分配VFIO DMA区域
*/
static struct vfio_dma *vfio_dma_alloc(struct rb_root *root,
dma_addr_t iova, size_t size)
{
struct vfio_dma *dma;
/* 1. 分配结构 */
dma = kzalloc(sizeof(*dma), GFP_KERNEL);
if (!dma)
return NULL;
/* 2. 初始化 */
dma->iova = iova;
dma->size = size;
dma->vaddr_max = 1;
INIT_LIST_HEAD(&dma->vaddr_list);
/* 3. 插入红黑树 */
vfio_dma_insert(root, dma);
return dma;
}
适用场景:PCIe设备直通、虚拟机设备分配、高性能计算。
第七部分:内核内部专用缓存
7.1 mempool - 内存池(紧急备用)
/**
* struct mempool_s - 内存池
* @brief 提供紧急备用内存,防止GFP_ATOMIC分配失败
*
* @note 源码位置:include/linux/mempool.h
* @see mm/mempool.c
*/
struct mempool_s {
spinlock_t lock; /*!< 保护空闲列表 */
int min_nr; /*!< 最小元素数量 */
int curr_nr; /*!< 当前元素数量 */
void **elements; /*!< 元素数组 */
void *pool_data; /*!< 池私有数据 */
mempool_alloc_t *alloc; /*!< 分配函数 */
mempool_free_t *free; /*!< 释放函数 */
wait_queue_head_t wait; /*!< 等待队列 */
};
/**
* mempool_alloc() - 从内存池分配
* @pool: 内存池
* @gfp_mask: GFP标志
*/
void *mempool_alloc(mempool_t *pool, gfp_t gfp_mask)
{
void *element;
unsigned long flags;
/* 1. 尝试从池中获取 */
spin_lock_irqsave(&pool->lock, flags);
if (likely(pool->curr_nr)) {
element = pool->elements[--pool->curr_nr];
spin_unlock_irqrestore(&pool->lock, flags);
return element;
}
spin_unlock_irqrestore(&pool->lock, flags);
/* 2. 池为空,尝试底层分配 */
element = pool->alloc(gfp_mask, pool->pool_data);
if (likely(element))
return element;
/* 3. 底层分配失败,但这是紧急情况 */
spin_lock_irqsave(&pool->lock, flags);
if (likely(pool->curr_nr)) {
element = pool->elements[--pool->curr_nr];
spin_unlock_irqrestore(&pool->lock, flags);
return element;
}
spin_unlock_irqrestore(&pool->lock, flags);
/* 4. 如果允许等待,则阻塞 */
if (!(gfp_mask & __GFP_WAIT))
return NULL;
/* 等待其他任务释放 */
wait_event(pool->wait, !pool->curr_nr);
goto retry;
}
EXPORT_SYMBOL(mempool_alloc);
适用场景:块I/O层(bio池)、中断处理程序、任何不允许失败的关键路径。
7.2 kfifo - 无锁环形队列
/**
* struct kfifo - 无锁FIFO环形缓冲区
* @brief 多生产者/消费者安全的无锁环形队列
*
* @note 源码位置:include/linux/kfifo.h
* @see kernel/kfifo.c
*/
struct kfifo {
unsigned char *buffer; /*!< 缓冲区 */
unsigned int size; /*!< 缓冲区大小(2的幂)*/
unsigned int in; /*!< 写指针 */
unsigned int out; /*!< 读指针 */
};
/**
* kfifo_init() - 初始化kfifo
* @fifo: kfifo结构
* @buffer: 缓冲区
* @size: 缓冲区大小(必须为2的幂)
*/
void kfifo_init(struct kfifo *fifo, void *buffer, unsigned int size)
{
/* size必须是2的幂 */
if (!is_power_of_2(size))
size = rounddown_pow_of_two(size);
fifo->buffer = buffer;
fifo->size = size;
fifo->in = 0;
fifo->out = 0;
}
EXPORT_SYMBOL(kfifo_init);
/**
* kfifo_in() - 向kfifo写入数据(无锁版本)
* @fifo: kfifo
* @from: 源数据
* @len: 长度
* @return: 实际写入字节数
*/
unsigned int kfifo_in(struct kfifo *fifo, const void *from,
unsigned int len)
{
unsigned long flags;
unsigned int ret;
spin_lock_irqsave(&fifo->lock, flags);
ret = kfifo_in_locked(fifo, from, len, &fifo->lock);
spin_unlock_irqrestore(&fifo->lock, flags);
return ret;
}
EXPORT_SYMBOL(kfifo_in);
/**
* __kfifo_put() - 无锁放入(单生产者/消费者安全)
*/
static inline unsigned int __kfifo_put(struct kfifo *fifo,
const unsigned char *buffer,
unsigned int len)
{
unsigned int l;
len = min(len, fifo->size - fifo->in + fifo->out);
/* 第一段:直到末尾 */
l = min(len, fifo->size - (fifo->in & (fifo->size - 1)));
memcpy(fifo->buffer + (fifo->in & (fifo->size - 1)), buffer, l);
/* 第二段:剩余部分从开头开始 */
memcpy(fifo->buffer, buffer + l, len - l);
fifo->in += len;
return len;
}
适用场景:中断与进程通信、日志缓冲区、串口驱动、任何需要无锁队列的场景。
7.3 relayfs - 中继文件系统
/**
* struct rchan_buf - 中继通道缓冲区
* @brief relayfs的每个CPU缓冲区
*
* @note 源码位置:kernel/relay.c
*/
struct rchan_buf {
void *start; /*!< 缓冲区起始 */
void *data; /*!< 当前写指针 */
size_t offset; /*!< 当前偏移 */
size_t subbuf_size; /*!< 子缓冲区大小 */
size_t n_subbufs; /*!< 子缓冲区数量 */
size_t bytes_consumed;/*!< 已消费字节数 */
struct rchan *chan; /*!< 所属通道 */
wait_queue_head_t read_wait; /*!< 读等待队列 */
struct dentry *dentry; /*!< debugfs dentry */
};
/**
* struct rchan - 中继通道
*/
struct rchan {
struct kref kref; /*!< 引用计数 */
struct rchan_callbacks *cb; /*!< 回调函数 */
struct dentry *parent; /*!< 父目录 */
struct rchan_buf *buf[NR_CPUS]; /*!< per-CPU缓冲区 */
size_t subbuf_size; /*!< 子缓冲区大小 */
size_t n_subbufs; /*!< 子缓冲区数量 */
int is_global; /*!< 是否全局通道 */
};
/**
* relay_open() - 打开中继通道
*/
struct rchan *relay_open(const char *base_filename,
struct dentry *parent,
size_t subbuf_size,
size_t n_subbufs,
struct rchan_callbacks *cb,
void *private_data)
{
struct rchan *chan;
int i;
/* 1. 分配通道结构 */
chan = kzalloc(sizeof(struct rchan), GFP_KERNEL);
if (!chan)
return NULL;
chan->cb = cb;
chan->subbuf_size = subbuf_size;
chan->n_subbufs = n_subbufs;
kref_init(&chan->kref);
/* 2. 为每个CPU创建缓冲区 */
for_each_possible_cpu(i) {
chan->buf[i] = relay_create_buf(chan);
if (!chan->buf[i])
goto free_bufs;
}
return chan;
free_bufs:
relay_destroy_channel(chan);
return NULL;
}
EXPORT_SYMBOL_GPL(relay_open);
适用场景:内核追踪(trace)、性能监控、调试数据导出。
第八部分:缓存管理技术对比总结
8.1 技术对比表
| 技术 | 设计模式 | 数据组织 | 内存类型 | 共享机制 | 典型场景 |
|---|---|---|---|---|---|
| sk_buff | 组合+享元 | 树形(分片) | kmalloc+page | 克隆/引用 | 网络数据包 |
| videobuf2 | 桥接+策略 | 队列+平面 | DMA-SG/contig | 缓冲区池 | 视频帧 |
| buffer_head | 简单对象 | 线性 | kmalloc | 引用计数 | 传统块I/O |
| bio | 向量化 | 向量数组 | page | 共享 | 现代块I/O |
| page cache | 缓存 | XArray树 | page | 共享映射 | 文件缓存 |
| dma_pool | 内存池 | 固定大小块 | DMA coherent | 池分配 | 小DMA缓冲区 |
| dma_buf | 门面 | 不透明 | 任意 | fd共享 | 跨设备共享 |
| mempool | 对象池 | 数组 | 任意 | 紧急备用 | 关键路径 |
| kfifo | 环形缓冲区 | 环形数组 | kmalloc | 无锁 | 中断通信 |
| relayfs | per-CPU | 环形 | vmalloc | 文件导出 | 内核追踪 |
8.2 设计模式分布
设计模式在缓存管理中的分布: ┌─────────────────────────────────────────────────────────────┐ │ 组合模式 │ sk_buff (分片树) │ ├─────────────┼───────────────────────────────────────────────┤ │ 桥接模式 │ videobuf2 (mem_ops抽象), dma_buf (ops抽象) │ ├─────────────┼───────────────────────────────────────────────┤ │ 策略模式 │ videobuf2_ops, dma_buf_ops, address_space_ops│ ├─────────────┼───────────────────────────────────────────────┤ │ 享元模式 │ sk_buff克隆, page cache共享 │ ├─────────────┼───────────────────────────────────────────────┤ │ 工厂模式 │ kmem_cache_create, dma_pool_create │ ├─────────────┼───────────────────────────────────────────────┤ │ 门面模式 │ dma_buf (统一接口屏蔽底层) │ ├─────────────┼───────────────────────────────────────────────┤ │ 观察者模式 │ buffer_head回调, bio_end_io │ ├─────────────┼───────────────────────────────────────────────┤ │ 迭代器模式 │ bio_vec迭代, kfifo读写 │ └─────────────┴───────────────────────────────────────────────┘
8.3 选择指南
根据场景选择缓存技术: ┌─────────────────────────────────────────────────────────────┐ │ 需要处理网络数据包? → sk_buff │ │ 需要处理视频帧? → videobuf2 │ │ 需要读写块设备? → bio (+ buffer_head兼容) │ │ 需要文件数据缓存? → page cache │ │ 需要小尺寸DMA缓冲区? → dma_pool │ │ 需要跨设备共享内存? → dma_buf + dma_heap │ │ 需要紧急备用内存? → mempool │ │ 需要无锁队列? → kfifo │ │ 需要内核态到用户态流数据? → relayfs │ │ 需要NUMA感知的对象缓存? → kmem_cache (SLUB) │ │ 需要物理连续大块内存? → CMA │ └─────────────────────────────────────────────────────────────┘
总结:Linux缓存管理的设计哲学
1. 分层抽象
每一层都提供清晰的接口,隐藏底层实现细节,使得上层可以专注于业务逻辑。
2. 专一化优化
不同场景使用不同的缓存技术,没有银弹。网络、存储、DMA各有其优化的专用结构。
3. 零拷贝优先
通过共享内存、引用计数、DMA-BUF等技术,最大限度减少数据拷贝,提升性能。
4. 紧急备用
通过mempool等机制,确保关键路径即使在内存紧张时也能分配成功。
5. 可观测性
通过relayfs、tracepoint等机制,让内核缓存的行为对开发者透明。
参考资料:
-
Linux内核源码:
include/linux/和mm/、fs/、drivers/ -
Linux内核文档:
Documentation/core-api/ -
Linux内核邮件列表:关于各缓存技术的讨论
-
《Linux内核设计与实现》(第3版)- Robert Love
-
《Understanding the Linux Kernel》- Daniel P. Bovet
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)