精华内容
下载资源
问答
  • Linux 内存管理 综合实验报告计算机与通信学院Linux 内存管理综合实验报告指导老师:孙建华组员 :夏槟 20040810720段翼真 20040810503米晓亮 200408107132008 年 1 月 3 日1 Linux内存管理综合实验简介31.1 综合...

    Linux 内存管理 综合实验报告

    计算机与通信学院

    Linux 内存管理

    综合实验报告

    指导老师:孙建华

    组员 :夏槟 20040810720

    段翼真 20040810503

    米晓亮 20040810713

    2008 年 1 月 3 日

    1 Linux内存管理综合实验简介3

    1.1 综合设计题目3

    1.2 综合设计小组成员3

    1.3 小组成员任务分配情况及每人所占工作比例3

    2 Linux 内存管理概述4

    2.1 Linux虚拟内存的实现结构 4

    2.2 内核空间和用户空间5

    2.3 虚拟内存实现机制间的关系7

    3 系统调用功能描述8

    3.1 do_page_fault()系统调用功能描述 8

    3.2 brk()系统调用功能描述8

    3.3mmap()系统调用功能描述8

    4 数据结构分析8

    4.1 数据结构示意图9

    4.2 Struct mm9

    4.3 Struct vma 11

    4.4 红黑树12

    5 系统调用代码分析13

    5.1 do_page_fault()系统调用分析 13

    5.1.1 有关常量及相关错误定义15

    5.1.2 代码分析15

    5.1.3 流程图21

    5.2 brk()系统调用分析21

    5.2.1 有关常量及相关错误定义23

    5.2.2 代码分析23

    5.2.3 流程图40

    5.3 mmap()系统调用分析40

    5.3.1 有关常量及相关错误定义42

    5.3.2 代码分析43

    5.3.3 流程图56

    6 心得体会62

    参考文献63

    相关工具63

    1 Linux 内存管理综合实验简介

    1.1 综合设计题目

    系统调用do_page_fault()、brk() 、mmap() 的调用流程,涉及到的主要数据结构,写出

    代码分析结果,并画出流程图来表示相关函数之间的相互调用关系。

    1.2 综合设计小组成员

    段翼真(20040810503 ) 米晓亮 夏槟(20040810720 )

    1.3 小组成员任务分配情况及每人所占工作比例

    段翼真负责 do_page_fault()系统调用的分析,写出代码分析结果,并画出流程图来表示

    相关函数之间的相互调用关系。所占工作比例 30 %。

    米晓亮负责 brk()系统调用的分析,写出代码分析结果,并画出流程图来表示相关函数

    之间的相互调用关系。所占工作比例 30 %。

    夏槟负责mmap()调用的分析写出代码分析结果,并画出流程图来表示相关函数之间的

    相互调用关系。并完成论文的整理书写。所占工作比例 40 %。

    2 Linux 内存管理概述

    Linux 是为多用户多任务设计的操作系统, 所以存储资源要被多个进程有效共享;且由

    于程序规模的不断膨胀,要求的内存空间比从前大得多。 Linux 内存管理的设计充分利用

    了计算机系统所提供的虚拟存储技术,真正实现了虚拟存储器管理。

    由于I386 具有代表性和普遍性,我们选择了 Intel386 的段机制和页机制作为 Linux 虚

    拟存储管理的硬件平台。

    Linux 的内存管理主要体现在对虚拟内存的管理。我们可以把Linux 虚拟内存管理功能

    概括为以下几点:

    ·大地址空间

    ·进程保护

    ·内存映射

    ·公平的物理内存分配

    展开全文
  • linux内存管理-系统调用brk()

    千次阅读 2021-10-23 19:23:14
    库函数malloc为用户进程(malloc本身就是该进程的一部分)维持一个小仓库,当进程需要使用更多的内存空间时就向小仓库要,小仓库中存量不足时就通过brk向内核批发。 前面讲过,每个进程拥有3GB字节

    尽管可见度不高,brk也许是最常使用的系统调用了,用户进程通过它向内核申请空间。人们常常并不意识到在调用brk,原因在于很少有人会直接使用系统调用brk向系统申请空间,而总是通过像malloc一类的C语言库函数(或语言成分,如C++中的new)间接地调用brk。如果把malloc想象成零售,brk则是批发。库函数malloc为用户进程(malloc本身就是该进程的一部分)维持一个小仓库,当进程需要使用更多的内存空间时就向小仓库要,小仓库中存量不足时就通过brk向内核批发。

    前面讲过,每个进程拥有3GB字节的用户虚存空间。但是,这并不意味着用户进程在这3GB字节的范围里可以任意使用,因为虚存空间最终得映射到某个物理存储空间(内存或磁盘空间),才真正可以使用,而这种映射的建立和管理则由内核处理。所谓向内核申请一块空间,是指请求内核分配一块虚存空间和相应的若干物理页面,并建立起映射关系。由于每个进程的虚存空间都很大(3GB),而实际需要使用的又很小,内核不可能在创建进程时就为整个虚存空间都分配好相应的物理空间并建立映射,而只能是需要用多少才分配多少。

    那么,内核怎样管理每个进程的3G字节虚存空间呢?粗略地说,用户程序经过编译、链接形成的映像文件中有一个代码段和一个数据段(包括data段和bss段),其中代码段在下,数据段在上。数据段中包括了所有静态分配的数据空间,包括全局变量和说明为static的局部变量。这些空间是进程所必须的基本要求,所以内核在建立一个进程的运行映象时就分配好些空间,包括虚存地址区间和物理页面,并建立好二者间的映射。除此之外,堆栈空间安置在虚存空间的顶部,运行时由顶向下延伸;代码段和数据段则在底部(注意,不要与X86系统结构中由段寄存器建立的代码段及数据段相混淆);在运行时并不向上伸展。而从数据段的顶部end_data到堆栈段地址的下沿这个中间区域则是一个巨大的空洞,这就是可以在运行时动态分配的空间。最初,这个动态分配空间是从进程的end_data开始的,这个地址为内核和进程所共知。以后,每次动态分配一块内存,这个边界就往上推进一段距离,同时内核和进程都要记下当前的边界在哪里。在进程这一边由malloc或类似的库函数管理,而在内核则将当前的边界记录在进程的mm_struct结构中。具体地说,mm_struct结构中有一个成分brk,表示动态分配区当前的底部。当一个进程需要分配内存时,将要求的大小与其当前的动态分配区底部边界相加,所得的就是所要求的的新边界,也就是brk调用时的参数brk。当内核能满足要求时,系统调用brk返回0,此后新旧两个边界之间的虚存地址就都可以使用了。当内核发现无法满足要求(例如物理空间已经分配完),或者发现新的边界已经过于逼近设于顶部的堆栈时,就拒绝分配而返回-1。

    系统调用brk在内核中的实现为sys_brk,其代码在mm/mmap.c中,这个函数既可以用来分配空间,即把动态分配区底部的边界往上推;也可以用来释放,即归还空间。因此,它的代码也大致上可以分成两部分。我们先读第一部分:

    sys_brk

    /*
     *  sys_brk() for the most part doesn't need the global kernel
     *  lock, except when an application is doing something nasty
     *  like trying to un-brk an area that has already been mapped
     *  to a regular file.  in this case, the unmapping will need
     *  to invoke file system routines that need the global lock.
     */
    asmlinkage unsigned long sys_brk(unsigned long brk)
    {
    	unsigned long rlim, retval;
    	unsigned long newbrk, oldbrk;
    	struct mm_struct *mm = current->mm;
    
    	down(&mm->mmap_sem);
    
    	if (brk < mm->end_code)
    		goto out;
    	newbrk = PAGE_ALIGN(brk);
    	oldbrk = PAGE_ALIGN(mm->brk);
    	if (oldbrk == newbrk)
    		goto set_brk;
    
    	/* Always allow shrinking brk. */
    	if (brk <= mm->brk) {
    		if (!do_munmap(mm, newbrk, oldbrk-newbrk))
    			goto set_brk;
    		goto out;
    	}
    

    参数brk表示所要求的新边界,这个边界不能低于代码段的终点,并且必须与页面大小对齐。如果新边界低于老边界,那就不是申请分配空间,而是释放空间,所以通过do_munmap解除一部分区间的映射,这是个重要的函数。其代码如下:

    sys_brk=>do_munmap

    
    /* Munmap is split into 2 main parts -- this part which finds
     * what needs doing, and the areas themselves, which do the
     * work.  This now handles partial unmappings.
     * Jeremy Fitzhardine <jeremy@sw.oz.au>
     */
    int do_munmap(struct mm_struct *mm, unsigned long addr, size_t len)
    {
    	struct vm_area_struct *mpnt, *prev, **npp, *free, *extra;
    
    	if ((addr & ~PAGE_MASK) || addr > TASK_SIZE || len > TASK_SIZE-addr)
    		return -EINVAL;
    
    	if ((len = PAGE_ALIGN(len)) == 0)
    		return -EINVAL;
    
    	/* Check if this memory area is ok - put it on the temporary
    	 * list if so..  The checks here are pretty simple --
    	 * every area affected in some way (by any overlap) is put
    	 * on the list.  If nothing is put on, nothing is affected.
    	 */
    	mpnt = find_vma_prev(mm, addr, &prev);
    	if (!mpnt)
    		return 0;
    	/* we have  addr < mpnt->vm_end  */
    
    	if (mpnt->vm_start >= addr+len)
    		return 0;
    
    	/* If we'll make "hole", check the vm areas limit */
    	if ((mpnt->vm_start < addr && mpnt->vm_end > addr+len)
    	    && mm->map_count >= MAX_MAP_COUNT)
    		return -ENOMEM;
    
    

    函数find_vma_prev的作用于以前在linux内存管理-几个重要的数据结构和函数博客中读过的find_vma基本相同,它扫描当前进程用户空间的vm_area_struct结构链表或AVL树,试图找到结束地址高于address的第一个区间,如果找到,则函数返回该区间的vm_area_struct结构指针。不同的是,它同时还通过参数prev返回其前一区间结构的指针。等一下我们就将看到为什么需要这个指针。如果返回的指针为0,或者该区间的起始地址也高于addr+len,那就表示想要解除映射的那部分空间原来就没有映射,所以直接返回0,。如果这部分空间落在某个区间的中间,则在解除这部分空间的映射以后会造成一个空洞而使原来的区间一分为二。可是,一个进程可以拥有的虚存区间的数量是有限制的,所以若这个数量达到了上限MAX_MAP_COUNT,就不再允许这样的操作。

    sys_brk=>do_munmap

    	/*
    	 * We may need one additional vma to fix up the mappings ... 
    	 * and this is the last chance for an easy error exit.
    	 */
    	extra = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
    	if (!extra)
    		return -ENOMEM;
    
    	npp = (prev ? &prev->vm_next : &mm->mmap);
    	free = NULL;
    	spin_lock(&mm->page_table_lock);
    	for ( ; mpnt && mpnt->vm_start < addr+len; mpnt = *npp) {
    		*npp = mpnt->vm_next;
    		mpnt->vm_next = free;
    		free = mpnt;
    		if (mm->mmap_avl)
    			avl_remove(mpnt, &mm->mmap_avl);
    	}
    	mm->mmap_cache = NULL;	/* Kill the cache. */
    	spin_unlock(&mm->page_table_lock);
    

    由于解除一部分空间的映射有可能使原来的区间一分为二,所以这里先分配好一个空白的vm_area_struct结构extra。另一方面,要解除映射的那部分空间也有可能跨越好几个区间,所以通过一个for循环把所有涉及的区间都转移到一个临时队列free中,如果建立了AVL树,则也要把这些区间的vm_area_struct结构从AVL树中删除。以前讲过,mm_struct结构中的指针mmap_cache指向上一次find_vma操作的对象,因为对虚存区间的操作往往是有连续性的(见find_vma的代码),而现在用户空间的结构有了变化,多半已经打破了这种连续性,所以把它清成0。至此,已经完成了所有的准备,下面就要具体解除映射了。

    sys_brk=>do_munmap

    	/* Ok - we have the memory areas we should free on the 'free' list,
    	 * so release them, and unmap the page range..
    	 * If the one of the segments is only being partially unmapped,
    	 * it will put new vm_area_struct(s) into the address space.
    	 * In that case we have to be careful with VM_DENYWRITE.
    	 */
    	while ((mpnt = free) != NULL) {
    		unsigned long st, end, size;
    		struct file *file = NULL;
    
    		free = free->vm_next;
    
    		st = addr < mpnt->vm_start ? mpnt->vm_start : addr;
    		end = addr+len;
    		end = end > mpnt->vm_end ? mpnt->vm_end : end;
    		size = end - st;
    
    		if (mpnt->vm_flags & VM_DENYWRITE &&
    		    (st != mpnt->vm_start || end != mpnt->vm_end) &&
    		    (file = mpnt->vm_file) != NULL) {
    			atomic_dec(&file->f_dentry->d_inode->i_writecount);
    		}
    		remove_shared_vm_struct(mpnt);
    		mm->map_count--;
    
    		flush_cache_range(mm, st, end);
    		zap_page_range(mm, st, size);
    		flush_tlb_range(mm, st, end);
    
    		/*
    		 * Fix the mapping, and free the old area if it wasn't reused.
    		 */
    		extra = unmap_fixup(mm, mpnt, st, size, extra);
    		if (file)
    			atomic_inc(&file->f_dentry->d_inode->i_writecount);
    	}
    
    	/* Release the extra vma struct if it wasn't used */
    	if (extra)
    		kmem_cache_free(vm_area_cachep, extra);
    
    	free_pgtables(mm, prev, addr, addr+len);
    
    	return 0;
    }

    这里通过一个while循环逐个处理所涉及的区间,这些区间的vm_area_struct结构都链接在一个临时的队列free中。在下一篇博客中读者将看到,一个进程可以通过系统调用mmap将一个文件的内容映射到其用户空间的某个区间,然后就像访问内存一样来访问这个文件。但是,如果这个文件同时又被别的进程打开,并通过常规的文件操作访问,则在二者对此文件的两种不同形式的写操作之间要加以互斥。如果要解除映射的只是这样的区间的一部分(735-737行),那就相当于对此区间的写操作,所以要递减该文件的inode结构中的一个计数器i_writecount,以保证互斥,到操作完成以后再予以恢复(751-752行)。同时,还要通过remove_shared_vm_struct看看所处理的区间是否是这样的区间,如果是,就将其vm_area_struct结构从目标文件的inode结构内的i_mapping队列中脱链。

    代码中的zap_page_range解除若干连续页面的映射,并且释放所映射的内存页面,或对交换设备上物理页面的引用,这才是我们在这里所主要关心的。其代码如下:

    sys_brk=>do_munmap=>zap_page_range

    
    /*
     * remove user pages in a given range.
     */
    void zap_page_range(struct mm_struct *mm, unsigned long address, unsigned long size)
    {
    	pgd_t * dir;
    	unsigned long end = address + size;
    	int freed = 0;
    
    	dir = pgd_offset(mm, address);
    
    	/*
    	 * This is a long-lived spinlock. That's fine.
    	 * There's no contention, because the page table
    	 * lock only protects against kswapd anyway, and
    	 * even if kswapd happened to be looking at this
    	 * process we _want_ it to get stuck.
    	 */
    	if (address >= end)
    		BUG();
    	spin_lock(&mm->page_table_lock);
    	do {
    		freed += zap_pmd_range(mm, dir, address, end - address);
    		address = (address + PGDIR_SIZE) & PGDIR_MASK;
    		dir++;
    	} while (address && (address < end));
    	spin_unlock(&mm->page_table_lock);
    	/*
    	 * Update rss for the mm_struct (not necessarily current->mm)
    	 * Notice that rss is an unsigned long.
    	 */
    	if (mm->rss > freed)
    		mm->rss -= freed;
    	else
    		mm->rss = 0;
    }
    

    这个函数解除一块虚存区间的页面映射。首先通过pgd_offset在第一层页面目录中找到起始地址所属的目录项,然后通过一个do-while循环从这个目录项开始处理涉及的所有目录项。

    /* to find an entry in a page-table-directory. */
    #define pgd_index(address) ((address >> PGDIR_SHIFT) & (PTRS_PER_PGD-1))
    
    #define __pgd_offset(address) pgd_index(address)
    
    #define pgd_offset(mm, address) ((mm)->pgd+pgd_index(address))

    对于涉及的每一个目录项,通过zap_pmd_range处理第二层的中间目录项。

    sys_brk=>do_munmap=>zap_page_range=>zap_pmd_range

    
    static inline int zap_pmd_range(struct mm_struct *mm, pgd_t * dir, unsigned long address, unsigned long size)
    {
    	pmd_t * pmd;
    	unsigned long end;
    	int freed;
    
    	if (pgd_none(*dir))
    		return 0;
    	if (pgd_bad(*dir)) {
    		pgd_ERROR(*dir);
    		pgd_clear(dir);
    		return 0;
    	}
    	pmd = pmd_offset(dir, address);
    	address &= ~PGDIR_MASK;
    	end = address + size;
    	if (end > PGDIR_SIZE)
    		end = PGDIR_SIZE;
    	freed = 0;
    	do {
    		freed += zap_pte_range(mm, pmd, address, end - address);
    		address = (address + PMD_SIZE) & PMD_MASK; 
    		pmd++;
    	} while (address < end);
    	return freed;
    }
    

    同样,先通过pmd_offset,在第二层目录表中找到起始目录项。对于采用二级映射的i386结构,中间目录表这一层是空的。定义如下:

    extern inline pmd_t * pmd_offset(pgd_t * dir, unsigned long address)
    {
    	return (pmd_t *) dir;
    }

    可见,pmd_offset把指向第一层目录项的指针原封不动地作为指向中间目录项的指针返回来了,也就是说把第一层目录当成了中间目录。所以,对于二级映射,zap_pmd_range在某种意义上只是把zap_page_range所做的事情重复了一遍。不过,这一次重复调用的是zap_pte_range,处理的是底层的页面映射表了。

    sys_brk=>do_munmap=>zap_page_range=>zap_pmd_range=>zap_pte_range

    
    static inline int zap_pte_range(struct mm_struct *mm, pmd_t * pmd, unsigned long address, unsigned long size)
    {
    	pte_t * pte;
    	int freed;
    
    	if (pmd_none(*pmd))
    		return 0;
    	if (pmd_bad(*pmd)) {
    		pmd_ERROR(*pmd);
    		pmd_clear(pmd);
    		return 0;
    	}
    	pte = pte_offset(pmd, address);
    	address &= ~PMD_MASK;
    	if (address + size > PMD_SIZE)
    		size = PMD_SIZE - address;
    	size >>= PAGE_SHIFT;
    	freed = 0;
    	for (;;) {
    		pte_t page;
    		if (!size)
    			break;
    		page = ptep_get_and_clear(pte);
    		pte++;
    		size--;
    		if (pte_none(page))
    			continue;
    		freed += free_pte(page);
    	}
    	return freed;
    }
    

    还是先找到在给定页面表中的起始表项,与pte_offset有关的定义如下:

    /* Find an entry in the third-level page table.. */
    #define __pte_offset(addr)	(((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))
    #define pte_offset(dir, addr)	((pte_t *)pmd_page(*(dir)) + __pte_offset(addr))

    然后就是在一个for循环中,对需要解除映射的页面调用ptep_get_and_clear将页面表项清成0:

    #define ptep_get_and_clear(xp)	__pte(xchg(&(xp)->pte_low, 0))
    

    最后通过free_pte解除对内存页面以及盘上页面的使用,这个函数的代码在mm/memory.c中:

    sys_brk=>do_munmap=>zap_page_range=>zap_pmd_range=>zap_pte_range=>free_pte

    
    /*
     * Return indicates whether a page was freed so caller can adjust rss
     */
    static inline int free_pte(pte_t pte)
    {
    	if (pte_present(pte)) {
    		struct page *page = pte_page(pte);
    		if ((!VALID_PAGE(page)) || PageReserved(page))
    			return 0;
    		/* 
    		 * free_page() used to be able to clear swap cache
    		 * entries.  We may now have to do it manually.  
    		 */
    		if (pte_dirty(pte) && page->mapping)
    			set_page_dirty(page);
    		free_page_and_swap_cache(page);
    		return 1;
    	}
    	swap_free(pte_to_swp_entry(pte));
    	return 0;
    }
    

    如果页面表项表明在解除映射前页面就已不在内存,则当前进程对该内存页面的使用已经解除,所以只需调用swap_free解除对交换设备上的盘上页面的使用。当然,swap_free首先是递减盘上页面的使用计数,只有当这个计数达到0时才真正地释放了这个盘上页面。如果当前进程是这个盘上页面的最后一个用户(或惟一的用户),则该计数递减后为0。反之,则要通过free_page_and_swap_cache解除对盘上页面和内存页面二者的使用。此外,如果页面在最近一次try_to_swap_out以后已被写过,则还要通过set_page_dirty设置该页面page结构中的PG_dirty标志位,并在相应的address_space结构中将其移入dirty_pages队列。函数free_page_and_swap_cache的代码在mm/swap_state.c中:

    sys_brk=>do_munmap=>zap_page_range=>zap_pmd_range=>zap_pte_range=>free_pte=>free_page_and_swap_cache

    
    /* 
     * Perform a free_page(), also freeing any swap cache associated with
     * this page if it is the last user of the page. Can not do a lock_page,
     * as we are holding the page_table_lock spinlock.
     */
    void free_page_and_swap_cache(struct page *page)
    {
    	/* 
    	 * If we are the only user, then try to free up the swap cache. 
    	 */
    	if (PageSwapCache(page) && !TryLockPage(page)) {
    		if (!is_page_shared(page)) {
    			delete_from_swap_cache_nolock(page);
    		}
    		UnlockPage(page);
    	}
    	page_cache_release(page);
    }
    

    以前讲过,一个由用户空间映射、可换出的内存页面(确切地说是它的page数据结构),同时在三个队列中。一是通过其队列头list链入某个换入、换出队列,即相应address_space结构中的clean_pages、dirty_pages以及locked_pages三个队列之一;二是通过其队列头lru链入某个LRU队列,即active_list、inactive_dirty_list或者某个inactive_clean_list之一;最后就是通过指针next_hash链入一个杂凑队列。当一个页面在某个换入、换出队列中时,其page结构中的PG_swap_cache标志位为1,如果当前进程是这个页面的最后一个用户(或唯一用户),此时便要调用delete_from_swap_cache_nolock将页面从上述队列中脱离出来。

    sys_brk=>do_munmap=>zap_page_range=>zap_pmd_range=>zap_pte_range=>free_pte=>free_page_and_swap_cache=>delete_from_swap_cache_nolock

    
    /*
     * This will never put the page into the free list, the caller has
     * a reference on the page.
     */
    void delete_from_swap_cache_nolock(struct page *page)
    {
    	if (!PageLocked(page))
    		BUG();
    
    	if (block_flushpage(page, 0))
    		lru_cache_del(page);
    
    	spin_lock(&pagecache_lock);
    	ClearPageDirty(page);
    	__delete_from_swap_cache(page);
    	spin_unlock(&pagecache_lock);
    	page_cache_release(page);
    }
    

    先通过block_flushpage把页面的内容冲刷到块设备上,不过实际上这种冲刷仅在页面来自一个映射到用户空间的文件时才进行,因为对于交换设备上的页面,此时的内容已经没有意义了。完成了冲刷以后,就通过lru_cache_del将页面从其所在的LRU队列中脱离出来。然后,再通过__delete_from_swap_cache,使页面脱离其他两个队列。

    sys_brk=>do_munmap=>zap_page_range=>zap_pmd_range=>zap_pte_range=>free_pte=>free_page_and_swap_cache=>delete_from_swap_cache_nolock=>__delete_from_swap_cache

    
    /*
     * This must be called only on pages that have
     * been verified to be in the swap cache.
     */
    void __delete_from_swap_cache(struct page *page)
    {
    	swp_entry_t entry;
    
    	entry.val = page->index;
    
    #ifdef SWAP_CACHE_INFO
    	swap_cache_del_total++;
    #endif
    	remove_from_swap_cache(page);
    	swap_free(entry);
    }
    

    这里的remove_from_swap_cache将页面的page结构从换入、换出队列和杂凑队列中脱离出来。然后,也是通过swap_free释放盘上页面,回到delete_from_swap_cache_nolock。最后是page_cache_release,即递减page结构中的使用计数。由于当前进程是页面的最后一个用户,并且在解除映射之前页面在内存中(见上面free_pte中的264行),所以页面的使用计数应该是2,这里(119行)调用了一次page_cache_release就变成了1。再返回到free_page_and_swap_cache中,这里(149行)又调用了一次page_cache_release,这一次就使其变成了0,于是就最终把页面释放,让它回到了空闲页面队列中。

    当回到do_munmap中的时候,已经完成了对一个虚存区间的操作。此时,一方面要对虚存区间的vm_area_struct数据结构和进程的mm_struct数据结构作出调整,以反映已经发生的变化,如果整个区间都解除了映射,则要释放原有的vm_area_struct数据结构。这些操作是由unmap_fixup完成的。其代码如下:
    sys_brk=>do_munmap=>unmap_fixup

    
    /* Normal function to fix up a mapping
     * This function is the default for when an area has no specific
     * function.  This may be used as part of a more specific routine.
     * This function works out what part of an area is affected and
     * adjusts the mapping information.  Since the actual page
     * manipulation is done in do_mmap(), none need be done here,
     * though it would probably be more appropriate.
     *
     * By the time this function is called, the area struct has been
     * removed from the process mapping list, so it needs to be
     * reinserted if necessary.
     *
     * The 4 main cases are:
     *    Unmapping the whole area
     *    Unmapping from the start of the segment to a point in it
     *    Unmapping from an intermediate point to the end
     *    Unmapping between to intermediate points, making a hole.
     *
     * Case 4 involves the creation of 2 new areas, for each side of
     * the hole.  If possible, we reuse the existing area rather than
     * allocate a new one, and the return indicates whether the old
     * area was reused.
     */
    static struct vm_area_struct * unmap_fixup(struct mm_struct *mm, 
    	struct vm_area_struct *area, unsigned long addr, size_t len, 
    	struct vm_area_struct *extra)
    {
    	struct vm_area_struct *mpnt;
    	unsigned long end = addr + len;
    
    	area->vm_mm->total_vm -= len >> PAGE_SHIFT;
    	if (area->vm_flags & VM_LOCKED)
    		area->vm_mm->locked_vm -= len >> PAGE_SHIFT;
    
    	/* Unmapping the whole area. */
    	if (addr == area->vm_start && end == area->vm_end) {
    		if (area->vm_ops && area->vm_ops->close)
    			area->vm_ops->close(area);
    		if (area->vm_file)
    			fput(area->vm_file);
    		kmem_cache_free(vm_area_cachep, area);
    		return extra;
    	}
    
    	/* Work out to one of the ends. */
    	if (end == area->vm_end) {
    		area->vm_end = addr;
    		lock_vma_mappings(area);
    		spin_lock(&mm->page_table_lock);
    	} else if (addr == area->vm_start) {
    		area->vm_pgoff += (end - area->vm_start) >> PAGE_SHIFT;
    		area->vm_start = end;
    		lock_vma_mappings(area);
    		spin_lock(&mm->page_table_lock);
    	} else {
    	/* Unmapping a hole: area->vm_start < addr <= end < area->vm_end */
    		/* Add end mapping -- leave beginning for below */
    		mpnt = extra;
    		extra = NULL;
    
    		mpnt->vm_mm = area->vm_mm;
    		mpnt->vm_start = end;
    		mpnt->vm_end = area->vm_end;
    		mpnt->vm_page_prot = area->vm_page_prot;
    		mpnt->vm_flags = area->vm_flags;
    		mpnt->vm_raend = 0;
    		mpnt->vm_ops = area->vm_ops;
    		mpnt->vm_pgoff = area->vm_pgoff + ((end - area->vm_start) >> PAGE_SHIFT);
    		mpnt->vm_file = area->vm_file;
    		mpnt->vm_private_data = area->vm_private_data;
    		if (mpnt->vm_file)
    			get_file(mpnt->vm_file);
    		if (mpnt->vm_ops && mpnt->vm_ops->open)
    			mpnt->vm_ops->open(mpnt);
    		area->vm_end = addr;	/* Truncate area */
    
    		/* Because mpnt->vm_file == area->vm_file this locks
    		 * things correctly.
    		 */
    		lock_vma_mappings(area);
    		spin_lock(&mm->page_table_lock);
    		__insert_vm_struct(mm, mpnt);
    	}
    
    	__insert_vm_struct(mm, area);
    	spin_unlock(&mm->page_table_lock);
    	unlock_vma_mappings(area);
    	return extra;
    }
    

    我们把这段代码留给读者。最后,当循环结束之时,由于已经解除了一些页面的映射,有些页面映射表可能整个都已经空白,对于这样的页面表(所占的页面)也要加以释放。这是由free_pgtables完成的。我们把它代码留给读者。

    sys_brk=>do_munmap=>free_pgtables

    
    /*
     * Try to free as many page directory entries as we can,
     * without having to work very hard at actually scanning
     * the page tables themselves.
     *
     * Right now we try to free page tables if we have a nice
     * PGDIR-aligned area that got free'd up. We could be more
     * granular if we want to, but this is fast and simple,
     * and covers the bad cases.
     *
     * "prev", if it exists, points to a vma before the one
     * we just free'd - but there's no telling how much before.
     */
    static void free_pgtables(struct mm_struct * mm, struct vm_area_struct *prev,
    	unsigned long start, unsigned long end)
    {
    	unsigned long first = start & PGDIR_MASK;
    	unsigned long last = end + PGDIR_SIZE - 1;
    	unsigned long start_index, end_index;
    
    	if (!prev) {
    		prev = mm->mmap;
    		if (!prev)
    			goto no_mmaps;
    		if (prev->vm_end > start) {
    			if (last > prev->vm_start)
    				last = prev->vm_start;
    			goto no_mmaps;
    		}
    	}
    	for (;;) {
    		struct vm_area_struct *next = prev->vm_next;
    
    		if (next) {
    			if (next->vm_start < start) {
    				prev = next;
    				continue;
    			}
    			if (last > next->vm_start)
    				last = next->vm_start;
    		}
    		if (prev->vm_end > first)
    			first = prev->vm_end + PGDIR_SIZE - 1;
    		break;
    	}
    no_mmaps:
    	/*
    	 * If the PGD bits are not consecutive in the virtual address, the
    	 * old method of shifting the VA >> by PGDIR_SHIFT doesn't work.
    	 */
    	start_index = pgd_index(first);
    	end_index = pgd_index(last);
    	if (end_index > start_index) {
    		clear_page_tables(mm, start_index, end_index - start_index);
    		flush_tlb_pgtables(mm, first & PGDIR_MASK, last & PGDIR_MASK);
    	}
    }
    

    回到sys_brk的代码中,我们已经完成了通过sys_brk释放空间的情景分析。

    如果新边界高于老边界,就表示要分配空间,这就是sys_brk的后一部分。我们继续往下看:

    sys_brk

    	/* Check against rlimit.. */
    	rlim = current->rlim[RLIMIT_DATA].rlim_cur;
    	if (rlim < RLIM_INFINITY && brk - mm->start_data > rlim)
    		goto out;
    
    	/* Check against existing mmap mappings. */
    	if (find_vma_intersection(mm, oldbrk, newbrk+PAGE_SIZE))
    		goto out;
    
    	/* Check if we have enough memory.. */
    	if (!vm_enough_memory((newbrk-oldbrk) >> PAGE_SHIFT))
    		goto out;
    
    	/* Ok, looks good - let it rip. */
    	if (do_brk(oldbrk, newbrk-oldbrk) != oldbrk)
    		goto out;
    set_brk:
    	mm->brk = brk;
    out:
    	retval = mm->brk;
    	up(&mm->mmap_sem);
    	return retval;
    }
    

    首先检查对进程的资源限制,如果所要求的新边界使数据段的大小超过了对当前进程的限制,就拒绝执行。此外,还要通过find_vma_intersection,检查所要求的那部分空间是否与已经存在的某一区间相冲突,这个inline函数的代码如下:

    sys_brk=>find_vma_intersection
     

    
    /* Look up the first VMA which intersects the interval start_addr..end_addr-1,
       NULL if none.  Assume start_addr < end_addr. */
    static inline struct vm_area_struct * find_vma_intersection(struct mm_struct * mm, unsigned long start_addr, unsigned long end_addr)
    {
    	struct vm_area_struct * vma = find_vma(mm,start_addr);
    
    	if (vma && end_addr <= vma->vm_start)
    		vma = NULL;
    	return vma;
    }
    

    这里的start_addr是老边界,如果find_vma返回一个非0指针,就表示在它之上已经有了一个已经映射区间,因此有冲突的可能。此时新的边界end_addr必须落在这个区间的起点之下,也就是让从start_addr到end_addr这个空间落在空洞中,否则便是有了冲突。在查明了不存在冲突以后,还要通过vm_enough_memory看看系统中是否有足够的空闲内存页面。

    sys_brk=>vm_enough_memory

    
    /* Check that a process has enough memory to allocate a
     * new virtual mapping.
     */
    int vm_enough_memory(long pages)
    {
    	/* Stupid algorithm to decide if we have enough memory: while
    	 * simple, it hopefully works in most obvious cases.. Easy to
    	 * fool it, but this should catch most mistakes.
    	 */
    	/* 23/11/98 NJC: Somewhat less stupid version of algorithm,
    	 * which tries to do "TheRightThing".  Instead of using half of
    	 * (buffers+cache), use the minimum values.  Allow an extra 2%
    	 * of num_physpages for safety margin.
    	 */
    
    	long free;
    	
            /* Sometimes we want to use more memory than we have. */
    	if (sysctl_overcommit_memory)
    	    return 1;
    
    	free = atomic_read(&buffermem_pages);
    	free += atomic_read(&page_cache_size);
    	free += nr_free_pages();
    	free += nr_swap_pages;
    	return free > pages;
    }
    

    通过了这些检查,接着就是操作的主体do_brk了。这个函数的代码在mm/mmap.c中:

    sys_brk=>do_brk

    
    /*
     *  this is really a simplified "do_mmap".  it only handles
     *  anonymous maps.  eventually we may be able to do some
     *  brk-specific accounting here.
     */
    unsigned long do_brk(unsigned long addr, unsigned long len)
    {
    	struct mm_struct * mm = current->mm;
    	struct vm_area_struct * vma;
    	unsigned long flags, retval;
    
    	len = PAGE_ALIGN(len);
    	if (!len)
    		return addr;
    
    	/*
    	 * mlock MCL_FUTURE?
    	 */
    	if (mm->def_flags & VM_LOCKED) {
    		unsigned long locked = mm->locked_vm << PAGE_SHIFT;
    		locked += len;
    		if (locked > current->rlim[RLIMIT_MEMLOCK].rlim_cur)
    			return -EAGAIN;
    	}
    
    	/*
    	 * Clear old maps.  this also does some error checking for us
    	 */
    	retval = do_munmap(mm, addr, len);
    	if (retval != 0)
    		return retval;
    
    	/* Check against address space limits *after* clearing old maps... */
    	if ((mm->total_vm << PAGE_SHIFT) + len
    	    > current->rlim[RLIMIT_AS].rlim_cur)
    		return -ENOMEM;
    
    	if (mm->map_count > MAX_MAP_COUNT)
    		return -ENOMEM;
    
    	if (!vm_enough_memory(len >> PAGE_SHIFT))
    		return -ENOMEM;
    
    	flags = vm_flags(PROT_READ|PROT_WRITE|PROT_EXEC,
    				MAP_FIXED|MAP_PRIVATE) | mm->def_flags;
    
    	flags |= VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
    	
    
    	/* Can we just expand an old anonymous mapping? */
    	if (addr) {
    		struct vm_area_struct * vma = find_vma(mm, addr-1);
    		if (vma && vma->vm_end == addr && !vma->vm_file && 
    		    vma->vm_flags == flags) {
    			vma->vm_end = addr + len;
    			goto out;
    		}
    	}	
    
    
    	/*
    	 * create a vma struct for an anonymous mapping
    	 */
    	vma = kmem_cache_alloc(vm_area_cachep, SLAB_KERNEL);
    	if (!vma)
    		return -ENOMEM;
    
    	vma->vm_mm = mm;
    	vma->vm_start = addr;
    	vma->vm_end = addr + len;
    	vma->vm_flags = flags;
    	vma->vm_page_prot = protection_map[flags & 0x0f];
    	vma->vm_ops = NULL;
    	vma->vm_pgoff = 0;
    	vma->vm_file = NULL;
    	vma->vm_private_data = NULL;
    
    	insert_vm_struct(mm, vma);
    
    out:
    	mm->total_vm += len >> PAGE_SHIFT;
    	if (flags & VM_LOCKED) {
    		mm->locked_vm += len >> PAGE_SHIFT;
    		make_pages_present(addr, addr + len);
    	}
    	return addr;
    }
    

    参数addr为需要建立映射的新区间的起点,len则为区间的长度。前面我们已经看到find_vma_intersection对冲突的检查,可是不知读者是否注意到,实际上检查的只是新区间的高端,对于其低端的冲突则并未检查。例如,老的边界是否恰好是一个已映射区间的终点呢?如果不是,那就说明在低端有了冲突。不过,对于低端的冲突是允许的,解决的方法是以新的映射为准,先通过do_munmap把原有的映射解除(见803行),再来建立新的映射,读者大概要问了,为什么对新区间的高端和低端有如此不同的容忍程度和对待呢?读者最好先想一想,然后再往下看。

    以前说过,用户空间的顶端是进程的用户空间堆栈。不管什么进程,在那里总是有一个已映射区间存在着的,所以find_vma_intersection中的find_vma其实不会返回0,因为至少用于堆栈的那个区间总是存在的。当然,在堆栈以下也可能还有通过mmap或ioremap建立的映射区间。所以,如果新区间的高端有冲突,那就可能是与堆栈的冲突,而低端的冲突则只能是与数据段的冲突。所以,对于低端可以让进程自己对可能的错误负责,而对于堆栈可就不能采取把原有的映射解除,另行建立新的映射这样的方法了。

    建立新的映射时,先看看是否可以跟原有的区间合并,即通过扩展原有区间来覆盖新增的区间(826-831行)。如果不行就得另行建立一个区间(838-852行)。

    最后,通过make_pages_present,为新增的区间建立起对内存页面的映射。其代码如下:

    sys_brk=>do_brk=>make_pages_present

    
    /*
     * Simplistic page force-in..
     */
    int make_pages_present(unsigned long addr, unsigned long end)
    {
    	int write;
    	struct mm_struct *mm = current->mm;
    	struct vm_area_struct * vma;
    
    	vma = find_vma(mm, addr);
    	write = (vma->vm_flags & VM_WRITE) != 0;
    	if (addr >= end)
    		BUG();
    	do {
    		if (handle_mm_fault(mm, vma, addr, write) < 0)
    			return -1;
    		addr += PAGE_SIZE;
    	} while (addr < end);
    	return 0;
    }
    

    这里所用的方法很有趣,那就是对新区间中的每一个页面模拟一次缺页异常。读者不妨想想,当从do_brk返回,进而从sys_brk返回之时,这些页面表项的映射是怎样的?如果进程从新分配的区间中读,读出的内容该是什么?往里面写,情况又会怎样?

    展开全文
  • Linux发展到现在,其内存管理机制足够成熟,内存条大小的发展速度已经足够应付日常跑应用的要求。但是,无论有多少内存可用,内存一直都是一种稀缺资源,合理的内存管理是软件正常运行的基础。对此,我对于Linux的...

    Linux发展到现在,其内存管理机制足够成熟,内存条大小的发展速度已经足够应付日常跑应用的要求。但是,无论有多少内存可用,内存一直都是一种稀缺资源,合理的内存管理是软件正常运行的基础。对此,我对于Linux的内存分配进行了一次探索,看看在linux自身有限的内存下,程序到底能申请多少可用的内存。

    1.可用内存

    Linux可以轻松打破MS-DOS内存模型的上限,而每个Linux系统总有自己的内存申请上限,这个上限和系统内存大小有什么关系呢?下面用一个程序进行测验。
    首先要做一些准备工作,了解自身系统的内存是多少,直接在终端敲free或free -h便可查看:
    在这里插入图片描述
    free 与 available的关系
    在 free 命令的输出中,有一个 free 列,同时还有一个 available 列。这二者到底有何区别?
    free 是真正尚未被使用的物理内存数量。至于 available 就比较有意思了,它是从应用程序的角度看到的可用内存数量。Linux 内核为了提升磁盘操作的性能,会消耗一部分内存去缓存磁盘数据,就是我们介绍的 buffer 和 cache。所以对于内核来说,buffer 和 cache 都属于已经被使用的内存。当应用程序需要内存时,如果没有足够的 free 内存可以用,内核就会从 buffer 和 cache 中回收内存来满足应用程序的请求。所以从应用程序的角度来说,available = free + buffer + cache。请注意,这只是一个很理想的计算方式,实际中的数据往往有较大的误差。

    可以看出我的系统内存总大小为12G左右,剩余可用1.5G,交换空间总大小为8G左右,剩余可用5.35G,用下面的程序不断的申请内存,可以验证申请的上限:

    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define ONE_K (1024)
    
    int main() {
        char *some_memory;
        int  size_to_allocate = ONE_K;
        int  megs_obtained = 0;
        int  ks_obtained = 0;
    
        while (1) {
            for (ks_obtained = 0; ks_obtained < 1024; ks_obtained++) {
                some_memory = (char *)malloc(size_to_allocate);
                if (some_memory == NULL) exit(EXIT_FAILURE);
                sprintf(some_memory, "Hello World");
            }
            megs_obtained++;
            printf("Now allocated %d Megabytes\n", megs_obtained);
        }
        exit(EXIT_SUCCESS);
    }
    

    输入的结果如下:
    在这里插入图片描述
    再次free,查看内存和交换空间状况:
    在这里插入图片描述
    从上述实验看出,申请内存并使用这块内存是有上限的,达到这个上限值,Linux操作系统为了保护自己的安全运行,会杀死这个程序。这个过程中,随着分配的内存大小接近物理内存时,运行速度明显变慢,很明显地感觉到硬盘操作,也就是有些暂时不访问的内容会换入交换空间,所以出现了程序被终止后,可用内存空间一下增长到了8G,空闲交换减少为0.12G的现象。总结一下就是,申请内存并且使用申请到的内存(写入数据),能申请到的内存总大小接近可用内存空间和剩余交换空间总和,结果为7.6G。可能大家会觉得好奇,之前可用内存空间和剩余交换空间总和为6.9G左右,运行完程序之后为什么变大了,是因为在内存不够的情况下,Linux清理了另外一些程序正使用的内存来使用,我系统上打开的网页在此程序运的时出现
    崩溃的现象,所以运行程序做实验时存在一定的风险。

    2.申请大量内存却不使用

    更有意思的是对于申请内存而不使用的情况下,可申请的内存将远大于系统实际的可用内存空间和剩余交换空间总和,实验程序如下:

    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    
    #define A_MEGABYTE (1024 * 1024)
    #define PHY_MEM_MEGS	8*1024 /* Adjust this number as required */
    
    int main()
    {
        char *some_memory;
        size_t  size_to_allocate = A_MEGABYTE;
        int  megs_obtained = 0;
    
        while (megs_obtained < (PHY_MEM_MEGS * 4)) {
            some_memory = (char *)malloc(size_to_allocate);
            if (some_memory != NULL) {
                megs_obtained++;
                sprintf(some_memory, "Hello World");
                printf("%s - now allocated %d Megabytes\n", some_memory, megs_obtained);
            }
            else {
                exit(EXIT_FAILURE);
            }
        }
        exit(EXIT_SUCCESS);
    }
    
    

    运行结果如下:

    在这里插入图片描述
    结果是系统实际可用内存空间和剩余交换空间总和为7.6G,但是申请32G的内存空间并没有报错,而且整个程序运行只是一瞬间的时间,也就是说并没有达到申请的上限。原因是malloc申请虚拟地址后,并没有立即与实际物理内存建立映射关系,而是在使用时才会通过缺页中断完成虚拟地址和物理地址映射的整个过程,所以在不使用物理内存的情况下,申请的空间能远远突破物理内存实际大小。

    展开全文
  • Linux内存管理详解.ppt

    2021-05-11 09:20:36
    Linux内存管理详解.ppt》由会员分享,可在线阅读,更多相关《Linux内存管理详解.ppt(25页珍藏版)》请在装配图网上搜索。1、Linux Memory Manager,Actions Microelectronics Co., Ltd.,柯锦玲 2009-10-21,Agenda,...

    《Linux内存管理详解.ppt》由会员分享,可在线阅读,更多相关《Linux内存管理详解.ppt(25页珍藏版)》请在装配图网上搜索。

    1、Linux Memory Manager,Actions Microelectronics Co., Ltd.,柯锦玲 2009-10-21,Agenda,Linux对外提供的内存管理接口,linux 内存镜像图,Linux 内存管理算法介绍,Linux如何防止内存碎片,Linux Image,蓝色字体部分 可用空间,Linux 如何防止内存碎片,内存碎片 内部碎片: 系统为了满足一小段内存区(连续)的需要,不得不分配了一大区域连续内存给它,从而造成了空间浪费 外部碎片 系统虽有足够的内存,但却是分散的碎片,无法满足对大块“连续内存”的需求,Linux 如何防止内存碎片,linux减少外部碎片。

    2、 伙伴系统(buddy算法)把内存块按大小分组管理,一定程度上减轻了外部碎片的危害,因为页框分 配不在盲目,而是按照大小依次有序进行,不过伙伴关系只是减轻了外部碎片,但并未彻底消除 ,但是伙伴系统同时又带来很多的内部碎片 linux减少外部碎片 SLAB,SLUB,SLOB分配器使得一个页面内众多小块内存可独立被分配使用,避免了内部分片,节约了空闲内存,Linux 如何防止内存碎片,linux 内存管理层次关系图1,Linux 如何防止内存碎片,linux 内存管理层次关系图2,Linux 内存管理算法介绍,Buddy System算法原理 Buddy System是一种经典的内存管理算法。在。

    3、Unix和Linux操作系统中都有用到。其作用是减少存储空间中的空洞、减少碎片、增加利用率。避免外碎片的方法有两种: a.利用分页单元把一组非连续的空闲页框映射到连续的线性地址区间。 b.开发适当的技术来记录现存的空闲连续页框块的情况,以尽量避免为满足对小块的请求而把大块的空闲块进行分割。 基于下面三种原因,内核选择第二种避免方法: a.在某些情况下,连续的页框确实必要。 b.即使连续页框的分配不是很必要,它在保持内核页表不变方面所起的作用也是不容忽视的。假如修改页表,则导致平均访存次数增加,从而频繁刷新TLB。 c.通过4M的页可以访问大块连续的物理内存,相对于4K页的使用,TLB未命中率降。

    4、低,加快平均访存速度。,buddy算法将所有空闲页框分组为11个块链表,每个块链表分别包含1,2,4,8,16,32,64,128,256,512,1024个连续的页框,每个块的第一个页框的物理地址是该块大小的整数倍。如,大小为16个页框的块,其起始地址是16*212的倍数。 例,假设要请求一个128个页框的块,算法先检查128个页框的链表是否有空闲块,如果没有则查256个页框的链 表,有则将256个页框的块分裂两份,一份使用,一份插入128个页框的链表。如果还没有,就查512个页框的链表,有的话就分裂为 128,128,256,一个128使用,剩余两个插入对应链表。如果在512还没查到,则返。

    5、回出错信号。 回收过程相反,static inline struct page *alloc_pages(gfp_t gfp_mask, unsigned int order) void __free_pages(struct page *page, unsigned int order) 分配内存的单位为页面数,分配时候传入order值, order 为0 ,1,2,.,n 分别分配1,2,4,2n个页面 伙伴系统(Buddy算法)分配的页面物理上是连续的,因此使用伙伴系统分配的内存大小最大为210 * PAGESIZE = 4K * 4K = 4M unsigned long __get_。

    6、free_pages(gfp_t gfp_mask, unsigned int order) void free_pages(unsigned long addr, unsigned int order) Buddy算法虽然能很好的减少外部碎片的产生,但是他却可用导致很多的内部碎片,slab/slob/slub的作用: 以页为最小单位分配内存对于内核管理系统物理内存来说的确比较方便,但内核自身最常使用 的内存却往往是很小(远远小于一页)的内存块比如存放文件描述符、进程描述符、虚拟内存区域描述符等行为所需的内存都不足一页。这些用来存放描述符的 内存相比页面而言,就好比是面包屑与面包。一个整页中可。

    7、以聚集多个这种这些小块内存;而且这些小块内存块也和面包屑一样频繁地生成/销毁。 为了满足内核对这种小内存块的需要,Linux系统采用了一种被称为slab分配器的技术。Slab分配器的实现 相当复杂,但原理不难,其核心思想就是“存储池”的运用。内存片段(小块内存)被看作对象,当被使用完后,并不直接释放而是被缓存到“存储池”里,留做下 次使用,这无疑避免了频繁创建与销毁对象所带来的额外负载。,slab 分配器的主要结构,SLUB,2.6.22中的SLAB内存管理代码将被SLUB代替。SLAB是经典的管理内核的内存的代码,但是slab维护了大量的对象队列,这些队列虽然可以很快地被分 配,但是过于复杂。

    8、,而且维护所占用的空间会随着系统节点的增加而急剧增长。 slub就是作为slab的可替代选项出现的。slub是一种不使用队列的分配器。slub取消了大量的队列和相 关维护费用,获得了极大的性能和伸缩性提高,并在总体上简化了slab结构,使用了基于每CPU的缓存,同时保留了slab的用户接口,而且slub还提 供了强大的诊断和调试能力,slob,slob是一个相对简单一些的分配器,主要使用在小型的嵌入式系统。在选择了CONFIG_EMBEDDED后,就可以选用CONFIG_SLOB选项,使用SLOB 分配器中。 slob是一个经典的K skbuff_head_cache = kmem_cache。

    9、_create(skbuff_head_cache, sizeof(struct sk_buff), 0, SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL); SLAB_HWCACHE_ALIGN:指定cache对齐(cache line对齐,如32字节对齐). SLAB_PANIC:指示创建失败时候,系统halt 除非你觉得分配的内存结构体会频繁地生成/销毁,否则 请直接使用kmalloc分配空间.,除非你觉得分配的内存结构体会频繁地生成/销毁,否则 请直接使用kmalloc分配空间. 例子: 分配: skb = kmem_cache_alloc_node(cache。

    10、, gfp_mask, node); 释放: kmem_cache_free(skbuff_head_cache, skb);,Vmalloc: 不能在中断中使用: void *vmalloc(unsigned long size) void vfree(const void *addr) Kmalloc: static inline void *kmalloc(size_t size, gfp_t flags) void kfree(const void *objp),Thank You !,Actions Microelectronics Co., Ltd.,end !,Actions Microelectronics Co., Ltd。

    展开全文
  • Linux内存管理 - slab分配器详解

    千次阅读 2021-01-23 15:24:18
    90分钟了解Linux内存架构,numa的优势,slab的实现,vmalloc的原理 5个方面分析linux内核架构,让你对内核不再陌生 手把手带你实现一个Linux内核文件系统 Linux有个叫伙伴系统的分配算法,这个算法主要解决分配连续...
  • RSS 是常驻内存集(Resident Set Size),表示该进程分配的内存大小。RSS 不包括进入交换分区的内存。RSS 包括共享库占用的内存(只要共享库在内存中)RSS 包括所有分配的栈内存和堆内存。VSZ 表示进程分配的虚拟内存。...
  • 所以内存回收在Linux内存管理中占据非常重要的地位,系统的内存毕竟是有限的,跑的进程成百上千,系统的内存越来越小,必须提供内存回收的机制,以满足别的任务的需求。在内存回收的过程中,会遇到以
  • 基于linux-5.10.13
  • Linux内存子系统管理模型 上面的三个部分主要做物理内存分配,包裹着它们的部分做的是地址映射 管理内容 内存管理子系统职能 管理:虚拟地址和物理地址的映射 管理:物理内存的分配 地址映射管理 内存管理子...
  • 不知道啊,反正就是这样的~~ F:那我研究一下 本系列旨在帮助常年工作在Linux环境下,对于Linux内存管理有初步认识又想深入了解的人员更进一步地学习Linux内存管理机制。如果你看明白上面的起因故事,那么证明你已经...
  • Linux 内存管理 综合实验报告计算机与通信学院Linux 内存管理综合实验报告指导老师:孙建华组员 :夏槟 20040810720段翼真 20040810503米晓亮 200408107132008 年 1 月 3 日1 Linux内存管理综合实验简介31.1 综合...
  • 最近经常有朋友问到为什么Linux下,安装各种环境后,无意间发现内存不够用了,如系统原有1G内存,差不多使用了90%多的内存,看起来似乎不够用,其实,这是Linux内存管理的特性。Linux的内存管理特性十分优秀,它不同...
  • Linux内存管理之slab 1:slab原理1. 为什么有了Buddy(伙伴系统)还需要slab?1.1 什么是伙伴系统?1.1.1 伙伴系统思想1.2 伙伴系统例子说明1.3 伙伴系统能解决的问题2 为什么需要引入slab算法?2.1 伙伴系统的缺点2.2 ...
  • Linux系统内存管理

    2021-05-11 11:28:58
    这正是Windows和Linux内存管理上的区别,乍一看,Linux系统吃掉我们的内存(Linux ate my ram),但其实这也正是其内存管理的特点。下面为使用free命令查看结果$<2>$ free -mtotal used free...
  • 内存管理架构 内存管理子系统架构可以分为:用户空间、内核空间及硬件部分3个层面,具体结构如下所示: 1、用户空间:应用程序使用malloc()申请内存资源/free()释放内存资源。 2、内核空间:内核总是驻留在内存中,...
  • 分区页框分配器之水位在讲分区页框分配器分配内存的时候,进入伙伴算法前用函数zone_watermark_fast(),来根据水位来判断当前内存情况。内存够的话采用伙伴算法分配,不够的话通过...
  • 但是大家有没有发现就算内存占用了很多,Linux依旧不卡顿,今天杜老师就聊一下Linux内存管理机制!内存管理在Linux中经常会发现空闲内存很少,似乎所有的内存都被系统占用了,表面的感觉是内存不够用了。其实不然,...
  • Linux/Unix内存管理(1)内存分配和回收的函数(运算符)用户层:C++ -> new 分配 delete 回收 (关键字)C -> malloc() 分配 free() 回收 (函数)Unix系统函数 -> brk() sbrk()Unix系统函数 -> mm...
  • Linux 内存管理 | 虚拟内存管理:虚拟内存空间、虚拟内存分配 Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器 在之前的两篇博客中,分别介绍了虚拟内存与物理内存的管理方式,那么对于操作系统来说,它...
  • Linux之glibc内存管理malloc和free glibc内存管理那些事儿 glibc内存管理 Glibc内存管理—ptmalloc内存分配策略(1) glibc内存管理ptmalloc源代码分析PDF Glibc内存管理 十问 Linux 虚拟内存管理 (glibc) (一) libc...
  • linux内存管理(十五)-内存池

    千次阅读 2021-07-14 09:15:54
    一、内存池原理 平时我们直接所使用的 malloc,...当有新的内存需要的时候,就直接从内存池中分出一部分内存块,若内存块不够再继续申请新的内存,这样做优势,使得内存分配效率得到提升。 二、内存池源码分析 1.内存
  • Linux内存管理和寻址详解
  • 原标题:别再说你不懂 Linux 内存管理了,10 张图给你安排的明明白白转自:LemonCode过去的一周有点魔幻,有印象的有三个新闻:天猫总裁绯闻事件,蘑菇街...今天来带大家研究一下Linux内存管理。对于精通CURD的业务...
  • 进程的虚拟地址空间,linux源代码里面有一个 struct mm_struct 结构来管理内存。我们看下这个结构体,这个结果一听在include/linux/mm_types.h文件中,下面会配上个人的一些注释: struct mm_struct { struct { ...
  • 存储节点(Node)、内存管理区(Zone)和页面(Page)
  • 日期 内核版本 架构 作者 GitHub CSDN 2016-09-01 Linux-4.7 ... Linux内存管理 1 前景回顾 1.1 UMA和NUMA两种模型 共享存储型多处理机有两种模型 均匀存储器存取(Uniform-Mem
  • 1. 数据结构 在Linux内存管理(五):描述物理内存中有介绍过,Zone的数据结构中free_area[MAX_ORDER]数组用于保存每一阶的空闲内存块链表。 struct zone { ... struct free_area free_area[MAX_ORDER]; } ____cache...
  • Linux 内存管理简图

    2021-01-25 20:31:17
    Linux内存管理简图
  • //找到结束地址是落在哪个vma内 if (last && end > last->vm_start) { //我们要在last处分裂vma成两份,因为我们只需要释放last前面的内存 int error = __split_vma(mm, last, end, 1);//分裂内存区域 if ...
  • 9.4 Linux内存管理

    2021-12-02 21:42:15
    Linux内存管理,采用了逻辑地址+线性地址,也就是段+页。 主要还是采用的还是页式内存管理,但不可避免的涉及段机制。 因为intel处理器的发展历史导致的,CPU的硬件结构就是如此,硬件在一定程度上,决定了软件。 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 508,051
精华内容 203,220
关键字:

linux内存管理

linux 订阅