define 으로  do { ... } while(0) 많이 쓰는 이유


Kernel code 를 보면 do { // some codes.. } while(0) 를 많이 쓰고 있다.


이에 대한 이유를 보면,

   1. 빈 구문은 compiler 로 부터 왜 #define FOO do {} while(0). 같이 썼는지 warning 을 받을 수 있다.

      (뭔가 구현 중에 있는 code 나 특정 define 이 정의되지 않을 때의 code가 없는 경우에 대체 후, compile 시 알림 같은 것으로 사용이 되려나.. 싶다.)

   2. 지역 변수 선언을 위해 쓰는 기본 block 으로 사용될 수 있다.

   3. 복잡한 macro를 이용해서 조건부 코드를 만들려고 할 때, 아래와 같이 만들 수 있을 것이다. 이것에 문제점을 보안하기 위해 do-while-0 블락을 사용할 것이다.

#define FOO(x) \
        printf("arg is %s\n", x); \
        do_something_useful(x);

위의 define으로 조건부 code를 만들어보자. 예를 들어,

if (blah == 2)
        FOO(blah);

위에서 처럼 만들었다면, interpreter 는 FOO 를 정의된 내용으로 대체를 할 것이다.

if (blah == 2)
        printf("arg is %s\n", blah);
        do_something_useful(blah);;

위에서 보듯이 printf 와 do_something_useful() 함수를 같이 쓰려는 목적과는 다르게, blah 가 2일 경우에만 printf 와 do_something_useful 가 같이 불릴 것이다. 이를 예방하기 위해, do { ... } while(0) 으로 define을 해두면,

if (blah == 2)
        do {
                printf("arg is %s\n", blah);
                do_something_useful(blah);
        } while (0);

위와 같이 변경 될 것이고 한번에 호출 될 수 있을 것이다.


  4. 또 다른 예제로는, define으로 지역변수 선언과 사용되는 것을 만들어 놓으면 아래와 같이 구현이 될 것인데(물론 여기에 나온 예제는 간단하고 억지스러움이 있을 수 있다.)

#define exch(x,y) { int tmp; tmp=x; x=y; y=tmp; }

위의 정의를 아래와 같이 사용한다면,

if (x > y)
        exch(x,y);          // Branch 1
else  
        do_something();     // Branch 2

이런식으로 구현을 하려고 할 것이고 이는 컴파일에 문제가 발생할 것이다. 이유는, 아래와 같이 번역(?) 되기 때문이다.

if (x > y) {                // Single-branch if-statement!!!
        int tmp;            // The one and only branch consists
        tmp = x;            // of the block.
        x = y;
        y = tmp;
}
;                           // empty statement
else                        // ERROR!!! "parse error before else"
        do_something();

세미콜론(;) 이 블락이 끝나자 마자 오게되어 발생하는 문제이다. 이것의 해결또한 do { ... } while(0) 으로 해결 할 수 있단다.

if (x > y)
        do {
                int tmp;
                tmp = x;
                x = y;
                y = tmp;
        } while(0);
else
        do_something();

위에서 보듯이 정리가 될 것이다. 이는 습관적인(?) rule 을 통해 code에 문제를 해결하기 위한 것이라 생각이 든다. 이미 kernel에는 많은 do { ... } while(0) 있다. 


추가적으로 GCC 는 do-while-0 블락을 대체 할 수 있는 것을 제공한다. 아래와 같이 사용하면 위에서 쓰는 것과 동일한 효과를 보는 것이다.(참고 자료 : http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Statement-Exprs.html#Statement-Exprs)

#define FOO(arg) ({         \
           typeof(arg) lcl; \
           lcl = bar(arg);  \
           lcl;             \
    })

알아두면 편리한 code 작성 법이다. 원본 URL 은 http://kernelnewbies.org/FAQ/DoWhile0 이다.


Namespace 의 구현 내용은 다음에..(언젠가는..)

2.3.3 Process Identification Numbers
 Unix process들은 항상 유일하게 확인 가능한 숫자하나를 할당받는다. 이 숫자는 Process identification number 또는 줄여서 PID 라고 부른다. 각 fork 나 clone system call로 생성된 process는 kernel 에 의해 system에 유일한 새로운 PID 를 할당받게 된다. 

