티스토리 뷰

dm-zoned-target.c가 I/O 과정의 흐름을 담당하는 함수들이 모여있었다면, dm-zoned-metadata.c는 I/O를 실행하기 위해 필요한 메타데이터들에 관한 함수들이 모여있는 코드입니다. Write 하기 위한 zone이 chunk와 매핑되어있는지, bio가 접근하는 chunk id는 몇 번인지, device 상태는 어떤지 등과 같은 원시적인 데이터와 관련된 로직들이 모여있습니다. 이 파일은 3000 라인이 넘는 많은 양의 코드이지만, 핵심적인 함수들을 우선 파악한다면 I/O 루틴을 보다 수월하게 이해할 수 있습니다.

dm-zoned I/O Routine

 

함수를 소개하기에 앞서, metadata.c 에서 가장 많이 사용되는 zone 구조체와 dmap 구조체를 먼저 이해하고 넘어가겠습니다 !

 

dm_zone

struct dm_zone {
	/* For listing the zone depending on its state */
	struct list_head	link;

	/* Device containing this zone */
	struct dmz_dev		*dev;

	/* Zone type and state */
	unsigned long		flags;

	/* Zone activation reference count */
	atomic_t		refcount;

	/* Zone id */
	unsigned int		id;

	/* Zone write pointer block (relative to the zone start block) */
	unsigned int		wp_block;

	/* Zone weight (number of valid blocks in the zone) */
	unsigned int		weight;

	/* The chunk that the zone maps */
	unsigned int		chunk;

	/*
	 * For a sequential data zone, pointer to the random zone
	 * used as a buffer for processing unaligned writes.
	 * For a buffer zone, this points back to the data zone.
	 */
	struct dm_zone		*bzone;
};

Zone 하나의 정보를 담고 있는 구조체입니다. Zone의 id, chunk, bzone, write pointer 등 다양한 멤버 변수를 가지고 있고, 그중 metadata와 관련된 멤버 변수인 chunk는 해당 zone에 매핑된 chunk id, bzone은 해당 zone이 dzone인 경우 random zone의 주소를 가지고, bzone인 경우 기존 데이터가 저장된 자신의 dzone의 주소를 값으로 가집니다.

 


dmz_map

struct dmz_map {
	__le32			dzone_id;
	__le32			bzone_id;
};

dmz_map 구조체는 chunk 매핑 테이블을 나타내는 구조체이며 멤버 변수로 dzone, bzone을 가지고 있습니다. 즉 해당 chunk에 매핑된 zone의 정보를 가지고 있습니다.

chunk와 dmap 구조도

dmap는 chunk 하나에 dmap이 하나씩 매핑된 1:1 매핑 구조로 설계되어있습니다. Chunk id가 dmap의 인덱스 역할을 합니다. 즉 chunk 0번은 dmap 0번, chunk N번은 dmap N번에 매핑되어있습니다. 멤버 변수인 dzone은 seq zone 또는 random zone 정보를 저장하고, bzone은 dzone이 seq zone을 저장할 때 해당 seq zone의 buffer로 쓰이는 random zone을 저장합니다.

 


dmz_get_chunk_mapping

struct dm_zone *dmz_get_chunk_mapping(struct dmz_metadata *zmd, unsigned int chunk, int op)
{
	struct dmz_mblock *dmap_mblk = zmd->map_mblk[chunk >> DMZ_MAP_ENTRIES_SHIFT];
	struct dmz_map *dmap = (struct dmz_map *) dmap_mblk->data;
	int dmap_idx = chunk & DMZ_MAP_ENTRIES_MASK;
	unsigned int dzone_id;
	struct dm_zone *dzone = NULL;
	int ret = 0;
	int alloc_flags = zmd->nr_cache ? DMZ_ALLOC_CACHE : DMZ_ALLOC_RND;

