프로세스는 구조는 아래와 같습니다. 아래의 구조를 PCB라고 부르며, 프로세스가 생성되면 고유의 pid(Process ID) 및 PCB가 주어집니다. 이 공간에 프로세스 이미지가 업로드되고, 실행하면 프로세스의 작업이 시작됩니다.
이 때 생성되는 pid는 파일에서의 inode와 같이, 프로세스와 1:1로 관리가 됩니다.(이 때 커널 메모리는 모든 프로세스가 공유합니다.)
1) STACK : 함수 실행 간 생성되는 데이터를 정적으로 할당
2) HEAP : 함수 실행 간 생성되는 데이터를 동적으로 할당
3) BSS : 초기화되지 않은 변수 값들을 저장
4) DATA : 초기화된 변수 값들을 저장
5) TEXT : 실행프로그램의 코드를 저장
운영체제가 Load되면 최초의 프로세스인 init이 생성되고, 이 프로세스에 pid = 1이 주어집니다. 리눅스에서는 일반적으로 init을 제외한 다른 프로세스를 생성할 때 fork() 시스템 콜을 사용하여 생성하게 됩니다.
프로세스의 생성 과정은 아래와 같습니다.
[프로세스 생성]
1) 부모 프로세스에서 fork()를 수행한다.
2) fork()를 하면 자식 프로세스의 경우 성공적으로 수행되면 새로운 가상메모리(4GB) 공간을 생성한 뒤 return 값으로 0을 주게 되는데(실패하면 -1), return 값이 0으로 확인되면 exec(실행파일)을 수행한다.
3) exec(실행파일)에 의해서 새 프로세스 공간에 실행파일을 덮어씌우고, 이를 처음부터 실행한다.
4) 자식 프로세스 마지막에 exit(종료 상태값)을 사용하여 자식 프로세스를 종료함과 동시에 종료 상태값을 전달한다.
5) 부모 프로세스에서 wait(종료 상태값)을 사용하여 자식 프로세스의 종료 상태값을 확인하고, 남은 부모 프로세스를 수행한다.
* copy-on-write : 부모 프로세스에서 자식 프로세스를 fork()할 때 새로운 가상메모리공간 4GB를 복사하여 만들게 되는데, 4GB를 복사하는데 시간이 매우 오래 걸린다. 이를 개선하기 위해 자식 프로세스에서는 처음에 생성된 뒤 부모의 메모리 공간(물리적)을 참조한다. 읽기 과정에서는 해당 참조값이 없지만, 자식 프로세스의 수행 과정에서 메모리에 write할 일이 생기면, 부모 프로세스의 메모리 공간(물리적)에 그대로 쓰는 것이 아니라 변화가 필요한 내용의 페이지만 새로운 공간에 복사하여 write를 진행한다.
[ 프로세스 관련 시스템 콜 ]
* fork() : 시스템 콜. 해당 시스템 콜이 실행되면, 운영체제는 새로운 프로세스 공간(PCB)를 만들고, fork() 시스템콜을 호출한 프로세스(부모 프로세스) 공간을 모두 복사해서 새로운 프로세스 공간에 붙여넣습니다. 이렇게 새로 만들어진 프로세스에는 새로운 pid가 부여됩니다. 이 때 부포 프로세스의 id를 ppid라고 하고, 해당 변수도 새롭게 만들어진 프로세스에 저장됩니다.
if(pid == 0) printf("This is Child process. PID is %d\n", pid); else if (pid > 0) printf("This is Parent process. PID is %d\n", pid); else printf("fork() is failed\n"); return 0; }
* exec() : 시스템 콜. 해당 시스템 콜을 호출한 프로세스 공간의 [TEXT, DATA, BSS] 영역을 새로운 프로세스(인자로 전달)의 이미지로 덮어씌웁니다. 따라서 인자로 실행파일을 전달해줘야 합니다.
exec()는 fork()와 다르게 여러 버전의 함수가 있습니다. 해당 함수들은 인자를 전달하는 방식이 다릅니다.
1) execl()
2) execlp()
3) execle()
4) execv()
5) execvp()
6) execve()
* getpid() : pid(Process ID)를 가져옵니다.
* getppid() :ppid(부모의 Process ID)를 가져옵니다.
* wait() : 시스템 콜, 부모 프로세스가 자식 프로세스보다 먼저 종료되는 일이 없도록, 자식 프로세스가 종료될 때까지 기다리게 하는 함수입니다. 자식 프로세스가 종료할 때 종료 상태값(정상 종료 : 0)을 저장하면, 해당 상태값을 참조하여 나머지 부모 프로세스를 수행합니다.
* exit() : 시스템 콜, 프로세스를 강제로 즉시 종료시킵니다. 인자로 프로세스 종료 상태 번호를 전달합니다.
[4, 3, 2, 1]이란 배열이 주어진다면, 이 배열을 [1, 2 3, 4]로 정렬하기 위해 필요한 swap의 수를 구하면 됩니다.
이 때, swap은 아무 위치에 있는 숫자끼리도 바꿀 수 있습니다.
위의 예를 보면,
[4,1] swap → [1, 3, 2, 4]
[3,2] swap → [1, 2, 3, 4]
따라서 위의 배열을 정렬하는데 필요한 최소의 swap 수는 2입니다.
1. 방법
1) for loop에 enumerate 함수를 사용하여 idx, 숫자를 순차적으로 뽑아냅니다.
2) 만약 idx와 숫자(x)가 일치하지 않는다면(정렬되어있지 않다는 뜻), 해당 숫자(x)의 고유 idx 위치에 숫자(x)를 입력합니다.(일치하면 for loop 지속)
3) swap 횟수를 1을 더해줍니다.
4) x의 고유idx에 존재하던 숫자(y)와 y의 고유 idx에 있는 숫자 z를 비교하고, 다르다면 swap한 뒤 swap 횟수에 1을 더합니다.(같아질 때까지 반복하고, 같다면 swap 횟수에 1을 빼고 for loop로 복귀합니다.)
5) for loop이 완료될 때까지 (2) - (4)를 반복한다.
* 이 때 for loop 복귀 전에 swap 횟수를 1을 빼주는 이유는, 위에서 주어진 예시[4,3,2,1]에서의 [1,4], [3,2]와 같이 한번 swap을 통하여 두 숫자가 제자리를 찾아가는 경우가 있습니다. 하지만 이 알고리즘에서는 해당 경우를 즉각적으로 검출해내지 못하므로, 1을 빼주는 것입니다.
Ex) Input : [4, 3, 2, 1] (이 때 보기 쉽게 idx는 1, 2, 3, 4로 지정하겠습니다. 실제로는 0, 1, 2, 3)
for idx, num in enumerate(Input):
→ idx = 1, num = 4
→ idx != num이므로 4가 있어야 할 원래 위치(idx : 4)에 4를 입력하고, num을 1로 바꿔줍니다. [swap 횟수 = 1]
→ [4, 3, 2, 4], num = 1
→ num(=1)이 있어야할 원래 위치(idx : 1)의 숫자(=4)와 num(=1)을 비교합니다.
컴퓨터 아키텍쳐에서 배운 CPU, Memory, Storage, Network로 이루어진 Hardware 위에 운영체제(OS)가 탑재되어 있고, 이 운영체제는 시스템 콜을 바탕으로 작동합니다. 이 위에는 각 프로그래밍 언어를 컴파일하는 컴파일러와 라이브러리/API가 있으며, 그 위에 사용자와 컴퓨터의 Interaction을 담당하는 Shell 및 Editor가 있습니다.
즉, 사용자가 Shell이나 Editor를 통해 작성한 응용프로그램은, 컴파일러를 통해 번역되어 필요한 작업을 시스템 콜을 통하여 OS에 요청하게 됩니다.
그럼 구성요소 각각에 대해서 조금 더 알아보겠습니다.
1. Shell : 사용자가 운영체제 기능과 서비스를 조작할 수 있도록 하는 인터페이스
*CLI / GUI 두가지 방식이 있다.
2. API : 라이브러리 혹은 함수의 모음집
3. 컴파일러 : 사용자가 작성한 코드를 컴퓨터가 이해할 수 있는 언어로 번역하는 시스템
4. 시스템 콜(시스템 호출 인터페이스): 사용자가 원하는 작업을 OS에 요청하는 방식
5. 운영체제 : 리눅스, 윈도우 같은 컴퓨터 자원 관리 및 사용자 요청 작업을 수행하는 프로그램
이제 본격적으로 운영체제에 대하여 알아보겠습니다.
사용자가 작성한 응용 프로그램은 CPU에서 원하는 작업을 수행하게 되는데, 이 때 직접 수행 가능한 것이 있고 반드시 시스템 콜을 거쳐서 작업을 수행해야만 하는 것이 있습니다. 시스템 콜을 통해서만 수행할 수 있는 영역은 커널 영역(모드)라고 하고, 그렇지 않은 영역을 사용자 영역(모드)라고 합니다. 이러한 영역은 CPU Protection Ring으로 아래와 같이 구체화되어 있습니다. 사용자 모드/커널 모드로 구분하여 권한을 부여함으로써, 컴퓨터는 임의의 사용자가 함부로 컴퓨터의 운영체제를 손상하는 것을 막을 수 있습니다.
다음 글에서는 컴퓨터 구조의 구성요소들을 컴퓨터가 어떻게 활용하고 분배하는지, 프로세스와 스케쥴링에 대해 알아보도록 하겠습니다 -!
SELECT CASE WHEN A = B AND B = C THEN 'Equilateral' WHEN A >=B+C OR B>=A+C OR C>=A+B THEN 'Not A Triangle' WHEN A = B OR A = C OR B = C THEN 'Isosceles' ELSE 'Scalene' END FROM TRIANGLES;