728x90

커널레벨(kernel Level)쓰레드와 유저 레벨(User Level)쓰레드

쓰레드를 생성해주는 대상은 커널일 수 있다.

이러한 경우 운영체제가 제공하는 시스템 함수 호출을 통해 쓰레드 생성을 요구해야한다. 이후 운영체제는 해당 쓰레드를 생성 및 관리하면서 새로운 흐름을 형성하도록 도와준다.

 

프로그래머 요청에 따라 쓰레드 생성 및 스케줄링하는 주체가 커널인 경우, 이를 가리켜 커널 레벨(kernel Level)쓰레드라 한다.

 

유저  영역(User 영역)은 사용자에 의해서 할당되는 메모리 공간이다. 코드 영역, 데이터 영역, 스택 및 힙 영역을 가리켜 유저 영역(User 영역)이라 한다.

 

커널 영역은 하낭에 프로세스에 할당된 총 메모리 공간 중에서 유저영역을 제외한 나머지 영역을 커널 영역이라 한다. 운영체제라는 하나의 소프트웨어를 실행시키기 위해서 필요한 메모리 공간을 커널영역(kernel 영역)이라 한다.

 

쓰레드에게 일을 시키기 위한 코드는 프로그래머가 개발하므로 쓰레드 A, B, C의 실행코드는 유저영역에 존재할 것이다. 스케줄러와 쓰레드(스케줄링 하는데 필요한 쓰레드 정보)는 커널영역에 존재한다. 이것이 커널레벨 쓰레드의 유형이다.

 

유저레벨(User Level)쓰레드

커널에 의존적이지 않은 형태로 쓰레드의 기능을 제공하는 라이버리를 활용할 수 있는데, 이러한 방식으로 제공되는 쓰레드가 유저레벨 쓰레드이다. 커널에서 제공하는 기능이 아니므로 실행시 유저영역에서 실행된다.

 

위 그림은 쓰레드를 지원하지 않는 운영체제에서의 유저레벨 쓰레드 모델을 적용한 그림이다. 

운영체제가 쓰레드를 지원하지 않기 때문에 스케줄러가 스케줄링하는 대상은 프로세스이다. 쓰레드를 스케줄링하는 스케줄러는 유저영역에서 실행된다. 유저레벨 쓰레드 모델을 적용할 경우, 운영체제는 쓰레드의 존재를 알지도 확인하지도 못한다.

 

커널모드(kernel Mode)와 유저 모드(User Mode)

Windows 운영체제는 동작할 때 커널모드와 유저모드 중 한가지 모드로 동작한다.

 

메모리는 활용대상에 따라서 유저영역과 커널영역으로 나뉜다. 유저영역은 사용자가 구현한 프로그램 동작시 사용하게 되는 메모리영역이다.

커널영역은 운영체제 동작시 사용하는 메모리 영역이다. 커널이 쓰레드를 지원할 경우 쓰레드 관리가 커널영역에서 이뤄지기 때문에 커널레벨 쓰레드 모델이라 하고, 커널이 지원하지 않을 경우에 라이브러리를 통해서 제공받아야하는데 이러한 경우 유저영역에서 쓰레드의 관리가 이뤄지기 때문에 유저레벨 쓰레드 모델이라한다.

 

유저영역에서의 메모리 참조 오류는 실행중인 프로그램에만 영향을 미치게 되지만, 커널 영역은 커널 코드가 실행되는 영역이므로 시스템 전체에 영향을 줄 수 있다.

 

일반적인 프로그램은 유저모드에서 동작한다. 그러다가 커널이 실행되어야 하는 경우에는 커널모드로의 전환이 일어난다. 즉 커널영역에서 실행해야할 경우 커널모드로 전환된다. 예를 들어 프로세스가 정해진 타임슬라이스가 지나고 스케줄러가 동작하려 할때 커널모드로의 전환이 일어난다. 스케줄러는 커널의 일부에 해당하기 때문이다.

 

커널모드와 유저모드의 차이점

 프로세스가 유저모드에서 동작할 때에는 커널영역으로의 접근이 금지된다. 유저모드에서 실행중인 프로그램이 커널영역으로 접근을 시도하면 시스템에서 오류가 발생함을 알리고 접근을 봉쇄한다.

그러나 커널 모드에서 동작할 때에는 모든 영역의 접근이 허용된다. 스케줄러의 경우 커널영역에서 커널모드로 동작하지만 유저모드의 프로세스들을 스케줄링한다.

 

windows 운영체제 차원에서 제공하는 시스템 함스들은 커널의 구동을 필요로 한다. 따라서 이러한 함수들을 호출할 때마다 모드의 전환(커널모드 <-> 유저모드)가 발생한다. 이러한 모드의 전환은 시스템에 부담을 주기 때문에 상황에 따라 적절한 반영이 요구된다.

 

