미새문지

크래프톤 정글 week05, day32 - User mode & Kernel mode, 시스템 콜(System Call) 본문

크래프톤 정글/TIL

크래프톤 정글 week05, day32 - User mode & Kernel mode, 시스템 콜(System Call)

문미새 2024. 2. 21. 00:35
728x90

실행 흐름

  1. 우리가 개발하는 프로그램은 일반적으로 유저 모드에서 실행된다.
  2. 프로그램 실행 중에 인터럽트(interrupt)가 발생하거나 시스템 콜(system call)을 호출하게 되면 커널 모드로 전환된다.
  3. 전환된 커널 모드에선 프로그램의 현재 cpu상태를 저장하고, 인터럽트나 시스템 콜을 직접 처리하게 된다. 그리고 처리가 완료되면 중단됐던 프로그램의 cpu 상태를 복원하고 다시 유저 모드로 전환되며 프로그램이 이어서 실행된다.

 

커널(kernel)

  • 운영체제의 핵심이며, 이를 규정짓는 매우 중요한 소프트웨어. 시스템의 전반을 관리/감독하는 역할이다.
  • 하드웨어의 자원을 자원이 필요한 프로세스에 나눠주고, 프로세스 제어, 메모리 제어, 시스템 콜 등을 수행하는 부분으로 운영체제의 가장 아래 계층에서 돌아간다.
  • 커널 모드가 있는 이유는 시스템을 보호하기 위해 잇으며, 만약 커널 모드가 없으면 우리가 만든 프로그램이 하드웨어를 점유하게 되고 프로세스가 조작되어 위험하다.

인터럽트(Interrupt)

  • 시스템에서 발생한 다양한 종류의 이벤트 혹은 그런 이벤트를 알리는 메커니즘
  • 인터럽트 종류
    • 전원(power)에 문제가 생겼을 때
    • I/O 작업이 완료 됐을 때
    • 시간이 다 됐을 때(timer 관련)
    • 0으로 나눴을 때 *
    • 잘못된 메모리 공간에 접근을 시도할 때 *
      • * 종류는 보통 trap이라고 불리기도 한다.
  • 인터럽트가 발생하면 cpu에서는 즉각적으로 인터럽트 처리를 위해 커널 코드를 커널 모드에서 실행한다.

시스템 콜(System call)

  • 프로그램이 OS 커널이 제공하는 서비스를 이용하고 싶을 때 시스템 콜을 통해 실행한다.
  • 운영체제의 기능을 사용하는 모든 프로그램은 시스템 호출을 통해 해당 기능을 호출해야 한다. 이를 통해 운영체제는 프로그램의 요청을 처리하고, 시스템의 자원을 효율적으로 관리할 수 있다.
  • 시스템 콜의 종류
    • 자원 관리 : 메모리, 프로세스, 파일, 네트워크 등의 자원 관리
    • 프로세스 관리 : 프로세스의 생성, 종료, 실행 등을 관리
    • 입출력 관리 : 키보드, 마우스, 모니터 등의 입출력 장치를 관리
    • 시스템 호출 처리 : 응용 프로그램이 요청한 시스템 호출을 처리
    • 하드웨어 제어 : CPU, 메모리, 디스크 등의 하드웨어를 제어

  • 시스템 콜이 발생하면 해당 커널 코드가 커널 모드에서 실행
  • 시스템 콜 & 인터럽트 예제 : 파일 read
    • t1 : 스레드1, t2 : 스레드2, 싱글코어 cpu
      • t1이 cpu에서 실행 중이고 t2가 대기 상태로 있을 때
        1. t1이 user mode에서 file을 시스템 콜의 read를 호출 했을 시 커널 모드로 전환
        2. 커널 모드에서 t1 cpu 상태를 저장된다.(파일을 읽을 준비) → t1은 waiting 상태로 전환
        3. cpu에서는 동작하는 게 없기 때문에 바로 다음 작업인 t2를 실행 준비를 하고 유저 모드로 전환
        4. 유저 모드에서 t2가 실행하다가 ssd에서 t1이 요청한 파일을 읽을 준비가 되었다고 인터럽트를 발생시킨다. 그럼 발생한 인터럽트를 처리하기 위해 다시 커널 모드로 전환
        5. 커널 모드에서 t2 cpu 상태를 저장하고, t1의 상태를 ready 상태로 바꾼다.
        6. t2의 cpu 상태를 복원하고 다시 통제권을 유저 모드로 넘겨준다.
        7. 아까 인터럽트 때문에 중단됐던 부분에서 다시 시작한다. 지금 방식이 멀티 태스킹 방식이기 때문에 timer라는 하드웨어를 통해 알려주는데 그 때 인터럽트가 발생해 커널 모드로 전환
        8. t2의 cpu 상태를 저장하고 ready상태가 된다. 그리고 t1이 running 상태가 되며 복원된다. 그리고 유저 모드로 통제권을 넘겨준다.
        9. t1이 실행되고 파일을 읽을 준비가 되었기 때문에 데이터를 읽어오는 작업을 한다.

  • 함수 종류
    • 프로세스 관리 : 프로세스 생성, 종료, 실행 등을 관리하는 함수
    • 메모리 관리 : 메모리 할당, 해제, 복사 등을 관리하는 함수
    • 파일 시스템 관리 : 파일 생성, 삭제, 읽기, 쓰기 등을 관리하는 함수
    • 네트워크 관리 : 네트워크 연결 설정, 데이터 전송 등을 관리하는 함수
    • 입출력 관리 : 키보드, 마우스, 모니터 등의 입출력 장치를 관리하는 함수

  • 시스템 호출의 예시
    • 프로세스 생성 : fork() 시스템 호출을 사용하여 새로운 프로세스를 생성할 수 있다.
