linux kernel analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv [email protected]...

57
i [email protected] Linux Kernel Analysis Process Scheduling Part June 3, 2007 [email protected]

Upload: vophuc

Post on 11-Mar-2018

239 views

Category:

Documents


13 download

TRANSCRIPT

Page 1: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

i

[email protected]

Linux Kernel Analysis

Process Scheduling Part

June 3, 2007

[email protected]

Page 2: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

ii

[email protected]

목 차

I. 개요 ...................................................................................................................................................................... 1

1. 리눅스란? ......................................................................................................................................................... 1

2. 커널 ................................................................................................................................................................... 2

3. 프로세스스케줄링 ........................................................................................................................................... 3

II. KERNEL/SCHED.C분석 ............................................................................................................................... 4

1. HEADER DEFINE PART ........................................................................................................................................ 4

2. DEFINE PART ...................................................................................................................................................... 6

3. RUNQUEUE구조체 ........................................................................................................................................... 10

4. 우선순위설정부분 ......................................................................................................................................... 20

5. CONTEXT_SWITCH(문맥교환)부분................................................................................................................. 32

6. SCHEDULER_TICK함수 .................................................................................................................................... 34

7. SCHEDULE()함수 ............................................................................................................................................. 36

8. WAITQUEUE(대기큐)관련 함수 ...................................................................................................................... 45

9. 시스템 콜 관련 함수 .................................................................................................................................... 47

III. 참고문헌 및 사이트 ..................................................................................................................................... 51

Page 3: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

iii

[email protected]

표 목 차

표 1 - 헤더파일에 대한 설명 ................................................................................................................... 4

표 2 - 프로세스의 전형적 우선순위 값 ................................................................................................. 8

표 3 - 실행큐 데이터 구조의 필드 ....................................................................................................... 11

표 4 - 실행 가능한 프로세스들의 종류 ............................................................................................... 12

표 5 - kmalloc함수에 들어가는 2번째 인자 ......................................................................................... 18

표 6 - 평균수면시간에 따른 보너스 값, 타임슬라이스 입도 .......................................................... 23

표 7 - 프로세스 디스크립터에서activated필드의 의미 ...................................................................... 29

표 8 - 함수호출규약 ................................................................................................................................ 31

표 9 - 실시간 프로세스 관련 시스템 콜 ............................................................................................. 49

Page 4: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

iv

[email protected]

그 림 목 차

그림 1 - Linux의 탄생과정 ........................................................................................................................ 1

그림 2 - 프로세스스케줄링 ....................................................................................................................... 3

그림 3 - sched.c의 헤더파일정의 .............................................................................................................. 4

그림 4 – NUMA 설정 시와 비 설정 시의 정의 ................................................................................... 6

그림 5 - NUMA에 대한 그림 ................................................................................................................... 6

그림 6 - 우선순위관련 정의 ..................................................................................................................... 7

그림 7 – 타임슬라이스 등 스케줄러 함수에 필요한 갖가지 정의 .................................................. 7

그림 8 - 프로세스 순위정의 ..................................................................................................................... 9

그림 9 - 각 프로세스 순위에 따른 타임슬라이스할당 ....................................................................... 9

그림 10 – 실행큐 구조체의 일부 .......................................................................................................... 10

그림 11 - CONFIG_SMP시 추가되는 내용 ........................................................................................... 10

그림 12 – 프로세스 디스크립터들의 리스트 정의 ............................................................................ 12

그림 13 - CPU실행큐와 task실행큐를 정의하는 모습 ........................................................................ 12

그림 14 – 문맥교환 관련 설정부분 ...................................................................................................... 13

그림 15 - task_rq_lock 정적 함수선언 ................................................................................................... 13

그림 16 - local_irq_save함수정의 ............................................................................................................ 14

그림 17 - flags 구조체 .............................................................................................................................. 14

그림 18 - compiler.h에 정의된 releases의 모습 .................................................................................... 14

그림 19 - CONFIG_SCHEDSTATS시의 처리 ........................................................................................ 15

그림 20 - 실행큐 상태출력 부분 ........................................................................................................... 16

그림 21 - schedstat_open함수 ................................................................................................................... 17

그림 22 - kmalloc함수의 내용 ................................................................................................................. 17

그림 23 - 실행큐를 잠그고, 인터럽트를 실행 못하도록 하는 부분 .............................................. 18

그림 24 - 락을 여러 개 이용해 임계 영역 보호하기 ....................................................................... 19

그림 25 - sched_info_dequeued함수 ......................................................................................................... 20

그림 26 - 타임슬라이스할당부분 ........................................................................................................... 20

그림 27 - sched_info_queued함수............................................................................................................. 20

그림 28 - sched_info_depart함수 .............................................................................................................. 21

그림 29 - sched_info_switch함수 .............................................................................................................. 21

그림 30 – enqueue_task, dequeue_task 함수 ............................................................................................ 21

그림 31 - enqueue_task_head함수의 정의 .............................................................................................. 22

그림 32 – 동적 우선순위 설정 함수 .................................................................................................... 22

Page 5: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

v

[email protected]

그림 33 - 태스크를 실행큐에 넣는 부분 ............................................................................................. 23

그림 34 - recalc_task_prio함수 ................................................................................................................. 24

그림 35 – 나노초 타이밍을 지피스로 바꾸거나 그 반대역할을 하는 매크로 ............................. 24

그림 36 – active_task함수 ......................................................................................................................... 25

그림 37 - deactivate_task함수정의 ........................................................................................................... 26

그림 38 - task_curr함수 ............................................................................................................................. 26

그림 39 - migrate_task함수 ....................................................................................................................... 26

그림 40 - wait_task_inactive함수 .............................................................................................................. 27

그림 41 - kick_process함수 ....................................................................................................................... 27

그림 42 - try_to_wake_up함수(1) ............................................................................................................. 28

그림 43 - try_to_wake_up함수(2) ............................................................................................................. 29

그림 44 - resched_task함수 ....................................................................................................................... 30

그림 45 - set_tsk_need_resched함수 ......................................................................................................... 30

그림 46 - sched_fork함수 .......................................................................................................................... 30

그림 47 - wake_up_new_task함수 ............................................................................................................ 31

그림 48 - wake_up_new_task함수(아랫부분) .......................................................................................... 32

그림 49 - context_switch함수 ................................................................................................................... 32

그림 50 - switch_to매크로 ........................................................................................................................ 33

그림 51 - scheduler_tick함수 .................................................................................................................... 34

그림 52 - scheduler_tick(계속) .................................................................................................................. 35

그림 53 - scheduler_tick(마지막) .............................................................................................................. 36

그림 54 - schedule()함수(00) .................................................................................................................... 36

그림 55 - in_atomic()매크로 ..................................................................................................................... 37

그림 56 - kernel_locked의 정의 ............................................................................................................... 37

그림 57 - schedule()함수(01) .................................................................................................................... 38

그림 58 - schedule()함수(02) .................................................................................................................... 39

그림 59 - schedule()함수(03) .................................................................................................................... 40

그림 60 - wake_sleeping_dependent함수 ................................................................................................. 40

그림 61 - schedule()함수(04) .................................................................................................................... 41

그림 62 - sched_find_first_bit매크로 ....................................................................................................... 42

그림 63 - schedule()함수(05) .................................................................................................................... 43

그림 64 - prefetch매크로 .......................................................................................................................... 43

그림 65 - clear_tsk_need_resched함수 ..................................................................................................... 43

그림 66 - rcu_qsctr_inc함수 ...................................................................................................................... 44

그림 67 - schedule()함수(06) .................................................................................................................... 44

그림 68 - prepare_arch_switch,finish_arch_switch매크로 ....................................................................... 45

Page 6: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

vi

[email protected]

그림 69 - wake_up_common함수 ............................................................................................................. 45

그림 70 - wake_up과 wake_up_locked함수 ............................................................................................ 46

그림 71 - sleep_on함수 ............................................................................................................................. 46

그림 72 - sleep_on함수관련 매크로 ....................................................................................................... 46

그림 73 - set_user_nice함수 ...................................................................................................................... 47

그림 74 - set_user_nice(이어서) ............................................................................................................... 48

그림 75 - sys_nice함수 .............................................................................................................................. 48

그림 76 - io_schedule함수 ........................................................................................................................ 50

그림 77 - 멀티프로세싱일 시 사용되는 함수의 일부(cpu_attach_domain) ..................................... 50

Page 7: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

1

[email protected]

I. 개요

1. 리눅스란?

우선 리눅스 커널분석을 하기 전에 리눅스에 대해 알아볼 필요가 있다.

1989년 핀란드 헬싱키대학에 재학 중이던 리누스 토발즈(Linus Torvalds)가 유닉스를 기반으

로 개발한 공개용 오퍼레이팅시스템(OS)으로, 1991년 11월 버전 0.02이 일반에 공개되면서

확대 보급되기 시작하였다. 유닉스(Unix)가 중대형 컴퓨터에서 주로 사용되는 것과는 달리,

리눅스는 워크스테이션이나 개인용 컴퓨터에서 주로 활용한다.

리눅스는 소스 코드를 완전 무료로 공개하여 전세계적으로 약 5백만 명이 넘는 프로그램

개발자 그룹을 형성하게 되었으며, 이들에 의해 단일 운영체제의 독점이 아닌 다수를 위한

공개라는 원칙하에 지속적인 업그레이드가 이루어지고 있다.

파일구성이나 시스템기능의 일부는 유닉스를 기반으로 하면서, 핵심 커널 부분은 유닉스와

다르게 작성되어 있다. 인터넷 프로토콜인 TCP/IP를 강력하게 지원하는 등 네트워킹에 특

히 강점을 지니고 있으며, 유닉스와 거의 유사한 환경을 제공하면서 무료라는 장점 때문에

프로그램 개발자 및 학교 등을 중심으로 급속히 사용이 확대되고 있다.

리눅스는 각종 주변기기에 따라 혹은 사용하는 시스템의 특성에 맞게 소스를 변경할 수 있

으므로 다양한 변종이 출현하고 있다.

출처 : 두산세계대백과

위와 같이 리눅스는 유닉스(UNIX)기반의 운영체제이며, 오픈 소스이기 때문에 무료인

운영체제이다. 또한 리누스 토발즈 뿐 아니라, 전 세계에서 이 운영체제를 위해 지원을 하고

있다. 우리가 리눅스 커널분석을 하는 이유도 오픈 소스이기 때문에 커널을 쉽게 구할 수

있고(www.kernel.org) 그만큼 커널분석에 대한 도움을 주는 문서들도 많기 때문에 리눅스를

하기로 결정하였다. 아래 그림은 리눅스의 탄생과정에 대한 그림이다.

그림 1 - Linux의 탄생과정

Page 8: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

2

[email protected]

2. 커널