커널레벨 쓰레드와 유저레벨 쓰레드의 장점 및 단점

커널레벨 쓰레드의 장점 및 단점

장점 : 커널에서 제공해주기 때문에 안전성 및 다양한 기능을 제공받을 수 있다.

단점 : 커널에서 제공해 주는 기능이기 때문에 유저모드에서 커널 모드로의 전환이 빈번하게 일어난다. 따라서 성능의            저하를 발생시킬 수 있다.

 

유저레벨 쓰레드의 장점 및 단점

장점 : 커널은 쓰레드의 존재조차 모른다. 오로지 유저 모드로 동작하기 때문에 유저 모드에서 커널 모드로의 전환이              필요 없다. 때문에 성능이 좋다.

단점 : 예를 들어 하나의 프로세스 내에 3개의 쓰레드 A,B,C가 있다. 이중 A쓰레드가 시스템 함수를 호출했는데 커널에            의해서 블로킹 되었다. 이럴 경우 B, C도 실행되지 않는다. 운영체제는 프로세스의 존재만 알지 쓰레드의 존재를            모른다.때문에 A쓰레드가 속해 있는 프로세스 전부가 블로킹이 된다.

728x90
728x90

멀티 프로세스 기반 프로그램

서버와 접속용 클라이언트 프로그램이 있다고 가정할 때, 일반 사용자들은 서버 접속용 클라이언트 프로그램을 통해 서버에 접속하여 특정 서비스를 요청한다. 서버는 이러한 요구를 처리하기 위해서 요청이 있을때 마다 자식 프로세스를 생성한다. 동시에 둘 이상의 접속자에게 원할한 서비스를 제공해 주기 위함이다.

 

멀티프로세스 운영체제 기반 프로그램의 문제점과 새로운 제안

둘 이상의 실행 흐름을 위해 프로세스를 추가적으로 생성하는 작업은 매우 부담스럽다. 많은 수의 프로세스 생성은 빈번한 컨텍스트 스위칭(Context Switching)으로 이어져 성능에 영향을 줄 수 있다.

 

컨텍스트 스위칭 : 프로세스의 상태 정보를 저장하고 복원하는 일련의 과정이다.

 

컨텍스트 스위칭의 빈도수는 시스템에 따라 다르지만 못해도 초당 수십회 이상 발생한다. 따라서 이러한 컨텍스트 스위칭은 성능 저하의 원인이 된다.

 

하나의 프로그램 내에서 둘이상의 실행흐름을 두기 위해 등장한 것이 쓰레드이다. 쓰레드는 프로세스 처럼 각각 독립된 구조가 아니다. 즉 쓰레드들 사이에는 공유하는 요소들이 있다. 쓰레드는 공유하는 요소가 있어서 컨텍스트 스위칭에 걸리는 시간이 프로세스보다 짧다.

 

쓰레드를 생성할 때마다 해당 쓰레드의 스택은 새로 생성해주고, Code 영역, Data 영역, Heap영역은 부모 쓰레드와 공유한다.

 

쓰레드의 특성 1 : 쓰레드마다 스택을 독립적으로 할당해준다. 쓰레드는 Code영역, Data영역, Heap영역을 공유하고 Stack영역은 독립적으로 할당된다. 스택은 함수호출시 전달되는 인자, 되돌아갈 주소 값 및 함수 내에서 선언하는 변수등을 저장하기 위한 메모리 공간이다. 이 메모리 공간이 독립적이라는 뜻은 추가적인 실행 흐름을 만들 수 있다는 의미가 된다. 즉 실행 흐름의 추가를 위한 최소조건이 독립된 스택의 제공이다.

 

쓰레드의 특성 2 : 코드영역을 공유한다.

위 그림은 코드영역을 공유하는 것을 보여준다. 프로세스 main 하나와 쓰레드 main 두개가 있다. 결과적으로 프로그램의 실행 흐름은 총 3개가 된다.

 

쓰레드의 특성 3 : 데이터 영역과 힘을 공유한다.

쓰레드간에 힙과 데이터 영역을 공유하기 때문에 힙이나 데이터 영역을 통해 쓰레드 간에 서로 통신하는 것이 가능하다. 즉 전역변수와 malloc 함수를 통해서 동적 할당된 메모리 공간은 공유가 가능하다.

데이터 영역과 힙의 공유가 좋은것만은 아니다. 메모리 영역을 공유하다 보면 문제가 발생할 수도 있기 때문이다.

 

컨텍스트 스위칭이 빨라진 쓰레드

쓰레드 컨텍스트 스위칭은 프로세스 컨텍스트 스위칭에 비해 빠르다. 그 이유는 공유하는 영역이 많아서 이다.

또한 세부적인 요소들에는 레지스터들이 있다.

