目录

散列表也是哈希表

kmap实现

page_address_map

pkmap_count

page_address_slot

哈希函数

kmap函数实现

kmap_init

kmap

kmap_high

page_address

map_new_virtual

set_page_address

kunmap

kunmap_high(struct page *page)函数实现

vmap实现

vmap函数

vunmap函数

ioremap

ioremap函数

__arm_ioremap_pfn_caller函数

pfn_valid(pfn)

后记


说明:

  • kernel版本:5.9.8
  • 调试工具:gdb + qemu + kernel-O0
  • 平台信息:ARM Vexpress ARMv7 4核 SMP
  • 使用工具:vim + cscope + visio
  1. vmap()用于相对长时间的映射,可同时映射多个pages,
  2. kmap()和kmap_atomic()则用于相对短时间的映射,只能映射单个page.
  3. 而 ioremap 函数,其功能是将给定的物理地址映射为虚拟地址。

注意此处的物理地址并不是真正内存的物理地址,而是cpu上的io memory。

本篇文章重点从以下方面分析:

  • 1、散列表
  • 2、kmap
  • 3、vmap
  • 4、ioremap

散列表也是哈希表


散列表:根据给定的关键字来计算关键字在表中的地址的数据结构,也就说,散列表建立了关键字和存储地址之间的一种直接映射关系。

散列函数:一个把查找表中的关键字映射成该关键字对应的地址的函数,记为Hash(key)=Addr

冲突:散列函数可能会把两个或者两个以上不同关键字映射同一地址,称这种情况为冲突,这些发生碰撞的不同关键字称为同义词

构造散列函数的tips:

  • 1、散列函数的定义域必须包含全部需要存储的关键字,而值域的范围则依赖于散列表的大小或地址范围
  • 2、散列函数计算出来的地址应该能等概率,均分布在整个地址空间,从而减少冲突的发生。
  • 3、散列函数应该尽量简单,能够在较短的时间内就计算出任一关键字对应的散列地址

常用Hash函数的构造方法:

  • 1、直接定址法
  • 2、除留余数法
  • 3、数字分析法
  • 4、平方取中法

kmap实现

用于相对短时间的映射,只能映射单个page.


page_address_map

struct page_address_map {                                                                                                         struct page *page;void *virtual;struct list_head list;
};

struct page_address_map:散列表中关键字(key=page)对应的结构,散列表中的结点

  • page:管理一个高端的物理page
  • virtual:page物理页面对应的虚拟地址
  • list:链接到page_address_htable[i]对应lh的头部的结点

pkmap_count


#define PTRS_PER_PTE        512
#define LAST_PKMAP      PTRS_PER_PTE
static int pkmap_count[LAST_PKMAP]
  • 1、pkmap_count[i]=0为空闲的虚拟地址可以直接使用的
  • 2、pkmap_count[i]=1已经被使用过且被释放了但是还在缓存中
  • 3、pkmap_count[i]>=2正在使用中
  • 4、pkmap_count[i]描述的虚拟地址为0xbfe00000+4k*i

来一张图描述pkmap_count虚拟地址之间的对应关系

page_address_slot


static struct page_address_slot {struct list_head lh;            /* List of page_address_maps */spinlock_t lock;            /* Protect this bucket's list */
} ____cacheline_aligned_in_smp page_address_htable[1<<PA_HASH_ORDER];
  • page_address_slot:散列表的描述结构,page_address_htable为散列表
  • lh:链接哈希表中的成员结点,此哈希表通过链表解决冲突问题

哈希函数


#define hash_long(val, bits) hash_32(val, bits)
#define hash_32 hash_32_generic
#define __hash_32 __hash_32_generic
#define PA_HASH_ORDER   7
#define GOLDEN_RATIO_32 0x61C88647
static inline u32 __hash_32_generic(u32 val)
{return val * GOLDEN_RATIO_32;
}
static inline u32 hash_32_generic(u32 val, unsigned int bits)
{/* High bits are more random, so use them. */return __hash_32(val) >> (32 - bits);
}
static inline u32 hash_ptr(const void *ptr, unsigned int bits)
{return hash_long((unsigned long)ptr, bits);
}
static struct page_address_slot *page_slot(const struct page *page)
{return &page_address_htable[hash_ptr(page, PA_HASH_ORDER)];
}