#include <stdio.h>
#include <unistd.h>

int main() {
    pid_t pid = fork(); // fork() 시스템콜을 호출하여 자식 프로세스를 생성하고, 생성된 자식 프로세스의 pid 값을 pid 변수에 저장

    if (pid == 0) { // 자식 프로세스인 경우에만 실행되는 조건문
        printf("Child process\n"); // 자식 프로세스가 실행될 때 "Child process"를 출력
    } else if (pid > 0) { // 부모 프로세스인 경우에만 실행되는 조건문
        printf("Parent process\n"); // 부모 프로세스가 실행될 때 "Parent process"를 출력
    } else { // fork() 시스템콜이 실패한 경우에 실행되는 조건문
        printf("Fork failed\n"); // fork() 시스템콜이 실패한 경우에 "Fork failed"를 출력
    }

    return 0; // 프로그램 종료
}

 

  • 파일 읽기 : open() 시스템 호출을 사용하여 파일을 열고, read() 시스템 호출을 사용해 파일의 내용을 읽을 수 있다.
#include <stdio.h>
#include <fcntl.h> // 파일 제어를 위한 헤더 파일
#include <unistd.h>

int main() {
    int fd = open("test.txt", O_RDONLY); // "test.txt" 파일을 읽기 전용으로 열고, 파일 디스크립터를 fd 변수에 저장

    if (fd == -1) { // 파일 열기가 실패한 경우에 실행되는 조건문
        printf("파일 열기 실패\n"); // 파일 열기가 실패한 경우에 "파일 열기 실패"를 출력
        return -1; // 프로그램 종료
    }

    char buf[100]; // 파일의 내용을 저장할 버퍼
    ssize_t nread = read(fd, buf, sizeof(buf)); // fd로 지정된 파일 디스크립터에서 buf 크기만큼의 데이터를 읽어와 nread 변수에 저장

    if (nread == -1) { // 파일 읽기가 실패한 경우에 실행되는 조건문
        printf("파일 읽기 실패\n"); // 파일 읽기가 실패한 경우에 "파일 읽기 실패"를 출력
        close(fd); // 사용이 끝난 파일 디스크립터를 닫음
        return -1; // 프로그램 종료
    }

    printf("파일 내용: %s\n", buf); // 파일의 내용을 출력
    close(fd); // 사용이 끝난 파일 디스크립터를 닫음

    return 0; // 프로그램 종료
}
  • 네트워크 연결 설정 : socket() 시스템 호출을 사용하여 네트워크 연결을 설정하고, connect() 시스템 호출을 사용하여 서버에 연결할 수 있다.
    • 시스템 호출은 운영체제의 기능을 사용하기 위해 필수적인 요소이며, 프로그램의 성능과 안정성에 큰 영향을 미친다.
    • 네트워크 연결 함수
      • socket() : 시스템 호출을 사용하여 소켓을 생성한다.
      • bind() : 시스템 호출을 사용하여 소켓을 바인드 한다.
      • listen() : 시스템 호출을 사용하여 클라이언트의 연결 요청을 대기한다.
      • accept() : 시스템 호출을 사용하여 클라이언트의 연결을 수락한다.
      • write() : 시스템 호출을 사용하여 클라이언트에게 메세지를 전송한다.
      • read() : 시스템 호출을 통해 클라이언트로부터 메세지를 수신한다.

  • 시스템 콜 함수
    • brk(), sbrk() : malloc 함수로 메모리 할당을 요청할 때 사용하는 시스템 콜
      • brk, sbrk는 힙의 크기를 변경하는데, 힙은 데이터 영역 직후에 시작해 위쪽으로 메모리 주소가 커지는 영역이다.
        • 즉, 현재 프로세스의 program break(힙의 꼭대기를 가리키는 변수)의 위치를 변경하는 함수이다.
      • heap 영역의 요청이 없을 때 까지는 Data Segment = Program Break이다.
        • brk와 sbrk의 차이
          • brk()
            • 해당 값이 합리적이고 시스템에 메모리가 충분할 때 program break를 brk의 인자인 addr에서 지정한 값으로 설정한다. 그리고 프로세스는 최대 데이터 크기를 초과하지 않는다.
            • 성공 시 0, 실패 시 -1을 반환한다.
          • sbrk
            • program break를 increment bytes만큼 증가시킨다. increment 인자를 0으로 설정해 sbrk()를 호출하는 건 현재의 program break의 위치를 찾을 때 쓰인다.
            • 성공 시 이전 program break 주소를 반환하며 실패 시 void* 포인터 형으로 -1을 반환한다.
              • program break가 증가한 경우, 이 반환 값은 새로 할당된 메모리의 시작 부분에 대한 포인터이다.
#include <stdio.h>

int main()
{
	printf("brk(0) : %d \n", brk(0));
	printf("sbrk(0) : %d \n", sbrk(0));

	return 0;
}

// 결과
// brk(0) : 0
// sbrk(0) : 0x8efe000

보통 개발할 때 직접 OS 시스템 콜을 사용한 적이 없었는데 파일 I/O, 네트워크 I/O, 프로세스/스레드 관련 작업을 할 수 있던 건 사용하고 있는 프로그래밍 언어들이 시스템 콜을 포장(wrapping)하여 간접적으로 사용할 수 있도록 제공했기 때문이다.

 

학습 시간 : 14 ~ 24시

728x90