PC(Program Counter) : PC는 다음 실행해야할 명령어의 위치를 가리킨다. 쓰레드는 코드영역을 공유하기 때문에 PC는 컨텍스트 스위칭을 하더라도 영향이 없을거라고 생각하기 쉽다. 그러나 PC는 프로그램 실행 흐름과 관련이 있다. 쓰레드 별로 main 함수를 독립적으로 가지고 있고, 함수 호출도 독립적으로 진행되기 때문에 쓰레드별로 PC가 가져야할 값이 다르다. 그렇기 때문에 쓰레드 컨텍스트 스위칭에서 PC는 프로세스 컨텍스트 스위칭과 같다.

 

fp(Frame Pointer), SP(Stack Pointer) : 쓰레드별로 별도의 스택을 가지고 있기 때문에 fp, spp 의 경우도 컨텍스트 스위칭이 발생한다.

 

범용 레지스터 : 보통 연산을 위해 임시 데이터 저장소로 쓰인다. 연산은 프로그램 흐름에 따라 진행되므로 당연히 컨텍스트 스위칭 시 레지스터들도 컨텍스트 스위칭이 발생한다. 그러나 전역으로 선언된 변수를 할당 하기로 결정하였다면, 쓰레드의 컨텍스트 스위칭시 전혀 영향을 받지 않을 것이다.

 

Windows에서의 프로세스와 쓰레드

Window에서 프로세스는 쓰레드를 담는 상자이다. 실제 프로그램의 흐름을 형성하는 것은 쓰레드 이기 때문이다. window 운영체제에서 프로세스는 상태(Running, Ready, Blocked)를 지니지 않는다. 상태를 지니는 것은 프로세스가 아니라 쓰레드이다.

 

즉 쓰레드가 입출력에 관한 연산을 할 경우 Blocked상태에 놓이게 된다. 쓰레드는 입출력 연산이 끝나면 Ready상태가 되고, 그다음  Running 상태가 된다.

스케줄링 알고리즘 또한 프로세스가 아닌 쓰레드 기반으로 작동한다.

 

728x90
728x90

함수 호출규약 : 함수 호출시 인자를 전달하는 방식과 스택 프레임을 반환하는 방식

 

_cdecl, _stdcall + &

함수 선언부에 주로 존재하는 _stdcall 이라는 키워드는 함수 호출규약을 지정하는 것이다. _stdcall 호출 규약에 따라서 STDCallFunction함수의 호출과 반환을 처리하라는 뜻이다.

ex) int __stdcall STDCallFunction(int a, int b, int c);

 

WINAPI, APIENTRY, CALLBACK 등의 키워드는 아래와 같이 정의되어 있는 매크로이다.

#define CALLBACK  __stdcall

#define WINAPI  __stdcall

 

Windows 시스템 함수 선언에서는 키워드 __stdcall를 직접 사용하지 않는다. CALLBACK이나 WINAPI라는 또다른 이름을 부여해서 그 함수의 특성 파악에 도움을 주도록 하고있다.

 

ex)int CALLBACK EventRoutine(void);

CALLBACK은 실제로 __stdcall로 정의되어 있으므로, EventRoutine이라는 함수는 __stdcall호출 규약을 따를 것이다. 또한 콜백(CallBack)함수임을 파악할 수 있다. 함수 호출 규약이 선언되어 있지 않은 함수들은 디폴트 속성으로 선언된다.

 

콜백(CallBack)함수란, Windows 시스템에 의해 자동으로 호출되는 함수를 의미한다. 특정 상황에서 호출되어야 할 함수를 등록시키는 것이 가능한데, 이때 등록이 되는 함수를 가리켜 콜백 함수라 한다.

 

 

Calling Conventions

Segment

Word Size

Calling

Convention

Parameters

in rgisters

Parameter order on stack

Stack

Cleanup by

32bit

__cdecl

 

C

Caller

__stdcall

 

C

Function

__fastcall

ecx,edx

C

Function

__thiscall

ecx

C


Function

64bit

windows

(MS, Intel)

rcx/xmm0,

C

Caller

rdx/xmm1,

r8/xmm2,

r9/xmm3,

Linux, BSD

(GNU, Intel)

rdi, rsi,

C

Caller

rdx, rcx, r8

r9, xmm0-7

32비트 기반 함수 호출규약

__cdecl은 C/C++의 디폴트 호출규약이다. 인자 전달 방식은 C언어 스타일을 따르는데, C언어 스타일은 전달되는 인자가 스택에 쌓이는 방식을 의미한다. 반환 시에는 함수를 호출하는 호출자가 스택프레임을 반환한다.

__stdcall와 __cdecl의 차이점은 스택프레임을 반환하는 주체이다.

__stdcall은 호출된 함수 내에서 스택프레임을 반환하도록 정의되어 있다.

__cdecl은 호출자가 스택 프레임을 반환한다.

 

