CS/Operating System

[Operating System] System Call(시스템 콜)

hanseongbugi 2024. 4. 6. 16:19

System Call?

  • System Call(시스템 콜)은 운영체제의 커널이 제공하는 서비스에 대해, 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스다.
  • 보통 C나 C++과 같은 고급 언어로 작성된 프로그램들은 직접 시스템 호출을 사용할 수 없기 때문에 고급 API를 통해 시스템 호출에 접근하게 하는 방법이다.
  • 사용자 프로그램이 디스크 파일에 접근하거나 화면에 결과를 출력하는 등의 작업이 필요한 경우, 즉 사용자 프로그램이 특권 명령의 수행을 필요한 경우 운영체제에 특권 명령의 대행을 요청하는 것이 시스템 콜이다.
  • 시스템 콜은 여려 종류의 기능으로 나누어진다.
  • 시스템 콜에는 번호가 할당되고 시스템 콜 인터페이스는 시스템 콜 번호와 시스템 콜 헨들러 함수 주소로 구성된 시스템 콜 테이블을 유지한다. 
  • 운영체제는 자신의 커널 영역에서 해당 인덱스가 가리키는 주소에 저장된 루틴을 수행한다.
  • 작업이 완료되면 CPU에게 인터럽트를 발생시켜 수행이 완료되었음을 알린다.

open()의 호출

  • open()이라는 시스템 콜의 인덱스가 가리키는 곳에 대한 처리 과정이다.
  • 경우에 따라 시스템 콜이 발생하였을 때, 추가적인 정보가 필요할 수 있다.
    • 추가적인 정보가 담긴 매개변수를 OS에 전달해야함
    • 이를 위해 매개변수를 CPU 레지스터에 전달
    • 매개변수를 메모리에 저장해 해당 메모리의 주소를 레지스터에 전달 할 수 있다.
    • 매개변수는 프로그램에 의해 스택(stack)에 전달(push) 될 수도 있다.

시스템 콜이 필요한 이유

  • 일반적으로 사용하는 프로그램의 종류는 응용 프로그램이다.
  • 유저레벨의 프로그램은 유저레벨의 함수들만으로는 많은 기능을 구현하기 힘들기 때문에, 커널(Kernel)의 도움을 받아야한다.
  • 이러한 작업은 응용프로그램으로 대표되는 유저 프로세스(User Process)에서 유저 모드로는 수행할 수 없다.
  • 반드시 Kernel과 관련된 작업은 커널 모드로 전환된 후에야 해당 작업을 수행할 권한이 생긴다.
    • 해커가 피해를 입히기 위해 악의적으로 시스템 콜을 사용하는 경우나, 초보 사용자가 하드웨어 명령어를 잘 몰라서 아무렇게 함수를 호출했을 경우에 시스템 전체를 망가뜨릴 수도 있다.
    • 이러한 명령어들은 특별하게 커널 모드에서만 실행할 수 있도록 설계되었고, 만약 유저 모드에서 시스템 콜을 호출할 경우에는 운영체제에서 불법적인 접근이라 여기고 trap을 발생시킨다.

유저 모드 & 커널 모드

유저 모드

  • PC register가 사용자 프로그램이 올라가 있는 메모리 위치를 가리키고 있을 때 현재 사용자 프로그램을 수행중이라고 하며 CPU 가 유저모드에서 수행중이라고 말한다.

커널 모드

  • PC register가 운영체제가 존재하는 부분을 가리키고 있다면 현재 운영체제의 코드를 수행중이라고 하며 CPU가 커널모드에서 수행중이라고 말한다.

일반 명령 & 특권 명령

  • CPU내에서 모드 비트를 통해 구분한다.
  • 0인 경우 커널 모드이며 1인 경우 유저 모드이다.

일반 명령(유저 모드)

  • 메모리에서 자료를 읽어와서 CPU 에서 계산하고 결과를 메모리에 쓰는 일련의 명령들, 모든 프로그램이 수행 가능하다.

특권 명령(커널 모드)

  • 보안이 필요한 명령, 입출력 장치, 타이머 등 각종 장치에 접근하는 명령

시스템 콜 예시

Copy

cp in.txt out.txt
  • in.txt에 있는 파일 내용과 같은 내용을 복사하여 out.txt파일을 만드는 것
  • 리눅스 터미널에 해당 명령어를 입력하였다면 순차적으로 아래와 같은 시스템 콜이 호출된다.

  1. 먼저 사용자로부터 입력을 받는데 이때 I/O 시스템 콜 호출이 필요하다.
  2. 이후 cp 프로그램을 실행시키면 먼저 in.txt 파일이 현재 디렉토리에서 접근 가능한지를 확인하기 위한 시스템 콜이 호출된다.
    • 이때 접근이 불가능하다면 에러를 발생시킨 후 프로그램이 종료되는데 이때 시스템 콜이 호출된다.
  3. 파일이 존재해 접근 가능하다면 복사한 파일을 저장하기 위해 output.txt 파일명이 있는지 검사하기 위한 시스템 콜이 호출된다.
  4. 이 때도 마찬가지로 이 파일 명이 존재하는지 존재하지 않는지 검사하기 위해 시스템 콜을 통해 확인한다.
    • 만약 파일 명이 이미 존재한다면, 덮어 씌워야 할지 아니면, 이어서 붙여야 하는지 User에게 물어볼 수 있는데 만약 저장하고자 하는 파일 이름이 겹치지 않다면, 파일을 저장해야 하는데 이 때도 시스템 콜을 이용한다.

