Naver Perl Community & Study Cafe


2010.06.22 10:20

Linux Kernel & Device Driver [1]



운이좋게도 Linux Kernel 교육을 신청했는데 제가 담첨이 되어

2박3일간 리눅스 커널 & 디바이스 드라이버 교육을 받을 수 있게 되었습니다.


교육장소는 아이오 교육센터 ( 서울시 금천구 가산동 )에 위치하여 있으며,

김정인 강사 (아이오 교육센터 원장, 아임구루 대표이사 love1770@empal.com ) 님께

교육을 받을 수 있었습니다.



아래는 첫째날의 교육내용입니다.


6/22일(화)

실습환경구축
,

커널 구조 분석

- 리눅스 커널 개요

- 커널 자료구조 접근

- 커널모드와 유저모드

- 부팅과정 및 초기화 코드 이해

 

프로세스 관리

- 멀티테스킹 기본 원리 이해

- 프로세스 컨텍스트 스위칭

- Kernel 2.6  O(1) 스케줄링 분석

- 프로세스 생성, 실행, 소멸 과정 이해



PPT 의 경우에는 회사내부의 자료같아 올리지 않고 간간히 실습 화면을 올려서 포스팅 하겠습니다.

실시간으로 정리한 내용을 올리므로 내용에 짜임새가 부족할 것 같습니다^^; ( 양해 부탁드려요~ )




출처 : http://www.rwc.uc.edu/thomas/Intro_Unix_Text/OS_Organization.html


첫번째 시간에는 커널 구조 분석에 대해 설명을 들었습니다.


UNIX Operating System 에 대하여 이론적으로 먼저 설명을 들었는데,

Operating System의 단편화된 이미지를 양파껍데기에 비유한 표현이 재미있었습니다 :)


UNIX 의 History 역사도 간단히 설명해 주셨는데

BSD 나 AT&T System V 사에 관련된 역사 이야기도 해주셨습니다.

(이런 내용은 어디에서 읽을 수 있을까요? ㅋ)



아래는 강의 중 유용한 정보들을 정리해 보았습니다.


- 리눅스의 오픈소스


기업에서 오픈소스를 쓸 때, 오픈소스를 사용했다고 기재해야하며

오픈소스를 쓴 부분에 대해서 기업도 소스를 공개해야 한다는 부분을

설명해 주셨습니다.

기업에서 오픈소스라고 함부러 썼다가 고소당하기도 합니다.


그러므로 기업에서는 오픈소스를 쓰면서 회사의 내부의 기술을 지켜내야 함으로

오픈소스 팀이 따로 있다네요... ^^;;




- 프로그램과 프로세서의 차이


프로세서 :  메모리에 할당해서 운영되고 있는 프로그램을 의미.

프로그램 :  컴파일되어 실행할 수 있도록 만들어진 파일.




fork() 함수와 스레드의 차이점을 설명해 주셨습니다.

정리하려니 블로그에 정리 된 내용들이 많네요^^;

fork는 자식 프로세서를 하나 더 만들어 주는것이고

스레드와 프로세스의 차이는 전역변수의 공유 유무가 가장 중요한것을 짚고 넘어가야 합니다. :)





출처 : http://www.joong.org/?cat=7




Process State Transition 에 대해서 자세하게 설명을 해 주셨습니다.

중요한 부분이라 그런지 생성되는 시뮬레이션을 통하여 예를들어 설명을 들었습니다.



운영체제에 대해서 재밌는 이야기를 하셨는데

윈도우가 가려지고 다시 보여질때 재빨리 다시 그려주는 것과,

그리고 지속적으로 반복시키면서 멀티 테스킹처럼 속이는 것 등등

"사용자의 편의성을 위해 얼마나 사기를 잘 때리느냐"가 중요하다고 이야기 하셨습니다. ㅋㅋ




윈도우 운영체제를 열심히 공부했던 기억이 있어 

리눅스 커널이 생소하고 궁금했는데, 막상 수업을 들었을때는

구조가 비슷해서 그런지 쉽게 이해가 잘 갔습니다. (내공 쌓은 보람이 있는 ㅡ_ㅠ)



강의중에 실습을 하나 하게 되었습니다.