__fastcall은 말그대로 함수 호출을 빠르게 처리하기 위한 호출규약이다. __fastcall은 함수 전달인자를 두개까지 레지스터를 사용한다. 첫번쨰 전달인자는 ecx, 두번쨰 전달인자는 edx를 통해 저장된다. 두개가 넘어서면 스택을 사용한다. 이 호출규약에서 레지스터를 사용하는 것이 함수 호출이 빨라지는 근거가 된다.

 

64비트 기반 함수 호출 규약

Windows기반에서는 총 8개의 레지스터를 활용해서 전달되는 인자로 저장하게 되는데, 실제로 레지스터에 저장되는 전달인자 개수는 4개에 지나지 않는다. rcx/xmm0는 첫번째 전달인자가 rcx 혹은 xmm0레지스터에 저장된다는 것을 의미한다. 총 4개의 전달인자까지만 레지스터를 통해 처리한다.

Linux 또는 BSD에서는 최대 14개의 인자까지 레지스터를 통해 처리하는 경우도 있다.

728x90
728x90

코드 영역은 프로그램이 동작하기 위한 프로그램 코드(컴파일 된 명령어들의 집합)가 올라가는 위치이다. 프로그램을 실행시키면 위와같은 메모리 구조가 형성되고 코드 영역에, 실행되어야할 명령어들이 올라가서 순차적인 실행이 이루어지게 된다.

 

명령어의 실행은 세단계(Fetch, Decode, Execution)로 구분되어 진행된다. 이중 첫번째 단계는 Fetch인데 이것은 명령어를 CPU내부로 가져오는 단계이다. 컴파일된 프로그램 코드가 코드영역에 올라간 다음부터 Fetch, Decode되고 Execution되는 것이다.

 

명령어 길이가 4바이트라고 하고, 실행 중인 프로그램이 현재 1036번지에 있는 명령어라면, 다음 번에는 1040번지에 있는 명령어가 Fetch 되어야 한다.

 

CPU가 메모리 영역 중 스택을 컨트롤 하기 위해서 SP레지스터를 두었던 것처럼, 명령어를 순차적으로 fetch하기 위해서 프로그램 카운터라 불리는 "PC 레지스터"를 둔다.  CPU는 Fetch, Decode, Execution 과정을 계속해서 진행하도록 구현되어 있기 때문에, Fetch 연산이 일어날때마다 자동적으로 PC값이 증가한다.

 

프로그램 카운터(PC)

위 그림에서 IR Register를 보여주는데 IR Register는 명령어를 가져오기 위해서 사용되는 레지스터 이다.

 

 

코드영역에서 함수 호출

위그림의 오른쪽은 컴파일된 바이너리 코드가 실행을 위해 코드영역에 올라가있는 모습이다. 함수 호출시 실행 흐름의 이동은 Program Count PC에 의해 이루어진다. 함수 호출전 PC의 값(return address)을 스택에 저장해 두고 이후 함수 호출로 인해 이동해야할 주소값을 저장해두면 자연스럽게 실행의 위치는 이동하게 된다.

 

728x90
728x90

함수 호출 인자의 전달방식

전달되는 인자가 함수 내에서는 유효하고, 함수 호출이 끝나면 사라지는것으로 봐서 지역변수처럼 스택에 할당된다고 할 수 있다. 그러나 모든 전달인자들이 반드시 스택에 할당되는 것은 아니다. 성능향상을 위해 일부 전달인자들은 레지스터에 저장하는 경우도 있다.

 

PUSH & POP 명령어 디자인

"SP가 가리키는 현재 위치에 전달되는 인자값을 저장하고 나서, SP를 증가시켜 다음 메모리 주소를 가리키게 한다."

이와 같은 인자 전달 연산을 위해 명령어를 구성해 보자.

 

STORE 명령어는 레지스터에 저장된 데이터를 메모리에 저장하는 명령어이다.

STORE   대상(레지스터), 목적지(메모리 주소)

 

prob1) 명령어 조합을 통해서 숫자 7을, SP가 가리키는 메모리 위치에 저장하라.

1.숫자 7을 레지스터에 저장한다. 이후 레지스터를 STORE의 피연산자에 숫자 7의 값을 가지고 있는 레지스터를 넣을 수 있다.

ADD r1, 7, 0

 

이제 r1을 피연산자로 둘 수 있다.

 

2.SP가 주소 정보를 담고 있어야 한다. 이때 Indirect 모드를 사용한다.

STORE SP, 0x40 명령어를 통해 SP에 0x40을 대입한다.

 

3.Indirect모드를 통해 0x40번지를 참조하여 데이터를 저장할 수 있다.

STORE r1,[0x40]

 

"숫자 7을 SP가 가리키는 메모리 위치에 저장하라"의 명령어 구성은 

ADD r1, 7, 0

STORE sp, 0x40

STORE r1, [0x40]

으로 구성할 수 있다.

위과정 이후 반드시 SP레지스터 값을 증가시켜야 한다. 다음에 들어오는 데이터를 저장하기 위해서다.

