QEmu Live Migration 소스코드 관련 질문
안녕하세요. QEmu의 Migration 관련 소스 코드에 기능을 좀 추가하려고 합니다.
이 과정에, 도무지 이해가 가지 않는 부분이 있어 질문을 드리고자 합니다.
추가하고자 하는 기능은, Migration 시에 복제하는 데이터의 일부분을 복제 대상에서 제외 시키려 합니다.
현재 게스트 내부에서 제외하고자 하는 부분의 물리 어드레스를 취득, 하이퍼콜로 호스트머신의 KVM 모듈에 전달->해당 물리 어드레스(게스트 물리 어드레스)를 KVM-QEmu가 Migration시 더티페이지만 전송하기 위하여 사용하는 더티 비트맵의 구조(KVMDirtybitmap)로 변환(이 구조는, 64비트 비트맵의 배열로 이루어져 있으며, 비트맵의 1비트는 1페이지(물리어드레스를 12비트치 쉬프트(2의12승=4096=4k비트) 시킨 어드레스 1개치의 더티 정보) 에 해당합니다.)->Copy_to_User로 QEMU에 전달, 비트맵의 Population bit를 비교(게스트의 취득 정보와 일치 확인)->체크포인트 이미지를 작성하는 ram_save_live함수를 수정하여, 취득한 비트맵에 해당하는 정보를 체크포인트 이미지 작성 대상에서 제외함
대충 이러한 과정으로 진행 하고 있습니다.
하지만, 마지막 단계에서 문제를 겪고 있습니다.
다음 링크가 ram_save_live의 LXR 페이지 입니다.
http://getfeus.iptime.org/lxr/http/source/arch_init.c?v=kemari-v0.2.9#L269
대충 설명을 하면,
소스에 진입하면 먼저 KVM의 더티 비트맵과 동기화 합니다.(281행)
단, 처음 불려졌다면, 더티 비트맵의 기준이 되는 기준점을 셋팅하지 않은 관계로 NULL이 리턴됩니다.(더티 라는 의미는, 어느 기준점을 기준으로 IO가 발생한 메모리 오브젝트를 의미합니다. 함수에 첫 진입한 상태라면 더티의 기준점이 없습니다.)
다음으로 첫 복제시 전 시스템의 메모리를 복제 하기 때문에(첫 복제가 끝나면, 더티메모리만을 복제합니다.) 더티비트맵의 데이터 구조를 살펴보고, 더티가 아닌 비트가 존재하면 그 비트를 1로 만들어 버립니다.
단, 이 경우 하기의 코드를 보시면 알 수 있으시듯, ram_list.phys_dirty[MIGRATION_DIRTY_IDX]
http://getfeus.iptime.org/lxr/http/source/cpu-all.h?v=kemari-v0.2.9#L933
아무튼, /* Make sure all dirty bits are set */의 부분에선, 마스터IDX에 해당하는 비트맵 구조의 비트가 1이 아닌 부분을 1로 만들어, 모든 데이터를 전송 할 수 있는 기반을 만듭니다.
이해가 되지 않는 부분은, ram_save_live에 진입할 때에 이미, 모든 비트맵이 1이고, /* Make sure all dirty bits are set */의 모든 코드를 삭제해도 아무 문제 없이 Migration 이 성공한다는 점 입니다.(이 부분이 좀처럼 이해되질 않습니다. 어디에서 bitmap을 초기화 하는질 모르겠습니다.)
305행에서 불러주는 함수는 KVM의 더티 비트 트랙킹 기능의 기준점을 활성화 합니다. 저 부분을 기준으로, IO가 발생한 데이터를, ram_save_live가 반복적으로 불러 질 경우, 2회 이후의 호출에선, 281행의 부분에서 qemu가 보유한 비트맵 데이터와 동기화 합니다. 이 코드를 쫓아봐야, 구체적으로 어떤 일을 하는지는 보실 수 없는데, 함수포인터로 초기화 함수에 등록된 함수를 불러버려서 추적이 힘듭니다.
http://getfeus.iptime.org/lxr/http/source/kvm-all.c?v=kemari-v0.2.9#L348
잘 쫓아가 보시면 이 함수가 불려집니다. 요녀석 역시, ram_list.phys_dirty[MIGRATION_DIRTY_IDX]를 직접 수정하지 않고, MASTER를 수정한 후, 동기화 시킵니다.
이러한 사전 작업이 끝나고, 구체적인 전송이 322행에서 이루어 집니다.
첫 전송시 전송하는 데이터중, 일부의 데이터를 전송 대상에서 제거하고 싶은 것이 제 목적이기 때문에, 저는 ram_list.phys_dirty[MIGRATION_DIRTY_IDX]의 비트맵을 직접 수정해야 하는 상황 입니다.
다음의 코드를 적어, 비트맵을 직접 수정해 보았습니다.
for (j=0; j
{
target_phys_addr_t end_addr=(base_gfn[j]+npages[j])<
target_phys_addr_t start_addr=(base_gfn[j])<
size_t len = npages[j] / HOST_LONG_BITS;//len is number of bitmaps
for (i=0; i
{
int popcount_tmp = 0;
size_t page_number = i;
target_phys_addr_t addr1 = page_number * HOST_LONG_BITS;
target_phys_addr_t addr2 = start_addr + addr1;
ram_addr_t ram_addr = cpu_get_physical_page_desc(addr2);
cpu_physical_memory_reset_dirty(ram_addr, ram_addr+TARGET_PAGE_SIZE,MIGRATION_DIRTY_FLAG);
popcount_tmp = popcount64(pagecache_bitmap[j][i]);
save_cnt += (64-popcount_tmp);
cpu_physical_memory_set_dirty_range_migration(ram_addr, leul_to_cpu(pagecache_bitmap[j][i]));//cpu_physical_memory_set_dirty_rangeg함수의 flag부분을 마스터에서 MIGRATION으로 변경한 추가된 함수
}
}
printf("save_cnt is %llu!!!\n",save_cnt);
save_cnt = 0;
하지만, http://getfeus.iptime.org/lxr/http/source/arch_init.c?v=kemari-v0.2.9#L183
GDB로 확인 결과 이곳에서 세그멘테이션폴트를 일으켜 버림이 확인 되었습니다.
제가 적은 코드의 문제점을 의심하고, 다음 코드를 적어 보았습니다.
QLIST_FOREACH(block, &ram_list.blocks, next)
{
for (addr = block->offset; addr < block->offset + block->length;
addr += TARGET_PAGE_SIZE)
{
if (cpu_physical_memory_get_dirty(addr,MIGRATION_DIRTY_FLAG))
{
//(addr >> TARGET_PAGE_BITS) / HOST_LONG_BITS
cpu_physical_memory_reset_dirty(addr, addr+TARGET_PAGE_SIZE,MASTER_DIRTY_FLAG);
}
}
}
단순히 모든 메모리 블록의 비트맵을 reset 하고있는 코드 이기에, 이 함수가 제대로 호출 될 경우, 전송대상이 없어서 Migration 이 발생하지 않고, Migration 이 종료되길 기대 하였습니다.
결과는, 역시나 같은 장소에서의 세그멘테이션폴트 였습니다.
http://getfeus.iptime.org/lxr/http/source/arch_init.c?v=kemari-v0.2.9#L134
등에서는 복제가 끝난 비트의 비트맵을 초기화 하고 있으나, 문제가 일어나지 않는데 왜 제 코드에서만은 문제가 일어나는지 알고 싶습니다.
이런 저런 시도를 해 보았으나, 원인을 규명하는데에 실패 하였습니다.
ram_list.phys_dirty[MASTER_DIRTY_IDX]를 아무리 수정해봐야 아무 소용이 없었습니다.
(첫 전송은 어차피 ram_list.phys_dirty[MIGRATION_DIRTY_IDX]를 기준으로 이루어 지기 때문에)
QEMU에 해박하신 분들의 조언좀 부탁 드리겠습니다.
세그멘테이션폴트의 원인은 알아냈습니다.
while ((last_block!=NULL)&&(current_addr != last_block->offset + last_offset));
으로 문제가 발생하는 행을 바꾼 결과 문제없이 진행 된 것으로 보아, block 이 1개도 dirty가 아니면, 함수가 수행조차 되지않는것 같습니다.
다음 코드로 변경하여 부분적으로 리셋을 하는것에 성공 하였습니다.
for (j=0; j {
target_phys_addr_t end_addr=(base_gfn[j]+npages[j])< target_phys_addr_t start_addr=(base_gfn[j])< size_t len = npages[j] / HOST_LONG_BITS;//len is number of bitmaps
for (i=0; i {
int popcount_tmp = 0;
size_t page_number = i;
target_phys_addr_t addr1 = page_number * HOST_LONG_BITS;
target_phys_addr_t addr2 = start_addr + addr1;
ram_addr_t ram_addr = cpu_get_physical_page_desc(addr2);
cpu_physical_memory_reset_dirty(ram_addr, ram_addr+TARGET_PAGE_SIZE,MIGRATION_DIRTY_FLAG);
popcount_tmp = popcount64(pagecache_bitmap[j][i]);
save_cnt += (64-popcount_tmp);
}
}
printf("save_cnt is %llu!!!\n",save_cnt);
save_cnt = 0;
하지만, 이상하게도,
QLIST_FOREACH(block, &ram_list.blocks, next)
{
for (addr = block->offset; addr < block->offset + block->length;
addr += TARGET_PAGE_SIZE)
{
if (!cpu_physical_memory_get_dirty(addr,MIGRATION_DIRTY_FLAG))
{
//cpu_physical_memory_set_dirty(addr);
printf("addr %lu is not Dirty!!!\n",addr);
}
}
}
이 코드로는 적용한 비트맵에 의해 더티가 아닌 메모리 블록(페이지64개를 뭉친것)이 발생이 확인 가능했지만, cpu_physical_memory_get_dirty_range는 모든 블록을 더티로 판단하고, 복제해 버리는 문제가 지속되고 있습니다.
http://getfeus.iptime.org/lxr/http/source/exec.c?v=kemari-v0.2.9#L2028
아마도 이 함수의 문제인것 같은데... 어디를 고쳐야 할지 감이 잡히지 않네요.
댓글 달기