哈希函数通过以上的各种转换之后的表达式为:

Hash(page)=page*GOLDEN_RATIO_32>>(32-PA_HASH_ORDER)

哈希函数的选择遵循构造散列函数的tips

再来一张图描述page_address_slotpage_address_map之间的关系

kmap函数实现


page如果是ZONE_NORMAL,则直接返回虚拟地址即可。

page如果是ZONE_HIGHMEM,则分以下情况:

  • 1、如果page在page_address_htable中直接返回虚拟地址
  • 2、如果page不在page_address_htable中,则分以下情况:
    • 如果只有pkmap_count[0]为0,则会进行一次flush_all_zero_pkmaps(),然后直接退出,因为pkmap_count[0]为0,则直接返回pkmap_count[0]对应的虚拟地址。
    • 如果有pkmap_count[i]的数组为0,i非0,i的下标最小,则直接拿到pkmap_count[i]对应的虚拟地址。
    • 如果全部非0,则分以下情况
  • 1、有可以的释放的,则通过flush_all_zero_pkmaps()释放,先判断pkmap_count[0]释放释放满足要求,如果不满足要求,然后再循环一次判断,发现有空闲的,则获取对应的虚拟地址。
  • 2、如果没有可以释放的,即都是在使用的,则需要加入等待队列,直到有空闲的释放被唤醒。

kmap_init


static void __init kmap_init(void)
{
#ifdef CONFIG_HIGHMEMpkmap_page_table = early_pte_alloc(pmd_off_k(PKMAP_BASE),PKMAP_BASE, _PAGE_KERNEL_TABLE);
#endif
}

从上可以看出上面对[KMAP_BASE,PAGE_OFFSET]进行PGD页表的填充,页表的port为_PAGE_KERNEL_TABLE,虚拟地址为FIXADDR_START,early_pte_alloc通过early_alloc函数pte_t*pte = alloc (PTE_HWTABLE_OFF + PTE_HWTABLE_SIZE)申请 512 * 4 + 512 *4其中硬件页表和软件各占一半,512硬件PTE表项,一个PTE表项对应4k,刚好2MB对应[KMAP_BASE,PAGE_OFFSET]大小,初始化完成之后,一级页表PGD表项由KMAP_BASE起始地址的PGD表项(PGD[0],PGD[1])指向对应pte的基地址。此后如果在[KMAP_BASE,PAGE_OFFSET]要映射对应的物理地址,只需要填充PTE表项即可。

kmap


static inline void *kmap(struct page *page)
{void *addr;might_sleep();if (!PageHighMem(page))addr = page_address(page);elseaddr = kmap_high(page);kmap_flush_tlb((unsigned long)addr);return addr;
}

1、判断page页面是否是ZONE_HIGHMEM,如果不是,则认为是ZONE_NORMAL,因为ZONE_NORMAL为低端ZONE,则为线性映射,直接获取虚拟addr通过lowmem_page_address函数

2、如果给是page指向ZONE_HIGHMEM的物理页面,需要kamp_high做物理地址和虚拟地址的映射

3、获取到映射完成的虚拟地址之后刷TLB后,返回虚拟地址

kmap_high


void *kmap_high(struct page *page)
{                                                                                                                                                                        unsigned long vaddr;lock_kmap();vaddr = (unsigned long)page_address(page);if (!vaddr)vaddr = map_new_virtual(page);pkmap_count[PKMAP_NR(vaddr)]++;BUG_ON(pkmap_count[PKMAP_NR(vaddr)] < 2);unlock_kmap();return (void*) vaddr;
}
  • 1、已经确定page是ZONE_HIGHMEM,通过page_address函数的hash表找到对应的虚拟地址,如果虚拟地址返回为NULL,则重新进行映射
  • 2、对pkmap_count[PKMAP_NR(vaddr)]++到2,说明此page对应虚拟地址已经被分配出去使用了
  • 3、返回映射成功的虚拟地址。

接下来重点分析page_address函数

page_address


