티스토리 뷰

dm-zoned를 구성하는 코드

dm-zoned는 메타 데이터를 관리하는 dm-zoned-metadata.c, target device를 다루는 dm-zoned-target.cregular block의 reclaim을 관리하는 dm-zoned-reclaim.c 총 3개의 C 파일과 dm-zoned.h 헤더 파일과 함께 구성되어 있습니다.


I/O write Routine

dm-zoned의 I/O write 루틴에서 다루는 함수가 정말 많기 때문에 관련된 모든 함수를 정확히 파악하기가 어렵습니다. 또한 참고할만한 레퍼런스가 없기 때문에 직접 코드를 하나씩 까보며 이해하는 과정이 필수입니다. 저 또한 해당 루틴을 파악하기 위해 커널에서 fio를 실행하여 trace 해보고, 코드를 분석하는 과정이 한 달 이상 소요되었습니다. 그러므로 함수들 중 실질적인 I/O write 과정에 중요한 함수들을 먼저 알아보고 제대로 파악한다면 전체 I/O routine을 파악하는데 많은 도움이 될 것 입니다.

dm-zoned I/O Routine


dmz_map

/*
 * Process a new BIO.
 */
static int dmz_map(struct dm_target *ti, struct bio *bio)
{
        struct dmz_target *dmz = ti->private;
        struct dmz_metadata *zmd = dmz->metadata;
        struct dmz_bioctx *bioctx = dm_per_bio_data(bio, sizeof(struct dmz_bioctx));
        sector_t sector = bio->bi_iter.bi_sector;
        unsigned int nr_sectors = bio_sectors(bio);
        sector_t chunk_sector;
        int ret;
        
        ...

        /* Initialize the BIO context */
        bioctx->dev = NULL;
        bioctx->zone = NULL;
        bioctx->bio = bio;
        refcount_set(&bioctx->ref, 1);
        
        /* Set the BIO pending in the flush list */
        if (!nr_sectors && bio_op(bio) == REQ_OP_WRITE) {
            spin_lock(&dmz->flush_lock);
            bio_list_add(&dmz->flush_list, bio);
            spin_unlock(&dmz->flush_lock);
            mod_delayed_work(dmz->flush_wq, &dmz->flush_work, 0);
            return DM_MAPIO_SUBMITTED;
        }

        /* Split zone BIOs to fit entirely into a zone */
        chunk_sector = sector & (dmz_zone_nr_sectors(zmd) - 1);
        if (chunk_sector + nr_sectors > dmz_zone_nr_sectors(zmd))
            dm_accept_partial_bio(bio, dmz_zone_nr_sectors(zmd) - chunk_sector);
        
        /* Now ready to handle this BIO */
        ret = dmz_queue_chunk_work(dmz, bio);
        if (ret) {
                DMDEBUG("(%s): BIO op %d, can't process chunk %llu, err %i",
                        dmz_metadata_label(zmd),
                        bio_op(bio), (u64)dmz_bio_chunk(zmd, bio),
                        ret);
                return DM_MAPIO_REQUEUE;
        }

        return DM_MAPIO_SUBMITTED;
}

device mapper로 입력된 bio 요청은 가장 먼저 dmz_map 함수로 전달됩니다. 먼저 인자로 받은 target과 bio를 이용해 metadata와 bioctx(bio context)를 초기화합니다. 이후 target device 정보와 bio의 sector 정보를 이용해 chunk work 구조체에 sector 정보를 할당하여 target device가 유효한지 확인하고, bio 요청을 flush list와 bio list(work queue)에 추가합니다.

 


dmz_queue_chunk_work

static int dmz_queue_chunk_work(struct dmz_target *dmz, struct bio *bio)
{       
        unsigned int chunk = dmz_bio_chunk(dmz->metadata, bio);
        struct dm_chunk_work *cw;
        int ret = 0;
        
        mutex_lock(&dmz->chunk_lock);
        
        /* Get the BIO chunk work. If one is not active yet, create one */
        cw = radix_tree_lookup(&dmz->chunk_rxtree, chunk);
        if (cw) {
                dmz_get_chunk_work(cw);
        } else {
                /* Create a new chunk work */
                cw = kmalloc(sizeof(struct dm_chunk_work), GFP_NOIO);
                
                ...
                INIT_WORK(&cw->work, dmz_chunk_work);
                refcount_set(&cw->refcount, 1);
                cw->target = dmz;
                cw->chunk = chunk;
                bio_list_init(&cw->bio_list);
                
                ret = radix_tree_insert(&dmz->chunk_rxtree, chunk, cw);
                ...
        }
        
        bio_list_add(&cw->bio_list, bio);
        
        if (queue_work(dmz->chunk_wq, &cw->work))
                dmz_get_chunk_work(cw);
out:    
        mutex_unlock(&dmz->chunk_lock);
        return ret;
}