- 초강력 프로세서 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include <unistd.h>

void my_sig(int signo)
{
// printf("CTRL+C\n");
 printf("signal %d",signo);
}
int main()
{
    signal(SIGINT,my_sig);
    signal(SIGQUIT,my_sig);
    signal(SIGKILL,my_sig);
    while(1){
       pause();
    }
    return 0;
}

이 코드의 경우에는

kill -2 같은 경우에도 안죽고

이럴 경우에는 kill -9로 죽여야 가능합니다.



완성된 코드를 처음부터 보여주는 것 보다

프로그램을 짜는 과정을 보여주면서 재밌게 설명해 주셨습니다.



커널 컴파일에 대해 설명하기 위해,

기초부터 차근차근 gcc로 컴파일 하는 과정을 설명해 주시는데

너무나 깊고 자세하게 설명을 해주셔서

녹화를 하고 싶다는 생각이 들더군요.

gcc 에 대한 옵션들도 모르는 부분이 많다는것을 느꼈습니다.



-execlp 함수

1
2
3
4
5
6
7
8
9
10
#include <stdio.h>
#include <unistd.h>
int main(){
   if( fork() == 0 ){
     execlp( "ls", "ls" ,NULL );
   } 
   wait(0);
   printf(" > ");
   return 0;
}


system 함수는 커널에서는 사용할 수 없는 함수이기 때문에,

실제로는 execlp 라는 함수를 이용하여 코드를 작성해야 합니다.

fork 는 분신술에 해당한다면 execlp는 둔갑술에 해당됩니다.

그래서 fork() 함수를 사용하여 0일때 execlp 함수를 사용해주며

wait(0) 함수를 써서 프롬프트가 나중에 출력되도록 설정해 줘야 합니다.



밑의 링크에 execlp 타입이 잘 정리되어 있네요 :)

Link : http://forum.falinux.com/zbxe/?document_srl=408557



리눅스 커널을 분석하기에 좋은 사이트입니다.

http://lxr.linux.no/

노르웨이의 사이트인데 커널이 업데이트될 때마다 사이트도 같이 업데이트 되는데

여기서 소스코드들을 쉽게 확인이 가능하더군요.

실제 커널의 스케줄 소스들을 가지고 분석을 해 주셨는데,

지인분이 곁들여 설명하면 이부분이

리눅스 코드중 가장 지저분한 코드라고 합니다 ( goto문도 많이 보이고 ㅋㅋ)



- 좀비 프로세서의 예

http://codepad.org/sgr1No1r

 

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <sys/wait.h>
int count = 0;
void my_sig(int signo){
  int status =0;
  while( waitpid(-1 , &status , WNOHANG )  > 0){  
    if( (status & 0x7f) == 0){
       printf(" 정상종료 %d \n",(status>>8)&0xff );
    }else if( (status & 0xff00) == 0 ){
      printf("비정상종료 %d \n",(status>>8)&0xff );
    }
    printf("count = %d \n",++count); // 올바르게 죽는지 Check
  }
}

int main(void){
 int i;
 signal(SIGCHLD, my_sig);
 for(i=0;i<1000;i++)
 {
  if( fork() == 0){
    sleep(3);
    aboart();
    // exit(7)    // exit_code = 7;
  }  
 }
  while(1){
    printf(".\n");
    sleep(1);
 }  
}


자식은 죽었는데 부모가 살아 있을경우 좀비 프로세스가 발생되게 됩니다.

그래서 wait() 함수를 적절히 써야 하는데

자식의 exit 코드를 확인 할 수 없기 때문에,

wait 함수에서 reference 로 변수를 넘겨주면 자식의 exit 코드를 확인 할수 있습니다.

그런데 실제로 exit 값을 확인해보면 하위 비트가 아닌 상위비트로 출력되기 때문에

(status>>8)&0xff  이 코드를 통해 추출을 해주어 출력해 줍니다.

이 코드에 따라 정상 종료와 비정상 종료를 추출 할 수 있습니다.

while( waitpid(-1 , &status , WNOHANG )  > 0)

모든 wait 된 status를 확인하여 자식이 죽을 때 좀비를 생성시키지 않는 노하우 가 필요합니다.