Process Identifiers
 각 process는 PID 뿐만 아니라 다른 식별자로 특성이 구분될 수 있다. 몇 가지 형태를 살펴보도록 한다. 

□ 하나의 Thread Group 내에 있는 모든 process는(즉, CLONE_THREAD flag와 함께 호출 된 clone system call 로 생성된 process 내의 서로 다른 실행 문맥-thread) 같은 thread group id(TGID)를 가진다. 만약 하나의 process가 thread를 사용하지 않는다면,  PID  와 TGID 값은 같다. 
ex)
  parent process에서 아래와 같은 시스템 콜을 사용하였을 때, 결과 예측값,
1. fork(), vfork(), clone(CLONE_CHILD_CLEARID | CLONE_CHILD_SETID)
    parent : TGID(1234), PID(1234)
    child : TGID(1235), PID(1235)
2. pthread_XXX(), clone(CLONE_THREAD)
    parent : TGID(1234), PID(1234)
    child : TGID(1234), PID(1235)

    thread group 에 있는 main process는 group leader 라고 부른다. task_struct 의 group_leader 변수(struct task_struct * group_leader)는 thread group 내에서 생성된 thread 들이 main process를 가리키기 위해 사용된다. 

□ 또다른 한가지는, 독립적으로 수행되는 process들을 하나의 process group로 결합시킬수 있다.(setpgrp system call 사용) task_struct 의 pgrp 요소(실제로 task_struct 내부에 명시적인 pgrp 변수는 없다)는 하나의 group 내애서 process group leader의 pid 값으로 모두 같다.


//pgrp 의 값구하는 code 참조.

Process group는 그 group내에 있는 모든 process에게 signal을 일괄적으로 보내는데 용이 하게 사용된다.(다양한 system programming을 하는데 도움이 된다는데...) 
process group 는 pipe 를 이용하여 연결 된 것을 포함한다. 
ex) 
위와 같이 사용된 ps process와 grep process는 하나의 process group 가 되는 것이다. 

□ 여러 process group 는 하나의 session으로 결합될 수 있다. 하나의 session 내부의 process들은 task 구조체의 session 요소에 모두 같은 session id 값을 가지고 있다. SID 값은 setsid system call 로 변경이 가능하다. 

 Namespace는 PID 들을 관리하기 위해 추가적인 복잡함이 더해진다. PID namespace들이 계측적으로 구성된다는 것을 다시 상기해보자. 새로운 namespace가 생성되면, 모든 pid들은 parent namespace에게 보여지게 되지만 child namespace에서는 parent namespace의 PID를 볼수 없다. 위의 상황을 유추해보면, 어떤 task들은 하나 혹은 그 이상(namespace 당 하나의 PID)의 PID 값을 가질 수 있다는 것을 알 수 있다. data structure 에도 이런 사항이 반영되어 Global ID 와 Local ID 로 나누어 관리한다. 

 □ Global ID는 kernel 그 자체내부나 최초 부팅을 포함하여 init task가 실행했던 namespace(최초 namespace) 내부에서 확인 가능한 숫자이다. 이는 시스템 전체에서 유일한 값을 가지게됨을 보장한다. 
□ Local ID는 특정 namespace에 속한 ID이며, 시스템 전체에서 유효하게 사용하지 못한다. 그 process가 속한 namespace에서만 통용되며, 다른 namespace에서 같은 ID 값을 가지고 사용될 수 있다. 

위의 Global PID 와 TGID 는 task_struct 에서 직접 관리 한다. 

이들의 type 는 모두 pid_t 이며, 이것은 __kernel_pid_t 을 typedef 한 것이다.( include/linux/types.h). 즉 이것은 각각의 architecture 마다 새로 정의 가능 하다. 
대게는 int 로 사용되어 2^32 의 서로 다른 ID가 시스템에서 사용 가능하다는 것이다.

 session ID와 process group ID는 task struct 내부에서 직접관리 되지 않는다. 위에서 살펴본 group_leader field 에서 가져 올 수 있다. 

<task_struct>->group_leader->pids[PIDTYPE_SID].pid //get session id
<task_struct>->group_leader->pids[PIDTYPE_PGRP].pid //get pgrp id