void *page_address(const struct page *page)
{unsigned long flags;void *ret;struct page_address_slot *pas;if (!PageHighMem(page))return lowmem_page_address(page);pas = page_slot(page);ret = NULL;spin_lock_irqsave(&pas->lock, flags);if (!list_empty(&pas->lh)) {struct page_address_map *pam;list_for_each_entry(pam, &pas->lh, list) {if (pam->page == page) {ret = pam->virtual;goto done;}}}
done:spin_unlock_irqrestore(&pas->lock, flags);return ret;
}
  • 1、此page已经确定是ZONE_HIGHMEM,则直接通过page_slot(page)函数(哈希函数)获取到此page的储存器地址
  • 2、因为page可能产生冲突,则需要通过lh链表头遍历此page是否在此链表中,如果存在则直接返回虚拟地址即可,说明之前此page已经建立好了映射,虚拟地址直接使用即可,如果未找到,则需要通过map_new_virtual(page)函数重新映射。

map_new_virtual

static inline unsigned long map_new_virtual(struct page *page)
{unsigned long vaddr;int count;unsigned int last_pkmap_nr;unsigned int color = get_pkmap_color(page);
start:count = get_pkmap_entries_count(color);/* Find an empty entry */for (;;) {last_pkmap_nr = get_next_pkmap_nr(color);if (no_more_pkmaps(last_pkmap_nr, color)) {flush_all_zero_pkmaps();count = get_pkmap_entries_count(color);}if (!pkmap_count[last_pkmap_nr])break;  /* Found a usable entry */if (--count)continue;{DECLARE_WAITQUEUE(wait, current);wait_queue_head_t *pkmap_map_wait = get_pkmap_wait_queue_head(color);__set_current_state(TASK_UNINTERRUPTIBLE);add_wait_queue(pkmap_map_wait, &wait);unlock_kmap();schedule();remove_wait_queue(pkmap_map_wait, &wait);lock_kmap();/* Somebody else might have mapped it while we slept */if (page_address(page))return (unsigned long)page_address(page);/* Re-start */goto start;}}vaddr = PKMAP_ADDR(last_pkmap_nr);set_pte_at(&init_mm, vaddr,&(pkmap_page_table[last_pkmap_nr]), mk_pte(page, kmap_prot));pkmap_count[last_pkmap_nr] = 1;set_page_address(page, (void *)vaddr);                                                                                                                               return vaddr;
}
static inline int no_more_pkmaps(unsigned int pkmap_nr, unsigned int color)
{       return pkmap_nr == 0;
}
static inline unsigned int get_pkmap_color(struct page *page)
{return 0;
}
static inline int get_pkmap_entries_count(unsigned int color)
{return LAST_PKMAP;
}
static inline unsigned int get_next_pkmap_nr(unsigned int color)
{           static unsigned int last_pkmap_nr;last_pkmap_nr = (last_pkmap_nr + 1) & LAST_PKMAP_MASK;return last_pkmap_nr;
}

get_pkmap_color(page)函数仅在特定体系结构(mips和xtensa)中使用,并且由于未在ARM中使用而返回0

color=0,count=512,last_pkmap_nr=1开始循环遍历pkmap_count [last_pkmap_nr]是否为0,如果为0,说明此PTE还未填充或者PTE无效。现在可以拿来用填充,如果512个表项全为非0,即所有的PTE都映射了page,即已经经过kmap和kumap,或者在kunmap中,但是没有实际的解映射,只是pkmap_count[last_pkmap_nr]--。此page对应的物理地址和对应的虚拟地址通过hashtable存储。

count=512,last_pkmap_nr=1count=511,last_pkmap_nr=2…count=1,last_pkmap_nr=512

然后此时if(no_more_pkmaps(last_pkmap_nr, color))成立

  • 1、先flush_cache_kmaps()
  • 2、循环遍历pkmap_count[i],如果等于1,则说明page已经kumap了,把pkmap_count[i] = 0,然后通过pte_clear清空pte和物理地址的页表的映射关系
  • 3、然后通过set_page_address(page, NULL)函数把page从hashtable删除,同时need_flush置1,然后延时刷TLB

然后再判断pkmap_count[0]是否满足要求,如果不满足再循环检测是否有空闲的pkmap_count[last_pkmap_nr]

count=512 last_pkmap_nr=0;count=511 last_pkmap_nr=1;count=510 last_pkmap_nr=2;count=1 last_pkmap_nr=511

如果count=0,还是无法找到空闲的pkmap_count[last_pkmap_nr]

