news 2026/4/20 9:01:22

Linux 内核中常见地址的设计原理及其API使用

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Linux 内核中常见地址的设计原理及其API使用

在Linux内核中,地址类型和它们的正确使用是理解内核内存管理的关键。本文将详细解释各种地址类型及其用法。

一、Linux内核中的主要地址类型

  1. 物理地址 (Physical Address)
phys_addr_t// 平台无关的物理地址类型

· 定义:CPU总线上的实际内存地址,对应RAM芯片上的物理位置

· 使用场景:DMA操作、硬件寄存器映射、物理页面管理

· 包含的头文件:#include <linux/types.h>

· 示例:

phys_addr_tphys_addr=page_to_phys(page);// 将struct page转换为物理地址dma_addr_tdma_handle=dma_map_single(dev,virt_addr,size,direction);
  1. 虚拟地址/内核逻辑地址 (Virtual/Kernel Logical Address)
void*// 通用指针unsignedlong// 用于计算的地址值

· 定义:内核直接映射区域的地址(通常是3GB/4GB分割中的1GB内核空间)

· 特点:

· 与物理地址有固定的线性映射关系(通常:virt = phys + PAGE_OFFSET)

· 可以通过virt_to_phys()和phys_to_virt()与物理地址相互转换

· 始终有效,可直接解引用(不需要特殊访问函数)

· 示例:

void*virt_addr=kmalloc(size,GFP_KERNEL);// 返回内核逻辑地址unsignedlongaddr=(unsignedlong)virt_addr;
  1. 内核虚拟地址 (Kernel Virtual Address)
void__iomem*// I/O内存的虚拟地址

· 定义:通过vmalloc()或ioremap()创建的地址,不一定有连续的物理映射

· 特点:

· 需要通过iowrite32()/ioread32()等函数访问(I/O内存)

· 或直接访问(vmalloc分配的内存)

· 示例:

// I/O内存映射void__iomem*io_addr=ioremap(phys_addr,size);u32 value=ioread32(io_addr+offset);iounmap(io_addr);// vmalloc分配void*vm_addr=vmalloc(size);memcpy(vm_addr,src,len);vfree(vm_addr);
  1. 用户空间地址 (User Space Address)
void__user*// 用户空间地址(需要特殊处理)

· 定义:用户进程虚拟地址空间中的地址

· 特点:

· 在内核中不能直接解引用

· 必须通过专门的复制函数访问

· 使用__user标记有助于静态分析工具(如Sparse)检查

· 包含的头文件:#include <linux/uaccess.h>

· 示例:

longcopy_to_user(void__user*to,constvoid*from,unsignedlongn);longcopy_from_user(void*to,constvoid__user*from,unsignedlongn);// 在系统调用中的典型用法staticssize_tmy_read(structfile*file,char__user*buf,size_tcount,loff_t*ppos){charkernel_buf[256];// ... 填充kernel_buf ...if(copy_to_user(buf,kernel_buf,min(count,sizeof(kernel_buf))))return-EFAULT;returnbytes_copied;}
  1. 总线地址 (Bus Address)
dma_addr_t// 设备看到的DMA地址

· 定义:设备进行DMA操作时使用的地址

· 特点:可能与物理地址不同(存在IOMMU时)

· 包含的头文件:#include <linux/dma-mapping.h>

· 示例:

dma_addr_tdma_handle;void*cpu_addr=dma_alloc_coherent(dev,size,&dma_handle,GFP_KERNEL);// 将dma_handle传递给设备进行DMAdma_free_coherent(dev,size,cpu_addr,dma_handle);
  1. 各类地址及其kernel API

  1. 地址类型层次

  1. 内核地址空间布局

32位系统

高地址

0xFFFFFFFF ±-----------------+

| 内核代码/数据 | <- 内核自身代码

0xFFC00000 ±-----------------+

| KMAP_ATOMIC区 | <- 每个CPU的原子映射槽(4KB/CPU)

0xFEBFFFFF ±-----------------+

| 固定映射区 | <- 特殊用途固定映射

0xFE000000 ±-----------------+

| 持久映射区 | <- kmap持久映射(4MB)

0xF8000000 ±-----------------+

| vmalloc区 | <- vmalloc动态分配 | |

0xF7FFFFFF ±-----------------+

| 空洞 |

0xC0000000 ±-----------------+ <- PAGE_OFFSET(3GB)

| 直接映射区 | <- 896MB物理内存的线性映射 | (低端内存) | | |

0x00000000 ±-----------------+

低地址

64位系统

0xFFFFFFFFFFFFFFFF ±-----------------+

| 规范地址空洞 |

0xFFFF800000000000 ±-----------------+