	dmz_lock_map(zmd);
again:
	/* Get the chunk mapping */
	dzone_id = le32_to_cpu(dmap[dmap_idx].dzone_id);
	if (dzone_id == DMZ_MAP_UNMAPPED) {
		...
		/* Allocate a random zone */
		dzone = dmz_alloc_zone(zmd, 0, alloc_flags);
		...
		dmz_map_zone(zmd, dzone, chunk);

	} else {
		/* The chunk is already mapped: get the mapping zone */
		dzone = dmz_get(zmd, dzone_id);
		...
	}

	...
    
	dmz_activate_zone(dzone);
	dmz_lru_zone(zmd, dzone);
out:
	dmz_unlock_map(zmd);

	return dzone;
}

dmz_get_chunk_mapping 함수는 chunk와 zone을 매핑하고 관리하는 가장 중요한 함수입니다. 먼저 인자로 받은 chunk를 DMZ_MAP_ENTRIES_MASK와 AND 연산을 하여 dmap_idx를 계산합니다. 현재 시스템에서 chunk는 총 127개(0~126) 번까지 존재하고 DMZ_MAP_ENTRIES_MASK는 512 - 1 -> 0001 1111 1111 값을 가집니다.

 

그러므로 AND 연산을 하면 chunk id가 그대로 dmap_idx로 입력됩니다. 이후 le32_to_cpu 함수를 이용해 해당 dmap에 할당되어 있는 멤버 변수 dzone_id를 가져옵니다.

 

해당 id가 UNMAPPED인 경우 dmz_alloc_zone 함수를 통해 free zone 1개를 할당받습니다. dzone_id의 초기값은 UNMAPPED(-1)입니다. 이후 해당 dmz_map_zone 함수를 통해 zone과 chunk를 매핑한 후 반환합니다. 

 

해당 id가 이미 매핑된 경우 dmz_get 함수를 통해 매핑된 zone을 받아서 사용합니다.


dmz_set_chunk_mapping

static void dmz_set_chunk_mapping(struct dmz_metadata *zmd, unsigned int chunk,
				  unsigned int dzone_id, unsigned int bzone_id)
{
	struct dmz_mblock *dmap_mblk = zmd->map_mblk[chunk >> DMZ_MAP_ENTRIES_SHIFT];
	struct dmz_map *dmap = (struct dmz_map *) dmap_mblk->data;
	int map_idx = chunk & DMZ_MAP_ENTRIES_MASK;

	dmap[map_idx].dzone_id = cpu_to_le32(dzone_id);
	dmap[map_idx].bzone_id = cpu_to_le32(bzone_id);
	dmz_dirty_mblock(zmd, dmap_mblk);
}

chunk와 dmap의 매핑을 관리하는 함수입니다. 매핑하기 위한 chunk와 dzone_id, bzone_id를 인자로 받아 dmap의 멤버 변수들에 값을 설정합니다. 여기서 map_idx는 get_chunk_mapping에서 설명한 idx와 동일합니다. dzone과 bzone을 매핑하고 싶으면 인자에 적절한 id 값이 전달되어 넘어오고, 매핑을 해제하고 싶은 경우 UNMAPPED 값이 전달되어 넘어옵니다.

 

Zone id를 저장할 때 little endian 방식을 사용하는 것을 볼 수 있는데, LE 방식이 하위 비트를 가져와 연산하는 속도가 빠르고 zone id가 수십 비트씩 저장되는 경우가 거의 없기 때문에 연산하는 경우가 많은 zone id를 little endian 방식으로 저장하지 않을까 추측해볼 수 있을 것 같습니다.

 


dmz_alloc_zone

