OS fork() & exec()

Wiiiiiii·2022년 9월 30일
0

Dev log

목록 보기
2/2
post-thumbnail

1. fork() 와 exec()

fork()와 exec()는 모두 한 프로세스가 다른 프로세스를 실행시키기 위해 사용하게 된다
exec에는 execl, execv 등 여러가지 함수군을 가지고 있다


2. fork(), exec() 의 차이점

fork() 시스템 호출은 새로운 프로세스를 위한 메모리를 할당한다는 것이다

그리고 fork()를 호출한 프로세스를 새로운 공간으로 복사하게 되고,
원래 프로세스는 원래 프로세스대로 작업을 실행하고
fork()를 이용해서 생성된 프로세스도 그 나름대로
fork() 시스템 콜이 수해된 라인의 다음 라인부터 실행이 된다
(새로 생성된 프로세스는 원래의 프로세스와 똑같은 코드를 가지고 있다)

반면 exec()는 fork()처럼 새로운 프로세스를 위한 메모리를 할당하지 않고
exec()를 호출한 프로세스가 아닌 exec()에 의해 호출된 프로세스만 메모리에 남게 된다

간단히 정리하면 fork()의 결과는 프로세스가 하나 더 생기는 것이다
= PID가 완전히 다른 또 하나의 프로세스가 생기는 것

반면 exec() 실행의 결과로 생성되는 새로운 프로세스는 없고
exec()를 호출한 프로세스의 PID가 그대로 새로운 프로세스에 적용이 되며
exec()를 호출한 프로세스는 새로운 프로세스에 의해 덮여 쓰여지게 된다


3. exec() 관련 함수

exec는 execl, execv, execlp, execvp 등이 있다
이들에 대하여 4가지만 간단히 구분하는 방법을 정리한다

exec를 먼저 l 계열 (execl, execlp)과 v 계열 (execv, execvp)로 나누어 알아보자

l 계열 : 인자를 열거하는 방식이 나열형
v 계열 : 인자를 열거하는 방식이 배열형

다음으로 p 가 붙은 계열 (execlp, execvp), 안 붙은 계열 (execl, execv) 로 나누어 정리하면

p가 안붙은 계열 : 경로를 지정해주면, 현재/절대경로를 기준으로 찾게 된다 (경로로 실행파일을 지정)
p가 붙은 계열 (path) : path에 잡혀있으면 실행된다 (실행파일의 이름만 지정)

exec 계열은 첫 번째 인자의 코드가 들어오고 나머지 기존에 exec아래에서 실행해야 할 코드는 전부 잃어버리게 된다는 점을 가짐


4. fork()와 exec() 의 실행 예시

우선 함수를 설명하기 앞서 process id 를 설명해야 이해가 쉽다

모든 프로세스는 고유의 식별자 (PID)를 갖는다

PID는 자기의 부모프로세스 식별자 (PPID)를 갖는다

→ 프로세스는 PID
→ PID 는 PPID

위 방식으로 프로세스 0부터 나와서 자식을 만들고 그 자식이 또 다른 자식을 만드는... 이런 식으로 시스템이 오퍼레이팅되면서 시스템 유지에 필요한 프로세스를 만드는 것

UNIX 에서 자식프로세스를 만드는 유일한 함수는 fork() 뿐이 없다

fork 함수의 특징은 반환 값이 2개라는 것인데 하나는 차일드 프로세스를 만들 경우 0을 리턴하고, 다른 하나는 차일드 프로세스의 ID 즉, PID를 리턴하는 것이다

이 함수의 또 다른 특징은 부모프로세스의 데이터영역을 그대로 카피하여 프로세스를 메모리에 할당 해 주는 것

exec() 함수는 현재 프로그램의 텍스트, 데이터, 스택 영역에 exec() 함수의 인자로 전달된 프로그램의 텍스트, 데이터, 스택 영역을 덮어씌워버리는 함수이다

