아래 예제에서 사용된 API들은 glibc에서 기본으로 제공되는 함수이며 swapcontext()의 내부 로직은 user level에서 직접 프로세스 실행에 대한 레지스터값을 저장하고 복원하는 방법을 상요하여 문맥을 전환 하고 있다.
그러나 문맥전환이 일어하는 과정에서 추가적으로 signal 정보를 저장, 복원 하는 작업도 발생하게 되는데 이로 인하여 system call overhead가 걸려서 문맥전환이 생각보다 느리게 수행되는 단점도 존재한다.
막약 signal을 다룰필요가 없는 프로그램을 작성한다면 glibc의 swapcontext() 함수 내부에서 signal 정보를 저장, 복원 하는 부분만 삭제하게 될 경우 커널의 스케쥴링으로는 도저히 볼수없는 정말 환상적인 문맥전환 성능을 만날수있다.
#include <stdio.h>
#include <stdint.h>
#include <signal.h>
#include <ucontext.h>
static ucontext_t main_ctx;
static ucontext_t test_ctxA;
static ucontext_t test_ctxB;
static uint64_t cnt = 0;
static uint64_t cnt_old = 0;
static void fiber_funcA(void)
{
for (;;) {
++cnt;
// Conext B 로 문맥전환
swapcontext(&test_ctxA, &test_ctxB);
}
}
static void fiber_funcB(void)
{
for (;;) {
++cnt;
// Conext A 로 문맥전환
swapcontext(&test_ctxB, &test_ctxA);
}
}
static void sighandler(int signal)
{
printf("%lu \n", cnt - cnt_old);
cnt_old = cnt;
alarm(1);
}
int main(void)
{
char stackA[2048];
char stackB[2048];
// 문맥전환 횟수 모니터링용 헨들러
signal(SIGALRM, sighandler);
alarm(1);
// Conext A fiber 생성
if (getcontext(&test_ctxA) < 0)
{
fprintf(stderr, "ERROR!!\n");
return 1;
}
test_ctxA.uc_stack.ss_sp = stackA;
test_ctxA.uc_stack.ss_size = sizeof(stackA);
test_ctxA.uc_link = &main_ctx;
makecontext(&test_ctxA, (void (*)(void))fiber_funcA, 0);
// Conext B fiber 생성
if (getcontext(&test_ctxB) < 0)
{
fprintf(stderr, "ERROR!!\n");
return 1;
}
test_ctxB.uc_stack.ss_sp = stackB;
test_ctxB.uc_stack.ss_size = sizeof(stackB);
test_ctxB.uc_link = &main_ctx;
makecontext(&test_ctxB, (void (*)(void))fiber_funcB, 0);
// Conext Main 에서 Conext A로 문맥 전환
swapcontext(&main_ctx, &test_ctxA);
return 0;
}
댓글 없음:
댓글 쓰기