ADD sp, sp, 4

최종적인 명령어 조합은 아래와 같다.

 

ADD, r1,7,0

STORE sp, 0x40

STORE r1, [0x40]

ADD sp, sp, 4

 

PUSH & POP

왼쪽 그림은 명령어 PUSH이다. 데이터를 스택에 넣고자 하는 경우 사용한다.

ex) "PUSH 0x02"   or    "PUSH r1"

SP값을 참조하여 해당 위치에 데이터 0x02 or r1의 값을 저장하고 SP의 값 또한 자동으로 증가시키는 명령어이다.

 

오른쪽 그림은 명령어 POP이다. 스택에 가장 마지막에 들어간 데이터를 꺼낸다는 것은 메모리에서 삭제함을 의미한다. 즉 SP를 감소시키는 것이다. 32bit 환경에서 SP의 증가와 감소는 4바이트 단위로 이루어진다.

POP 명령어와 같은 일을 하는 명령어는 다음과 같이 구성할 수 있다.

"ADD sp, sp, -4"   or "SUB  sp, sp, 4"

 

C코드를 어셈블리 코드로 바꾸기

1.PUSH fp를 통해 이전 스택 프레임 포인터를 스택에 저장한다.

2. ADD fp, sp, -4는 1.의 PUSH fp를 통해 SP가 4 만큼 증가되어 있으므로 -4한 값을 fp에 저장한다.

3.PUSH 7

4. PUSH 8

3.PUSH 7과 4.PUSH 8은 각각 함수의 전달인자로 넣어준다.

728x90
728x90

ATPCS(ARM-Thumb Procedure Call Standard)

이는 함수의 전달인자와 리턴 어드레스(함수 호출이 완료되고 나면 돌아갈 주소)를 레지스터에 저장하기로 결정하고, 저장 방식에 대한 표준을 정의한 것이다. 이 표준을 고려하여 ARM코어의 레지스터들도 디자인 되어 있고, ARM컴파일러도 이 표준에 맞게 바이너리 코드를 생성하도록 디자인 되어있다.

 

스텍 프레임(Stack Frame)구조

함수 호출 과정에서 할당되는 메모리 블록(지역 변수 선언으로 인해 할당되는)을 가리켜 스택 프레임이라 한다. 위 그림을 보면 main함수에 변수 a와 b가 선언되어 있다. 따라서 스택에도 a, b가 할당되어 main 함수의 스택 프레임을 구성한다.

 

return에 의해 함수 호출이 완료되면 해당 함수의 지역변수에는 접근이 불가능하다. 할당되었던 메모리가 반환되었기 때문이다.

 

SP(Stack Pointer)레지스터

지역변수를 저장하는 메모리 공간을 스택이라 이름 붙이는 이유는 메모리의 구조적 특성(First In, First Out)때문이다.

스택 프레임은 가장 먼저 할당되면 가장 나중에 반환된다. 그리고 가장 나중에 할당되면, 가장 먼저 반환된다.

 

스택에 데이터를 쌓거나 반환하기 위해서는 현재 어느 위치까지 데이터를 저장했는지 기억해야 한다. 이것을 하는 것이 CPU내에 존재하는 SP(Stack Pointer)이다.

지역 변수는 a,b,c ... 순으로 스택에 할당된다. SP 레지스터 값은 이렇게 변수가 하나씩 할당될 때마다 증가한다. 증가하면서 다음 변수가 할당될 메모리 위치를 가리킨다. 함수가 종료할 때에도 SP레지스터 값을 이동시켜야 한다. 호출된 함수가 종료될 경우 그 함수 내에서 선언된 변수들을 동시에 모두 반환해야 하기 때문이다.

 

함수가 종료되고 스택 프레임 단위로 SP를 아래로 이동시킬 때는 얼마만큼 SP를 이동시켜야 하는지 알 수가 없다.

이를 해결해 주는 것이 프레임 포인터 레지스터이다.

 

프레임 포인터(Frame Pointer) 레지스터

되돌아갈(함수 호출 이전의) SP 위치를 저장해 놓으면 함수 반환시 스택 프레임 단위로 SP위치 조절이 가능하다. 이 역할을 하는 레지스터를 가리켜 fp(Frame Pointer)레지스터라 한다.

 

sp레지스터에 저장된 값을 fp레지스터에 저장하는 상황을 보여준다. fct1에서 많은 변수를 선언하더라도 fp에 저장된 값을 참조해서 fct1함수 호출 이전 위치로 sp를 이동시킬 수 있다.

스택프레임 포인터를 이용하여 정확히 함수의 스택 프레임만 반환할 수 있게 된 것이다.

 

 

fp 레지스터의 문제점

위처럼 두번쨰 함수 호출시 sp레지스터가 이전 fp값을 덮어버리기 때문에 문제가 발생한다. fct1의 값이 사라져 레지스터 값을 참조할 수 없게 된다.

 