즉, exec 계열의 함수를 사용하면 새로운 프로세스를 현재 프로세스 위치에 덮어 쓰므로 현재 프로세스는 종료되게 된다

  • CASE 1

    위 코드의 실행 결과는 두 번째 printf 문이 출력되지 않는다
    원인은 execl 가 ls 프로세스를 실행함과 동시에 현재 프로세스를 덮었기 때문에 이후 등장하는 printf 문을 알 수가 없다

  • CASE 2

    위 코드의 실행 결과는 두 번째 printf 문이 출력된다
    fork()를 이용하여 자식프로세스를 메모리에 할당하고 그 곳에서 execl()을 사용하게 되면 현재 프로세스를 죽이지 않고 자식 프로세스 생성 후 그 곳에 exec를 사용하게 된다


5. System Call 에서의 fork, exec

System Call 로 하드웨어를 컨트롤 하면서 여러가지 작업을 할 수 있는데
System Call 을 활용한 작업 영역을 3 가지로 구분해보자

  1. File I/O
  2. Process Control
  3. InterProcess Communication

fork와 exec는 그 중 Process Control 영역이다

fork와 exec는 따로 이야기 하기 곤란하다
fork는 독자적으로 쓰일 수 있겠으나 exec는 독자적으로 쓰기에는 너무 한정적이기 때문이다

프로그램에서 exec를 호출하게 되면 현재 메모리에 상주하고 있는 이후 프로그램은 무시되어버린다!

→ exec 로 호출되는 프로그램이 현재 메모리에 올라와 있는 프로그램을 덮어서 로딩되기 때문이다

그러나 별도의 메모리 공간을 할당하고 그 할당된 공간에서 exec를 실행하게 되면 다른 메모리 공간에서 실행되고 있는 원래의 Process는 자기 갈 길을 갈 수 있다
그리고 이런 일을 하는 것이 fork 이다

Process Control 을 한다는데, 그럼 Process란 무엇일까?

Process 와 자주 비교 언급되는 것은 Thread 이다

둘의 차이는 독립된 메모리 공간을 할당 받냐 받지 않느냐의 차이다

네모 박스는 하나의 프로세스를 나타내고, 실은 말 그대로 쓰레드를 나타낸다
하나의 쓰레드는 독립적으로 실행될 수 없는데, 왜냐하면 하나의 프로세스에 종속되기 때문이다

하나의 프로세스는 운영체제의 가상 메모리 공간에 독립적인 할당 공간 에서 로딩이 된다
쓰레드는 프로세스에 종속되기 때문에 마찬가지로 할당된 메모리 공간에서 움직인다

그러므로 메인 Procedure 에서 선언된 변수나 함수는 그 프로세스에서 일을 하는 모든 쓰레드가 접근할 수 있게 된다

그러나 쓰레드가 동작하는 순서로 프로그래머가 동기화 할 수 없는 경우가 많다

(쓰레드의 많은 예제 중에 여러 개의 쓰레드에 전역변수 값을 1 씩 올리며 출력하는데, 순서대로 올라가지 않는 것을 생각해보자)

--

프로세스와 쓰레드를 설명할 수 있는 쉬운 예는 팟플레이어와 같은 프로그램이다

avi 파일을 클릭하면 이 프로그램은 자막이 있으면 자막을 보여주, 사운드를 들려주고, 영상을 보여주며 전체화면으로 바꾸면 끊기지 않고 전체화면으로 바꿔준다

이것은 단일 프로세스의 단일 쓰레드로는 구현이 불가능하다
(불가능하다기 보다는 대단히 어려운 과정을 거쳐야 한다)

사운드를 들려주는 쓰레드, 영상을 보여주는 쓰레드, 프레임을 조정하는 쓰레드, 자막을 관리하는 쓰레드가 각자 자기 할일을 하고 있으며 각각의 쓰레드가 CPU의 자원을 독점적으로 쓰지 않고 적절히 양보하면서 쓰기 때문에 가능하다