그러면 커널이란 무엇일까? 모든 컴퓨터는 운영체제(OS)라는 시스템을 가지고 있다. 이런

운영체제에서 가장 중요한 프로그램을 커널(Kernel)이라고 말하는데, 이 커널은 시스템이

부팅하게 되면, 램으로 로딩되어, 시스템 동작에 필요한 여러 가지 절차를 수행한다. 즉

운영체제의 총 관리자(Operating System Administrator)라고 할 수 있으며, 관리자인 만큼 커널

하위의 프로그램들에 대한 통제의 권한도 가지고 있다. 일반적으로 말하는 운영체제가

안정적이다. 라는 말은 곧, 커널이 상당히 안정적이라는 의미가 된다.

그러면 리눅스의 커널은 어떤 방식이며, 어떤 특징을 지니고 있는지 알아보도록 하겠다.

가. 우선 리눅스 커널은 모놀리식커널이다. (커널에는 모놀리식과 마이크로식이 있다.)

모놀리식 커널이란, 각 커널 계층은 커널 프로그램에 하나로 통합되고 현재 프로세스를

대신하여 커널 모드에서 동작하게 된다. 또한 리눅스는 모놀리식 커널의 단점(램의

활용능력, 다양한 아키텍처로의 이식)을 보완하기 때문에 모듈(Module)이라는 개념을

제공한다. 모듈은 커널에 링크/언링크할 수 있는 개체라고 할 수 있다. 단, 마이크로

커널처럼 커널과 따로 프로세스 형태로 동작하지는 않는다.

나. 커널쓰레드를 지원한다. 이 커널쓰레드는 프로세스간의 문맥교환보다 자원을 적게

차지하는 쓰레드 사이의 전환을 커널상에서 지원해준다는 말이다. 당연히 멀티쓰레드

어플리케이션도 지원한다.

다. 리눅스의 커널은 선점형 커널이다. 리눅스 2.6 커널부터 리눅스는 “preemptible

kernel”이라는 명령어를 통해 특권레벨의 동작에 끼어들 수 있게 되었다.

라. 멀티프로세서를 지원한다. 요즘처럼 일반 개인 사용자도 듀얼코어 CPU 를 사용하는

시대에서 이 지원은 매우 큰 이점으로 작용된 다. 커널 소스분석에서도 나오지만

NUMA 를 위해 SMP(Symmetric Multiprocessing)기술을 지원하며, 또한 CPU 가 여러 개라

각각의 CPU 를 전체 통제하기 위한 Big Kernel Lock 기술도 지니고 있다.

마. 리눅스는 다양한 파일시스템을 지원한다. 기존의 유닉스나 구 버전의 리눅스,

솔라리스는 ext2 를 표준으로 한다(물론 신 버전의 리눅스 커널도 지원한다.). 하지만

각각의 운영체제들은 자기만의 새로운 기술을 사용하기 위해(예를 들어 저널링 기술),

또는 자기 운영체제만의 최적화면 파일 시스템을 위해 자기들만의 FS(File System)을

만들게 된다. 리눅스는 이러한 대다수의 파일시스템을 거의 다 지원하여 다른

시스템에서 리눅스로의 포팅작업을 원활하게 할 수 있도록 해준다.

Page 9: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

3

[email protected]

3. 프로세스스케줄링

그러면 우리가 분석할 프로세스스케줄링은 무엇일까?

그림 2 - 프로세스스케줄링

일반적으로 우리가 가지고 있는 PC 를 생각해보면, 각 PC 는 하나의 CPU 를 가지고 자신의

일을 수행한다. 하지만 하나의 운영체제(OS)에서 돌아가는 프로세스는 알다시피 여러 개가

돌아가고 있다.(이 문서를 보고 있는 지금도 익스플로러와 음악을 듣는 사람이 있을지도

모른다.) 이런 상황에서 이런 여러 개의 프로세스를 동시에(동시처럼 보이게) 만들려면 각

프로세스를 사람이 식별하기 힘든 빠른 속도로 돌아가면서 실행해주는 방법이 있다. (여기에

대한 자세한 설명은 이 문서 외의 사항이므로 생략하기로 한다.) 이런 각 프로세스를

얼마만큼 작동시킬 것인지를 제어하는 것을 프로세스 스케줄링 이라고 한다. 일반적으로 한

프로세스를 실행하고 다른 프로세스는 준비/대기 상태에 있다가, 현재 실행중인 프로세스를

일시 중단/대기 하고, 새로운 프로세스를 실행하는 식으로 스케줄링이 이루어 진다.

리눅스에 대한 설명은 이 정도로 하고, 이제 커널 분석을 시작해보도록 하겠다. 이 문서는

프로세스스케줄링에 관한 부분만을 분석할 것이고, 커널버전은 2.6.10 을 사용하겠다. 이

문서가 작성되고 있는 현재(07/05/31)에는 커널버전 2.6.20.1 까지 나와있는 상황이다. 이

문서는 파일다운로드 없이 손쉬운 분석을 위해 http://lxr.linux.no/ 사이트에 있는 커널내용을

