简介: CSDN博客专家,专注Android/Linux系统,分享多mic语音方案、音视频、编解码等技术,与大家一起成长!
优质专栏:Audio工程师进阶系列【原创干货持续更新中……】🚀
优质专栏:多媒体系统工程师系列【原创干货持续更新中……】🚀
优质视频课程:AAOS车载系统+AOSP14系统攻城狮入门实战课【原创干货持续更新中……】🚀
人生格言: 人生从来没有捷径,只有行动才是治疗恐惧和懒惰的唯一良药.
🍉🍉🍉文章目录🍉🍉🍉
- 🌻1.前言
- 🌻2.介绍
- 2.2.1 🐥 `kmap_atomic函数`
- 2.2.2 🐥 `kunmap_atomic`
- 2.2.3 🐥 用法场景
- 2.2.4 🐥 注意问题
- 🌻3.代码实例
- 🐓3.1 kmap_atomic和kunmap_atomic读写页表项
- 🐓3.2 中断上下文中使用kmap_atomic和kunmap_atomic
- 🐓3.3 kmap_atomic和kunmap_atomic在内核中处理DMA缓冲区
🌻1.前言
本篇目的:Linux内核之临时映射内核内存:kmap_atomic用法实例
🌻2.介绍
- kmap_atomic 和 kunmap_atomic 是 Linux 内核中用于在驱动程序中临时映射内核内存的函数。
- 它们通常用于驱动程序中需要在中断上下文中访问内核内存时。
2.2.1 🐥 kmap_atomic函数
kmap_atomic 函数用于将给定的页面映射到内核虚拟地址空间中。这个映射是临时的,并且是为了在执行操作后立即解除映射。它在执行期间对内存区域进行了加锁,以避免其他代码修改它。
这个函数通常在驱动程序中的中断上下文中使用,因为在中断上下文中无法直接访问用户空间的内存,所以需要将需要访问的内存映射到内核空间。
使用 kmap_atomic 函数后,驱动程序可以使用返回的内核虚拟地址来访问页面的内容。
2.2.2 🐥 kunmap_atomic
kunmap_atomic 函数用于解除 kmap_atomic 函数创建的临时映射。它通常在完成对内存的访问后调用,以释放内核虚拟地址空间。
在调用 kunmap_atomic 之后,内存页面将不再映射到内核虚拟地址空间,因此不能再使用返回的指针进行访问。
解除映射后,页面可以自由地由内核重新分配或者进行其他操作。
2.2.3 🐥 用法场景
kmap_atomic
和kunmap_atomic
通常用于以下场景:
- 中断处理程序:当硬件中断触发,中断处理程序需要快速访问高端内存中的数据结构时,可以使用这两个函数。
- 软中断和任务let:在执行软中断或任务let时,如果需要访问高端内存,同样可以使用这两个函数。
- 底半部处理程序:底半部处理程序通常在原子上下文中执行,因此也需要使用这些函数来访问高端内存。
2.2.4 🐥 注意问题
- 尽管
kmap_atomic
和kunmap_atomic
为访问高端内存提供了一种便捷的方式,但它们的使用需要非常小心,因为不当的使用可能会导致系统不稳定。例如,如果在映射后没有及时取消映射,可能会导致映射槽耗尽,进而影响系统的正常运行。此外,由于映射是临时的,因此不能依赖于这些映射在长时间内保持有效。 - 在非原子上下文中,Linux内核提供了其他机制来映射和取消映射高端内存,如
kmap
和kunmap
,它们允许睡眠,并且更适合在进程上下文中使用。 kmap_atomic
和kunmap_atomic
是Linux内核中用于在原子上下文中临时映射和取消映射高端内存页的关键函数。它们为内核提供了一种在不能睡眠的情况下访问高端内存的机制,但使用时需要谨慎,以避免潜在的系统稳定性问题。
🌻3.代码实例
🐓3.1 kmap_atomic和kunmap_atomic读写页表项
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
static int __init kmap_example_init(void)
{
unsigned long phys_addr = 0x100000; // 物理地址
pgd_t *pgd;
pte_t *pte;
unsigned long *virt_addr;
unsigned long page_num;
// 获取对应物理地址的页面框号
page_num = phys_addr >> PAGE_SHIFT;
// 通过物理地址找到对应的页表项
pgd = pgd_offset_k(phys_addr);
if (pgd_none(*pgd) || pgd_bad(*pgd)) {
pr_err("Invalid pgd\n");
return -EINVAL;
}
pte = pte_offset_kernel(pgd, phys_addr);
if (!pte || pte_none(*pte)) {
pr_err("Invalid pte\n");
return -EINVAL;
}
// 使用kmap_atomic将物理页面映射到内核空间
virt_addr = (unsigned long *)kmap_atomic(pfn_to_page(page_num));
if (!virt_addr) {
pr_err("Failed to map physical address\n");
return -ENOMEM;
}
// 在内核空间中写入数据到页面
*virt_addr = 0xDEADBEEF;
// 输出写入的数据
pr_info("Data written: 0x%lx\n", *virt_addr);
// 使用kunmap_atomic解除映射
kunmap_atomic(virt_addr);
return 0;
}
static void __exit kmap_example_exit(void)
{
pr_info("kmap_example unloaded\n");
}
module_init(kmap_example_init);
module_exit(kmap_example_exit);
MODULE_LICENSE("GPL");
🐓3.2 中断上下文中使用kmap_atomic和kunmap_atomic
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/mm.h>
#define SHARED_IRQ 1
static irqreturn_t irq_handler(int irq, void *dev_id)
{
unsigned long *virt_addr;
// 获取一个物理页面
virt_addr = (unsigned long *)kmap_atomic(alloc_page(GFP_ATOMIC));
if (!virt_addr) {
pr_err("Failed to allocate memory\n");
return IRQ_NONE;
}
// 写入数据到页面
*virt_addr = 0xCAFEBABE;
// 输出写入的数据
pr_info("Data written in interrupt context: 0x%lx\n", *virt_addr);
// 解除映射
kunmap_atomic(virt_addr);
return IRQ_HANDLED;
}
static int __init kmap_interrupt_init(void)
{
if (request_irq(SHARED_IRQ, irq_handler, IRQF_SHARED, "kmap_interrupt", (void *)irq_handler)) {
pr_err("Failed to register interrupt handler\n");
return -EBUSY;
}
pr_info("kmap_interrupt module loaded\n");
return 0;
}
static void __exit kmap_interrupt_exit(void)
{
free_irq(SHARED_IRQ, (void *)irq_handler);
pr_info("kmap_interrupt module unloaded\n");
}
module_init(kmap_interrupt_init);
module_exit(kmap_interrupt_exit);
MODULE_LICENSE("GPL");
🐓3.3 kmap_atomic和kunmap_atomic在内核中处理DMA缓冲区
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/dma-mapping.h>
#define DMA_BUFFER_SIZE (1024 * 1024) // 1MB DMA缓冲区大小
static dma_addr_t dma_buffer_phys;
static void *dma_buffer_virt;
static int __init dma_example_init(void)
{
// 分配DMA缓冲区
dma_buffer_virt = dma_alloc_coherent(NULL, DMA_BUFFER_SIZE, &dma_buffer_phys, GFP_KERNEL);
if (!dma_buffer_virt) {
pr_err("Failed to allocate DMA buffer\n");
return -ENOMEM;
}
// 使用kmap_atomic将DMA缓冲区映射到内核空间
dma_buffer_virt = kmap_atomic(pfn_to_page(PHYS_PFN(dma_buffer_phys)));
if (!dma_buffer_virt) {
pr_err("Failed to map DMA buffer\n");
dma_free_coherent(NULL, DMA_BUFFER_SIZE, dma_buffer_virt, dma_buffer_phys);
return -ENOMEM;
}
// 在DMA缓冲区中执行操作,例如写入数据
// 使用kunmap_atomic解除映射
kunmap_atomic(dma_buffer_virt);
pr_info("DMA buffer allocated and mapped\n");
return 0;
}
static void __exit dma_example_exit(void)
{
// 释放DMA缓冲区
dma_free_coherent(NULL, DMA_BUFFER_SIZE, dma_buffer_virt, dma_buffer_phys);
pr_info("DMA buffer freed\n");
}
module_init(dma_example_init);
module_exit(dma_example_exit);
MODULE_LICENSE("GPL");