内核 kmap_atomic分析

本文详细解析了kmap_atomic函数的设计目的与实现原理,重点介绍了其高效性与原子操作特性,探讨了多CPU环境下如何避免页映射冲突,并通过实例说明了页表建立的过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、为什么会有这个函数?

我想主要的原因就是kmap_atomic在效率上要比kmap提升不少。

在kmap函数中,会有如下几个比较耗时的部分:

1)、page_address函数
2)、Sleep for somebody else to unmap their entries
代码如下:
171 ____________DECLARE_WAITQUEUE(wait, current); 
173 ______________set_current_state(TASK_UNINTERRUPTIBLE);
174____________add_wait_queue(&pkmap_map_wait,&wait);                                                                           
175 ____________unlock_kmap();
176 ____________schedule();
177 ____________remove_wait_queue(&pkmap_map_wait, &wait);
3)、lock_kmap函数,需要加锁啊!!!
相反kmap_atomic函数,从名字就能看出,原子操作一气呵成。。没有sleep,没有锁。

2、怎么实现的?需要考虑什么问题?

首先得知道这个函数的主要目的是实现page 到 vaddr的转化。
其次我们得考虑多cpu和多任务,大家知道     kernel可以在多个cpu上同时运行不同的task,然而它们共同使用一个内存地址空间,因此如何能保证N个cpu调用kmap_atomic不会将page映射到一个虚拟地址(vaddr)呢?

我们来看看kmap_atomic是如何实现的?

1)、定义一个percpu变量__kmap_atomic_idx,同时在当前cpu上禁用抢占,直到unmap的时候才开启,这样就保证了同一cpu其它任务不会调用该函数。除非该进程在unmap之前睡眠,如果真的那样,别的进程就很可能在同一cpu重入kmap_atomic函数了,然后就可能映射到同一虚拟地址,因此在原子映射期间最好不要休眠。
2)、设计了一个完美的公式

    type = kmap_atomic_idx_push();  //递增一个percpu变量,返回递增后的结果,增加一个计算type的函数表示kmap_atomic函数可以重入,就是上面说的该进程在unmap之前睡眠情况,但一般情况下不会发生冲入,所以该值应该是1,unmap时该值会--。当然重入的次数是有限制的,不会超过KM_TYPE_NR。
    idx = type + KM_TYPE_NR*smp_processor_id();//不同的cpusmp_processor_id()的值是不同的,因此,不同的cpu得到的idx不同。
    vaddr = __fix_to_virt(FIX_KMAP_BEGIN + idx);//不同的idx得到的vaddr就不同了。
    set_pte(kmap_pte-idx, mk_pte(page, prot)); //设置页表
    return (void *)vaddr;

总结,通过percpu变量+禁抢占+加计算公式,就实现了,不同cpu的不同进程调用kmap_atomic函数得到的vaddr是不同的。同时这也给我们实现atomic提供了一种思路,实际上atomic一般都会有一个percpu变量。

3、得到vaddr之后,页表的建立。
我们就以arm为例,来说明一下,arm初始化的时候,会通过create_mapping来创建一些页表,比如map_lowmem就会调用create_mapping函数来建立整个低端内存的映射。

同样地在函数devicemaps_init中会有如下代码:
 967 ____map.pfn = __phys_to_pfn(virt_to_phys(vectors_page));
 968 ____map.virtual = 0xffff0000;
 969 ____map.length = PAGE_SIZE;
 970 ____map.type = MT_HIGH_VECTORS;
 971 ____create_mapping(&map);
这里有一点需要说明一点,虽然只映射了一个PAGE_SIZE,但是如果内核采用的二级页表(即存在pgd,pte),而不是一级页表(只有pgd)的话,会在create_mapping函数中,分配512个pte项(即512个pte指针ptep),远远大于KM_TYPE_NR个数。但是如果采用一级页表的话,这就会有问题,这种情况应该不能用kmap_atomic函数,因为通过TOP_PTE(vaddr)宏是得不到ptep的,因此目前代码中有如下保护:
 67 #ifdef CONFIG_DEBUG_HIGHMEM
 68 ____/*
 69 ____ * With debugging enabled, kunmap_atomic forces that entry to 0.
 70 ____ * Make sure it was indeed properly unmapped.
 71 ____ */
 72____BUG_ON(!pte_none((TOP_PTE(vaddr))));                                                                                     
 73 #endif
 

由此,我们可以看到kmap_atomic使用的是地址空间顶部的一小段地址空间(0xffff0000开始),内核逻辑将这一小段地址空间分成了若干个节(slot),每一节的大小是一个page的大小,可以用来映射一个page。虽然总共可用512个slot,但是只能用KM_TYPE_NR个,有点遗憾!!!


原文地址: http://blog.chinaunix.net/uid-26817832-id-3358944.html

static int mfill_atomic_pte_copy(pmd_t *dst_pmd, 242 struct vm_area_struct *dst_vma, 243 unsigned long dst_addr, 244 unsigned long src_addr, 245 uffd_flags_t flags, 246 struct folio **foliop) 247 { 248 void *kaddr; 249 int ret; 250 struct folio *folio; 251 252 if (!*foliop) { 253 ret = -ENOMEM; 254 folio = vma_alloc_folio(GFP_HIGHUSER_MOVABLE, 0, dst_vma, 255 dst_addr, false); 256 if (!folio) 257 goto out; 258 259 kaddr = kmap_local_folio(folio, 0); 260 /* 261 * The read mmap_lock is held here. Despite the 262 * mmap_lock being read recursive a deadlock is still 263 * possible if a writer has taken a lock. For example: 264 * 265 * process A thread 1 takes read lock on own mmap_lock 266 * process A thread 2 calls mmap, blocks taking write lock 267 * process B thread 1 takes page fault, read lock on own mmap lock 268 * process B thread 2 calls mmap, blocks taking write lock 269 * process A thread 1 blocks taking read lock on process B 270 * process B thread 1 blocks taking read lock on process A 271 * 272 * Disable page faults to prevent potential deadlock 273 * and retry the copy outside the mmap_lock. 274 */ 275 pagefault_disable(); 276 ret = copy_from_user(kaddr, (const void __user *) src_addr, 277 PAGE_SIZE); 278 pagefault_enable(); 279 kunmap_local(kaddr); 280 281 /* fallback to copy_from_user outside mmap_lock */ 282 if (unlikely(ret)) { 283 ret = -ENOENT; 284 *foliop = folio; 285 /* don't free the page */ 286 goto out; 287 } 288 289 flush_dcache_folio(folio); 290 } else { 291 folio = *foliop; 292 *foliop = NULL; 293 } 294 295 /* 296 * The memory barrier inside __folio_mark_uptodate makes sure that 297 * preceding stores to the page contents become visible before 298 * the set_pte_at() write. 299 */ 300 __folio_mark_uptodate(folio); 301 302 ret = -ENOMEM; 303 if (mem_cgroup_charge(folio, dst_vma->vm_mm, GFP_KERNEL)) 304 goto out_release; 305 306 ret = mfill_atomic_install_pte(dst_pmd, dst_vma, dst_addr, 307 &folio->page, true, flags); 308 if (ret) 309 goto out_release; 310 out: 311 return ret; 312 out_release: 313 folio_put(folio); 314 goto out; 315 }
最新发布
03-21
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值