fork() 와 exec()는 Process Control을 한다고 하였으니 한 프로그램에서 곰플레이어와 같은 쓰레드를 호출하는 것과 같은 Control을 하는 것이 아닐까?
아니면 팟플레이어와 같은 Process 에서 자막관리 쓰레드 같은 여러 쓰레드를 Control하는 것을 얘기하는 것일까?


6. fork() Detail

Header File : <unistd.h>
Basic Shape of function : pid_t fork(void);

→ 사실 pid_t 라는 구조형 때문에 <sys/types.h> 도 헤더파일에 포함해주어야 한다, pid_t 대신에 int를 사용해도 무리가 없긴 하다
(#define pid_t int 이기 때문 // 수업에서 교수님은 유닉스 기반 OS의 함수 리턴은 인티저 형식이 원칙이라고 하셨다!)

fork 는 현재 프로세스에서 다른 프로세스를 만든다

현재 프로세스를 부모 프로세스 (Parent Process) 라고 하며, 만들어진 다른 프로세스를 자식 프로세스 (Child Process) 라고 한다

fork 가 리턴하는 pid_t 값이 0이면 자식, 0보다 크면 부모이다 (-1 이면 에러)

fork() 가 실행되는 순간 A프로세스와 똑같은 프로세스 하나가 별도의 메모리 공간에 생성된다
이때 두 프로세스의 변수 값, PC(Program Count) 값은 정확히 똑같다

단 pid 값만 유일하게 달라진다

따라서 pid 값을 이용해 Child Process 가 할일을 하게 하고, Parent Process 가 할 일을 하게 하면 된다

이때 Child Process가 우선적으로 실행이 되지만, 실행시간이 길 경우 OS의 Process Scheduler에 따라서 Child Process 들과 Parent Process가 비동기식으로 경합하게 된다


7. exec() Detail

exec 라는 함수 명은 사실 없다
exec는 어떤 일을 하는 family 명칭으로 보는게 더 정확하다

exec family가 하는 일은 현재 실행되는 프로세스에서 다른 프로세스 일을 하게 하는 것이다

예를 들어 어떤 문서에서 어떤 문자들이 출현 했는지를 판단한 뒤, 출현한 경우 문서 내에서 자주 쓰이는 키워드를 추출한다면 2가지 프로세스로 나눌 수 있을 것이다

이때 자주 쓰이는 키워드를 추출하는 프로그램이 별도로 만들어져 있거나 만들었다면, 어떤 문자들이 출현한 문서를 찾는 프로그램에서 exec family를 이용해 만들어진 프로그램 object를 이용할 수 있다

exec family는 다른 Process를 실행시킬 때 현재 Process 메모리 공간에 덮어 써 버린다는 것이다

따라서 exec 함수가 성공하면 이후 명령은 수행되지 않는다

그러나 exec이 실패했다면 exec의 프로세스가 메모리 공간에 로딩되어지지 않기 때문에 이후 명령이 실행 될 것이다

따라서 exec를 호출할 때 원래의 Process가 사라져 버리는 것을 원하지 않는다면 fork() 를 호출해 Child Process 에서 새로운 Process 를 로딩해 일을 하게 하면 되는 것이다

wait((int*)0); 는 Child Process 가 종료될 때까지 기다린다
그리고 "ls completed"를 출력하게 된다

사실 fork 와 exec는 shell 동작 방식을 잘 설명하는데 이용될 수 있는데, 사용자가 shell에서 명령을 하면 그것을 수행하고 완료하면 다시 shell로 돌아가게 된다

shell이 fork를 한 뒤 exec를 행하고 Child Process 가 완료되면 다시 대기 상태로 돌아간다고 볼 수 있다

참고 |

https://velog.io/@yh_lee/System-Call

https://woochan-autobiography.tistory.com/207

https://moaimoai.tistory.com/148

profile
리액트를 주로 연구중입니다! FE Junior

0개의 댓글