则把先创建一个等待队列,然后把此任务加入到等待队列中,同时把此任务设置为不可屏蔽中断状态,等待唤醒,如果有其他任务释放了pkmap_count[last_pkmap_nr],即是把pkmap_count[last_pkmap_nr]设置为1了,会wakeup此任务,然后从等待队列中删除此任务,然后先尝试在哈希表中是否能找到对应的page,如果找不到,需要重新start即是上述流程。<为何需要先page_address,有可能同一个page在其他高优先级的进程kmap了也没拿到,但是等有空闲得到时候,高优先级任务可能优先拿到,占了一个pkmap_count,低优先级的任务也要占一个pkmap_count,导致同一个page占了两个pkmap_count>

  • 4、通过last_pkmap_nr找到对应的虚拟地址,然后用通过set_pte_at函数填充PTE页表建立映射。
  • 5、把pkmap_count[last_pkmap_nr] = 1表示此PTE已经被填充
  • 6、通过set_page_address(page, (void *)vaddr)函数把page加入哈希表中

set_page_address

void set_page_address(struct page *page, void *virtual)
{unsigned long flags;struct page_address_slot *pas;struct page_address_map *pam;BUG_ON(!PageHighMem(page));pas = page_slot(page);if (virtual) {      /* Add */pam = &page_address_maps[PKMAP_NR((unsigned long)virtual)];pam->page = page;pam->virtual = virtual;spin_lock_irqsave(&pas->lock, flags);list_add_tail(&pam->list, &pas->lh);spin_unlock_irqrestore(&pas->lock, flags);} else {        /* Remove */spin_lock_irqsave(&pas->lock, flags);list_for_each_entry(pam, &pas->lh, list) {if (pam->page == page) {list_del(&pam->list);spin_unlock_irqrestore(&pas->lock, flags);goto done;}}spin_unlock_irqrestore(&pas->lock, flags);}
done:return;
}
  • 1、通过page找到对应存储page_address_map的地址
  • 2、如果虚拟地址非NULL,则说明是add,如果为空则为remove
  • 3、如果是add的话即先通过虚拟地址找到对应的page_address_maps[PKMAP_NR((unsigned long)virtual)]然后更新page_address_map中的page和virtual,通过page_address_map的list添加到page_address_slot中的lh的头部中list_add_tail(&pam->list, &pas->lh);
  • 4、如果是remove的话,找到对应存储地址,然后遍历page_address_slot中的page和当前page是否相等,如果相等就直接list_del

kunmap

释放过程

  • 1、page如果是ZONE_NORMAL则直接返回
  • 2、page如果是ZONE_HIGHMEM, pkmap_count[nr],然后判断等待队列是否为空,如果非空,则唤醒等待队列中的任务。
static inline void kunmap(struct page *page)
{might_sleep();if (!PageHighMem(page))return;kunmap_high(page);
}
  • 1、如果是指向ZONE_NORMAL的page,则直接返回
  • 2、如果是指向ZONE_HIGHMEM的page ,则调用 kunmap_high(page)

kunmap_high(struct page *page)函数实现

void kunmap_high(struct page *page)
{unsigned long vaddr;unsigned long nr;                                                                                                                                                    unsigned long flags;int need_wakeup;unsigned int color = get_pkmap_color(page);wait_queue_head_t *pkmap_map_wait;lock_kmap_any(flags);vaddr = (unsigned long)page_address(page);BUG_ON(!vaddr);nr = PKMAP_NR(vaddr);/** A count must never go down to zero* without a TLB flush!*/need_wakeup = 0;switch (--pkmap_count[nr]) {case 0:BUG();case 1:/** Avoid an unnecessary wake_up() function call.* The common case is pkmap_count[] == 1, but* no waiters.* The tasks queued in the wait-queue are guarded* by both the lock in the wait-queue-head and by* the kmap_lock.  As the kmap_lock is held here,* no need for the wait-queue-head's lock.  Simply* test if the queue is empty.*/pkmap_map_wait = get_pkmap_wait_queue_head(color);need_wakeup = waitqueue_active(pkmap_map_wait);}unlock_kmap_any(flags);/* do wake-up, if needed, race-free outside of the spin lock */                                                                                                      if (need_wakeup)wake_up(pkmap_map_wait);
}
  • 1、通过get_pkmap_color(page)函数获取color=0,--pkmap_count[nr]表示释放
  • 2、通过page找到vaddr地址,如果释放不存在的page则BUG_ON,然后通过PKMAP_NR(addr)找到数组下标nr
  • 3、通过get_pkmap_wait_queue_head(color)获取等待队列的头,然后查看等待队列中是否有任务需要唤醒,如果有则把need_wakeup稍后去唤醒,如果空,则无需唤醒。