토대로 작성하였다. 그러면 sched.c (http://lxr.linux.no/source/kernel/sched.c?v=2.6.10) 파일을

기준으로 분석을 시작하겠다.

Page 10: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

4

[email protected]

II. kernel/sched.c 분석

1. Header Define Part

위의 파일은 커널 스케줄러와 해당 시스템 콜에 관한 내용을 포함하고 있다. 우선 파일의

최초 헤더파일 정의를 확인해 보자.

그림 3 - sched.c의 헤더파일정의

이 헤더파일에 대한 설명은 다음과 같다.

표 1 - 헤더파일에 대한 설명

헤더파일종류 설 명

mm.h 리눅스 커널의 가상메모리 관리정의

module.h 커널내부에서 동적으로 로드되는 모듈정의

nmi.h 커널의 감시기 시스템에 대한 정의(아키텍처 지원 시)

init.h 초기화 관련 함수, 초기변수에 대한 정의

Page 11: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

5

[email protected]

uaccess.h 비어있는 메모리에 대한 접근함수정의

highmem.h 상위 메모리 페이지에 대한 정의

smp_locck.h 스케줄러에 의한 Kernel Lock활성화

nmu_context.h LDT(HT BUS의 옛말)를 이용한 복사나 파괴에 대한 정의

interrupt.h IRQ에 대한 정의

completion.h 핸들러의 데이터 구조가 완성대기에 대한 정의

kernel_stat.h 커널 상태(CPU상태, 문맥교환 등)에 대한 정의

security.h 리눅스 보안 플러그 인에 대한 정의

notifier.h 루틴의 상태를 넘겨주는 체인을 처리/관리하는 것에 대한 정의

profile.h 기본 커널에 대한 프로파일 정의

suspend.h 프로세스 상태의 정지/보류에 관한 정의

blkdev.h 각 프로세스들의 I/O상태 예측에 관한 정의

delay.h “loops_per_jiffy" 값을 이용한 지연루틴에 대한 정의

smp.h Smp에 대한 정의

timer.h timer함수들에 대한 정의

rcupdate.h 상호배타적인 읽기-복사 업데이트 메커니즘에 대한 정의

cpu.h cpu정의

percpu.h 사용CPU에 대한 버전 및 정보획득 헤더파일

kthread.h 별다른 조작 없이 커널쓰레드의 생성/중지를 위한 인터페이스 정의

seq_file.h 순차적 시뮬레이션 방법을 위한 모듈정의

syscalls.h 리눅스 시스템 콜 인터페이스에 대한 정의

times.h 시간에 대한 정의

tlb.h 가상메모리의 성능 향상을 위한 메모리 버퍼(tlb)기술의 정의

unistd.h 시스템 콜 넘버에 대한 정의

위에서 본 것과 같이 sched.c 는 CPU 관리, 메모리 관리, 스케줄링, 시간, 쓰레드, 시스템 콜

넘버(참고: 리눅스의 시스템 콜 넘버는 윈도우와 다르게 메이저 패치가 나와도 거의 변경이

Page 12: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

6

[email protected]

되지 않는다. 윈도우는 서비스 팩이 나올 때 마다 시스템 콜 넘버를 전체적으로 변경한다.)

등 커널에 필요한 거의 모든 헤더파일들을 불러온다.

2. Define Part

최초 정의는 CONFIG_NUMA 즉, NUMA(Non-Uniform Memory Architecture ) 일 경우부터

시작된다.

그림 4 – NUMA 설정 시와 비 설정 시의 정의

NUMA는 멀티 프로세서에서 사용되는 메모리 구조의 한가지로, 이 구조에서는 메모리의

접근 시간이 CPU 대비 메모리의 상대적인 위치에 의해 결정된다. 즉, CPU의 로컬 메모리일

경우 메모리 접근 시간이 매우 빠르며, 다른 CPU의 로컬 메모리거나, 여러 프로세스가 공

유하는 메모리일 경우 접근 시간이 느려지는 특성을 가지고 있다.

그림 5 - NUMA에 대한 그림

출처 : http://blog.naver.com/lazylune?Redirect=Log&logNo=50016986136

위와 같이, NUMA 가 설정되어 있을 때와 아닐 때의 정의를 따로 구현한다.

그 후, 우선순위에 대한 정의를 실시한다.

Page 13: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

7

[email protected]

그림 6 - 우선순위관련 정의

주석문을 통해 각 프로세스의 유저레벨을 기준한 프로세스 우선순위에 대한 정의 내용임을

알려주고 있다. 우선 62~64 줄은 user-nice 값들을 정적 우선순위로 변환하거나 또는 그 반대로

변환시키는 부분이다.

71~73 줄은 사용자 우선순위에 대한 설정으로 여러 스케줄러 파라미터를 scaling 할 시 더 잘

다룰 수 있도록 변환된 nice value 값을 설정하며 0~39 의 범위를 가진다.

그림 7 – 타임슬라이스 등 스케줄러 함수에 필요한 갖가지 정의

그림에서도 보이다시피, 이 설정에서 타임슬라이스 설정을 3 가지로 나누는데 최소

타임슬라이스는 5msec 로 1 jiffy 라고도 말을 한다. 기본설정은 100msecs 이고 최대

800msec 까지 설정이 된다. 그리고 CHILD 와 PARENT 에 대한 패널티 설정과 최대슬립시간

등이 정의 되는 모습이다. 참고로 타임슬라이스는 만기된 후에 다시 채워진다.

위의 코드에선 최대보너스 값과 최대평균수면시간공식도 확인할 수 있다. 최고 보너스는

유저 우선순위의 최대값과 우선순위에 따른 보너스 값(94 줄의 값:25)을 곱한 값을 100 으로

나눈 값을 가진다. 유저의 우선순위의 최대 값은 1 부터 시작할 시 40 을 가지게 되므로, 최대

보너스 값은 10 을 가지게 된다. 또한 최대평균수면시간 또한 최대 보너스 값 10 에 기본

타임슬라이스인 100Hz/1000 을 곱하여 1hz 즉 1 초의 값을 가지게 된다.,

Page 14: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

8

[email protected]

표 2 - 프로세스의 전형적 우선순위 값

101 번 줄부터는 대화식 프로세스인지 아닌지에 따른 타임슬라이스의 할당과 관련된 부분을

만나게 된다. 만약 태스크가 대화식태스크이면 그것을 타임슬라이스가 소진된 후, 다시 준비

큐의 활동적인 배열 안에 끼워 넣는다. 비록 즉시 구동되진 않지만, 다른 대화식태스크와

라운드로빈 방식으로 작동된다. 즉 다음의 예와 표 2 를 보면 더욱 쉽게 알 수 있을 것이다.

TASK_INTERACTIVE(-20): [1,1,1,1,1,1,1,1,1,0,0]

TASK_INTERACTIVE(-10): [1,1,1,1,1,1,1,0,0,0,0]

TASK_INTERACTIVE( 0): [1,1,1,1,0,0,0,0,0,0,0]

TASK_INTERACTIVE( 10): [1,1,0,0,0,0,0,0,0,0,0]

TASK_INTERACTIVE( 19): [0,0,0,0,0,0,0,0,0,0,0]

여기서 X 축은 -5~+5 까지의 동적인 순위를 의미하며, 여기서 의미하는 숫자 1 은 대화형

태스크를 의미한다. 여기서 +19 의 값을 가진 태스크는 절대로 „대화형‟에 충분한 활성배열에

포함될 수 없으며 반대로 -20 은 과다한 CPU 점유를 가지게 될 것이다. 0 은 물론 이 두 값의

중간자 역할을 지니게 된다.

Page 15: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

9

[email protected]

그림 8 - 프로세스 순위정의

이 부분이 끝나면, 이번엔 각각의 user-nice(일반프로세스의 우선순위)값들에 따른

타임슬라이스 값을 설정해주는 부분을 보여준다. 당연히 권한이 높으면 해당 권한에 맞게

높은 타임슬라이스 값을 가지게 된다.

그림 9 - 각 프로세스 순위에 따른 타임슬라이스할당

Page 16: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

10

[email protected]

3. Runqueue 구조체

이렇게 모든 define 이 완료된 후 이제부터 이 파일의 메인이 시작된다. 메인은 per-CPU

runqueue 데이터 구조에 대한 것으로, 최초 실행큐라는 이름의 구조체를 생성한다.

그림 10 – 실행큐 구조체의 일부

이 구조체는 SMP 설정을 했을 경우 따른 구조체와 SCHEDSTATS 의 설정이 되어있는 경우

기존 틀에서 구조체가 더 추가된다.

그림 11 - CONFIG_SMP시 추가되는 내용

SMP 설정이 되어있는 경우에는 /include/linux/sched.h 에 있는 sched_domain 구조체(CPU 의

최소/최대/최저휴식시간 등이 포함되어 있다.)와 /include/linux/list.h 에 있는 list_head 구조체를

포함하고 있다.

Page 17: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

11

[email protected]

실행큐는 데이터구조 중 매우 중요한 편에 속하니 해당 데이터구조의 필드를 열거해 보겠다.

표 3 - 실행큐 데이터 구조의 필드

Page 18: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

12

[email protected]

이 데이터 구조 중 아무래도 제일 중요한 부분은 실행큐의 가장 중요한 역할을 위한

프로세스의 리스트와 관련된 필드일 것이다. 프로세스는 한 실행큐에만 속하며, 한 실행큐에

머무는 이상 실행 큐를 소유한 CPU 에 의해서만 수행될 수 있다. 또한 데이터 구조를 보면

두 개의 prio_array_t 가 정의 되어 있는 것을 볼 수 있는데, name 을 확인하면 각각 active 와

expired 라는 두 가지 요소를 지니고 있다. 이 두 가지는 이중연결리스트로 되어있으며,

활성프로세스의 리스트와 만료된 프로세스의 리스트를 뜻하는 것으로 표로 나타내면 다음과

같다.

표 4 - 실행 가능한 프로세스들의 종류

종 류 설 명

활성 프로세스 실행 가능한 프로세스들은 자신의 Time-quantum을 쓰지 않아도 실행될

수 있다.

만료된 프로세스 이 실행가능프로세스들은 자신의 Time-quantum을 모두 소비하였기 때문

에 활동중인 프로세스가 만료될 때까지 실행이 금지된다.

또한 이 두 종류의 프로세스는 지속적으로 변경되기 때문에 두 개의 데이터 구조의 역할

또한 지속적으로 변경된다. 참고로 프로세스 리스트는 모든 프로세스 디스크립터를 연결하며,

실행큐(runqueue) 리스트는 모든 실행 가능한 프로세스(즉, TASK_RUNNING 상태)의 프로세스

디스크립터를 서로 연결한다. 두 경우 모두 init_task 프로세스 디스크립터는 리스트의 헤더

역할을 한다.

그림 12 – 프로세스 디스크립터들의 리스트 정의

이렇게 구조체와 몇 개의 정의가 추가되고 난 후, 문맥교환 관련된 정의를 실시한다.

그림 13 - CPU실행큐와 task실행큐를 정의하는 모습

Page 19: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

13

[email protected]

여기서 cpu_rq(cpu)의 define 을 통해 각각의 CPU 에 맞는 처리를 실시하고, task_rq(p)정의에서

쓰레드의 정보를 얻어와 그것을 현재 CPU 에 맞는 처리를 해서 값을 리턴 해준다.

그림 14 – 문맥교환 관련 설정부분

이제는 문맥교환부분인 task_rq_lock(현재의 실행큐를 잠그고, 보낸 태스크로 교체한다. 그리고

인터럽트 기능을 억제시킨다.)에 대한 설정을 실시한다.

그림 15 - task_rq_lock 정적 함수선언

이 부분에서는 runqueue_t 라는 함수를 정의한다. 이 함수 내부에는 runqueue 의 구조체와

local_irq_save 함수(하나의 데이터정의와 어셈블리어로 작성되어 있으며 flags 와 __dummy 라는

변수에 어셈블리어로 처리된 값을 넣어주고 flags 변수의 값을 리턴 시켜준다.)를 이용한

결과물을 사용하는 모습을 볼 수 있다.(__acquire 의 경우엔 332 줄의 __releases 를 확인할 때

볼 수 있다.) 그리고 마지막에 rq 값을 리턴 시켜준다.(rq 값은 task_rq 라는 298 줄에 define 되어

있는 내용을 통해 나온 결과값이다.)

Page 20: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

14

[email protected]

그림 16 - local_irq_save함수정의

그리고 inline 이라는 구조체를 사용하는 task_rq_unlock 이라는 리턴 값이 없는 함수를

정의한다.

Task_rq_unlock 은 첫 번째 매개변수로 바로 위에서 정의한 runqueue_t 구조체와

/fs/nfsd/export.c 내부에 있는 flags 구조체를 매개변수로 가진다.

그림 17 - flags 구조체

그리고 /include/linux/compiler.h 에 define 된 __releases(x)를 이용한다.

그림 18 - compiler.h에 정의된 releases의 모습

Page 21: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

15

[email protected]

여기서 __attribute__는 입출력 드라이버에 관련된 내용으로 입출력장치의 포트넘버와

비트넘버를 정의하고 각 SCI 에 맞는 설정을 해주는 모습을 볼 수 있다. 세세한 내용으로

들어가면 과제의 범위를 넘어가므로 생략한다. 그리고 뒤에 나온 context 는

/security/selinux/ss/context.h 에 구조체가 정의되어 있으며, u32 라는 임의의 데이터 형으로 user,

role, type 이라는 세 개의 변수를 보함하고 있다. 만약 설치 시 보안을 중시하는 SELINUX 를

활성화 시킨다면, mls_range 라는 구조체(/security/selinux/ss/mls_type.h)도 포함한다. 이제

스케줄링 상태를 보여주는 부분을 만나게 된다.

그림 19 - CONFIG_SCHEDSTATS시의 처리

이 부분의 처음은 CONFIG_SCHEDSTATS 가 설정되었을 시, 스케줄링 상태버전을 정의 하고,

show_schedstat 라는 구조체를 생성한다.이 구조체는 version 과, timestamp 를 출력하도록 되어

있다. 여기서 timestamp 를 출력 시 jiffies 란 변수 값을 확인하는데, 이 의미는 아래와 같다.

Jiffies(지피스)란?

시스템은 정확한 시간을 측정하기 위해서 소프트웨어적이 아닌 하드웨어적인 것을 이용해

야 한다. 시스템 타이머는 진동수(frequency)라고 하는 미리 프로그램 된 주기마다 인터럽트

를 발생시키는데, 이 인터럽트를 바로 시스템 타이머 인터럽트라고 하며, 그 인터럽트가 한

번 발생될 때마다 이 지피스라는 값을 1씩 증가시키게 된다. 진동수라는 것은 시스템마다

다르지만 보통 100Hz이며 이 것은 include/param.h에서 확인할 수 있다. 즉 이것은 1초에 진

동이 100번 있다라고 해석할 수 있으며, 또 1초에 100만큼의 지피스가 증가한다고도 볼 수

있다. 식으로 나타내면 1sec = jiffies/frequency로 표현이 가능하고 이 지피스 값을 이용해서

프로세스의 스케줄링이 얼마나 되었는지부터 기타 등등의 시간을 정확하게 알 수 있다. 이

지피스의 값을 /linux/jiffies.h에서 확인 가능 하다.

출처 : ko.wikipedia.org

Page 22: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

16

[email protected]

그리고 SMP 가 설정되었을 시에는 추가 구조체와 dcnt 라는 0 으로 초기화한 변수를 선언한다.

SMP란?

SMP(Symmetric Multi Processing)란 대칭형 멀티 프로세싱으로 여러 개의 CPU들이 메모리와

입출력 버스, 데이터경로를 공유해서 쓰는 경우, CPU 두 개가 마치 하나가 동작하는 것처럼

만들어주는 기술을 의미한다. 만약 SMP를 지원하는 프로그램을 사용시 해당 프로그램에

여러 개의 CPU가 하나처럼 연산을 수행하므로 효율성이 증가하게 된다. 하지만 CPU가 많

아지면서 들어가는 비용과 자원의 과다로 인해 효율성의 저하가 일어나게 된다. 그럼으로

인해 64개 이상은 MPP(Massively Parallel Processing:고도 병렬처리) 구조인 각자의 입출력 버

스를 가지는 구조를 이용해, 여러 프로세서가 각 부분을 동시에 수행시키는 방법을 사용한

다.

출처 : http://www.terms.co.kr

그 후 실행큐의 상태를 출력하는 seq_printf(/fs/seq_file.c, line 288 )에 정의된 문이 나타난다. 이

seq_print 는 첫 번째 인자로 seq_file 의 구조체를 *m 이라는 포인터 변수, 나머지를 인자로

받는다.

그림 20 - 실행큐 상태출력 부분

첫 번째 CPU(include/linux/cpu.h, line 28)를 시작으로 실행큐에서 각각의 인자를 받아서

출력한다. 그 다음 for 문을 이용해서 SCHED_IDLE 시점부터 MAX_IDLE 이 될 때까지

pt_gained 와 pt_lost 의 값을 출력한다. 이제 schedstat_open 이라는 정적 함수에 대해 알아보자.

이 함수는 inode 라는 데이터구조와 file 이라는 데이터구조를 변수로 한다. 우선 size 라는

변수를 선언하고 이 변수 내부에는 페이지크기와 „온라인상에 있는 프로세서들의 개수+1‟를

곱한 값을 할당한다.

Page 23: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

17

[email protected]

그림 21 - schedstat_open함수

그 후 buf 라는 문자형 포인터 배열을 선언하고 그 주소에 kmalloc(include/linux/slab.h, line

85 )의 함수처리 결과를 넣어준다.

그림 22 - kmalloc함수의 내용

그러면 이 kmalloc 의 내용에 대해 알아보자. kmalloc 함수는 할당된 메모리의 특성을 주거나

메모리 할당 시점에 처리 방식을 매개변수 값으로 줄 수 있다. 그 할당크기를 size 값으로

Page 24: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

18

[email protected]

지정해 줄 수 있고 이 사이즈 값은 32*size 값 을 최대 크기로 가진다. 두 번째 인자 값에는

일반적으로 아래의 3 가지 요소가 들어갈 수 있다.

표 5 - kmalloc함수에 들어가는 2번째 인자

GFP_KERNEL

. 동적 메모리 할당이 항상 성공하도록 요구한다

. 커널이 관리하는 메모리가 충분치 않을 경우에는(남은 메모리가

min_free_page보다 작을 경우) 디바이스 드라이버를 호출한 프로세스가 멈추

고, 동적 메모리를 할당할 수 있는 상태가 될 때까지 잠든다.

. kmalloc()함수를 인터럽트 서비스에 사용시 이 값을 사용하면 안 된다.

GFP_ATOMIC . 커널에 할당 가능한 메모리가 없으면 즉시 NULL을 반환한다.

GFP_DMA

. 연속된 물리 메모리를 할당 받을 때 사용한다.

. 디바이스 드라이버가 동작하는 메모리 공간은 물리적 메모리가 아닌 가상

주소 메모리이므로 실제 물리적 공간은 분할되어 있을 수 있다. 이럴 때는

GFP_DMA를 사용하면 된다.

즉 위의 예에선 커널에게 동적 메모리를 해당 사이즈만큼 요구하는 것이 된다. 그리고

buf 라는 포인터 변수에 해당 사이즈만큼의 동적 메모리를 할당한다. 그리고 res 라는

변수(residual: 잔여의)를 선언한다 그리고 if 문을 이용해서 동적 메모리 할당이 제대로 되지

않았으면, 메모리의 양이 충분치 않다는 리턴 값을 보내준다.

그 후 single_open 함수(fs/seq_file.c, line 354)를 이용해서 잔여메모리의 유무를 확인한 후, 해당

값이 없으면, 구조체로 만든 seq_file 형태의 포인터변수 *m 에 데이터와 값들을 넣어준다.

만약 잔여메모리가 있으면 kfree 를 이용(kmalloc 과 같은 기능을 가진다.)해서 버퍼

사이즈만큼 동적 메모리를 할당한다. 마지막에 잔여 메모리 사이즈를 리턴 한다.

그림 23 - 실행큐를 잠그고, 인터럽트를 실행 못하도록 하는 부분

이 부분은 실행큐를 잠그고(스핀 락 실시) 지역 CPU 의 인터럽트를 금지시키는 매크로를 호출

(local_irq_disable)한 후에 실행큐에 있는 현재 실행 중인 프로세서를 rq 변수에 넣어주고, 해당

Page 25: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

19

[email protected]

프로세서를 중지한 후, 그 프로세스의 정보를 리턴 시킨다. 두 번째 함수(451~455)는

spin_unlock_irq 매크로를 이용해서 스핀 락을 해제하고, 지역인터럽트를 허용해 준다.

스핀 락(Spin Lock)이란?

우선 스핀 락을 설명하기 전에 락킹에 대해 설명하겠다. 락킹은 광범위하게 사용하는 동기

화 기법으로 커널 제어 경로에서 공유 자료 구조에 접근해야 하거나 또는 임계 영역에 들

어가야 할 때 반드시 이에 대한 락을 획득해야 한다. 이 락킹 메커니즘을 통해 보호하는 자

원은 커널 제어경로가 자원에 접근하려고 할 때 자원을 사용할 수 있을 때만 성공한다. 아

래그림은 C1과C2는 각각의 프로세스가 있으므로 잠겨있고 C3는 열려있는 모습을 보여주는

그림이다.

그림 24 - 락을 여러 개 이용해 임계 영역 보호하기

스핀 락은 멀티프로세싱 환경에서 동작하도록 고안한 특별한 종류의 락이다. 커널 제어 경

로는 스핀 락이 열려 있으면 락을 획득하여 실행을 계속하고, 반대로 다른 CPU에서 실행중

인 커널 제어 경로에 의해 스핀 락이 잠겨있으면 락이 해제될 때까지 대기 명령어 루프를

실행한다. 참고로 스핀 락이 실행하는 명령어 루프는 “바쁜 대기”방식이고 스핀 락의 상태

값인 slock이 1일시 열림이고 0일시 잠김 상태를 나타낸다.

출처 : Understanding the Linux Kernel 3rd Editon, 215page, O’Relly

지역 인터럽트 금지(Local Interrupt Disabling)이란?

인터럽트 금지는 일련의 커널 코드를 임계 영역으로 다루게 하는 핵심 메커니즘 중 하나

로, 하드웨어 장치가 IRQ시그널을 발생시키더라도 커널 제어 경로는 계속해서 작업을 진행

할 수 있도록 해준다. 따라서 인터럽트 핸들러에서도 접근하는 자료 구조를 효과적으로 보

호하는 방법을 제공한다. 하지만, 지역 인터럽트 금지만으로는 다른 CPU에서 실행하는 핸

들러에서 동시에 자료 구조에 접근하는 것까지 막을 수는 없다. 따라서 멀티프로세서 시스

템에서는 이 지역 인터럽트 금지를 스핀 락과 함께 사용한다.

출처 : Understanding the Linux Kernel 3rd Editon, 231page, O’Relly

Page 26: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

20

[email protected]

다음부분은 활성배열로부터 프로세스를 dequeue 시킬 시 발생하는 함수로 해당프로세스에

CPU 를 할당한다. 만료된 큐에서 dequeue 와 requeue 를 할 필요 없이, 대화식태스크의 후에

해당 만료된 큐는 현재의 활성 큐가 비어진 후에 바로 활성 큐가 된다.

그림 25 - sched_info_dequeued함수

4. 우선순위설정부분

그 후 각 프로세스가 얼마나 CPU 할당을 기다렸는지에 따른 타임슬라이스를 할당하는

부분이 나온다.

그림 26 - 타임슬라이스할당부분

위와 같이 run_delay(실행지연시간)과 last_arrival(마지막으로 큐에 도착한 시간)을 이용해서

타임슬라이스를 할당하는 모습을 볼 수 있다.

그림 27 - sched_info_queued함수

그림 27 은 프로세스가 활성 또는 만료된 배열에 큐 되었을 때 호출되는 함수로 만약

타임슬라이스에 마지막으로 큐 된 기록이 없는 프로세스(신규)라면 이 마지막 큐 기록에

jiffies 값을 넣어준다.

Page 27: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

21

[email protected]

그림 28 - sched_info_depart함수

그림 28 는 활성화되어 실행중인 프로세스가 자발적(I/O,이벤트 등) 또는 비자발적

(타임슬라이스 만료 등)으로 중지되었을 때 호출 되는 함수로, 얼마나 오랫동안

실행되었는지를 계산할 수 있다. 함수에 나타난 것과 같이 마지막으로 배열에 도착한 시간을

jiffies 값에서 뺀 후, 그 변경 값을 cpu_time 값에 더해준다. 만약 실행큐에 있다면, 해당

내용을 실행큐의 cpu_time 에 적용한다.

그림 29 - sched_info_switch함수

그림 29 에서는 태스크 스위칭이 타임슬라이스 만료로 인한 비자발적으로 발생하였을 시

나타나는 함수이다. 두 개의 인자로 앞 요소(prev), 다음요소(next)를 받는다.

그림 30 – enqueue_task, dequeue_task 함수

Page 28: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

22

[email protected]

dequeue_task 함수는 프로세스 디스크립터를 실행큐에서 삭제하고 enqueue_task 함수는

프로세스 디스크립터를 실행큐에 넣는 작업을 실시한다. 그 후, 프로세스의 동적

우선순위(prio)와, 우선순위 비트맵(bitmap – 우선순위리스트가 비어있지 않으면 설정된다.)을

인자로 받는 __set__bit 매크로를 이용해서 비트를 set(1)하고 nr_active++;를 통해 리스트에

연결된 프로세스 디스크립터의 수를 1 증가시킨다.

그림 31 - enqueue_task_head함수의 정의

이 함수는 프로세스디스크립터를 실행큐의 제일 앞에 붙이게 해주는 함수이다. 이 함수는

원격 큐의 맨 앞부분에 있는 태스크를 해당 지역 큐의 맨 앞으로 옮길 때 사용하는 함수이다.

그림 32 – 동적 우선순위 설정 함수

동적 우선순위 설정 함수는 기존의 정적 우선순위에서 보너스/패널티에 의해 지속적으로

수정되는 값이다. 이 보너스/패널티 값은 평균수면시간을 이용해서 0 부터 최대 10 까지 값을

가진다. 이 정적 우선순위와 동적 우선순위에 대한 설명은 아래와 같다.

정적 우선순위(static priority): 프로세스 실행 중 우선순위가 변하지 않는 것으로 구현이

용이하고 동적 우선순위에 비해 오버헤드가 적다.

동적 우선순위(dynamic priority): 처음에 정해진 우선순위를 상황에 맞게 계속 조정하여

사용하므로 구현이 복잡하고 정적 우선순위에 비해 오버헤드가 크다.

이 동적인 우선순위는 최고 100 에서 최저 139 까지의 값을 가질 수 있다. 위 식에서도

보이다시피, 보너스 값은 “현재해당프로세스의 보너스 값에서 최대로 가질 수 있는 보너스

값을 2 로 나눈 값을 뺀 값”을 가진다. 그 보너스 값을 프로세스의 정적 우선 순위 값에서

빼주면 이 값은 동적 우선순위 값이 되게 된다. 보너스 값을 그림 6 에 나타난 것과 같이

Page 29: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

23

[email protected]

수면시간에 영향을 받게 된다. 아래 표는 평균 수면시간에 따른 보너스 값이다. 참고로

앞에도 나왔다시피, 평균 수면 시간은 1 초보다 큰 값은 불가능하다.(그림 7 의 97 번 줄참고)

표 6 - 평균수면시간에 따른 보너스 값, 타임슬라이스 입도

위 표는 이 평균수면시간에 따른 보너스 값을 확인할 수 있다.

그림 33 - 태스크를 실행큐에 넣는 부분

이제 태스크를 실행큐에 넣는 부분이다. 위에서도 보이다시피 두 가지 함수가 나타나 있는데

두 개는 각각 사용시기가 다르다. 첫 번째 함수는 일반적인 태스크를 실행큐에 넣는

부분으로, 이 부분은 위에서 정의한 enqueue_task 함수를 이용해서 프로세스 디스크립터를

실행큐에 넣어주고, 리스트에 연결된 프로세스 디스크립터를 한 개 증가시킨다. 그 후, rq-

>nr_runnung++;즉, 실행큐의 포인터에서 프로세스의 개수를 하나 늘려준다. 두 번째 함수는

쉬고 있는 태스크를 실행큐의 맨 앞부분에 붙이는 것으로 enqueue_task_head 함수를 이용해서

Page 30: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

24

[email protected]

실행큐의 맨 앞에 해당 태스크를 붙여주고 프로세스 디스크립터를 증가시킨 후, 실행큐의

프로세스의 개수를 하나 늘려주는 역할을 한다.

그림 34 - recalc_task_prio함수

이 함수는 recalc_task_prio 라는 함수 명에서도 유추할 수 있겠지만 태스크의 우선순위를 다시

계산하는 부분이다. sleep_time 이란 변수를 2 개 선언한 후 하나의 변수(__sleep_time)에는 현재

시간에서 해당 태스크의 타임스탬프 값을 뺀 값을 저장하고 이 값이 최대수면평균값(1 초)를

넘어간다면 다른 변수(sleep_time)에게 최대수면평균값을 부여한다. 만약 최대수면평균값을

넘지 않는다면, 첫 번째 변수의 값을 두 번째 변수에 할당한다.

그림 35 – 나노초 타이밍을 지피스로 바꾸거나 그 반대역할을 하는 매크로

662 줄부터는 프로세스가 mm(커널쓰레드)이고, activated != -1(TASK_UNINTERRUPTIBLE 에서

깨어났는 지의 여부-아래 표 7 참조)이고 수면시간이 프로세스의 주어진 기본수면시간 넘어서

계속 수면 상태인지를 확인한다.

만약 3 개의 조건이 모두 만족 시 해당 프로세스에게 수면평균시간(900)을 부여한다. 이

수면평균시간은 최대수면시간(1 초)-기본 타임슬라이스 값(100)을 뺀 값을 나노초로 환산한

Page 31: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

25

[email protected]

값(1000-100900)을 넣어주고,(나노초로 환산하는 매크로는 그림 32 에 정의되어있다.)

프로세스의 대화형 크레딧이 크레딧리미트(100)보다 크지 않다면, 대화형 크레딧 값을

1 증가시켜준다.

2 번에서의 판단 값이 false 일시에는 수면평균시간이 낮으므로 현재수면시간에 “최대 보너스

값(10)-현재프로세스의 보너스 값”으로 나온 값을 곱한 값을 수면시간으로 설정하여

수면시간을 증가시켜준다.

그리고 만약 프로세스가 로우크레딧이고 수면시간이 프로세스 타임슬라이스 값보다 크다면

수면시간에 대한 타임슬라이스 값을 할당해준다.

이 부분은 이렇게 대화형태스크에 맞게 수면평균시간과 수면시간을 조정하고 이 조정 값으로

프로세스의 우선순위가 결정되게 된다.

그림 36 – active_task함수

activate_task 함수는 실행큐로 옮겨진 태스크의 우선순위를 재설정해주는 역할을 한다.

코드에서도 보이겠지만 방금 전 페이지에서 나왔던

Page 32: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

26

[email protected]

recalc_task_prio (739 줄)의 함수를 볼 수 있다.

745 줄에서는 프로세스가 활동을 하지 않을 시에 대한 설정으로 만약 인터럽트가 걸려있는

중이라면 activated 에 2 를 할당하고 그렇지 않다면 1 을 할당해준다. 이 수치가 2 라는 뜻은

인터럽트처리 후에 바로 인터럽트로 인해 중지된 프로세스를 재개시킨다는 의미이다.(표 7

참조)마지막으로 프로세스 디스크립터를 활성 프로세스 리스트에 추가해준다.

그림 37 - deactivate_task함수정의

그림 36 은 실행큐로부터 해당 태스크를 지우는 함수이다.

그림 38 - task_curr함수

그림 37 은 태스크가 자기가 시피유를 사용하는지 물어보는 함수이다. 해당 함수는 리턴

값으로 시피유가 해당태스크를 사용하는지에 대한 응답 값을 넘겨준다.

그림 39 - migrate_task함수

840 줄에 나오는 이 함수 SMP 설정 시 설정되는 함수로, 현재 태스크의 실행큐를 잠궈둔다.

그리고 쓰레드가 옮겨지는 동안 기다리는 중이라면 리턴 값으로 TRUE 를 보내준다. 만약

해당 태스크가 실행큐에 없다면,(848 줄과 같이 배열에 없거나 해당 프로세스가 실행 중이지

않을 시) 해당 프로세스에 CPU 를 설정해준다. 실행큐에 있다면, init_completion

Page 33: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

27

[email protected]

(linux/completion.h)를 이용해서 큐를 중지시키고 태스크를 이동시킨 후,list_add 를 이용해서

migration_queue 를 리스트의 맨 앞에 삽입 시킨다.(앞에 인자가 리스트헤더 주소를 지정하고

있기 때문이다) 리턴 값으로는 1(TRUE)를 돌려준다.

그림 40 - wait_task_inactive함수

wait_task_inactive 함수는 활성상태의 태스크를 대기시켜주는 함수이다. 우선

task_rq_lock 함수를 이용하여 해당 프로세스가 있는 실행큐의 락을 획득하고, 만약 프로세스가

실행큐의 배열에 존재한다면, 프로세스를 실행상태의 정보를 preempted 변수에 저장하고 락을

해제한 후, cpu 를 대기상태(relax)상태에 둔다 만약 이 프로세스가 실행큐의 배열에 존재하지

않을 때까지 위의 행동을 반복하고. 배열에 존재하지 않는다면, 바로 실행큐의 락을

해제한다.

그림 41 - kick_process함수

kick_process 함수 또한 SMP 설정 시 함수가 설정되며, 실행 중인 쓰레드를 커널로 보내거나

커널에서 빼내는데 사용되는 함수이다.

우선 함수 실행 중 커널 선점이 발생하여 다른 프로세스에게 태스크가 할당되는 것을 막기

위해 선점카운터를 증가(locking)시킨다. 만약 프로세스가 할당 받은 CPU 가

Page 34: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

28

[email protected]

smp_processor_id 와 다르고, 해당 프로세스가 실행 중이라면, 시피유의 재 스케줄링을

실시한다. 완료된 후에는 선점카운터를 감소시킨다.

그림 42 - try_to_wake_up함수(1)

975 번 줄부터는 try_to_wake_up 이란 함수가 1124 번 줄까지 정의되어 있는 모습을 볼 수

있다. 150 줄에 다다르는 이 함수는 sleep 상태 또는 정지상태인 프로세스를 오버헤드 없이

프로세스의 상태를 TASK_RUNNING 상태로 변경시켜주고, CPU 의 실행 큐에 추가하여서

프로세스를 깨우는 역할을 한다. 만약 해당 태스크가 이미 활성화 되어 있다면 리턴 값으로

FALSE 를 반환한다. 그러면 해당 함수의 코드를 보도록 하자.

우선 cpu, flags, old_state 라는 변수를 지정, success 값을 0 으로 할당하고, 실행큐의

포인터변수를 선언해준다. 그 후 마지막으로 프로세스를 수행 중 이였던 CPU 가 소유한

실행큐를 잠그고 지역 인터럽트를 막기 위해 task_rq_lock 을 실시하고(1000 줄), 현재

프로세스의 상태를 old_state 변수에 저장해둔다. 여기서 if 문을 이용해 두 가지 상황으로

갈라지게 되는데, 만약 해당 프로세스가 실행큐의 배열에 위치한다면 out_running 으로

이동하게 된다.

cpu 변수에는 현재프로세스가 할당된 시피유에 대한 정보를 넣어주고, this_cpu 에는

smp_prrocessor_id 의 값을 할당하게 된다. 그 후 1093 줄까지는 SMP 설정 시의 내용이므로

생략하고 1094 줄부터 확인해보도록 하자.

Page 35: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

29

[email protected]

그림 43 - try_to_wake_up함수(2)

프로세스가 TASK_UNINTERRUPTIBLE 상태일 시 해당 실행큐(rq)의 nr_uninterruptible 필드를

감소시키고 프로세스 디스크립터의 activated 필드를 -1 로 설정한다, 다른 말로, TASK_

UNINTERRUPTIBLE 상태이며 깨어나는 중으로 해준다. 아래 표는 이 activated 필드에 대한

세부 설명이다.

표 7 - 프로세스 디스크립터에서activated필드의 의미

Page 36: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

30

[email protected]

그 후 active_task 로 실행큐로 옮겨진 태스크의 우선순위를 재설정해준다. 그 후 목표시피유가

지역시피유가 아니거나, sync 플래그의 설정이 되어 있지 않다면, 한번 더 if 문을 이용해서

새로운 실행 가능 프로세스가 실행 큐의 현재 프로세스보다 동적 우선순위가 더 높은지를

확인한다. 더 높다면 실행큐의 curr(현재프로세스)을 선점하기 위한 resched_task 함수

(kernel/sched.c, line 786)를 호출한다.

그림 44 - resched_task함수

만약 멀티프로세서라면 이 함수는 TIF_NEED_RESCHED 플래그를 검사하고, rq-

>curr 프로세스의 TIF_POLLING_NRFLAG 플래그가 지워졌는지 검사한다.

TIF_NEED_RESCHED 플래그는 set_tsk_need_resched()(include/linux/sched.h, 1025 줄) 함수를

호출해서 설정하는 모습을 확인할 수 있다.

그림 45 - set_tsk_need_resched함수

그리고 프로세스의 상태를 TASK_RUNNING 으로 변경 후에 task_rq_unlock()함수로 실행 큐의

잠금을 풀고 지역 인터럽트를 다시 허용시킨다. 프로세스가 성공적으로 깨어날 시엔 1 을

반환하고 실패 시 0 을 반환한다.

그림 46 - sched_fork함수

Sched_fork 함수는 fork 에 의해 생성된 프로세서의 스케줄러 설정을 하는 함수이다. 함수를

보면 처음 보는 fastcall 이라는 구문을 볼 수 있을 것이다. 이 fastcall 은 함수호출 규약인데,

Page 37: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

31

[email protected]

이런 함수호출 규약은 3 가지 방식이 있다. 표를 통해 간단히 함수호출 규약의 차이점에 대해

알아보자.

표 8 - 함수호출규약

함수호출규약방식 내 용

Stdcall

(Standard Calling Convention)

-함수 호출 받는 곳에서 스택 청소

-Stack을 이용해 아규먼트를 전달하며, 오른쪽에서 왼쪽으로

전달된다.(pascall방식(“왼쪽오른쪽”이였으나 변경되었다.)

Cdecl

(C calling Convention)

-함수 호출하는 곳에서 스택 청소

-Stack을 이용해 아규먼트를 전달하며, 오른쪽에서 왼쪽으로

전달된다.(코드사이즈 증가의 단점을 지니고 있음)

Fastcall -함수 호출 받은 곳에서 스택 청소

-Register를 이용해서 아규먼트를 전달한다.

다시 함수로 돌아와서, 우선 프로세스 디스크립터의 상태필드에 TASK_RUNNING 를

넣어주고, 프로세스의 실행리스트를 초기화 해준다. 그리고 해당 프로세스를 실행큐의 배열에

위치시킨다. 그 후 프로세스에 스핀 락 초기 설정을 해준 후, 지역인터럽트를 금지시킨다. 그

후 프로세스의 타임슬라이스, 타임스탬프 등 프로세스의 우선순위 설정을 위한 자료를

넣어주고 지역인터럽트를 다시 활성화 시켜준다. 참고로 타임슬라이스는 부모프로세스와

자식프로세스간에 공유한다.

그림 47 - wake_up_new_task함수

Page 38: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

32

[email protected]

wake_up_new_task 함수는 새로 생긴 태스크에 대한 문맥 설정 및 실행큐에 넣고 깨우는

작업을 실시해 주는 함수이다. 함수 실행 시, 실행큐를 잠그고, 목표시피유와 지역시피유에

대한 정보를 넣어준 후, 프로세스디스크립터의 상태필드가 TASK_RUNNING 상태가 아닐 시

BUG_ON 함수를 이용해서 커널버그로 판단하여 자식 프로세스를 죽이는 역할을 한다. 그 후

실행큐의 필드를 하나 증가시켜 준다.(1274 줄)그 후 그 자식 프로세스의 평균수면시간필드와

우선순위 필드 등을 설정해주고 지역시피유와 목표시피유가 동일할 경우이고 현재프로세스가

실행큐에 들어가 있다면 __active_task 함수를 실시하고, 그렇지 않다면 자식프로세스의

우선순위를 현재 프로세스의 우선순위와 동일하게 한 후, 실행리스트의 마지막에 위치시키고,

활성배열과 실행큐의 실행리스트를 증가시켜준다.

그림 48 - wake_up_new_task함수(아랫부분)

그림 47 은 이 함수의 자식프로세스의 설정이 끝나고 부모프로세스의 수면평균시간을 재설정

해주는 부분이다. 재설정의 이유는 부모와 자식프로세스는 다른 시피유할당관계를 가지고

있기 때문이다.

5. Context_switch(문맥교환)부분

그림 49 - context_switch함수

Page 39: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

33

[email protected]

1376 줄부터는 문맥교환부분이다. 이 부분은 프로세스가 사용하는 주소공간을 교체하는

작업을 실시하며 교체되어서 나갈 프로세스의 디스크립터인 prev 와 이제 새로 실행할

프로세스 디스크립터인 next 를 인자로 받는다. 우선 각각의 구조체를 정의한 후 새로 실행할

프로세스의 mm 포인터가 NULL 을 갖는다면(이전 프로세스와 주소공간이 동일하다면)

이전프로세스가 가졌던 oldmm 이라는 포인터 값을 active_mm 에 할당하고, atomic_inc 로

mm_count 를 하나 증가시켜준다. 그렇지 않다면, switch_mm 을 이용해서 주소공간을

교체해준다. 만약 현재 진행 중인 프로세스의 mm 포인터가 NULL 일 시에는 active_mm 에

NULL 을 할당하고, 기존에 정의한 구조체인 *oldmm 값을 실행큐의 prev_mm 에 할당해준다.

그리고 나서 switch_to 매크로를 실시하고 교체되어 나가게 된 프로세스의 디스크립터인

prev 를 리턴 시킨다.

그러면 이 switch_to 매크로(include/asm-i386/system.h, line 15 )의 내용을 알아보자.

그림 50 - switch_to매크로

이 부분은 각 CPU 에 맞게 어셈블리어로 제작되어 있다 이 부분에서 약간 이상한 점은 위

context_switch 함수에서 이 매크로를 호출 시에는 마지막 인자가 prev 인데 해당 매크로에서

받을 시에는 last 로 받는 모습을 볼 수 있다. 이유는 switch 되기 전과 후의 상태를 저장하기

위해 동일 변수 두 개를 받고 받을 시 변수 명을 바꿔준 것이다. 어셈블리코드를 보면,

27~30 줄까지가 output 와 input 인자를 정의한 것으로 0 부터 순서대로 숫자게 붙게

된다.(edi 는 예외)

이 내용을 알아두고 분석을 시작해보면, 최초에 스택 프레임의 베이스포인트를 저장하고,

현재 esp 의 값을 바뀌게 될 프로세스의 thread 의 esp 저장한다. 그 후, movl %5, %%esp 로 새로

실행할 프로세스 쓰레드의 esp 값이 esp 로 들어가게 되며, 그로 인해 esp 는 이제 next 의

스택을 가리키게 된다.

Page 40: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

34

[email protected]

그 후 next->thread.eip 를 push 하고 이곳으로 __switch_to 를 이용하여 타고나면 두 개의

프로세스의 스택이 바뀌게 되고, popl %%ebp 로 ebp 가 가리키는 스택도 새로운 프로세스의

스택으로 바뀌게 된다.

이 부분 이후로는 SMP 설정 시에 실행큐 두 개를 안전하게 락 거는 함수 등 멀티프로세서에

대한 함수정의가 나오니 생략하도록 한다.

그 부분을 쭉 넘어가서 만나게 되는 부분이 scheduler_tick 함수이다.

6. scheduler_tick 함수

그림 51 - scheduler_tick함수

이 함수는 현재 프로세스의 타임슬라이스 카운터를 낮추고, 프로세스에 할당량이

소진되었는지의 여부를 판단하는데 사용된다. 이제 이 함수의 코드를 하나하나 분석해보자.

우선 2291 줄은 실행큐의 time_stamp_last_tick 필드에 sched_clock 함수로부터 나오는 나노초로

바꾼 timeschedulercount 의 값을 저장해준다. 그 후 하드웨어적 인터럽트와 소프트웨어적

인터럽트가 있을 시에 각각 cpustat 의 인터럽트필드와 소프트웨어적 인터럽트 필드에

sys_tick 을 더해주고 sys_tick 을 0 으로 초기화 시켜준다.

Page 41: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

35

[email protected]

만약 프로세스가 실행큐에서 idle 상태를 가지고 있다면, 실행큐에 I/O 대기가 있는 지

확인해서 있을 시엔 oiwait 필드에 sys_tick 을 더해주고, 아닐 시에는 idle 필드에 sys_tick 을

더해준다. idle 상태에서 만약 실행큐가 스케줄링을 하고 있다면 out 으로 바로 이동을

실시하고 아닐 시에는 rebalance_tick 함수로 밸런싱을 다시 맞춰준다.

그리고 2322 줄부터는 프로세서의 배열필드가 실행큐의 활성프로세스 리스트를 가지고 있지

않다면, 프로세스는 타임슬라이스를 모두 소진한 것이지만, 바뀌지는 않고 재 스캐줄링을

위해 set_tsk_need_resched 이라는 함수를 호출하고 out 으로 바로 이동을 실시한다. 만약

리스트를 가지고 있다면 스핀 락을 이용해서 실행큐를 잠근다.

실행큐를 잠근 후, 현재 프로세스가 real-time 프로세스인지 확인 후 맞는다면

scheduler_tick 함수는 할 일이 없기 때문에 타임 퀀텀이 소진되었다고 판단하면, 최대한

프로세스가 빨리 선점되도록 하기 위해 아래의 작업을 실시한다.

프로세스의 policy 필드가 SCHED_RR 이고 타임슬라이스 카운터가 감소 및 퀀텀소진여부를

확인한다. 두 개 다 맞는다면 프로세스의 타임슬라이스에 이 프로세스의 태스크 타임

슬라이스를 부여하고, 프로세스의 처음 실행 시 설정되는 타임슬라이스가 0 으로 설정된 후,

재 스케줄링함수를 호출한다. 재 스케줄링이 끝난 후에는 활성프로세스 리스트에서 이

프로세스를 디큐 및 인큐시키고 out_unlock 으로 이동을 실시한다.

그림 52 - scheduler_tick(계속)

만약 real-time 프로세스가 아니라면 이 프로세스의 타임 슬라이스 카운터가 감소 되었는지와

퀀텀이 소진되었는지 여부를 확인 후 TRUE 가 나왔다면, 활성프로세스 리스트에서 이

프로세스를 디큐 시키고 재 스케줄링을 실시한 후 우선순위와 타임슬라이스를 재설정하고,

처음 실행 시 설정되는 타임슬라이스를 0 으로 설정한다. 만약 실행큐의

expired_timestamp 필드가 0 이라면, 현재 jiffices(틱)값을 이 필드에 넣어준다. 그리고

TASK_INTERACTIVE 하거나 실행큐에서 만료된 프로세스는 이 실행큐에 현재 프로세스를

인큐 시켜준다.

Page 42: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

36

[email protected]

그 후, 이 프로세스의 정적 우선순위의 값이 만료된 프로세스의 정적 우선순위 보다 크다면,

이 프로세스의 정적 우선 순위 값을 만료된 프로세스의 정적 우선순위에 부여한다. 만약

TASK_INTERACTIVE 하지도 않고 실행큐에서 만료된 프로세스가 오래 기다리지 않는다면

실행큐의 활성프로세스리스트필드에 이 프로세스를 넣어준다.(2636 줄)

타임퀀텀이 소진되지 않았을 시(2365 줄), 타임슬라이스를 적절히 검사해서 처리해준다.

그림 53 - scheduler_tick(마지막)

마지막 부분은 잠금을 풀어주는 부분과 리밸런싱을 시켜주는 작업을 가지며 마치게 된다.

7. schedule()함수

그림 54 - schedule()함수(00)

Page 43: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

37

[email protected]

이제 이 sched.c 의 메인이고 이 문서의 메인이라고 할 수 있는 schedule()함수를 만나게

되었다.

이 함수는 함수 명과 같이 스케줄러를 구현하는 함수이다. 스케줄러 구현은 실행 큐 리스트

중에서 실행할 프로세스를 찾아 CPU 를 할당한다는 의미이다. 이제 코드의 내용을 보면서 이

함수의 자세한 역할에 대해 알아보도록 하자.

우선 2543 줄을 보면 current 프로세스디스크립터의 exit_state 필드가 활성화 되어있고,

EXIT_DEAD 이거나 EXIT_ZOMBIE 상태(zombie 상태란 자식프로세스가 종료 되기 전에

부모프로세스가 종료되어 자식프로세스가 종료되었을 시 부모프로세스가 wait 상태가

아님으로 인해 PCB 를 가지고 있는 상태로 메모리에 상주되어 있는 상태를 말한다.)인지를

확인한 후, in_atomic()(include/linux/hardirq.h, line 65 )을 이용한 preemption 을 할 수 있는 지의

여부를 확인한다.

그림 55 - in_atomic()매크로

위 그림에도 나왔다시피 만약 CONFIG_PREEMPT 가 정의 되어있지 않다면, 단순히

preempt_count 가 0 인지의 여부만 확인한다. 만약 위의 정의가 이루어 졌다면,

preempt_count 내부의 PREEMPT_ACTIVE 를 clear(~PREEMPT_ACTIVE)시켜준 후 그 값을

kernel_locked (include/ linux/smp_lock.h, line 10 )과 비교하게 된다. !=이므로 당연히 같다면 0 을

리턴하고 다르다면 1 을 리턴 해준다.이 kernel_locked 는 smp 설정되어있지 않을 시에는

smp_lock.h 코드의 51 번째 줄과 같이 1 을 리턴 시키며, CONFIG_LOCK_KERNEL 이 정의

되었을 시에는 current 프로세스의 lock_depth 필드가 0 과 같거나 크다면 1, 작다면 0 을 리턴

해준다.

그림 56 - kernel_locked의 정의

참고로 아래 나온(12~13) get_kernel_lock 과 put_kernel_lock 은 커널에 대한 전역 lock 을

획득/해제하는 역할을 해준다.

Page 44: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

38

[email protected]

만약 in_atomic()값이 0 이라면 preemption 이 가능하다는 의미가 되며, 0 이 아닌 다른 값이

리턴 될 시에는 불가능하다는 의미로 커널에러 내용을 출력하고 이 프로세스의 스택을

덤프한다.

그림 57 - schedule()함수(01)

2554 줄부터는 커널 선점을 금지시키고(preemp_disable();), current(현재 프로세스)의 반환 값을

prev 에 저장하고, 이 프로세스가 커널 락을 가지고 있을 시 해제 하도록 한다. 그 후, 지역

실행큐의 정보를 rq 변수에 저장해둔다.

그 다음 현재 프로세스가 idle 상태이고 TASK_RUNNING 상태가 아니라면, 에러를 출력하고,

스택을 덤프한다.(2564~2567 줄) 2569 줄부터 실행큐의 스케줄링 카운터를 1 증가시켜준다. 그

다음, TSC 를 읽어서 나노초로 변환시켜주는 sched_clock 함수를 호출한 후 이 타임스탬프를

now 지역 변수에 저장한다. 그리고 now 에서 prev 의 timestamp 필드를 뺀 값이

NS_MAX_SLEEP_AVG 값보다 작다면 실행시간에 전자의 값을 넣어주고, 아닐 시에는 후자의

값을 넣어준다.

Page 45: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

39

[email protected]

그림 58 - schedule()함수(02)

prev 에 HIGH_CREDIT 이 부여되어 있을 시, 실행시간은 프로세스의 평균 수면시간을

반환하는 CREDIT_BONUS 를 이용해서 그 prev 에 맞는 값을 주고, 지역 인터럽트를

금지시키는 spin_lock_irq 을 실행한다.

그리고 종료 중일 프로세스일지도 모르므로, PF_DEAD 플래그를 조사하여 TRUE 일 시 상태

값에 EXIT_DEAD 를 지정한다.

그 후 switch_count 에 prev 의 nivcsw 필드 값을 넣어주고 이 prev 의 상태가 커널 모드에서

선점되지 않았다면 switch_count 를 prev 의 nvcsw 필드 값으로 수정한다.

그 다음 prev 의 상태가 TASK_INTERRUPTIBLE 이고, 프로세스에 블록 하지 않는 대기 중인

시그널이 있을 시에 이 프로세스의 상태를 TASK_RUNNING 상태로 설정하고 아니라면, 이

프로세스의 상태가 TASK_UNINTERRUPTIBLE 인지 확인 후 실행큐의 nr_uninterruptible 필드를

증가시킨다.

마지막으로 deactivate_task 함수를 이용하여 실행 큐에서 프로세스를 제거한다.

Page 46: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

40

[email protected]

그림 59 - schedule()함수(03)

만약 실행큐에 실행 가능한 프로세스가 없다면 다른 실행큐에서 이쪽 큐로 실행 가능한

프로세스를 옮기기 위한 idle_balance 함수(kernel/sched.c, line 2077 )를 호출해준다. 만약 이

함수가 프로세스를 옮기는 일에 실패하면 next 에 실행큐의 idle 상태정보를 넣어주고,

실행큐의 만료된 타임슬라이스 값을 0 을 준 후 wake_sleeping_depent 함수(kernel/sched.c, line

2400 )를 호출한다.

그림 60 - wake_sleeping_dependent함수

Page 47: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

41

[email protected]

위의 코드에서도 확인할 수 있다시피, 이 함수는 idle 상태의 시피유들의 실행 가능한

프로세스들을 다시 스케줄링 하도록 해준다.

다시 스케줄링을 실시한 후 프로세스에 실행 가능한 프로세스를 옮겼다면,

switch_tasks 부분으로 이동을 실시하게 된다.

그리고 애초에 실행 가능한 프로세스가 있었다면, dependent_sleeper 함수(include/linux/sched.h,

line 107 )를 이용해서 실행큐의 락을 재 획득한다. 만약 실행큐가 비어있을 시에는 idle loop 를

돌게 된다.

if 문이 끝난 후에는 실행큐가 실행 가능한 프로세스를 포함한 것이 되므로 실행 가능한

프로세스 중에 활성화된 것이 있는지를 검사하기 시작한다.

그림 61 - schedule()함수(04)

2635 줄에서 array 에 실행큐디스크립터의 활성프로세스필드 값을 넣어준다. 만약 이 배열의

nr_active 필드(활성화되어 있는 프로세스)의 값을 확인해서 활성프로세스가 없다면,

schedstat_inc 함수를 호출하여 실행큐의 스위치스케줄링필드(sched_switch)값을 하나

증가시켜주고, 실행큐의 expired 필드와 active 필드 값을 교환한다. 즉 만료된 프로세스는

활성화되고, active 필드 값은 비어있었으므로, 만료된 프로세스를 빈 상태에서 받을 준비를 할

수 있게 된다.

Page 48: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

42

[email protected]

만약 활성프로세스가 있다면(2646 줄)실행큐의 sched_noswitch 필드 값만을 증가시켜준다.

이제 이 활성화된 프로세스 리스트에서 실행 가능 프로세스를 추출할 시점이다. 우선 이

활성프로세스 배열의 bitmap 필드(비트마스크)를 확인하여, 0 이 아닌 첫 번째 비트를

찾는다.(sched_find_ first_bit 매크로(include/asm-i386/bitops.h, line 391))

그림 62 - sched_find_first_bit매크로

그림 61 에서 볼 수 있듯이, 각 bitmap 필드의 값을 확인하는 모습과 이 필드의 배열이 총

5 개로 이루어져 있음을 알 수 있다. __ffs 는 32 비트의 WORD 타입변수에서 1 로 설정된

최하위 비트의 인덱스를 반환해주는 함수이다. 참고로 비트마스크의 한 비트는 해당

우선순위 리스트가 비어있지 않을 시 설정된다. 그 후, 활성프로세스배열에서 큐 필드의 값을

위에서 찾은 최상의 프로세스리스트 의 첫 번째 프로세스 디스크립터 값을 합쳐서 재

설정해준다. 새롭게 정의된 큐 필드를 이용하여, 이 큐의 next 필드 값을 주소로 가지는

실행리스트(run_list)인 list_head 필드를 포함한 task_t 구조의 주소 값을 next 변수에

지정해준다.즉, prev(이전실행프로세스)를 대체할 프로세스의 디스크립터 주소를

저장한다.(list_entry 함수)

이제는 이 next 가 전통프로세스이고(!rt_task(next)), TASK_INTERRUPTIBLE 이거나

TASK_STOPPED 상태이면(표 7 참조), delta 변수에 now 변수에서 next 의 timestamp 필드 값을 뺀

값을 할당해주고 새로 실행할 프로세스의 배열을 array 변수에 저장하고, 그 프로세스를 디큐

시킨다.(dequeue_task(next, array) 그 다음, 그 프로세스가 실행큐에 추가된 이래 지난 시간을

평균 수면시간에 더해준다. 그렇게 정의된 우선선위를 가진 프로세스를 다시 인큐 시켜준다.

그 다음 프로세스를 TASK_RUNNING 상태로 변경한다.

Page 49: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

43

[email protected]

그림 63 - schedule()함수(05)

이 부분부터는 프로세스전환(switch_tasks)부분이다. 즉, 다음에 수행할 프로세스를 결정하는

부분이다. 처음에 실행할 프로세스가 idle 상태이면 실행큐의 sched_goidle 의 값을

증가시켜주고, prefetch(next)( include/asm-i386/processor.h, line 634)라는 매크로를 볼 수 있다.

그림 64 - prefetch매크로

이 부분은 next 프로세스 디스크립터의 첫 번째 필드 값을 하드웨어 캐시에 놓으라는 명령을

실시하는 부분이다. 이 부분은 schedule()함수의 능률향상을 위해 있으며, 다음 명령이 수행될

때까지 병렬로 디스크립터 자료가 이동하므로 next 에 영향을 미치지 않는다.

다시 switch_tasks 로 돌아와서, clear_tsk_need_resched()함수(include/linux/sched.h, line 1030)를 만날

수 있다.

그림 65 - clear_tsk_need_resched함수

Page 50: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

44

[email protected]

이 함수는 clear_tsk_thred_flag 함수를 호출해서, TIF_NEED_RESCHED 플래그를 지워주는 일을

수행한다. 그 다음에 나오는 함수인 rcu_qsctr_inc 함수(include/linux/rcupdate.h, line 115)는

CPU 가 침묵상태로 가는 중임을 나타내준다.

그림 66 - rcu_qsctr_inc함수

그 다음 schedule()함수는 prev 프로세스가 사용한 시간(run_time)을 평균수면시간에서 빼주고,

이 변경된 평균시간이 0 이거나 0 보다 작다면, 평균시간을 0 으로 설정해주고, 여기서 만약,

prev 프로세스가 HIGH_CREDIT 이거나 LOW_CREDIT 이면 interactive_credit 을 감소시킨다.

그리고 프로세스의 timestamp 값을 갱신시켜준다.

sched_info_switch(kernel/sched.c, line 543)함수는 앞에서 설명한 것과 같이 비자발적인

문맥교환시 실행하는 함수이다. 앞에서 설명했으므로, 자세한 내용은 생략한다.

prev 가 활성프로세스와 우선순위가 같거나 또는 prev 보다 높은 우선순위의 프로세스가 없을

경우, prev 프로세스가 next 프로세스일 확률이 높기 때문에 두 가지 경우로 나눠서 처리를

실시한다.

만약 prev 와 next 가 다른 프로세스라면, 실제로 프로세스의 전환이 일어나게 된다.

next 의 타임스탬프 값을 갱신해주고 실행큐의 context_switch 의 횟수를 증가(nr_switches 필드를

증가)시켜준 후, next 프로세스를 실행큐의 현재프로세스로 설정하고 switch_count 포인터

변수를 증가시켜준다.

그림 67 - schedule()함수(06)

Page 51: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

45

[email protected]

그 다음 prepare_arch_switch 매크로(kernel/sched.c, line 305)를 만날 수 있다. 이 매크로는

i386 환경에서는 별다른 일을 수행하지 않는다.

그림 68 - prepare_arch_switch,finish_arch_switch매크로

이 매크로는 context switching 을 하기 위한 아키텍처에 의존한 준비과정을 실시한다, 그

다음의 context_switch 함수(kernel/sched.c, line 1377)는 실제로 문맥교환을 실시하는 부분이다.

이 부분은 앞에서 설명하였으므로 이 문서의 앞부분을 참조하면 된다.

Barrier()함수는 메모리의 UPDATE 의 완전성을 보장하기 위한 함수이다. 마지막으로

finish_arch_switch()매크로를 호출해서 실행큐의 락을 해제하고, 문맥교환을 완료하게 된다.

8. waitqueue(대기큐)관련 함수

그림 69 - wake_up_common함수

schedule 함수 다음에는 대기 큐 설정에 관련된 wake_up 함수들이 있다. 함수 명만 봐도 알 수

있듯이, 대기 큐의 프로세스를 깨워서 TASK_RUNNING 상태로 만들어 주는 역할을 하는

함수이다. 다들 거의 비슷한 기능을 수행하므로 우선 처음으로 볼 수 있는 것은

wake_up_common 함수만을 분석해보도록 하겠다. 우선 list_for_each_safe 매크로

(include/linux/list.h, line 360)가 호출된다. 이 매크로는 task_list 라는 이중연결리스트를 검색을

실시한다.

그리고 list_entry 를 이용하여, 각각의 task_list 요소마다 wait_queue_t 의 주소를 계산하여

curr 에 넣어준다.그 다음에 이 curr 의 func 필드에 저장되어 있는 대기 큐에 식별된

프로세스를 깨우는 함수의 주소를 이용하여, 프로세스가 정상적으로 깨어났고 깨어난

프로세스가 배타적 프로세스이면(flags & WQ_FLAG_EXCLUSIVE) break 를 만나고, 루프가

종료된다.

Page 52: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

46

[email protected]

참고로 wake_up 이나, wake_up_locked 함수도 wake_up_common 함수를 호출해서 처리하는

방식이기 때문에 핵심적인 내용은 동일하다.

그림 70 - wake_up과 wake_up_locked함수

wake_up 관련 함수들이 끝나고 나면 sleep_on 관련 함수들을 볼 수 있다.

그림 71 - sleep_on함수

내용을 보면 define 된 것들을 호출하는 모습을 볼 수 있다. 이 define 내용은 2872 줄부터

나와있다. 그 부분을 참조해서 어떤 역할을 하는지 알아보겠다.

그림 72 - sleep_on함수관련 매크로

Page 53: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

47

[email protected]

위 정의화면을 보면 알 수 있듯이 우선 init_wait_queue_entry 를 이용해서 대기 큐를 초기화

해준다. 그 다음, 현재프로세스의 상태를 TASK_UNINTERRUPTIBLE 상태로 설정하고, 대기

큐에 이 프로세스를 할당한 후 schedule 함수를 호출하고, 스케줄링을 하여 다른 프로세스가

실행된다. sleep 상태의 프로세스가 깨게 되면, 대기 큐에서 해당 프로세스를 없애는 역할을

한다.

9. 시스템 콜 관련 함수

그림 73 - set_user_nice함수

sleep_on 형식의 함수들의 정의가 끝나면 set_user_nice 함수를 만나게 된다. 이 부분은 최초

프로세스의 NICE 값이 같이 넘어온 인자인 nice 값과 같거나, nice 값의 한계 값(-20~19)을

넘어가게 되면 아무런 처리 없이 리턴 시킨다. 만약 위의 사항에 맞지 않는다면 실행큐의

락을 획득한다.

만약 이 프로세스가 실행중인 프로세스라면 프로세스의 정적 우선순위필드에 NICE 값을

우선순위로 변경하여 넣어준 후, out_unlock 으로 이동을 실시한다. 아니라면, 이 프로세스가

활성프로세스배열에 위치하고 있다면 이 프로세스를 디큐 시켜준다. 프로세스의 현재

우선순위는 old_prio 에 저장하고 nice 값으로 받는 우선순위는 new_prio 에 넣어서 후자에서

전자를 뺀 후, 그 값을 서로 빼서 delta 에 할당해준다.

Page 54: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

48

[email protected]

그림 74 - set_user_nice(이어서)

그 다음 프로세스의 정적 우선순위필드에 nice 값을 기준으로 우선순위를 설정해서 할당해

주고, 위에서 계산한 delta 값과 프로세스의 원래 우선순위 값을 더해서 새로운 우선순위를

설정해준다. 이 우선 순위 후 활성프로세스배열에 들어갈 수 있게 된다면, 인큐를 실시한다.

인큐를 실시 후, 태스크의 우선순위가 증가됐거나, 프로세스를 실행하고, 우선순위가

낮아졌다면, resched_task 함수를 이용하여 다시 재 스케줄링을 실시한다. 마지막으로,

해당실행큐의 락을 해제하면서 이 함수를 종료한다.

그림 75 - sys_nice함수

Page 55: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

49

[email protected]

이 함수는 함수명과 같이 시스템 콜이다. 위에 set_user_nice 함수를 설명했기 때문에 특별히

설명할 내용은 없지만, 간단히 다른 점을 설명하자면, 이 함수는 프로세스가 자신의 기본

우선순위를 바꿀 수 있게 해주고 실제로 유닉스 명령어인 nice 가 이 함수를 이용한 명령어로

사용자가 스케줄링 우선순위를 바꿀 수 있게 해주는 역할을 지니고 있다.

이 sys_nice 함수 뿐이 아니라, 계속해서 시스템 콜 함수들이 나오게 된다. 중간에 일반

함수인 setscheduler 함수가 나오게 되는데 scheduling 정책을 바꿔주는 역할을 하는 함수로

지금 커널분석과는 상관성이 떨어지므로 생략한다. 시스템 콜 함수들에 대한 간단한 설명을

하면 다음과 같다.

표 9 - 실시간 프로세스 관련 시스템 콜

시스템 콜 함수 내 용

sched_getscheduler pid매개 변수로 지정한 프로세스에 현재 적용하는

스케줄링 정책을 설정

sched_setscheduler pid매개 변수로 지정한 프로세스에 현재 적용하는

스케줄링 정책을 설정

sched_getparam pid에 해당하는 프로세스의 스케줄링 인자를 이용하여

current프로세스의 스케줄링 인자를 얻어온다.

sched_setparam sched_setscheduler와 비슷하지만, policy필드 값을 설정하지 않는다.

sched_yeild 프로세스를 „일시 중지‟상태를 만들지 않고

자발적 반납을 하도록 설정

sched_get_priority_min policy매개변수를 이용한 스케줄링 정책의 실시간

정적 우선순위의 최소값을 반환

sched_get_priority_max policy매개변수를 이용한 스케줄링 정책의 실시간

정적 우선순위의 최대값을 반환

sched_rr_get_interval pid매개변수로 지정한 실시간 프로세스의 라운드로빈 타임퀀텀을

사용자 모드 주소 공간에 있는 구조체에 기록한다.

시스템 콜 함수들을 보다 보면 중간에 io_schedule 을 볼 수 있다.

Page 56: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

50

[email protected]

그림 76 - io_schedule함수

이 함수는 task 가 IO 상태로 변환되면서 수면 상태가 되면 실행큐의 nr_iowait 의 값을

증가시키고 실행할 프로세스를 위해 스케줄링함수를 호출한다. 스케줄링 함수가 종료되면

수면상태를 종료하고 nr_iowait 값을 감소시킨다. io_schedule_timeout 함수는 프로세스의 수면이

종료하게 되었을 때 사용되는 함수이다. 여기서부터 뒷부분은 화면출력(show_task,

show_status)함수 와 멀티프로세싱일 때 (CONFIG_SMP)의 thread 이동 등에 대한

함수(migration_thread 등), sched_domain 함수 등이 기술되어 있어 주제와는 거리에 멀기 때문에

사실상 프로세스 스케줄링에 대한 코드는 끝났다고 할 수 있다.

그림 77 - 멀티프로세싱일 시 사용되는 함수의 일부(cpu_attach_domain)

Page 57: Linux Kernel Analysispds4.egloos.com/pds/200707/20/74/linux_kernel_process...iv n0fate@xstone.org 그 림 목 차 그림 1 - Linux의 탄생과정 1 그림 2 - 프로세스스케줄링

51

[email protected]

III. 참고문헌 및 사이트

Understanding the Linux Kernel, 2nd / 3rd Edition, O‟Relly 3 장(프로세스), 7 장(프로세스스케줄러)

www.term.co.kr – SMP 란?

http://blog.naver.com/lazylune?Redirect=Log&logNo=50016986136 – NUMA 에 대한 설명

ko.wikipedia.org(위키대백과사전) – jiffices 란?