system call, setpgrp() 또는 setsid() 를 이용해 각각의 값을 설정 할 수 있다. 
(현재 2.6.35 의 내용을 하고 있으므로 책의 내용과는 상이 할 수 있다. interface 및 자료 구조 내부의 내용이 변경되었슴.)

2.3.1 Process Types
 일반적인 Unix process는 binary code로 구성되고 chronological(연대순의, 번역하기가 어려운 단어라..) thread (컴퓨터는 한시점에 코드를 통해 한시점에 하나의 경로로 실행하는 의미의 말) 그리고 application에게 할당된 자원의 셋(메모리, 파일 등)을 가진 것이다. 새로운 process들은 fork exec 시스템 콜의 조합으로 생성된다. 

fork 는 현재 process를 복제하여 생성한다. 이 복사본은 child process라 불린다. 원래의 process의 모든 자원은 적절한 방법으로 복사되어 시스템 콜 이후에 최초 process의 독립적인 두개의 객체가 있게 된다. 이 객체들은 어떤 방법으로 연결되어 있진 않지만, 열린 파일, 같은 작업 디렉토리, 메모리의 같은 데이터(data의 복사본을 각각 가지고 있게됨) 등을 가지고 있다. 

exec 는 수행중인 process를 실행 가능한 binary 파일로 부터 다른 application 을 로드한다. 결국 새로운 program을 로드한다는 말이다. exec 은 새로운 process를 생성하지 못하기 때문에 fork 시스템 콜로 process를 새로 복사한 후, 시스템에 추가적인 새로운 application을 생성하기 위해 exec을 호출한다. 

Linux는 위 두개의 system call 이외에 추가적인 clone 시스템 콜을 제공한다. 원칙적으로는 clone 은 fork와 같은 방식으로 구동된다. 하지만 새로 생성된 process는 그것의 parent process와 완전히 독립적이지 않으며 parent와 몇몇 자원은 공유한다. 이 시스템 콜은 어떤 자원은 복사되고, 어떤 자원은 공유하게 하는지에 정의가 가능하다.-예를 들면, memory에 있는 data, 열린 파일들, signal handler등이 있다. 

clone은 tread를 구현할 때 사용된다. 그렇지만 thread를 수행하기 위해서는 이것만 가지고는 할 수 없다. user level에서 완전히 실행되기 위해서는 라이브러리들이 필요하다. 예를 들면, Linuxthreads Next Generation Posix Threads와 같은 라이브러리 들이다. 
Chapter 2, Process Management and Scheduling


2.3 Process Representation
Process와 program에 관련된 Linux Kernel의 모든 알고리즘은 include/sched.h 에 정의되어 있는 task_struct 라는 data 자료구조 내부에 있다. 이 자료구조는 시스템의 중심적 역할은 하는 것들 중에 하나이다. scheduler의 구현부를 다루기 전에, 이 기본적인 자료구조를 알아야 한다. 

task 구조체는 많은 process를 연결하는 요소들을 포함하고 있다. 구조체 내부의 요소들을 잘 알지 못하고는 차후에 나오는 내용을 이해시키기는 어려울 것이다. 

task 구조체는 다음과 같이 정의되어 있다.

task_struct 소스 내용을 모두 넣는 것이 쓰는 사람도 읽는 사람도 불편할 것이니 다른 방법을 사용한다. 물론, 필요한 부분은 넣어야겠지..


task_struct 의 링크는 
이다. 링크에서 보는 바와 같이 2.6.37.1 version 의 소스이다. 책의 내용과 조금 차이가 있을 수 있다. 

소스를 보면 알겠지만, 이 구조체의 많은 정보를 다 소화하기란 어려운 일일 것이다. 그렇지만, 이 구조체를 process의 특정 한 상태를 표현하는 등의 부분 부분으로 나누어 본다면 조금 수월할 것이다. 

Process 상태 및 실행 정보 : 지연된 signal, 사용된 binary 포멧(또한 다른 시스템의 binary 포멧을 위한 emulation 정보 등), process ID(pid), 자신의 부모 process 및 관련 process의 연결 포인터, 우선순위 값, 마지막으로 program을 실행한 시간 정보(CPU time)

□ 할당된 가상 메모리(virtual memory) 정보

