Linux 파트너 시스템 (3) -- 할당 페이지

수준 이 제한 되 어 있 으 니, 묘사 가 부적 절 한 점 은 지적 해 주 십시오. 전재 할 때 출처 를 밝 혀 주 십시오.http://blog.csdn.net/vanbreaker/article/details/7621289 
      앞에서 파트너 시스템 의 원리 와 리 눅 스 파트너 시스템 의 데이터 구 조 를 소 개 했 는데 지금 은 파트너 시스템 이 페이지 를 어떻게 분배 하 는 지 알 아 보 겠 습 니 다.실제로 파트너 시스템 분배 페이지 의 알고리즘 은 복잡 하지 않 지만 메모 리 를 분배 할 때 조각의 발생 (이전 메커니즘 과 관련) 을 최대한 줄 이 고 메모리 가 부족 할 때 각종 적 극적인 수단 을 취해 야 하기 때문에 내부 분배 페이지 의 관련 함 수 를 완전 하 게 분석 하 는 것 이 비교적 복잡 하고 방대 하 다.여기 서 우 리 는 분배 할 때 가장 일반적인 상황 에 만 관심 을 가지 고 다른 상황 의 처 리 는 나중에 따로 꺼 내 토론 한다.
       우 리 는alloc_pages_nodemask () 이 함수 가 분석 을 시작 하면 모든 분배 페이지 의 함수 가 최종 적 으로 이 함수 위 에 떨 어 집 니 다. 이것 은 파트너 시스템 의 입구 입 니 다.
 
struct page *
__alloc_pages_nodemask(gfp_t gfp_mask, unsigned int order,
			struct zonelist *zonelist, nodemask_t *nodemask)
{
	/*  gfp_mask           */
	enum zone_type high_zoneidx = gfp_zone(gfp_mask);
	struct zone *preferred_zone;
	struct page *page;
	/*  gfp_mask          */
	int migratetype = allocflags_to_migratetype(gfp_mask);

	gfp_mask &= gfp_allowed_mask;

	lockdep_trace_alloc(gfp_mask);

	might_sleep_if(gfp_mask & __GFP_WAIT);

	if (should_fail_alloc_page(gfp_mask, order))
		return NULL;

	/*
	 * Check the zones suitable for the gfp_mask contain at least one
	 * valid zone. It's possible to have an empty zonelist as a result
	 * of GFP_THISNODE and a memoryless node
	 */
	if (unlikely(!zonelist->_zonerefs->zone))
		return NULL;

	/* The preferred zone is used for statistics later */
	/* zonelist   zone_idx high_zoneidx      ,           */
	first_zones_zonelist(zonelist, high_zoneidx, nodemask, &preferred_zone);
	if (!preferred_zone)
		return NULL;

	/* First allocation attempt */
	page = get_page_from_freelist(gfp_mask|__GFP_HARDWALL, nodemask, order,
			zonelist, high_zoneidx, ALLOC_WMARK_LOW|ALLOC_CPUSET,
			preferred_zone, migratetype);
	if (unlikely(!page))
		/*                            ,             */
		page = __alloc_pages_slowpath(gfp_mask, order,
				zonelist, high_zoneidx, nodemask,
				preferred_zone, migratetype);

