scheduler 예제코드 질문..
안녕하세요.
[리눅스 커널 내부구조]라는 책을 통하여 리눅스 운영체제, 커널 기초에 대하여 공부하고 있는 대학생입니다.
지금은 이 책의 Chapter 10, 2번째인 scheduler 코드를 보면서 이해하고 있는데요.
사용자가 thread_create()함수를 통해 임의의 쓰레드를 만들고 thread_switch(), schedule()함수등을 통해 context switching을 어플리케이션 레벨에서 구현하는 코드입니다.
코드파일은 글에도 올렸지만 압축파일로 전체내용을 첨부하였습니다. (출처: 교학사 홈페이지)
이 코드내 thread_switch()함수는 inline asembly를 이용해 현재 레지스터 값을 push 및 pop, scheduling하는 함수입니다.
그리고 이 thread_switch()함수를 parent_task()라는 함수에서 SIGUSR1에 대한 handler로 지정하였고(sigaction함수 이용), fork()를 통해 자식프로세스에서
1초마다 부모에게 SIGUSR1 신호를 주게끔하여 context switching을 하도록 구현하였습니다.
핸들러 처리가 끝나고 사용자정의 thread구조체 내에 있는 callback function에 돌아가 그 callback function 처리를 하여 printf()를 하게 되는데,
어떻게 pop이 끝나고 thread_create()의 전달인자인 callback함수가 수행되는지 궁금하여 gcc debugger 프로그램인 gdb를 통해 추적하다가
디버거로 시그널 핸들러처리가 1step씩 추적이 안되어 게시판에 글을 남기게 되었습니다...
어떻게 callback 함수로 넘어가는지 궁금합니다..(현재 running task의 retaddr변수값)
또한 gdb에서 어떻게 설정해야 디버깅 도중 1초마다 signal이 발생하여 handler가 발생해도 handler code를 1step씩 debugging 할수 있는지 알려주시면 감사하겠습니다!
구동은 VMWare로 centos 6.6(32bit)에서 하였습니다.
예제 내 task_struct에 해당하는 사용자정의 구조체
typedef struct task_info_tag { unsigned long stack[THREAD_STACKSIZE]; unsigned long sp; int task_id; TaskStatus status; struct task_info_tag *next; struct task_info_tag *prev; }*TaskInfo;
parent_task()
void parent_task(void *context) { // signal 처리를 위한 정보를 위한 구조체 struct sigaction act; sigset_t masksets; pid_t pid; // signal set 초기화 sigemptyset( &masksets ); // signal handler로 thread_switch() 등록 act.sa_handler = thread_switch; act.sa_mask = masksets; act.sa_flags = SA_NODEFER; // signal 수신 때 취할 action 설정 sigaction( SIGUSR1, &act, NULL ); if( ( pid = fork() ) == 0 ) { while(1) { sleep(1); kill( getppid(), SIGUSR1 ); } } else{ while (1) { // child_task가 1개 남았을 때, 즉, parent_task만 남았을 때 if ( gh_sch.child_task == 1 ){ kill( pid, SIGINT ); break; } }; } }
thread_create()
TaskInfo thread_create(TaskFunc callback, void *context) { TaskInfo taskinfo; // task를 위한 공간 할당 taskinfo = malloc(sizeof(*taskinfo)); memset(taskinfo, 0x00, sizeof(*taskinfo)); { struct frame *f = (struct frame *)&taskinfo->stack[THREAD_STACKSIZE - sizeof(struct frame)/4]; // taskinfo로 할당된 공간 중 stack부분 뒤쪽에 frame을 위한 공간으로 할당 // 이에 task가 수행되면서 stack공간을 활용 int i; for(i = 0; i < THREAD_STACKSIZE; ++i) { // stack overflow check taskinfo->stack[i] = i; } memset(f, 0, sizeof(struct frame)); f->retaddr = (unsigned long)callback; f->retaddr2 = (unsigned long)thread_kill; f->data = (unsigned long)context; taskinfo->sp = (unsigned long)&f->flags; f->ebp = (unsigned long)&f->eax; } // task 생성에 따라 gh_sch에 child task가 늘었음을 표시 gh_sch.child_task ++; // gh_sch.child_task 값으로 task_id 할당 taskinfo->task_id = gh_sch.child_task; // task 생성시 TASK_READY로 상태를 설정함 taskinfo->status = TASK_READY; // taskinfo구조체들의 linkedlist에 새 thread의 taskinfo 구조체를 삽입 task_insert(taskinfo); return taskinfo; }
thread_switch()
static unsigned long spsave, sptmp; void thread_switch() { asm( "push %%eax\n\t" "push %%ebx\n\t" "push %%ecx\n\t" "push %%edx\n\t" "push %%esi\n\t" "push %%edi\n\t" "push %%ebp\n\t" "push %%ebp\n\t" "mov %%esp, %0" : "=r" (spsave) ); gh_sch.running_task->sp = spsave; scheduler(); sptmp = gh_sch.running_task->sp; asm( "mov %0, %%esp\n\t" "pop %%ebp\n\t" "pop %%ebp\n\t" "pop %%edi\n\t" "pop %%esi\n\t" "pop %%edx\n\t" "pop %%ecx\n\t" "pop %%ebx\n\t" "pop %%eax\n\t" ::"r" (sptmp) ); //끝나고 현재 running_task에 대한 callback function을 수행하게됩니다. (retaddr1) }
callback function
void test_func_one(void* context) { int i = 0; while (1) { i++; printf("TASK 1 : %5d\n", i); sleep(1); if ( i == 15 ){ break; } } }
main함수
// my_scheduler의 main 함수 int main(void ) { thread_init(); thread_create(test_func_one, NULL); //첫 인자: callback function thread_create(test_func_two, NULL); thread_create(test_func_three, NULL); thread_wait();//parent_task()호출 return 0; }
cf) Chap10내에 2_Sched 폴더에 가면 헤더파일, make파일, 메인파일, c파일 모두 있습니다.
첨부 | 파일 크기 |
---|---|
![]() | 39.87 KB |
[schoi0@sel-schoi0-d2
.
다운받아 실행해주셨군요! 감사합니다.
이 코드에 따르면 SIGUSR1 시그널에 따른 handler인 thread_switch가 끝난 상태가 inline asembly명령인 pop명령이 마지막일 것입니다.
이 상태에서 어떻게 현재 running_task의 retaddr1으로 넘어가 "TASK 1: ", "TASK 2 : "등이 출력되는지 알고 싶네요.. 궁금증은 여전히 ㅠ..
gdb내 handle 명령과 nostop noprint를 통하여 핸들러가 확인되도 디버깅이 끊기지 않고 결과가 출력되는건 이해했으나
어떻게 해야 gdb내에서 handler함수내 코드(이 경우, thread_switch가 되겠군요)를 1 line씩 실행가능할까요? 그래야 핸들러내에서 레지스터나 변수값의 변화를 순차적으로 추적할 수 있을텐데요...
문서를 보시던가 검색을 조금이라도 하시는 게
문서를 보시던가 검색을 조금이라도 하시는 게 좋겠습니다.
전의 것도 그렇고, 구글에서 검색하면 결과물의 상위 세번째 안에 전부 있는 내용들입니다.
댓글 달기