| 内核模块 | | vmalloc区 | | vmemmap区 | <- 稀疏内存模型结构 | 直接映射所有物理内存 | <- 可映射TB级内存

0xFFFF800000000000 ±-----------------+

| 用户空间 | <- 128TB用户地址空间

0x00007FFFFFFFFFFF ±-----------------+

| 用户空间空洞 |

0x0000000000000000 ±-----------------+

二、关键转换函数和宏

物理地址 ↔ 虚拟地址转换

#include<asm/io.h>// 内核逻辑地址转换(仅限直接映射区域)phys_addr_tphys=virt_to_phys(virt_addr);void*virt=phys_to_virt(phys_addr);// 页帧号转换structpage*page=virt_to_page(virt_addr);void*virt=page_to_virt(structpage*page);// 通用映射(适用于任何物理地址)void__iomem*ioremap(phys_addr_tphys_addr,size_tsize);voidiounmap(void__iomem*addr);

用户空间地址访问

#include<linux/uaccess.h>// 基本复制函数unsignedlongcopy_to_user(void__user*to,constvoid*from,unsignedlongn);unsignedlongcopy_from_user(void*to,constvoid__user*from,unsignedlongn);// 验证用户空间指针是否可访问intaccess_ok(constvoid__user*addr,size_tsize);// 获取/设置单个值intget_user(x,ptr);// 从用户空间获取值intput_user(x,ptr);// 写入用户空间// 字符串操作longstrncpy_from_user(char*dst,constchar__user*src,longcount);longstrnlen_user(constchar__user*s,longn);

DMA地址转换

#include<linux/dma-mapping.h>// 一致性DMA映射(缓存一致)void*dma_alloc_coherent(structdevice*dev,size_tsize,dma_addr_t*dma_handle,gfp_tflag);voiddma_free_coherent(structdevice*dev,size_tsize,void*cpu_addr,dma_addr_tdma_handle);// 流式DMA映射(需要同步)dma_addr_tdma_map_single(structdevice*dev,void*ptr,size_tsize,enumdma_data_directiondir);voiddma_unmap_single(structdevice*dev,dma_addr_taddr,size_tsize,enumdma_data_directiondir);// DMA同步voiddma_sync_single_for_cpu(structdevice*dev,dma_addr_taddr,size_tsize,enumdma_data_directiondir);voiddma_sync_single_for_device(structdevice*dev,dma_addr_taddr,size_tsize,enumdma_data_directiondir);

API使用与选择决策树

地址转换API使用

谢谢关注,后续会持续分享关于AI,GPU,Linux开发,操作系统,图形学,高性能计算,芯片行业讯息。欢迎感兴趣的伙伴关注微信公众号参与讨论沟通:

请关注公众号获取完整系统资料


请关注微信公众号:颇锐克科技共享


图片
上下文地址转换API
图片
三、实际使用示例

示例1:混合使用各种地址类型

#include<linux/module.h>#include<linux/kernel.h>#include<linux/fs.h>#include<linux/uaccess.h>#include<linux/slab.h>#include<linux/io.h>staticintmy_device_mmap(structfile*filp,structvm_area_struct*vma){structmy_device*dev=filp->private_data;unsignedlongoffset=vma->vm_pgoff<<PAGE_SHIFT;unsignedlongphys=dev->phys_base+offset;unsignedlongvsize=vma->vm_end-vma->vm_start;unsignedlongpsize=dev->mem_size-offset;// 将物理地址映射到用户空间if(vsize>psize)return-EINVAL;returnremap_pfn_range(vma,vma->vm_start,phys>>PAGE_SHIFT,vsize,vma->vm_page_prot);}staticssize_tmy_device_write(structfile*filp,constchar__user*buf,size_tcount,loff_t*pos){structmy_device*dev=filp->private_data;char*kernel_buf;intret;// 1. 在内核空间分配缓冲区(内核逻辑地址)kernel_buf=kmalloc(count,GFP_KERNEL);if(!kernel_buf)return-ENOMEM;// 2. 从用户空间复制数据(用户空间地址 -> 内核空间地址)if(copy_from_user(kernel_buf,buf,count)){kfree(kernel_buf);return-EFAULT;}// 3. 处理数据...// 4. 可能需要进行DMA操作dma_addr_tdma_addr=dma_map_single(dev->device,kernel_buf,count,DMA_TO_DEVICE);// 设置设备寄存器(使用I/O映射地址)iowrite32(dma_addr,dev->io_addr+REG_DMA_ADDR);iowrite32(count,dev->io_addr+REG_DMA_LEN);// 5. 清理dma_unmap_single(dev->device,dma_addr,count,DMA_TO_DEVICE);kfree(kernel_buf);returncount;}