스택에 저장하자, 프레임 포인터(Frame Pointer)

여러 함수를 호출할 때 fp를 덮어쓰는 문제점을 해결하는 방법은 덮어 쓰기 전에 fp에 저장된 값을 다른곳에 저장해 두는 것이다. 즉 함수 호출이 일어날 때마다 fp레지스터에 저아되어 있는 값을 스택에 저장하는 것이다. 그리고 이후에 새로운 값으로 fp레지스터를 채운다.

1.fct2함수가 호출되기 직전에 sp레지스터에는 주소값 20이 들어가 있다. 현재 스택 주소를 가리키는 것이다.

2.fct2함수가 호출되기 직전에 fp레지스터에는 주소값 8이 들어가 있다. fct1의 스택 프레임 포인터이다.

3.fct2 함수가 호출되면서, fp레지스터에 저장된 값(주소값 8)을 현재 sp레지스터가 가리키는 위치 20번지에 먼저 저장한다. 그다음 fp레지스터에 sp레지스터 값 20을 저장한다.

4.fct2 함수 호출이 완료되어 반환한다면, fp레지스터에 저장된 값을 참조해서 sp레지스터 값을 20으로 변경한다. 이는 fct2함수의 스택 프레임을 날리는 것이다.

5.현재 sp레지스터가 가리키는 위치(주소 20번지)에 저장되어 있는 값을 fp레지스터에 옮겨다 놓는다. 이로써 fct1함수 호출이 완료되는 상황에서 sp의 위치를 8번지에 가져다 놓을 수 있게 된다.

 

728x90
728x90

멀티 프로세스 기반 운영체제는 실행중인 프로세스들에게 골고루 CPU를 할당하는 일이 필요한데, 운영체제의 일부분인 스케줄러가 이러한 일을 담당한다.

 

일반 OS와 리얼타임(Real-Time)OS의 차이점

RTOS와 일반 OS의 차이는 응답성(응답 속도)에 있다.

RTOS는 어떠한 작업이 생기기만을 기다리다가 작업이 생기면 그 일을 끝낼때 까지 다른일은 하지 않는다.

그렇기 때문에 RTOS는 응답성은 매우 좋지만 범용적인 OS에 비해 사용하는 영역이 제한적이다.

 

일반 OS는 여러가지 일(다양한 프로세스)을 동시에 진행하기 때문에 응답성은 조금 떨어지지만 범용적으로 사용이 가능하다.

 

RTOS는 일반 OS에 비해 사용되는 목적이 구체적이고 제한적이여서 단순하게 디자인 되어있고 일반 OS에 비해서 훨씬 가볍다.

 

SoftRTOS vs Hard RTOS

SoftRTOS의 경우 일반 OS와 별 차이가 없다. 하는일이 특화되어 일반 OS에 비해 응답성이 좋은 것 뿐이다.

 

전통적인 RTOS는 Hard RTOS를 말한다. Hard RTOS에서 중요한 것은 데드라인(Dead Line)이다. 자동차 또는 핵발전소 등등 데드라인이 중요한 시스템에서 데드라인을 충족시키는 능력이 있는 RTOS를 Hard RTOS라고 한다.

 

선점형(preemptive)OS와 비선점형(Non-Preemptive)OS

비선점형 OS는 현재 실행중인 프로세스 보다 높은 우선순위의 프로세스가 등장한다고 해서 실행의 대상을 바로 변경하지 않는다. 새로 등장한 우선순위의 프로세스가 실행될려면 현재 실행중인 프로세스가 CPU를 양보하거나 블로킹 상태에 놓일 때까지 기다려야 한다.

 

선점형 OS는 현재 실행중인 프로세스보다 높은 우선순위의 프로세스가 등장하면 스케줄러에 의한 실행순서 조정이 적극적으로 가해진다. 비선점형 OS에 비해 스케줄러가 하는일이 많다. 그리고 우선순위가 높은 프로세스가 먼저 실행되는 구조이다. Windows도 선점형 OS이다.

 

우선  순위(Priority) 스케줄링 알고리즘

우선순위 스케줄링 알고리즘이란 각각의 프로세스마다 우선순위를 부여해서 우선순위가 높은 프로세스를 먼저 실행시키는 방식이다. 우선순위가 7인 프로세스가 실행중일 때 우선순위가 2인 프로세스는 결코 실행되지 않는다. 이러한 상황을 기아(Starvation)상태라 한다.

우선순위 스케줄링 알고리즘은 우선순위가 높은 프로세스를 먼저 실행하는 알고리즘이다. 우선순위가 높은 프로세스가 종료되어야 다음 우선순위 프로세스가 작동한다.

그러나 기아(Starvation)상태에 빠지는 것은 드물다. 높은 우선순위 프로세스가 I/O작업을 하는 과정에서 우선순위가 낮은 프로세스가 실행기회를 얻을 수도 있기 때문이다.

 