struct dm_zone *dmz_alloc_zone(struct dmz_metadata *zmd, unsigned int dev_idx,
			       unsigned long flags)
{
	struct list_head *list;
	struct dm_zone *zone;
	int i;
	...
	i = 0;
again:
	if (flags & DMZ_ALLOC_CACHE)
		list = &zmd->unmap_cache_list;
	else if (flags & DMZ_ALLOC_RND)
		list = &zmd->dev[dev_idx].unmap_rnd_list;
	else
		list = &zmd->dev[dev_idx].unmap_seq_list;

	...
	zone = list_first_entry(list, struct dm_zone, link);
	list_del_init(&zone->link);

	if (dmz_is_cache(zone))
		atomic_dec(&zmd->unmap_nr_cache);
	else if (dmz_is_rnd(zone))
		atomic_dec(&zone->dev->unmap_nr_rnd);
	else
		atomic_dec(&zone->dev->unmap_nr_seq);

	...
	return zone;
}

인자로 넘어오는 flag에 따라 필요한 zone을 반환하는 함수입니다. Zone list는 linked list로 구성되어있고 Cache, Random, Sequential 타입에 따라 필요한 zone list를 할당받은 후 list의 첫 번째 zone을 할당받습니다. 여기서 zone list는 매핑되어있지 않은, 즉 free zone으로만 구성되어있습니다.


 

dmz_map_zone

void dmz_map_zone(struct dmz_metadata *zmd, struct dm_zone *dzone,
		  unsigned int chunk)
{
	/* Set the chunk mapping */
	dmz_set_chunk_mapping(zmd, chunk, dzone->id,
			      DMZ_MAP_UNMAPPED);
	dzone->chunk = chunk;
	if (dmz_is_cache(dzone))
		list_add_tail(&dzone->link, &zmd->map_cache_list);
	else if (dmz_is_rnd(dzone))
		list_add_tail(&dzone->link, &dzone->dev->map_rnd_list);
	else
		list_add_tail(&dzone->link, &dzone->dev->map_seq_list);
}

dmz_set_chunk_mapping 함수에 dzone->id를 담아 호출하여 chunk와 zone을 매핑하는 함수입니다. 이후 zone의 멤버 변수인 chunk에도 chunk 값을 할당해 zone <-> chunk 양방향 연결을 유지합니다.


dmz_unmap_zone

void dmz_unmap_zone(struct dmz_metadata *zmd, struct dm_zone *zone)
{
	unsigned int chunk = zone->chunk;
	unsigned int dzone_id;

	if (chunk == DMZ_MAP_UNMAPPED) {
		/* Already unmapped */
		return;
	}

	if (test_and_clear_bit(DMZ_BUF, &zone->flags)) {
		/*
		 * Unmapping the chunk buffer zone: clear only
		 * the chunk buffer mapping
		 */
		dzone_id = zone->bzone->id;
		zone->bzone->bzone = NULL;
		zone->bzone = NULL;

	} else {
		/*
		 * Unmapping the chunk data zone: the zone must
		 * not be buffered.
		 */
		if (WARN_ON(zone->bzone)) {
			zone->bzone->bzone = NULL;
			zone->bzone = NULL;
		}
		dzone_id = DMZ_MAP_UNMAPPED;
	}

	dmz_set_chunk_mapping(zmd, chunk, dzone_id, DMZ_MAP_UNMAPPED);

	zone->chunk = DMZ_MAP_UNMAPPED;
	list_del_init(&zone->link);
}

dmz_set_chunk_mapping 함수에 매핑을 해제하고 싶은 zone에 UNMAPPED를 담아 매핑을 해제하는 함수입니다. test_and_clear_bit 함수가 참인 경우, 즉 dzone의 매핑은 유지하고 bzone의 매핑만 해제하고 싶은 경우 bzone의 값을 모두 NULL로 처리한 후 bzone_id를 DMZ_MAP_UNMAPPED 값으로 담아 호출합니다.

test_and_clear_bit 함수가 참이 아닌 경우, 즉 dzone과 bzone 모두 매핑을 해제하고 싶은 경우에는 dzone_id와 bzone_id 모두 DMZ_MAP_UNMAPPED 값으로 담아 호출합니다.

댓글