vmap实现

vmap()用于相对长时间的映射,可同时映射多个pages,


vmap函数


void *vmap(struct page **pages, unsigned int count,unsigned long flags, pgprot_t prot)
{struct vm_struct *area;unsigned long size;     /* In bytes */might_sleep();if (count > totalram_pages())return NULL;size = (unsigned long)count << PAGE_SHIFT;area = get_vm_area_caller(size, flags, __builtin_return_address(0));if (!area)return NULL;if (map_kernel_range((unsigned long)area->addr, size, pgprot_nx(prot),pages) < 0) {vunmap(area->addr);return NULL;}return area->addr;
}

1、先在vmalloc区域获取size大小的虚拟地址

2、找到虚拟地址后通过map_kernel_range函数把虚拟地址和物理地址绑定映射,即是填充PGD和PTE页表

vunmap函数


void vunmap(const void *addr)
{       BUG_ON(in_interrupt());might_sleep();if (addr)__vunmap(addr, 0);
}

1、如果在中断上下文,直接BUG_ON

2、然后通过__vunmap函数释放,因为第二个参数为0,即不释放对应物理地址对应的物理page

ioremap

ioremap 函数,其功能是将给定的物理地址映射为虚拟地址。


ioremap函数

void __iomem *ioremap(resource_size_t res_cookie, size_t size)
{return arch_ioremap_caller(res_cookie, size, MT_DEVICE,__builtin_return_address(0));
}
void __iomem * (*arch_ioremap_caller)(phys_addr_t, size_t,                                                                                                               unsigned int, void *) =__arm_ioremap_caller;
void __iomem *__arm_ioremap_caller(phys_addr_t phys_addr, size_t size,                                                                                                   unsigned int mtype, void *caller)
{   phys_addr_t last_addr;unsigned long offset = phys_addr & ~PAGE_MASK;unsigned long pfn = __phys_to_pfn(phys_addr);/** Don't allow wraparound or zero size*/last_addr = phys_addr + size - 1;if (!size || last_addr < phys_addr)return NULL;return __arm_ioremap_pfn_caller(pfn, offset, size, mtype,caller);
}
  • 1、phys_addr找到对应物理地址的页内偏移
  • 2、由phys_addr找到物理页帧号
  • 3、做些简单参数检查size非0及环绕

__arm_ioremap_pfn_caller函数

static void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn,unsigned long offset, size_t size, unsigned int mtype, void *caller)
{const struct mem_type *type;int err;unsigned long addr;struct vm_struct *area;phys_addr_t paddr = __pfn_to_phys(pfn);
#ifndef CONFIG_ARM_LPAE/** High mappings must be supersection aligned*/if (pfn >= 0x100000 && (paddr & ~SUPERSECTION_MASK))return NULL;
#endiftype = get_mem_type(mtype);if (!type)return NULL;/** Page align the mapping size, taking account of any offset.                                                                                                        */size = PAGE_ALIGN(offset + size);/** Try to reuse one of the static mapping whenever possible.*/if (size && !(sizeof(phys_addr_t) == 4 && pfn >= 0x100000)) {struct static_vm *svm;svm = find_static_vm_paddr(paddr, size, mtype);if (svm) {addr = (unsigned long)svm->vm.addr;addr += paddr - svm->vm.phys_addr;return (void __iomem *) (offset + addr);}}/** Don't allow RAM to be mapped with mismatched attributes - this* causes problems with ARMv6+*/if (WARN_ON(pfn_valid(pfn) && mtype != MT_MEMORY_RW))return NULL;area = get_vm_area_caller(size, VM_IOREMAP, caller);if (!area)return NULL;addr = (unsigned long)area->addr;area->phys_addr = paddr;err = ioremap_page_range(addr, addr + size, paddr,__pgprot(type->prot_pte));if (err) {vunmap((void *)addr);return NULL;}flush_cache_vmap(addr, addr + size);return (void __iomem *) (offset + addr);
}
  • 1、由pfn获取物理地址
  • 2、如果物理地址大于4G则需要16MB对齐
  • 3、由get_mem_type(mtype)函数获取内存类型,设置页表权限