라운드 로빈(Round-Robin) 스케줄링 알고리즘

우선순위가 동일한 프로세스 간에 실행시간 배분을 위해 Windows는 라운드 로빈 스케줄링 알고리즘도 적용하고 있다.

라운드 로빈 알고리즘은 같은 우선순위 프로세스들에게 정해진 시간 간격 만큼만 실행을 하고 우선순위가 동일한 다른 프로세스에게 CPU할당을 넘기는 방식을 제공한다.

실행의 최소 시간간격을 가리켜 퀀텀(Quantum)혹은 타임 슬라이스(Time Slice)라 한다. 동일한 우선순위의 모든 프로세스는 이 타임 슬라이스 기준으로 CPU할당을 넘긴다.

 

Windows 운영체제는 프로세스를 스케줄링 하는데 있어서 우선순위, 그리고 라운드 로빈 기반의 알고리즘을 적용하고 있다.

 

스케줄링알고리즘에 의해서 스케줄링이 진행되는 시점

스케줄러가 언제 동작하는지 이해하는 것도 중요하다.

라운드 로빈 알고리즘에 의하면 프로세스의 실행시간 간격에 해당하는 매 타임 슬라이스 마다 스케줄러는 동작해야한다.

 

우선순위 알고리즘 관점에서는 새로운 프로세스가 등장할 때 마다 스케줄러는 현재 실행중인 프로세스와 새로운 프로세스를 비교해야 한다. 즉 스케줄러는 새로운 프로세스가 생성될 때 마다 동작해야 한다. 또한 실행중인 프로세스가 종료되면 다른 프로세스를 실행시켜야 하므로 이 경우에도 스케줄러가 동작해야한다.

 

블로킹 상황에서는 현재 실행중인 프로세스가 블로킹 상태에 놓이면, 다음 실행될 프로세스 선정을 위해 스케줄러가 동작한다.

 

Windows 프로세스 우선순위

 Windows는 총 6단계의 우선순위 계층을 제공한다.

         Priority                                                             Meaning

IDLE_PRIORITY_CLASS                                             기존 우선순위 4

NORMAL_PRIORITY_CLASS                                      기존 우선순위 9

HIGH_PRIORITY_CLASS                                            기존 우선순위 13

REALTIME_PRIORITY_CLASS                                      기존 우선순위 24

 

 

 

 

프로그램 코드상에서 실행속도 조절을 원한다면 Sleep함수의 호출을 통해서 Blocked상태로 만드는것이 좋은 방법이다.

비록 잠시이긴 하지만 자신보다 우선순위가 낮거나 같은 프로세스에게 Running기회를 넘겨주기 때문이다.

728x90
728x90

Windows의 파이프 메커니즘의 두가지 종류.

1.이름없는 파이프(Anonymous Pipe)

2.이름있는 파이프(Named Pipe)

 

메일 슬롯과 파이프

메일슬롯은 네트워크로 연결되어 통신하는 프로세스들이나, 부모 자식간의 연관 관계가 전혀 없는 프로세스들 사이에서 통신 할 때 유용한 IPC 기법이다. 이름없는 파이프는 관계가 있는(부모 자식 관계, 형제 관계)프로세스들 사이에서 통신하는 경우에 유용하다.

 

이름있는 파이프, 이름이 있다는 것은 주소정보가 있다는 뜻이다. 즉 메일 슬롯처럼 서로 관계가 없는 프로세스들 사이에서도 주소정보를 공유하여 데이터를 주고받을 수 있다는 뜻이다. 또한 메일 슬롯과 달리 양방향 통신이 가능하다.

 

메일슬롯은 단방향 통신만을 지원하지만 브로드 캐스트(Broad Cast)방식의 데이터 전송이 가능하다. 따라서 여러사람에게 동시에 메시지를 전달하는 시스템 구현시 메일슬롯이 유용하다.

 

특징 정리

메일슬롯 : 단방향 통신 방식, 브로드캐스트 전송가능, 메일슬롯에 주소가 할당되어 주소를 기반으로 통신하기 때문에                   관계없는 프로세스들 사이 통신가능

이름없는 파이프 : 단방향 통신 방식, 파이프를 통해서 생성된 핸들 기반 통신 -> 프로세스들 사이에 관계 필요

이름있는 파이프 : 양방향 통신 방식, 브로드 캐스트 X

 

이름없는 파이프(Anonymous Pipe)

BOOL CreatePipe(

    PHANDLE hReadPipe,

    PHANDLE hWritePipe,

    LPSECURITY_ATTRIBUTES lpPipeAttributes,

    DWORD nSize

);

1.hReadPipe : 파이프는 생성시 두개의 핸들을 얻게된다. 각각 데이터가 들어가는 쪽과 나가는 쪽이다. (데이터를 쓰는쪽, 읽는 쪽) 첫번째 인자는 데이터를 읽기위한 파이프 쪽의 핸들을 얻게된다.