Bio를 핸들링 하기 전, bio chunk work를 받아오는 함수이며 dmz_map 함수에 의해 호출됩니다. dmz_bio_chunk(dmz->metadata, bio) 를 이용해 전달받은 bio에서 접근하는 chunk id를 계산하고 radix_tree_lookup 함수를 이용해 chunk에 할당된 work의 주소를 받아옵니다. 할당된 work가 없는 경우 INIT_WORK 매크로를 이용해 dmz_chunk_work를 호출하고 bio 리스트에 chunk work의 bio를 추가합니다.


dmz_chunk_work

static void dmz_chunk_work(struct work_struct *work)
{
	struct dm_chunk_work *cw = container_of(work, struct dm_chunk_work, work);
	struct dmz_target *dmz = cw->target;
	struct bio *bio;

	mutex_lock(&dmz->chunk_lock);

	/* Process the chunk BIOs */
	while ((bio = bio_list_pop(&cw->bio_list))) {
		mutex_unlock(&dmz->chunk_lock);
		dmz_handle_bio(dmz, cw, bio);
		mutex_lock(&dmz->chunk_lock);
		dmz_put_chunk_work(cw);
	}

	/* Queueing the work incremented the work refcount */
	dmz_put_chunk_work(cw);

	mutex_unlock(&dmz->chunk_lock);
}

이렇게 추가된 bio는 dmz_chunk_work에서 처리 됩니다. bio list에서 bio를 가져와 dmz_handle_bio에 전달합니다.


dmz_handle_bio

static void dmz_handle_bio(struct dmz_target *dmz, struct dm_chunk_work *cw,
                           struct bio *bio)
{
        struct dmz_bioctx *bioctx =
                dm_per_bio_data(bio, sizeof(struct dmz_bioctx));
        struct dmz_metadata *zmd = dmz->metadata;
        struct dm_zone *zone;
        int ret;

        dmz_lock_metadata(zmd);

        zone = dmz_get_chunk_mapping(zmd, dmz_bio_chunk(zmd, bio),
                                     bio_op(bio), bio);
		...
        
        /* Process the BIO */
        if (zone) {
                dmz_activate_zone(zone);
                bioctx->zone = zone;
                dmz_reclaim_bio_acc(zone->dev->reclaim);
        }
        
	switch (bio_op(bio)) {
        case REQ_OP_READ:
                ret = dmz_handle_read(dmz, zone, bio);
                break;
        case REQ_OP_WRITE:
                ret = dmz_handle_write(dmz, zone, bio);
                break;
        case REQ_OP_DISCARD:
        case REQ_OP_WRITE_ZEROES:
                ret = dmz_handle_discard(dmz, zone, bio);
                break;
        default:
                DMERR("(%s): Unsupported BIO operation 0x%x",
                      dmz_metadata_label(dmz->metadata), bio_op(bio));
                ret = -EIO;
        }

        /*
         * Release the chunk mapping. This will check that the mapping
         * is still valid, that is, that the zone used still has valid blocks.
         */
        if (zone)
                dmz_put_chunk_mapping(zmd, zone);
out:
        dmz_bio_endio(bio, errno_to_blk_status(ret));

        dmz_unlock_metadata(zmd);
}

모든 I/O는 반드시 dmz_handle_bio 함수를 거쳐 분기됩니다. 먼저 dmz_get_chunk_mapping 함수를 이용해 write하기 위한 zone을 할당받습니다. dmz_get_chunk_mapping 함수는 사용할 zone을 할당하는 함수입니다. dm-zoned-metadata.c에 포함된 함수이기 때문에 해당 내용을 설명할 때 다루도록 하겠습니다. 이후 bio의 타입에 맞는 read, write, discard 함수를 호출합니다.


dmz_handle_write

static int dmz_handle_write(struct dmz_target *dmz, struct dm_zone *zone,
                            struct bio *bio)
{
        struct dmz_metadata *zmd = dmz->metadata;
        sector_t chunk_block = dmz_chunk_block(zmd, dmz_bio_block(bio));
        unsigned int nr_blocks = dmz_bio_blocks(bio);

        ...

        if (dmz_is_rnd(zone) || dmz_is_cache(zone) ||
            chunk_block == zone->wp_block) {
                /*
                 * zone is a random zone or it is a sequential zone
                 * and the BIO is aligned to the zone write pointer:
                 * direct write the zone.
                 */
                return dmz_handle_direct_write(dmz, zone, bio,
                                               chunk_block, nr_blocks);
        }