process 자격 : user ID, group ID, process가 특정 명령을 수행할 권한 정보 등. System call 을 통해 process 정보등을 확인 하고 변경할 수 있다. 

사용된 file : program code를 포함하는 binary file 뿐만 아니라 process가 다루는 모든 file의 filesystem 정보는 저장해야한다. 

□ Thread 정보, process의 CPU 관련 runtime data 를 기록하게 됨.(그 외 남은 구조체의 field 는 사용된 하드웨어와 의존적이지 않다 - stack 정보같은 것인듯.)

□ 다른 process와 함께 같이 작업할 때, Process간 통신(Interprocess Communication)에 대한 정보

□ process가 signal에 응답하기 위해 등록한 signal handler

task 구조체는 간단하게 값들이 구성되어 있지 않다. 각종 data를 연결하기 위한 포인터 등으로 구성되어 있다. 중요한 변수들 몇몇을 자세히 설명해본다. 



state 는 process의 현재 상태를 기술한다. (volatile long으로 선언됨) 

TASK_RUNNING : Task가 수행 가능한 상태이다. 이것은 실제 CPU에 할당되어 수행중이라는 것은 아니다. scheduler에 의해 선택될 때까지 이 task는 기다릴 수 있다. 이 상태는 process가 실행 가능한 상태이며 외부 event를 기다리고 있지 않다는 것이다. 

TASK_INTERRUPTIBLE : 어떤 event를 기다리는 잠자고 있는(sleeping) process를 위한 설정이다. 기다리던 event가 발생하게 되면, 이 상태는 TASK_RUNNING 으로 변경되면 scheduler에 의해 선택되면 바로 실행이 가능하게 된다. 

TASK_UNINTERRUPTIBLE : kernel의 명령으로 잠들어 있는 process를 disable 시킨 상태. kernel이 직접 해제하지 않는다면, 외부 signal에 의해 깨어나 수행할 수 없다.

TASK_STOP : process가 특정목적을 위해 멈춰있는 경우이다.(예를 들면, debugger의 break point에서 멈추게 함.)

TASK_TRACED : 이 process의 상태는 ptrace 매커니즘을 이용해 process가 특정 시점에서 trace되고 있는 상태로 일반적인 STOP 된 task와 구별하기 위함이다. 

이 다음에 나오는 상수는 종료되는 process의 상태를 나타내준다. 이것은 exit_state 항목에 저장된다.
EXIT_ZOMBIE : 2.2 에서 설명된 zombie 상태를 나타낸다.

EXIT_DEAD : 시스템에서 완전히 제거되기 전에 parent process에서 알맞은 wait system call을 호출한 뒤의 process 상태. 이 상태는 하나의 task 안에서 여러 개의 thread가 수행될 때 중요하게 사용되는 상태이다. 

Linux는 process의 시스템 resource 사용 제한을 위해 resource limit (rlimit) 메커니즘을 제공한다. 이 메커니즘은 task_struct 안에 signal 구조체 포인터가 있다. process signal 관리를 위한 구조체 내부에 rlim이라는 배열이 존재한다. ( 아마 책에는 task_struct 내부에 rlim 배열이 있다고 하는데, 소스를 보니 task_struct --> singal_struct *signal-->struct rlimit rlim[] 로 되어 있다.)

rlimit 구조체는 include/linux/resource.h 에 정의되어 있다. 

이 정의(definition)은 다른 많은 resource를 수용하기 위해 매우 일반적으로(?) 유지된다. 
rlim_cur : process의 현재 자원 제한. 이것은 soft limit 로 참조된다.
rlim_max : process가 허가된 최대 자원의 제한. 이것은 hard limit 으로써 참조된다.

setrlimit system call은 현재 자원제한을 증가시키거나 감소시키는데 사용한다. 그렇지만 이 값은 rlimt_max 값을 초과할 수 없다. getrlimit system call로 현재 limit 값을 확인 할 수 있다. 

이 제한적인 자원은 rlim 배열의 index로 자신의 위치를 확인 할 수 있는데, 이것은 kernel에서 상수값으로 미리 정의를 해두어 연결된 자원과 배열의 위치를 연결했다. Table 2-1을 보면 정의된 상수와 그것의 의미를 기술했다. System programming 책을 보면 자원 제한관련 예제 및 더 상세한 내용을 볼 수 있다. 또한, setrlimit(2) man page를 봐도 조금 더 자세한 내용을 볼 수 있다. 

 Linux 는 특정 유닉스 시스템과 binary 호완성을 제공하기 위한 노력을 해왔기 때문에 아키텍쳐 마다 상수의 값들은 서로 다를 수 있다.