示例2:简单的字符设备驱动程序

#include<linux/fs.h>#include<linux/uaccess.h>#defineBUFFER_SIZE1024staticchardevice_buffer[BUFFER_SIZE];staticssize_tdevice_read(structfile*filp,char__user*buffer,size_tlength,loff_t*offset){intbytes_to_copy;intbytes_copied=0;// 验证用户空间缓冲区是否可访问if(!access_ok(buffer,length))return-EFAULT;bytes_to_copy=min(length,(size_t)BUFFER_SIZE);// 从内核缓冲区复制到用户空间if(bytes_to_copy){if(copy_to_user(buffer,device_buffer,bytes_to_copy))return-EFAULT;bytes_copied=bytes_to_copy;}returnbytes_copied;}

最佳实践和注意事项

  1. 始终使用正确的类型:

    · 用户空间指针 → void __user *

    · I/O内存指针 → void __iomem *

    · DMA地址 → dma_addr_t

  2. 地址验证:

    // 对于用户空间指针if(!access_ok(VERIFY_READ,user_ptr,size))return-EFAULT;// 对于I/O映射地址if(!request_mem_region(phys_addr,size,"my_device"))return-EBUSY;
  3. 错误处理:

    · 复制函数返回未复制的字节数(0表示成功)

    · 映射函数可能返回NULL或错误码

    · 始终检查返回值

  4. 内存屏障:

    // 在访问设备内存时需要iowrite32(value,addr);wmb();// 写内存屏障
  5. 架构注意事项:

    · 32位 vs 64位系统地址大小不同

    · 大端序 vs 小端序系统

    · 使用#ifdef CONFIG_64BIT等条件编译

四、调试工具

  1. 地址打印:

    printk(KERN_INFO"Virtual address: %p\n",virt_addr);printk(KERN_INFO"Physical address: %pa\n",&phys_addr);printk(KERN_INFO"User address: %p\n",user_addr);
  2. 地址检查:

    #include<linux/vmalloc.h>// 检查地址是否在内核空间if(!is_vmalloc_addr(addr))// 是直接映射地址// 检查地址是否是有效用户空间地址if(!access_ok(VERIFY_READ,addr,size))// 无效地址

理解这些地址类型及其正确用法对于编写稳定、安全的内核代码至关重要。错误使用地址类型可能导致内核崩溃、安全漏洞或硬件损坏。

谢谢关注,后续会持续分享关于AI,GPU,Linux开发,操作系统,图形学,高性能计算,芯片行业讯息。欢迎感兴趣的伙伴关注微信公众号参与讨论沟通:

请关注公众号获取完整系统资料


请关注微信公众号:颇锐克科技共享


版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/17 7:40:48

350万美元改写开源格局:Cogito v2 109B MoE开启大模型效率革命

350万美元改写开源格局&#xff1a;Cogito v2 109B MoE开启大模型效率革命 【免费下载链接】cogito-v2-preview-llama-109B-MoE 项目地址: https://ai.gitcode.com/hf_mirrors/unsloth/cogito-v2-preview-llama-109B-MoE 导语 Deep Cogito推出的Cogito v2 109B MoE模型…

作者头像 李华
网站建设 2026/4/18 13:54:15

Llama-Factory是否支持睡眠障碍咨询?健康管理闭环

Llama-Factory 与睡眠障碍咨询&#xff1a;构建可落地的 AI 健康管理闭环 在智能设备无处不在的今天&#xff0c;越来越多的人开始关注自己的睡眠质量。从 Apple Watch 的“睡眠追踪”到小米手环的“深睡分析”&#xff0c;数据采集已经变得轻而易举。但问题也随之而来——我们…

作者头像 李华
网站建设 2026/4/16 17:50:13

29、日期时间值格式化显示与多记录合并观测

日期时间值格式化显示与多记录合并观测 1. 日期和时间值的格式化显示 在数据处理中,SAS将日期和时间值存储为数值变量。为了在报告中显示有意义的日期和时间值,需要对数据应用格式。 1.1 WEEKDATEw. 格式 WEEKDATEw. 格式用于以显示星期、月份、日期和年份的形式写入日期…

作者头像 李华
网站建设 2026/4/19 23:58:42

30、从单个原始数据记录创建多个观测值

从单个原始数据记录创建多个观测值 在处理数据时,我们常常会遇到原始数据文件在一个记录中包含多个观测数据的情况。为了减少整个数据文件的大小,数据可能会以这种方式存储。下面将介绍几种处理原始数据的技术,这些技术基于数据的排列方式。 读取重复数据块 读取重复数据…

作者头像 李华