	trace_mm_page_alloc(page, order, gfp_mask, migratetype);
	return page;
}
  • 우선 해 야 할 일 은 지 정 된 분배 관리 구역 을 찾 아서 관리 구역 의 번 호 를 하 이 에 저장 하 는 것 이다.zoneidx 중
  • 그 다음 에 첫 번 째 분 배 를 시도 합 니 다. 절 차 는 지정 한 관리 구역 에서 관리 구역 을 스 캔 하기 시작 합 니 다. > 충분 한 관리 구역 을 찾 습 니 다. > 지정 한 이전 유형 링크 에서 메모 리 를 분배 합 니 다. > 지정 한 이전 유형 에서 찾 지 못 하면 다른 이전 유형 에서 찾 습 니 다
  • 만약 에 두 번 째 단계 가 각 지역 에서 분 배 를 만족 시 킬 수 있 는 메모 리 를 찾 지 못 한다 면 관리 구역 의 메모리 가 확실히 부족 하 다 는 것 을 의미한다. 그래서 느 린 속도 로 분 배 를 시작 했다. 자주 사용 하지 않 는 페이지 를 바 꾸 려 고 시도 하 는 등 내부 핵 회 는 이번 분배 에서 더욱 적 극적인 모습 을 보일 것 이다.그 중의 세부 사항 은 다른 복잡 한 것들 과 관련 되 었 으 니 나중에 분석 하 자
  • static struct page *
    get_page_from_freelist(gfp_t gfp_mask, nodemask_t *nodemask, unsigned int order,
    		struct zonelist *zonelist, int high_zoneidx, int alloc_flags,
    		struct zone *preferred_zone, int migratetype)
    {
    	struct zoneref *z;
    	struct page *page = NULL;
    	int classzone_idx;
    	struct zone *zone;
    	nodemask_t *allowednodes = NULL;/* zonelist_cache approximation */
    	int zlc_active = 0;		/* set if using zonelist_cache */
    	int did_zlc_setup = 0;		/* just call zlc_setup() one time */
    
    	/*        */
    	classzone_idx = zone_idx(preferred_zone);
    zonelist_scan:
    	/*
    	 * Scan zonelist, looking for a zone with enough free.
    	 * See also cpuset_zone_allowed() comment in kernel/cpuset.c.
    	 */
        /*           ,                ,
    	    ,  high_zoneidx   ZONE_HIGHMEM,      HIGHMEM-->NORMAL-->DMA,
    	    high_zoneidx  ZONE_NORMAL,      NORMAL-->DMA*/
    	for_each_zone_zonelist_nodemask(zone, z, zonelist,
    						high_zoneidx, nodemask) {
    		if (NUMA_BUILD && zlc_active &&
    			!zlc_zone_worth_trying(zonelist, z, allowednodes))
    				continue;
    
    		/*                    CPU*/
    		if ((alloc_flags & ALLOC_CPUSET) &&
    			!cpuset_zone_allowed_softwall(zone, gfp_mask))
    				goto try_next_zone;
    
    		BUILD_BUG_ON(ALLOC_NO_WATERMARKS < NR_WMARK);
    		if (!(alloc_flags & ALLOC_NO_WATERMARKS)) {
    			unsigned long mark;
    			int ret;
    			
                /*  alloc_flags          ,pages_min?pages_low?pages_high?
    			         ,                     */
    			mark = zone->watermark[alloc_flags & ALLOC_WMARK_MASK];
    
    			/*               ,          */
    			if (zone_watermark_ok(zone, order, mark,
    				    classzone_idx, alloc_flags))
    				goto try_this_zone;
    
    			if (zone_reclaim_mode == 0)
    				goto this_zone_full;
    
    			/*         NUMA         */
    			ret = zone_reclaim(zone, gfp_mask, order);
    			switch (ret) {
    			case ZONE_RECLAIM_NOSCAN:/*      */
    				/* did not scan */
    				goto try_next_zone;
    			case ZONE_RECLAIM_FULL:  /*          */
    				/* scanned but unreclaimable */
    				goto this_zone_full;
    			default:
    				/* did we reclaim enough */
    				if (!zone_watermark_ok(zone, order, mark,
    						classzone_idx, alloc_flags))
    					goto this_zone_full;
    			}
    		}
    
    try_this_zone:/*  2^order  */
    		page = buffered_rmqueue(preferred_zone, zone, order,
    						gfp_mask, migratetype);
    		if (page)
    			break;
    this_zone_full:
    		if (NUMA_BUILD)
    			zlc_mark_zone_full(zonelist, z);
    try_next_zone:
    		if (NUMA_BUILD && !did_zlc_setup && nr_online_nodes > 1) {
    			/*
    			 * we do zlc_setup after the first zone is tried but only
    			 * if there are multiple nodes make it worthwhile
    			 */
    			allowednodes = zlc_setup(zonelist, alloc_flags);
    			zlc_active = 1;
    			did_zlc_setup = 1;
    		}
    	}
    
    	if (unlikely(NUMA_BUILD && page == NULL && zlc_active)) {
    		/* Disable zlc cache for second zonelist scan */
    		zlc_active = 0;
    		goto zonelist_scan;
    	}
    	return page;
    }
    
    
  • 지 정 된 관리 구역 부터 zonelist 에서 정 의 된 순서에 따라 관리 구역 을 옮 겨 다 닌 다
  • 이 관리 구역 의 수위 선 이 정상 이면 buffered 를 호출 합 니 다.rmqueue () 이 관리 구역 에서 분배
  • 관리 구역 의 수위 선 이 너무 낮 으 면 NUMA 구조 에서 페이지 수 거 신청
  • static inline
    struct page *buffered_rmqueue(struct zone *preferred_zone,
    			struct zone *zone, int order, gfp_t gfp_flags,
    			int migratetype)
    {
    	unsigned long flags;
    	struct page *page;
    	int cold = !!(gfp_flags & __GFP_COLD);
    	int cpu;
    
    again:
    	cpu  = get_cpu();
    	if (likely(order == 0)) {/*order 0,        */
    		struct per_cpu_pages *pcp;
    		struct list_head *list;
    
    		pcp = &zone_pcp(zone, cpu)->pcp;/*    CPU   pcp*/
    		list = &pcp->lists[migratetype];/*            */
    		local_irq_save(flags);
    
    		/*      ,          ,          2^batch   list*/
    		if (list_empty(list)) {
    			pcp->count += rmqueue_bulk(zone, 0,
    					pcp->batch, list,
    					migratetype, cold);
    			if (unlikely(list_empty(list)))
    				goto failed;
    		}
    
    		if (cold)/*       ,         */
    			page = list_entry(list->prev, struct page, lru);
    		else     /*       ,         */
    			page = list_entry(list->next, struct page, lru);
            
    		list_del(&page->lru);
    		pcp->count--;
    	} else {
    		if (unlikely(gfp_flags & __GFP_NOFAIL)) {
    			/*
    			 * __GFP_NOFAIL is not to be used in new code.
    			 *
    			 * All __GFP_NOFAIL callers should be fixed so that they
    			 * properly detect and handle allocation failures.
    			 *
    			 * We most definitely don't want callers attempting to
    			 * allocate greater than order-1 page units with
    			 * __GFP_NOFAIL.
    			 */
    			WARN_ON_ONCE(order > 1);
    		}
    		spin_lock_irqsave(&zone->lock, flags);
    		/*                      */
    		page = __rmqueue(zone, order, migratetype);
    		spin_unlock(&zone->lock);
    		if (!page)
    			goto failed;
    		__mod_zone_page_state(zone, NR_FREE_PAGES, -(1 << order));
    	}
    
    	__count_zone_vm_events(PGALLOC, zone, 1 << order);
    	zone_statistics(preferred_zone, zone);
    	local_irq_restore(flags);
    	put_cpu();
    
    	VM_BUG_ON(bad_range(zone, page));
    	if (prep_new_page(page, order, gfp_flags))
    		goto again;
    	return page;
    
    failed:
    	local_irq_restore(flags);
    	put_cpu();
    	return NULL;
    }
    

     
  • 이 함 수 는 두 가지 상황 으로 나 누 어 처리 하 는데 하 나 는 한 페이지 상자 만 분배 하 라 는 것 이 고 다른 하 나 는 여러 개의 연속 페이지 상자
  • 를 분배 하 라 는 것 이다.
  • 단일 페이지 에 대해 커 널 선택 은 모든 CPU 페이지 상자 의 고속 캐 시 에서 분 배 됩 니 다. 그 핵심 설명 구조 도 MIGRATE 입 니 다.TYPES 개의 링크 는 링크 의 요소 가 모두 한 페이지 에 불과 합 니 다.이 페이지 들 은 핫 페이지 와 콜 드 페이지 로 나 뉘 는데, 핫 페이지 란 CPU 캐 시 에 있 는 페이지 이 고, 반대로 콜 드 페이지 는 캐 시 에 존재 하지 않 는 페이지 이다.단일 페이지 상자 의 신청 에 대해 열 페이지 를 분배 하면 효율 을 높 일 수 있다.주의해 야 할 것 은 체인 헤더 에 가 까 운 페이지 가 더 워 질 수록 링크 끝 에 가 까 운 페이지 가 추워 집 니 다. 한 페이지 상 자 를 풀 때마다 페이지 상 자 는 링크 의 머리 에 삽입 되 기 때 문 입 니 다. 즉, 머리 에 가 까 운 페이지 상 자 는 최근 에 풀 렸 기 때문에 고속 캐 시 에 존재 할 가능성 이 가장 높 습 니 다
  • .
  • 연속 적 인 페이지 상자 분배 에 대해 호출rmqueue () 로 분배 완료
  • static struct page *__rmqueue(struct zone *zone, unsigned int order,
    						int migratetype)
    {
    	struct page *page;
    
    retry_reserve:
    	
    	page = __rmqueue_smallest(zone, order, migratetype);
    
    	/*              MIGRATE_RESERVE(   MIGRATE_RESERVE,
    	                     )*/
    	if (unlikely(!page) && migratetype != MIGRATE_RESERVE) {
    		page = __rmqueue_fallback(zone, order, migratetype);
    
    		/*
    		 * Use MIGRATE_RESERVE rather than fail an allocation. goto
    		 * is used because __rmqueue_smallest is an inline function
    		 * and we want just one call site
    		 */
    		if (!page) {
    			migratetype = MIGRATE_RESERVE;
    			goto retry_reserve;
    		}
    	}
    
    	trace_mm_page_alloc_zone_locked(page, order, migratetype);
    	return page;
    }
    

     
  • 우선 지 정 된 이전 유형 에 따라 호출rmqueue_smallest () 는 대응 하 는 메모리 블록 을 분배 합 니 다. 이 함 수 는 파트너 시스템 의 알고리즘 구현
  • 입 니 다.
  • 분배 에 실패 하면 지정 한 이전 유형 에 충분 한 메모리 가 없어 분 배 를 만족 시 키 지 못 한 다 는 것 을 의미한다. 이 때 는 fallbacks 에서 정의 한 순서에 따라 다른 이전 링크 에서 찾 아야 한다.rmqueue_fallback () 함수 가 비교적 복잡 하고 이동 유형 을 이용 하여 파편 을 피 하 는 사상 을 나타 내 며 뒤에 따로 꺼 내 분석
  • static inline
    struct page *__rmqueue_smallest(struct zone *zone, unsigned int order,
    						int migratetype)
    {
    	unsigned int current_order;
    	struct free_area * area;
    	struct page *page;
    
    	/* Find a page of the appropriate size in the preferred list */
    	for (current_order = order; current_order < MAX_ORDER; ++current_order) {
    
    		/*           free_area*/
    		area = &(zone->free_area[current_order]);
    
    		/*        free_list           */
    		if (list_empty(&area->free_list[migratetype]))
    			continue;
    		
            /*                  */
    		page = list_entry(area->free_list[migratetype].next,
    							struct page, lru);
    		list_del(&page->lru);
    		rmv_page_order(page);/* page private   0*/
    		area->nr_free--;         /*     1*/
    		
    		/*    ( current_order>order    )*/
    		expand(zone, page, order, current_order, area, migratetype);
    		return page;
    	}
    
    	return NULL;
    }
    
    
    
     
    
    static inline void expand(struct zone *zone, struct page *page,
    	int low, int high, struct free_area *area,
    	int migratetype)
    {
    	unsigned long size = 1 << high;/*order high         */
    
    	/*   order low,         order high
    	    high  low         ,                order      */
    	while (high > low) {
    		area--;/*area 1     order   area*/
    		high--;/*high 1         */
    		size >>= 1;/*    size    2*/
    		VM_BUG_ON(bad_range(zone, &page[size]));
    
    		/*  size                  ,
    		               order     */
    		list_add(&page[size].lru, &area->free_list[migratetype]);
    		area->nr_free++;/* order      1*/
    		set_page_order(&page[size], high);/*  private  high*/
    	}
    }
    
     

    한 블록의 포 지 셔 닝 은 블록의 첫 번 째 시작 페이지 에 대응 하 는 설명자 와 order (size) 로 포 지 셔 닝 할 수 있 으 므 로 한 블록의 첫 번 째 페이지 설명자 체인 을 해당 하 는 링크 에 넣 으 면 됩 니 다.

    좋은 웹페이지 즐겨찾기