limit은 kernel의 매우 다른 부분과 연관되어 있기 때문에, kernel은 반드시 대응되는 하위 시스템의 limit 값을 확인해야 한다. 

만약 resource type이 limits(거의 모든 자원의 기본 설정임) 설정 없이 사용되었다면, RLIM_INFINITY 의 값으로 rlim_max 가 설정된 것이다. 예외적으로 사용된 경우를 보자, 
□ 열린 파일들의 수(RLIMIT_NOFILE, 기본적으로 1024로 제한한다.)
□ 사용자가 가질 수 있는 process의 개수(RLIMIT_NRPROC)은 "max_thread / 2"로 정의한다. 여기서 max_thread는 global 변수이며, 가용한 RAM의 1/8이 thread 정보를 관리하는데만 사용하고 20개의 thread가 최소의 메모리만을 사용하도록 thread의 생성 개수를 정의한다. (이문장은 번역에 어려움을 겪어 최대한으로 부드럽게 하려고 노력하였음.)

init task를 위한 부팅 때 자원 자한은 include/asm-generic-resouce.h 안에 INIT_RLIMTS 로 정의되어 있다. 

각 process의 proc filesystem을 통해 rlimit 값을 확인 할 수 있다. 
현재 나의 system 정보는 : VMware Server 2.0.1에 Ubuntu 10.10을 설치했다. 10.10의 kernel version은 2.6.35-22 다. 
rlimit 값을 확인 하기 위해, 

proc/self/limits 파일을 읽었다. proc file system의 self 라는 file은 symbolic link 이며 현재 수행중인 process를 가르키고 있다. 

Table 2-1: Process 관련 자원 제한. 
 상수  의미
 RLIMIT_CPU  최대 할당 할 수 있는 CPU 시간
 RLIMIT_FSIZE  사용할 수 있는 file 최대 크기
 RLIMIT_DATA  data segment의 최대 크기
 RLIMIT_STACK  (user mode) stack의 최대 크기
 RLIMIT_CORE core dump file의 최대 크기 
 RLIMIT_RSS  Resident Size Set 의 최대 크기; 다른 말로는 process가 사용할 수 있는 
최대 page frame의 개수이다. 현재 사용되지 않은 것도 포함함.
 RLMIT_NPROC  실제 UID 에 연관된 사용자가 하나의 process를 가지고 생성할 수 있는
process의 최대 개수(fork의 제한 인듯) - 조금 더 알아봐야 할듯.
 RLIMIT_NOFILE  하나의 process가 제어할 수 있는 파일의 개수(open files)
 RLIMIT_MEMLOCK  swap 되지 않도록 할 수 있는 page 의 개수
 RLIMIT_AS  하나의 process가 차지할 수 있는 가상 주소 공간의 최대 사이즈
 RLIMIT_LOCKS  file lock의 최대 개수
 RLIMIT_SIGPENDING  지연된 signal의 최대 개수
 RLIMIT_MSGQUEUE  message queue의 최대 개수
 RLIMIT_NICE  non-real time process들을 위한 최대 nice 레벨
 RLIMIT_RTPRIO  real time 우선 순위의 최대치.




Chapter 2, Process Management and Scheduling


2.2.1 Preemptive Multitasking
 Linux process 관리 구조를 알기 위해서는 process 실행의 두가지 모드를 알아야한다.(kernel mode와 user mode) 요즘 출시되는 CPU들은 최소한 두가지 다른 실행 모드가 존재하며, 한가지는 제한이 없는 권한을 가지고 실행되며 다른 한가지는 다양한 제한이 걸려있는 상태로 실행된다는 것이다.(제한이 있다는 것에 예를 들면, 특정 물리 메모리 영역을 접근하는 것 등이 될 것이다.) 이런 구분은 시스템의 어떤 한 부분을 간섭하는 것을 보호하고 시스템에 존재하는 process들을 잡아(?-다른 process들에게 넘어가거나 접근하는 것을 막는다는 뜻인가) 둘수있는 잠겨진 "새장과 같은" 것을 생성해줘야 한다. 