        /*
         * This is an unaligned write in a sequential zone:
         * use buffered write.
         */
        return dmz_handle_buffered_write(dmz, zone, bio, chunk_block, nr_blocks);
}

만약 bio operation 타입이 REQ_OP_WRITE, 즉 쓰기 요청이라면 dmz_handle_write가 호출됩니다. 이 함수는 전달받은 zone의 타입에 따라 direct / buffered write의 여부를 결정합니다. Zone이 random zone 이거나, sequential zone 이고 bio의 chunk block이 zone write pointer block과 일치한다면 dmz_handle_direct_write 함수를 호출하고 그렇지 않다면 dmz_handle_buffered_write 함수를 호출합니다.


dmz_handle_direct_write

static int dmz_handle_direct_write(struct dmz_target *dmz,
                                   struct dm_zone *zone, struct bio *bio,
                                   sector_t chunk_block,
                                   unsigned int nr_blocks)
{       
        struct dmz_metadata *zmd = dmz->metadata;
        struct dm_zone *bzone = zone->bzone;
        int ret;
        
        ...
        
        /* Submit write */
        ret = dmz_submit_bio(dmz, zone, bio, chunk_block, nr_blocks);
        if (ret)
                return ret;
        
        /*
         * Validate the blocks in the data zone and invalidate
         * in the buffer zone, if there is one.
         */ 
        ret = dmz_validate_blocks(zmd, zone, chunk_block, nr_blocks);
        ...

        return ret;
}

 

write 대상 zone이 random zone 이거나, sequential zone 이고 bio의 chunk block이 zone write pointer block과 일치하는 경우 write를 처리하는 함수입니다. dmz_submit_bio에 전달되는 zone 인자는 dzone(data zone)이 전달됩니다.


dmz_handle_buffered_write

static int dmz_handle_buffered_write(struct dmz_target *dmz,
                                     struct dm_zone *zone, struct bio *bio,
                                     sector_t chunk_block,
                                     unsigned int nr_blocks)
{
        struct dmz_metadata *zmd = dmz->metadata;
        struct dm_zone *bzone;
        int ret;

        ...
        
        /* Submit write */
        ret = dmz_submit_bio(dmz, bzone, bio, chunk_block, nr_blocks);
        if (ret)
                return ret;

        /*
         * Validate the blocks in the buffer zone
         * and invalidate in the data zone.
         */
        ret = dmz_validate_blocks(zmd, bzone, chunk_block, nr_blocks);
        ...

        return ret;
}

write 대상 zone이 sequential zone 이고 bio의 chunk block이 zone write pointer block과 일치하지 않는 경우 write를 처리하는 함수입니다. dmz_submit_bio에 전달되는 zone은 bzone(buffer zone)이 전달됩니다.


dmz_submit_bio

static int dmz_submit_bio(struct dmz_target *dmz, struct dm_zone *zone,
                          struct bio *bio, sector_t chunk_block,
                          unsigned int nr_blocks)
{
        struct dmz_bioctx *bioctx =
                dm_per_bio_data(bio, sizeof(struct dmz_bioctx));
        struct dmz_dev *dev = zone->dev;
        struct bio *clone;

        if (dev->flags & DMZ_BDEV_DYING)
                return -EIO;

        clone = bio_clone_fast(bio, GFP_NOIO, &dmz->bio_set);
        if (!clone)
                return -ENOMEM;

        bio_set_dev(clone, dev->bdev);
        bioctx->dev = dev;
        clone->bi_iter.bi_sector =
                dmz_start_sect(dmz->metadata, zone) + dmz_blk2sect(chunk_block);
        clone->bi_iter.bi_size = dmz_blk2sect(nr_blocks) << SECTOR_SHIFT;
        clone->bi_end_io = dmz_clone_endio;
        clone->bi_private = bioctx;

        bio_advance(bio, clone->bi_iter.bi_size);

        refcount_inc(&bioctx->ref);
        submit_bio_noacct(clone);

        if (bio_op(bio) == REQ_OP_WRITE && dmz_is_seq(zone))
                zone->wp_block += nr_blocks;

        return 0;
}

인자로 받은 zone에 대해 실질적인 I/O를 실행하는 함수입니다. bio의 clone을 만들어 clone을 이용해 submit_bio_noacct 함수를 실행합니다. 왜 clone을 만들어 실행하는지는 아직까지 정확히 파악하지 못했습니다. seq zone을 처리한 경우에는 해당 zone의 write pointer를 업데이트 합니다. 이후 과정은 좀 더 low level한 I/O 과정이기 때문에 dm-zoned를 이해하기 위한 목적에서는 더 이상 필요하지 않습니다.

댓글