운영체제

[운영체제] 프로그램과 프로세스

ssh9308 2024. 5. 10. 09:00
반응형

프로그램(Program) 이란?

 

프로그램은 하드디스크와 같은 저장장치에 저장된 명령문의 집합체를 뜻한다.

애플리케이션이나 앱이라고도 불리고 windows 운영체제에서는. exe 확장자 파일로 존재한다.

프로그램은 컴퓨터 관점에서 하드디스크 즉, 저장 장치만 사용하는 수동적인 존재이다.

 

 

 

프로세스(Process) 란?


실행중인 프로그램이라고 간단하게 표현할 수 있다.

실행 중인 프로그램이란 하드디스크에 저장된 프로그램이 메모리에 올라갔을 때 

실행 중인 프로그램, 즉 프로세스라고 불린다.

프로세스는 메모리도 사용하고 운영체제의 CPU 스케줄링 알고리즘에 따라서 CPU 도 사용하고 

 

필요에 따라 입출력을 하기 때문에 능동적인 존재라고 말할 수 있다.

 

 

 

프로세스의 구조 

 

프로세스는 Code 영역, Data 영역, Stack 영역, Heap 영역이 존재한다.

 



Code 영역에는 자신을 실행하는 코드가 저장되어 있고 

Data 영역은 전역 변수와 Static 변수가 저장되어 있다.

Stack 영역에는 지역변수와 함수 호출을 했을 때 필요한 정보들이 저장된다.

Heap 영역은 프로그래머가 동적으로 메모리를 할당하는 데에 쓰이는 영역이다. 

 

c언어에서 malloc(), free() 함수를 호출하면 

Heap 영역에서 자원을 할당/해제할 수 있다.

 

 

 

 

프로세스가 되는 과정

 

프로그램이 프로세스가 되는 과정을 C언어를 통해서 이해해 보자.

 

아래와 같은 C 언어 코드가 있다고 가정해 보자.

 

#include <stdio.h>

int main()
{
    int num_1 = 5;
    int num_2 = 3;

    int res = num_1 + num_2;

    return 0;
}

 

 

위의 프로그램은 간단한 덧셈 알고리즘이다.

기본적으로 C언어는 스크립트언어가 아니라서 컴파일이 선행되어야 실행이 가능해진다.

 

컴파일 과정은 먼저 전처리기를 거쳐서 매크로로 정의한 숫자를 치환하고 필요한 파일을 불러오면서 시작한다.

 

전처리기를 거친다면 파일의 확장자는 ". i"가 된다.

 

그다음 컴파일러가 컴파일을 진행한다.

컴파일을 마치면 고수준인 C언어를 저수준 언어인 어셈블리어로 바꿔준다.

어셈블리어는 명령어가 기계어랑 일대일 매칭이 되기 때문에 기계어와 가장 가까운 언어라고 볼 수 있다.

컴파일러를 거치면 파일의 확장자는. s 가 된다.

이제 어셈블러가 어셈블리어를 기계어로 바꿔준다.

그럼 해당 파일은 0,1로 이루어진 기계어로 구성되고 파일의 확장자는. o 가 된다.

기계어로 구성되어 있기 때문에 파일을 텍스트 에디터로 열어보면 글씨가 깨져서 보인다.

마지막으로 링커가 링킹을 한다.

링킹이란 여러 가지 라이브러리나 다른 소스코드를 연결하는 것이다.

이렇게 링킹까지 거치면 윈도 os 경우의 파일의 확장자는. exe 가 되고,

 

linux, mac os의 경우에는 특별히 확장자가 붙지는 않는다.

이제 해당 실행 파일을 더블클릭하게 되면 하드디스크에 존재하는 실행파일이 메모리에 올라가게 되고 

이렇게 올라간 프로그램은 프로세스라는 새로운 이름으로 불리게 된다.

해당 프로세스는 올라간 시점부터 운영체제에 의해 관리된다.

 

 


운영체제의 전략에 따라서 프로세스가 실행될 텐데 

 