이는 고성능 서버에서 꼭 필요한 개념입죠 :)


- manpage Tip


강사님이 시스템 콜에 관련된 Man 페이지를 Vi에서 계속 쉽게 접근 하셨는데

어떻게 하셨는지 물어보니 해답이 2K 였습니다.

2번이 시스템 콜이고 K가 Man페이지를 보는 방법이라

Vi 에서 쉽게 슥삭슥삭하면서 강의하면서 man 페이지를 오갔습니다.

멋지네요 :)




리눅스에 커널에 메모리 부분에서

자료구조가 굉장히 많이 들어가는것을 확인 할 수 있엇습니다.

레드 블랙 트리라던지 , 링크드 리스트 등등

커널 분석을 할때, 기초가 잘혀 있는것이 중요하다 라는것을 새삼스레 느끼게 되네요.



- list_entry 함수


#define list_entry(ptr, type, member) \
          ((type *)((char *)(ptr)-(unsigned long)(&((type *)0)->member)))


http://lxr.linux.no/linux-old+v2.4.31/include/linux/list.h#L187

커널에 list_entry 라는 매크로 함수가 있는데

이는 구조체의 값에 따라 그 값을 확인할 수 있는 함수입니다.

이 커널 소스가 왜 이렇게 만들어 졌으며,

왜 이렇게 접근해야 하는지 자세하게 설명을 해 주셨습니다.

막상 위의 소스만 봐서는 쉣뜨;;;


- do_timer


void do_timer(struct pt_regs *regs)
{
       (*(unsigned long *)&jiffies)++;
#ifndef CONFIG_SMP
        /* SMP process accounting uses the local APIC timer */

        update_process_times(user_mode(regs));
#endif
        mark_bh(TIMER_BH);
        if (TQ_ACTIVE(tq_timer))
                mark_bh(TQUEUE_BH);
}


일반적인 시간은 1990년 1월 1일부터 현재까지의 초를 의미하는데

jiffies 같은 경우에는 시스템에 돌아가는 세부적인 시간을 의미합니다.



- update_process_times

위의 코드에서 함수를 따라가면 아래와 같은 코드가 나옵니다.


void update_process_times(int user_tick)
{
        struct task_struct *p = current;
        int cpu = smp_processor_id(), system = user_tick ^ 1;
        update_one_process(p, user_tick, system, cpu);
        if (p->pid) {
                if (--p->counter <= 0) {
                        p->counter = 0;
                        /*
                         * SCHED_FIFO is priority preemption, so this is
                         * not the place to decide whether to reschedule a
                         * SCHED_FIFO task or not - Bhavesh Davda
                         */
                        if (p->policy != SCHED_FIFO) {
                                p->need_resched = 1;
                        }
                }
                if (p->nice > 0)
                        kstat.per_cpu_nice[cpu] += user_tick;
                else
                        kstat.per_cpu_user[cpu] += user_tick;
                kstat.per_cpu_system[cpu] += system;
        } else if (local_bh_count(cpu) || local_irq_count(cpu) > 1)
                kstat.per_cpu_system[cpu] += system;
}

p->count 를 점점 감소시키면서 다 감소 되었을 때 우선순위를 높이는 코드입니다.

책에서 개념만 보던것이 실제로 소스코드를 보면서 확인하니

이해가 확확 되는군요 :)



이후로는 커널의 소스를 계속적으로 분석하면서

메모리 관리가 어떻게 이루어졌는지 설명 하였습니다.

그리고 어셈까지 분석을 해주시며 친절하게 설명을 해 주셨습니다.



실시간 정리를 하려고 했는데

수업내용이 점점 빨라져서 여기까지만 적겠습니다. ㅡ_ㅠ


두서없이 정리를 했는데...

강의는 정말로 좋았습니다.


하지만 중간중간에 실습이 있었으면 하는 아쉬움이 있습니다.

머릿속으로 생각할 시간이 좀 필요했는데...

슉슉~ 지나가버리니 나중에는 지쳐버리는 사람이 많더군요.



이제 1일차! 2일차에는 실습을 좀 할듯 합니다 :)

아자아자 화이팅!


 

신고
Trackback 0 Comment 5