Fork

  • 새로운 Process를 생성할 때 사용하는 시스템 콜
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[]) {
    printf("pid : %d", (int) getpid()); // pid : 29146
    
    int rc = fork();					// 주목
    
    if (rc < 0) {					    // (1) fork 실패
        exit(1);
    }
    else if (rc == 0) {					// (2) child 인 경우 (fork 값이 0)
        printf("child (pid : %d)", (int) getpid());
    }
    else {								// (3) parent case
        printf("parent of %d (pid : %d)", rc, (int)getpid());
    }
}

// 결과
pid : 29146
parent of 29147 (pid : 29146)
child (pid : 29147)
  • PID는 프로세스 식별자를 뜻하며, UNIX 시스템에서 PID는 프로세스에게 명령을 내릴 때 사용한다.
  • Fork()가 실행되는 순간 프로세스가 하나더 생기게 된다. 
    • 이때 생긴 Child 프로세스는 fork를 만든 Parent 프로세스와 동일한 복사본을 가지게 된다.
    • 이때 운영체제는 위와 똑같은 2개의 프로그램이 동작한다고 생각하여 fork()가 return될 차례라고 생각한다.
    • 따라서 새로 생성된 Child Process는 main에서 시작하지 않고 if문 부터 시작하게 된다.
  • fork()로 인해 반환되는 값으로 child와 parent는 다른 값을 가지게 된다.
    • parent는 자식의 PID 값을 가진다.
    • child는 0을 가진다.
    • 완전히 동일한 복사본이라고 할 수 없음
  • Parent와 Child의 fork값이 다른 것은 매우 유용한 방식이다.
  • 단, Scheduler가 부모를 먼저 수행할지 아닐지 확신할 수 없다.

Wait

  • child 프로세스가 종료될 때까지 기다리는 시스템 콜
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    printf("pid : %d", (int) getpid()); // pid : 29146
    
    int rc = fork();					// 주목
    
    if (rc < 0) {					    // (1) fork 실패
        exit(1);
    }
    else if (rc == 0) {					// (2) child 인 경우 (fork 값이 0)
        printf("child (pid : %d)", (int) getpid());
    }
    else {								// (3) parent case
        int wc = wait(NULL)				// 추가된 부분
        printf("parent of %d (wc : %d / pid : %d)", wc, rc, (int)getpid());
    }
}

// 결과
pid : 29146
child (pid : 29147)
parent of 29147 (wc : 29147 / pid : 29146)
  • wait를 통해서, child의 실행이 끝날 때까지 기다려줌.
  • parent가 먼저 실행되더라도, wait ()는 child가 끝나기 전에는 return하지 않으므로, 반드시 child가 먼저 실행됨.

Exec

  • fork는 동일한 프로세스의 내용을 여러 번 반복할 때 사용된다.
  • child에서 parent와 다른 동작을 하고 싶을 때는 exec를 사용할 수 있음
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>

int main(int argc, char *argv[]) {
    printf("pid : %d", (int) getpid()); // pid : 29146
    
    int rc = fork();					// 주목
    
    if (rc < 0) {					    // (1) fork 실패
        exit(1);
    }
    else if (rc == 0) {					// (2) child 인 경우 (fork 값이 0)
        printf("child (pid : %d)", (int) getpid());
        char *myargs[3];
        myargs[0] = strdup("wc");		// 내가 실행할 파일 이름
        myargs[1] = strdup("p3.c");		// 실행할 파일에 넘겨줄 argument
        myargs[2] = NULL;				// end of array
        execvp(myarges[0], myargs);		// wc 파일 실행.
        printf("this shouldn't print out") // 실행되지 않음.
    }
    else {								// (3) parent case
        int wc = wait(NULL)				// 추가된 부분
        printf("parent of %d (wc : %d / pid : %d)", wc, rc, (int)getpid());
    }
}

 

  • exec가 실행되면, execvp( 실행 파일, 전달 인자 ) 함수는, code segment 영역에 실행 파일의 코드를 읽어와서 덮어 씌운다.
  • 씌운 이후에는, heap, stack, 다른 메모리 영역이 초기화되고, OS는 그냥 실행한다.
    • 즉, 새로운 Process를 생성하지 않고, 현재 프로그램에 wc라는 파일을 실행한다.
    • 그로인해서, execvp() 이후의 부분은 실행되지 않는다.

 

 

 

출처

https://velog.io/@nnnyeong/OS-%EC%8B%9C%EC%8A%A4%ED%85%9C-%EC%BD%9C-System-Call

 

[OS] 시스템 콜, System Call

시스템 호출(system call)은 운영 체제의 커널이 제공하는 서비스에 대해, 응용 프로그램의 요청에 따라 커널에 접근하기 위한 인터페이스이다.사용자 프로그램이 디스크 파일을 접근하거나 화

velog.io

https://github.com/gyoogle/tech-interview-for-developer

 

GitHub - gyoogle/tech-interview-for-developer: 👶🏻 신입 개발자 전공 지식 & 기술 면접 백과사전 📖

👶🏻 신입 개발자 전공 지식 & 기술 면접 백과사전 📖. Contribute to gyoogle/tech-interview-for-developer development by creating an account on GitHub.

github.com