일반적으로 kernel은 자신이 가지고 있는 data 만 접근 가능하고 시스템에 있는 다른 application에게 간섭할 수 없는 user mode 안에 있다.(이 부분은 무슨 말인지 써놓고 무슨말인지 확실치 않다.)

만약 한 process가 system data나 함수들의 접근을 원한다면(후자는 모든 process들 사이에 공유된 자원을 관리한다, 예- filesystem 영역) 반드시 kernel mode로 변경하여 수행해야 한다. 물론 kernel mode에서 통제되고(만약 그렇지 않으면, 현재 만들어진 보호 매커니즘이 모두 불필요해지는 것이다. ) 명확히 정해진 루틴으로 실행되어야 한다. 이와 같은 것은 system call 이라는 특별한 함수를 통해 이루어진다. 자세한 사항은 13장에서 더 자세히..

user mode로 부터 kernel mode로의 전환은 interrupt에 의해서도 일어난다.(이것은 interrupt 발생시 자동으로 전환된다.) user application에서 의도적으로 호출되는 system call(system data나 함수를 이용 목적을 위해)과는 달리 독단적(?)으로 수행된다. -의도되지 않은 상태에서 발생함- interrupt의 발생은 현재 process실행과 무관하게 처리해 줄 수 있어야 한다. 예를 들면, 외부 블럭(block) 장치에서 요청한 data를 RAM 으로 복사 완료되면 이 data가 시스템의 어떤 process를 위한 것이었던 간에 interrupt는 발생하게 된다. 비슷하게, network 장치에서도 interrupt를 통해 data package 도착을 알려주게 된다. 반면, network으로 들어온 package는 interrupt를 받아 처리함에 있어 그 data는 현재 수행되고 있는 process에게 전달 된다.(외부 블럭 예제와는 다른 경우 인 것이다.) Linux는 interrupt를 다르게 처리를 하지만, 수행되고 있는 process들은 이같은 상황을 모르고 자신의 작업을 진행한다. 

Kernel의 선점 scheduling 모델은 어떤 상태의 process가 interrupt를 받는지에 대한 구조를 만들어 두었다.

□ Normal Process는 항상 interrupt를 받을 수 있다.(다른 process에 의한 interrupt 발생조차도 다 받는다). 중요한 process(오랫동안 keyboard 입력을 기다리는 편집기 등)
가 실행 상태가 되면 scheduler는 즉시 실행 할 것인지에 대한 결정을 내린다. 현재 그 process가 수행중일 때도 그 같은 고려를 한다. 이와 같은 선점은 시스템의 응답시간을 높이는데 중요한 역할을 했다.

□ 만약 kernel mode에서 system call을 수행하고 있을 때, 어떤 process도 CPU 사용을 취소할 수 없다. 즉, scheduler는 다른 process를 선택하기 이전에 system call의 수행을 완료를 해야한다는 것이다. 하지만 system call은 interrupt에 의해 잠시 보류될 수 있다. 

□ Interrupt는 user mode와 kernel mode에 있는 process들을 중지할 수 있다. 그것은 interrupt가 발생한 시점 부터 가능한한 빨리 처리해야 하는 중요한 것이기 때문에 가장 높은 우선순위를 가진다. 

Kernel 선점은 2.5 버전 개발을 진행하면서 선택사항으로(make menuconfig 수행된 메뉴) 추가되었다. 이 선택사항(option)은 kernel mode에서 system call을 수행 중일 때에도 급히(?) 처리해야 하는 다른 process가 생기면 교체될 수 있는 기능인 것이다.(물론 interrupt 핸들러가 수행중일 때는 불가하다.) 비록 kernel이 system call을 최대한 빨리 수행을 마쳐야 함에도 불구하고(빨리 수행해야하는 이유는 scheduler가 system call을 마무리 하여 다른 process의 수행권을 보장하고 진행하기 위함) 신뢰성있는 어떤 application들은 일정한 data 스트림을 요구하여 많은 시간이 필요하게 되는 경우가 있다. kernel 선점은 이와 같은 대기 시간을 줄이고 부드러운(?) program 실행을 가능하게 한다. 하지만 선점 기능은 하나의 CPU를 가진 시스템에서 병렬적인 접근이 많아지고 이것을 보호하기 위해 많은 data structure가 요구되기 때문에 증가된 kernel 복잡성으로 비용이 증가하게 된다. (향후, 2.8.3 장에서 자세히 다룬다.)