[MT_DEVICE] = {       /* Strongly ordered / ARMv6 shareddevice */.prot_pte   = PROT_PTE_DEVICE | L_PTE_MT_DEV_SHARED |L_PTE_SHARED,.prot_l1    = PMD_TYPE_TABLE,.prot_sect  = PROT_SECT_DEVICE | PMD_SECT_S,.domain     = DOMAIN_IO,},
  • 4、如果页内偏移为0xff0,size为2,则只需要4k虚拟地址即可,如果size为0x10,则需要两4k大小的虚拟地址映射。
  • 5、无static mapping暂不分析
  • 6、mtype !=MT_MEMORY_RW成立,pfn_valid函数稍后重点分析
  • 7、通过get_vm_area_caller函数获取虚拟地址
  • 8、通过虚拟地址和物理地址及页表属性建立映射,填充PGD PTE页表。
  • 9、flush_cache_vmap刷cachevmap

pfn_valid(pfn)

int pfn_valid(unsigned long pfn)
{phys_addr_t addr = __pfn_to_phys(pfn);if (__phys_to_pfn(addr) != pfn)                                                                                                                                      return 0;return memblock_is_map_memory(addr);
}
bool __init_memblock memblock_is_map_memory(phys_addr_t addr)
{       int i = memblock_search(&memblock.memory, addr);if (i == -1)return false;return !memblock_is_nomap(&memblock.memory.regions[i]);
}
static int __init_memblock memblock_search(struct memblock_type *type, phys_addr_t addr)
{unsigned int left = 0, right = type->cnt;do {unsigned int mid = (right + left) / 2;if (addr < type->regions[mid].base)right = mid;else if (addr >= (type->regions[mid].base +type->regions[mid].size))left = mid + 1;elsereturn mid;} while (left < right);return -1;
}
static inline bool memblock_is_nomap(struct memblock_region *m)
{       return m->flags & MEMBLOCK_NOMAP;
}
  • 1、由pfn找到物理地址
  • 2、memblock_is_map_memory由此函数判断memory是否是map区域

如果在memblock.memory区域找到了,说明是memblock.memory区域,否则不是memory区域返回false=0,即WARN_ON不执行异常,如果是memblock.memory区域则需要判断此memblock是否是MEMBLOCK_NOMAP属性,如果是WARN_ON不执行异常,如果不是则触发异常警告。


void (*arch_iounmap)(volatile void __iomem *) = __iounmap;void iounmap(volatile void __iomem *cookie)
{arch_iounmap(cookie);
}
void __iounmap(volatile void __iomem *io_addr)
{void *addr = (void *)(PAGE_MASK & (unsigned long)io_addr);struct static_vm *svm;/* If this is a static mapping, we must leave it alone */svm = find_static_vm_vaddr(addr);if (svm)return;vunmap(addr);
}
  • 1、无static_vm直接vunmap(addr)
  • 2、如果在中断上下文,直接BUG_ON
  • 3、然后通过__vunmap函数释放,因为第二个参数为0,即不释放对应物理地址对应的物理page

后记


本文重点分析了kmap vmap ioreamp的实现,其中vmap,ioremap中的部分函数在vmalloc的实现中已经分析过。对于kmap vmap ioremap的差异对比和使用场景这里暂未仔细做分析,因为如果你知道它的实现细节了,它的使用注意事项也就了然于胸了。从对内存管理的刚开始的懵懂到现在有种面朝大海春暖花开的感觉。