이를 cpu 관점에서 살펴보자.

 

CPU는 0,1 로만 이루어진 기계어만을 실행하는데,

 

해당 기계어를 보면 가독성이 너무 떨어지게 되어 이해가 불가능하므로

 

C언어로 짠 코드를 디스어셈블리하여 어셈블리 코드를 보자.

 

일단 특정 코드를 디스어셈블리 하게 되면 어떤 CPU 아키텍처냐에 따라서

 

다른 코드가 나올 수 있다. 현재 글쓴이의 환경은 Mac os M1 프로세스 기반에서 테스트하고 있다.

 

어셈블리 코드는 아래와 같다.

 

	.section	__TEXT,__text,regular,pure_instructions
	.build_version macos, 13, 0	sdk_version 13, 0
	.globl	_main                           ; -- Begin function main
	.p2align	2
_main:                                  ; @main
	.cfi_startproc
; %bb.0:
	sub	sp, sp, #16
	.cfi_def_cfa_offset 16
	mov	w0, #0
	str	wzr, [sp, #12]
	mov	w8, #5
	str	w8, [sp, #8]
	mov	w8, #3
	str	w8, [sp, #4]
	ldr	w8, [sp, #8]
	ldr	w9, [sp, #4]
	add	w8, w8, w9
	str	w8, [sp]
	add	sp, sp, #16
	ret
	.cfi_endproc
                                        ; -- End function
.subsections_via_symbols

 

 

_main 아래의 코드를 살펴보자.

 

. cfi_startproc이라는 뜻은 "Call Frame Information"의 시작을 나타내며, 디버깅과 예외 처리를 위한

 

메타데이터 시작점을 표시하는 코드이다.

 

; %bb.0:
	sub	sp, sp, #16

 

위의 코드는 스택포인터 ('sp')를 16바이트로 감소시켜

 

로컬 변수 및 임시 데이터를 위한 공간을 스택에 확보해 주는 코드이다.

 

	.cfi_def_cfa_offset 16

 

해당 명령은 스택 프레임의 오프셋을 설정하여 현재의 "Call Frame Address"를 조정하는 코드이다.

 

	mov	w0, #0

 

해당 코드는 함수의 반환 값인 'w0' 레지스터에 0을 저장한다.

 

main 함수의 반환형이 int 이므로 정상 종료를 나타내기 위해 0을 반환하는 것이다.

 

	str	wzr, [sp, #12]

 

위의 코드는 'wzr' (zero register)의 값을 스택의 12번지 오프셋에 위치에 저장한다.

 

이는 'res' 변수의 초기화 과정에 해당하지 않으며, 컴파일러에 의한 최적화의 일부이다.

 

	mov	w8, #5
	str	w8, [sp, #8]

 

'w8' 레지스터에 5를 저장하고

 

이 값을 스택의 8번지 오프셋 위치에 저장한다. (num_1 = 5)

 

	mov	w8, #3
	str	w8, [sp, #4]

 

'w8' 레지스터에 3을 저장하고,

 

이 값을 스택의 4번지 오프셋 위치에 저장한다. (num_2 = 3)

 

	ldr	w8, [sp, #8]
	ldr	w9, [sp, #4]

 

스택에서 'num_1'과 'num_2'의 값을 각각 'w8', 'w9' 레지스터로 로드한다.

 

	add	w8, w8, w9

 

'w8'과 'w9'를 더한 결과를 'w8'에 저장한다. (res = num_1 + num_2)

 

	str	w8, [sp]

 

계산된 결과 'res'를 스택의 최상단에 저장한다.

 

	add	sp, sp, #16

 

함수 종료 전에 스택 포인터('sp')를 원래대로 복원한다.

 

	ret

 

함수에서 반환하며, 이는 프로그램의 흐름을 호출자에게 다시 넘겨주는 역할을 수행하는 코드이다.

 

	.cfi_endproc

 

'Call Frame Information' 블록의 끝을 나타낸다.

 

 

반응형