Chapter 2, Process Management and Scheduling


2.2 Process Life Cycle
 하나의 Process는 항상 실행 준비가 된 상태가 아니다. 때때로, Process는 외부 자원의 event 를 기다리고 있는 경우가 있다.(text 편집기에서 keyboard 입력 대기를 위한 경우). 이런 경우는 event(keyboard 입력)이 있을 때까지 process는 실행 될 수 없다. 

scheduler는 process들의 교체를 할때 시스템에 있는 모든 Process의 상태를 알고 있어야 한다. 이는 할일이 없는 process임에 불구하고 CPU 시간을 할당하는 일은 없어야 한다는 것이다. 시간 할당과 중요한 점은 각 process의 상태를 전환(예, 실행 상태 --> 대기상태)시키는 일이다. 예를 들어, 만약 하나의 process가 주변 장치의 data를 기다리고 있다면, scheduler는 process의 상태를 data가 도착할 때까지 실행 대기 상태로 변경해줘야 한다. 

하나의 process는 다음과 같은 상태를 가진다
□ Running -- Process는 실행 중이다. 
□ Waiting  -- Process는 실행 가능한 상태이지만 CPU 를 다른 process가 점유하여 사용 중이기 때문에 기다리는 상태이다. 이 상태의 process 는 scheduler에 의해 다음으로 실행 가능하다. 
□ Sleeping -- Process는 잠든(?) 상태이고, 수행될 수 없다. 외부 장치에 의해 data나 event를 기다리고 있는 상태이며, process가 event를 받기 전까지 scheduler가 선택할 수 없다. 

시스템은 하나의 process table에 그것들의 상태들과 관계없이(running, waiting, sleeping) 모든 process들을 저장한다. 그렇지만, sleeping 상태의 process는 scheduler가 실행 준비가 되지 않은 상태을 알고 있어야 함으로 특별히 "표시"를 해 둔다. 또한 외부 event를 기다리고 있는 process가 event가 발생시 적절한 시점에 깨어나 수행할 수 있도록 다수의 Queue 로 관리하고 있다. 

그림 2-2

실행 가능한 process의 queue에 다양한 상태전의를 알아보도록 하자. 하나의 process가 실행 가능한 상태이지만, 다른 process가 CPU를 점유하고 있는 상태라 CPU를 사용하기 위한 대기 상태이다. (이것의 상태는 "Waiting" 이다). 그것은 scheduler가 CPU 시간을 할당 할 때까지 "waiting" 상태로 남아있을 것이다. 일단 scheduler가 선택을 하면, 그 process의 상태는 "running"으로 바뀔 것이다. (그림 2-2 의 4번 전이)

scheduler가 process의 CPU 자원 사용을 그만 두게 하기로 결정했다면, 그 process의 상태는 "running"에서 "waiting"으로 변경된다. (그림 2-2 의 2번 전이), 그리고 새롭게 cycle을 시작한다. "Sleeping" 상태는 두 가지 경우가 있는데, 하나는 signal을 받아 방해(interrupt) 받을 수 있는 것과 그렇지 못한 것이다. 이시점에서는 sleeping의 경우의 수는 다루지 않는다. 

만약 process가 event를 기다리고 있는 상태라면, 그 process의 상태는 "running"에서 "sleeping" 상태로 변경된다. 하지만, sleeping 상태의 process는 바로 running 상태로 변경이 이루어지지 않는다. 일단 기다리던 event가 발생했을 경우, 그 process는 waiting(그림 2-2의 3번 전이)로 변경되고 다음 번 실행을 기다리게 된다. 

Program 실행이 종료되면(사용자가 application을 종료한 경우 등), process의 상태는 running에서 stopped로 변경된다(그림 2-2  에서 5번 전이)