linux内存管理:kmap、vmap、ioremap相关推荐

  1. 【原创】(十二)Linux内存管理之vmap与vmalloc

    背景 Read the fucking source code! --By 鲁迅 A picture is worth a thousand words. --By 高尔基 说明: Kernel版本: ...

  2. Linux内存管理:Fixmaps(固定映射地址)和ioremap

    目录 Fixmaps和ioremap 映射 ioremap工作原理 早期ioremap的使用 Links 相关阅读 Fix-Mapped地址是一组特殊的编译时地址,其对应的物理地址不必是线性地址减__ ...

  3. 高端内存映射之kmap持久内核映射--Linux内存管理(二十)

    日期 内核版本 架构 作者 GitHub CSDN 2016-09-29 Linux-4.7 X86 & arm gatieme LinuxDeviceDrivers Linux内存管理 在内 ...

  4. Linux内存管理:memblock(引导期间管理内存区域)

    目录 介绍 内存块 内存块初始化 Memblock API 获取有关内存区域的信息 Memblock调试 链接 相关阅读 看原文:<Linux内存管理:memblock> 介绍 内存管理是 ...

  5. linux内存管理子系统采用基于内存区域,Linux 内存管理之highmem简介

    一.Linux内核地址空间 一般来说Linux 内核按照 3:1 的比率来划分虚拟内存(X86等):3 GB 的虚拟内存用于用户空间,1GB 的内存用于内核空间.当然有些体系结构如MIPS使用2:2 ...

  6. Linux内存管理之高端内存映射

    一:引子 我们在前面分析过,在linux内存管理中,内核使用3G->4G的地址空间,总共1G的大小.而且有一部份用来做非连续空间的物理映射(vmalloc).除掉这部份空间之外,只留下896M大 ...

  7. 一文掌握 Linux 内存管理

    作者:dengxuanshi,腾讯 IEG 后台开发工程师 以下源代码来自 linux-5.10.3 内核代码,主要以 x86-32 为例. Linux 内存管理是一个很复杂的"工程&quo ...

  8. linux内存管理之malloc

    对于内核的内存管理,像kmalloc,vmalloc,kmap,ioremap等比较熟悉.而对用户层的管理机制不是很熟悉,下面就从malloc的实现入手.( 这里不探讨linux系统调用的实现机制. ...

  9. Linux内存管理知识总结(一)

    以下源代码来自 linux-5.10.3 内核代码,主要以 x86-32 为例 Linux 内存管理是一个很复杂的"工程",它不仅仅是对物理内存的管理,也涉及到虚拟内存管理.内存交 ...

最新文章

  1. Bitmap too larget to be uploaded into a texture的解决方法
  2. 温铁军、林毅夫、陈平,从学术、现实等多方面来分析,谁的价值高?
  3. linux数据库都备份什么,Linux运维学习之数据库备份与恢复
  4. “国货之光” 完美日记的微服务实践和优化思路
  5. leetcode329. 矩阵中的最长递增路径(dfs)
  6. tableau linux无网络安装_四十二、Linux网络管理,软件安装,进程管理总结
  7. 妲己机器人怎么升级固件_台湾重金设计的3D妲己,亮瞎了
  8. linux 智联 网卡设置,Linux初学者DNS配置指南(四)配置Bind常见问题
  9. python手动安装包_python pip如何手动安装二进制包
  10. unity 改变ui文字_如何在Unity中实现逐字打印UI中的Text文字
  11. 通过ODBC连接PostgreSQL和Greenplum
  12. 计算机黑屏闪光标,电脑开机黑屏只有光标在闪的解决方法
  13. hdmi接口和计算机连接,hdmi接口,教您hdmi接口怎么连接电视
  14. 做好里程碑就是项目成功了一半
  15. linux下phylip软件构建NJ树,利用phylip构建进化树详解
  16. 《相关性准则——大数据时代的高效能之道》一一1.6 相关性准则
  17. 从零到一学习计算机视觉:朋友圈爆款背后的计算机视觉技术与应用 | 公开课笔记...
  18. 华为如何关闭系统更新提示
  19. 蓝桥杯第十届国赛C++研究生组 试题 A: 三升序列
  20. 不同公司系统的对接心得

热门文章

  1. linux ftp 警告暗号话,ssh,FTP到远程服务器时,显示自定义的警告信息
  2. vue动态加载静态资源
  3. [BZOJ1998][Hnoi2010]Fsk物品调度
  4. 解决fatal: unable to connect to github.com问题
  5. WPF读写config配置文件
  6. BCD码和十六进制的区别【转】
  7. 给大家发一个DDOS防御包算法公式
  8. 1-1-Html技术
  9. 襄阳汽车职业学院计算机专业,襄阳汽车职业技术学院毕业设计模板.docx
  10. java udp文件_Java对文件的操作及UDP,TCP