2.hWritePipe : 데이터를 쓰는쪽에 해당하는 핸들을 얻는다.

3.lpPipeAttributes : 보안관련 정보

4.nSize : 파이프의 버퍼 사이즈를 지정하는 용도이다. 0을 전달하면 디폴트 사이즈로 버퍼크기가 결정된다.

 

파이프 생성시 생성되는 출력용핸들, 입력용 핸들을 상속한다면 부모 자식 프로세스간 메시지 전송이 가능해진다.

 

 

이름있는 파이프(Named Pipe)

1.CreateNamedPipe 함수를 통해 이름있는 파이프 생성

2.ConnectNamedPipe 함수 호출을 통해 파이프 연결 요청을 기다리는 상태로 변경

3.클라이언트에서 CreateFile함수를 통해 파이프와 연결

 

CreateNamedPipe 함수

HANDLE CreateNamedPipe(

      LPCTSTR   lpName,

      DWORD  dwOpenMode,

      DWORD  dwPipeMode,

      DWORD   nMaxInstances,

      DWORD   nOutBufferSize,

      DWORD   nInBufferSize,

      DWORD nDefaultTimeOut,

      LPSECURITY_ATTRIBUTES   lpSecurityAttributes

);

1.lpName : 파이프 이름 지정 ex)\\.\pipe\pipename

2.dwOpenMode : 파일 모드 지정과 유사

      _PIPE_ACCESS_DUPLEX : 읽기, 쓰기 모두 가능

      _PIPE_ACCESS_INBOUND : 읽기만 가능

      _PIPE_ACCESS_OUTBOUND : 쓰기만 가능

 

3.dwPipeMode : 데이터 전송 타입, 데이터 수신 타입, 블로킹 모드 세가지를 설정한다.

4.nMaxInstance : 생성할 수 있는 파이프의 최대 개수를 지정한다.

                      PIPE_UNLIMITED_INSTANCES가 전달되면 생성 가능한 최대 개수만큼 생성된다.

5.nOutBufferSize : 이름 있는 파이프의 출력 버퍼 사이즈를 설정한다. 0입력시 Windows 디폴트 값 설정

6.nInBufferSize : 이름 있는 파이프의 입력 버퍼 사이즈 지정 0입력시 Windows 디폴트 값 설정

6.nDefaultTimeOut : WaitNamedPipe함수에 적용할 기본 만료시간을 밀리세컨드 단위로 설정한다.

7.lpSecurityAttributes : 보안 속성 지정

 

dwPipeMode 설정

데이터 전송 방식 : PIPE_TYPE_BYTE(바이트), PIPE_TYPE_MESSAGE(메세지) 데이터 전송시 바이너리 형태로 전송할 것인지, 메시지 방식으로 전송할 것인지를 결정한다. PIPE_TYPE_MESSAGE 전달시 텍스트 모드로 전송한다.

 

데이터 수신 방식 : PIPE_READMODE_BYTE, PIPE_READMODE_MESSAGE 바이너리 방식으로 읽을 것인지, 메시지 방식으로 읽을 것인지를 결정한다.

 

함수 리턴 방식 : PIPE_WAIT(블로킹), PIPE_NOWAIT(넌-블로킹) 무조건 PIPE_WAIT전달

 

각 모드들은 or연산으로 조합할 수 있다.

 

NamedPipe 사용시 유용한 함수들

connectNamedPipe 함수

BOOL ConnectNamedPipe(

        HANDLE hNamedPipe,

        LPOVERLAPPED lpOverlapped

);

1.hNamedPipe : CreateNamedPipe에서 생성된 파이프 핸들을 전달한다.

2.lpOverlapped : 중첩 I/O를 위한 전달인자이다.

 

WaitNamedPipe함수

BOOL WaitNamedPipe(

    LPCTSTR lpNamedPipeName,

    DWORD nTimeOut

);

1.lpNamedPipeName : 상태확인의 대상이 되는 파이프 이름

2.nTimeOut : 타임-아웃 시간 설정

 

SetNamedPipeHandleState 함수

BOOL SetNamedPipeHandleSTate(

     HANDLE hNamedPipe,

     LPDWORD lpMode,

     LPDWORD lpMaxCollectionCount,

     LPDOWRD lpCollectDataTimeout

);

1.hNamedPipe : 파이프와의 연결 속성 변경을 위한 핸들 지정

2.일기모드와 함수 리턴방식에 대한 값을 OR(|)연산하여 전달

3.lpMaxCollectionCount : 서버로 데이터를 보내기에 앞서서 버퍼링할 수 있는 최대바이트 크기 지정

4.lpCollectionDataTimeout : 서버로 데이터를 보내기에 앞서서 버퍼링허용 최대시간 지정

 

728x90

+ Recent posts