위에 설명되지 않은 process의 특별한(?) 상태가 있는데, 그것은 "zombie" 상태이다. 이름에서도 알수 있듯이, process가 죽었지만 어찌된 영문인지 여전히 살아있는 상태로 보이는 것이다. 다시 말하면, 그 process들은 사용하던 자원(RAM, 주변장치의 연결 등)을 반납하고 다시는 실행될 수 없는 상태로 소위 죽은 것이다. 그렇지만 process table에 그것들을 위한 공간이 존재하기 때문에 살아있는 것처럼 보인다는 것이다. 

Process가 Zombie 상태로 들어가는 경우는, UNIX 시스템의 process 생성과 종료 구조에 관련되어 있다. 하나의 program가 종료하는 상태는 두 가지가 있다.. 한가지는, 다른 process나 사용자에 의해서 강제 종료되어지는 경우다.(이런 경우는 대게 SIGTERM 이나 SIGKILL signal을 종료대상 process에게 전달되어 이루어진다.-이는 process가 일반적으로 종료하는 경우와 동등한 효과를 가진다), 다른 한가지는, child process가 종료되는 시점에 parent process가 이미 wait4 시스템 콜을 실행하여 child의 종료상태를 parent가 받는 경우이다. 결국 parent process가 child의 종료상태를 인지하고 kernel에게 알려줘야 한다는 것이다. 그 시스템 콜은 child process에게 할당된 자원을 kernel이 해제해주게 된다. 

위에 기술했던 상황 모두 zombie 상태는 발생하게 된다. 하나의 process는 종료와 process table에서 제거되는 시점 사이에 잠시 zombie 상태를 거처간다. 어떤 경우는(parent process가 잘못 구현되어 wait 시스템 콜을 호출하지 않고 종료한 경우), child process가 종료상태를 parent에 알려주지 못한 상태에서 종료를 하여 썼던 자원은 해제가 되었겠지만 시스템의 다음 rebooting 까지 process table을 차지 할 수 있다.(zombie 상태로 오래 남아있는 경우다) 이것은 pstop 명령어로 확인 될 수 있다. process table에 남아 있는 zombie 상태는 kernel의 아주 작은 영역을 차지하고 있어 큰 문제가 되질 않는다. 


* Site
1. Linux kernel newbies
   Comment : 간략하게 둘러본 결과, 각종 문서의 링크가 잘 정리 되어 있고 "FAQ" 을 보면 kernel compile 방법에서 부터 참고할 만한 것이 있다. 

2. LinuxChix Kernel Hacking Lessons 

3. Linux Kernel Mailing list
   URL : https://lkml.org/
   Comment : bug 및 patch 사항

4. Linux Weekly News
   URL : http://lwn.net/
   Comment : Linux Weekly News 이다. 여기는 매주 업데이트 되지만, 구독을 하지 않는다면 한주전의 사항만 확인 가능하다. 

* 도서

1. Linux Kernel in a Nutshell
    - Greg Kroah-Hartman, O'Reilly Media, Inc. (December 14, 2006)
    - kernel build 및 간략한 개발 방법 등.
    - E-book (pdf version) 등을 검색하여 얻을 수 있다.

2. Understanding the Linux Kernel, 3rd Edition
    - Daniel Bovet, Marco Cesati, O'Reilly Media, Inc. (November 14, 2006)
    - 내용이 방대하고 자세히 나온 책, 차근차근 읽어 나가기엔 너무 지루 한듯.
      발췌용으로 자세히 알고 싶은 부분을 중점으로 읽으면 좋을 듯하다.
    - 한글판이 있음, E-book (pdf version) 등을 검색하여 얻을 수 있다.

3. Linux Kernel Development, 3rd Edition 
    - Robert Love, Addison Wesley (2010)
    - 적당한 분량의 책, 물론 읽기에는 조금 벅찬 듯
    - 2rd Edition은 한글판을 본 듯 한데, 3rd Edition은 모르겠음. 

4. Linux Device Drivers
    - Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman

5. Professional Linux Kernel Architecture
    - Wrox, Wolfgang Mauerer
    - 현재 틈틈히 보고 있는 책, 책의 분량은 방대하며 소스 레벨에서 하나 하나 설명해 놓은 책, 정리하며 봐야 할듯 
      - E-book (pdf version) 등을 검색하여 얻을 수 있다.

+ Recent posts