본문 바로가기
교육 및 책/실습과 그림으로 배우는 리눅스 구조

사용자 모드로 구현되는 기능(시스템 콜)

by oneny 2023. 8. 3.

시스템 콜

프로세스는 프로세스의 생성이나 하드웨어의 조작 등 커널의 도움이 필요한 경우 시스템 콜을 통해 커널에 처리를 요청한다. 시스템 콜의 종류는 다음과 같다.

  • 프로세스 생성, 삭제
  • 메모리 확보, 해제
  • 프로세스 간 통신(IPC)
  • 네트워크
  • 파일시스템 다루기
  • 파일 다루기(디바이스 접근)

 

CPU의 모드 변경

시스템 콜은 CPU의 특수한 명령을 실행해야만 호출된다. 프로세스는 보통 사용자 모드로 실행되고 있지만 커널에 처리를 요청하기 위해 시스템 콜을 호출하면 CPU에서는 인터럽트(interrupt) 이벤트가 발생해 CPU는 사용자 모드에서 커널 모드로 변경되어 커널은 요청한 내용을 처리한다. 요청한 내용 처리가 끝나면 커널 내의 시스템 콜 처리가 종료되어 다시 사용자 모드로 전환되어 프로세스의 동작을 계속 진행한다.

커널에서는 요청한 내용을 처리하기 전에 프로세스의 요구가 유효한지 확인한다(ex. 시스템의 메모리 용량 이상의 메모리를 요구하는 것 등). 요구 사항이 맞지 않다면 커널은 시스템 콜을 실패했다고 처리한다.

 

시스템 콜 호출의 동작 순서

#include <studio.h>

int main(void)
{
  puts("hello world");
  return 0;
}

 

$ strace -o hello.log ./hello
hello world

$ cat hello.log

프로세스가 어떠한 시스템 콜을 호출했는지는 strace 명령어를 통해 확인할 수 있다. strace의 출력과 프로그램 자체의 출력이 섞이지 않도록 '-o' 옵션을 사용해 strace의 출력을 별도의 파일로 저장하면 위와 같은 결과를 확인할 수 있다.

strace 각각의 줄은 1개의 시스템 콜 호출이다. 여기서 빨간 박스를 보면 데이터를 화면이나 파일 등에 출력하는 'write()' 시스템 콜이 "hello world\n" 문자열을 화면에 출력하는 것을 확인할 수 있다. 이렇게 C/C++, Java, Python 어떤 언어로 작성하든 상관없이 프로그램이 커널에 처리를 요청할 때에는 결국 시스템 콜을 호출한다.

 

사용자/커널 모드 실행비율 측정

sar -P ALL 1(간격) 1(횟수)

프로세스가 사용자 모드와 커널 모드 중 어느 쪽에서 실행되고 있는지의 비율은 'sar' 명령어로 확인할 수 있다. 각 CPU 코어가 어떤 종류의 처리를 실행하고 있는지를 1초 단위로 측정할 수 있다.

CPU 코어에서 실행되고 있던 처리의 종류는 '%user'부터 '%idle'까지 필드에 표시되는데 한 줄의 필드값을 모두 더하면 100이 된다.(CPU 필드값이 'all'이 표시된 줄은 전체 CPU 코어의 평균치를 나타낸다.)

 

사용자 모드에서 프로세스를 실행하고 있는 시간의 비율은 '%user'와 '%nice'의 합계로 얻을 수 있다. CPU 코어가 커널 모드에서 시스템 콜 등의 처리를 실행하고 있는 시간의 비율은 '%system'으로 얻을 수 있다. 이 데이터를 측정할 때, CPU 코어는 대부분의 시간 동안 '%idle'이 100에 가까운 값이였다. '%idle'은 CPU 코어상에 프로세스도, 커널도 움직이고 있지 않은 idle 상태인 것을 의미한다.

 

무한루프 실행

// loop 프로그램(loop.c)
int main(void)
{
  for (;;)
    ;
}

시스템 콜을 호출하지 않고 계속 유저 모드에서 무한루프를 도는 프로그램을 실행하여 sar로 확인하면 위와 같은 결과가 출력되는 것을 확인할 수 있다. 

 

위 그림처럼 통계 정보를 측정하고 있던 1초 동안, CPU 코어 3에서는 유저 프로세스, 즉 loop 프로그램이 계속해서 동작하고 있었던 것을 알 수 있다.

 

 

시스템콜 무한루프 실행

#include <sys/types.h>
#include <unistd.h>

int main(void)
{
  for (;;)
    getppid();
}

프로그램을 컴파일해서 실행하면 위와 같은 통계 정보를 출력하는 것을 확인할 수 있다.

실행 결과를 보면 통계 정보를 측정하고 있던 1초 동안 한 일을 알 수 있으며 이를 해석하면 다음과 같다.

  • CPU 0 내에서는 ppidloop 프로그램을 34.78%의 비율로 실행하고 있다.
  • ppidloop 프로그램의 요청에 대응하여 이 프로그램의 부모 프로세스를 얻는 커널의 처리를 65.22% 비율로 실행하고 있다.

 

%system이 100%가 아닌 이유는 main() 함수 안에 getppid()를 무한루프하기 위해 루프 처리를 하는데 ppidloop 프로그램이 프로세스를 사용하고 있기 때문이다. 이러한 내용을 그림으로 나타내면 위와 같다.

 

시스템 콜의 소요 시간

$ strace -T -o hello.log ./hello

$ cat hello.log

strace에 '-T' 옵션을 붙여 각종 시스템 콜 처리에 걸린 시간을 마이크로초 단위로 정밀하게 측정할 수 있다. %system의 점유율이 높을 때 구체적으로 어떤 시스템 콜에 시간이 걸리고 있는지를 확인할 경우 이 기능을 사용하면 편리하다.

여기서 "hello world\n" 문자열을 출력하는 처리에 639마이크로초가 걸렸던 것을 확인할 수 있다. strace에는 이것뿐만 아니라 시스템 콜이 호출된 때의 시간을 마이크로초 단위로 표시하는 '-tt' 옵션도 있다.

 

시스템 콜의 wrapper 함수

리눅스에는 프로그램의 작성을 도와주기 위해 프로세스 대부분에 필요한 여러 라이브러리 함수가 있다. 시스템 콜은 보통의 함수 호출과는 다르게 C 언어 등의 고급언어에서는 절대 직접 호출할 수 없다. 아키텍처에 의존하는 어셈블리 코드를 사용해 호출할 필요가 있다.

 

mov $0x6e,%eax
syscall

x86_64 아키텍처에는 getppid() 시스템 콜을 위처럼 호출한다. 우리는 이런 어셈블리 언어를 사용하지 않으면 자세한 의미를 이해할 필요가 없다. 이렇듯 OS의 도움이 없다면 각 프로그램은 시스템 콜을 호출할 때마다 아키텍처에 의존하는 어셈블리 언어를 써서 고급언어로부터 어셈블리 코드를 호출해야 하기 때문에 다른 아키텍처에 사용할 수 없어 의존성을 띄게 된다.

 

이러한 문제를 해결하기 위해 OS는 내부적으로 시스템 콜을 호출하는 일만 하는 함수를 제공하는데 이를 시스템 콜 wrapper라고 한다. wrapper 함수는 아키텍처별로 존재하고, 고급언어로 써진 사용자 프로그램으로부터 각 언어에 대응하여 준비된 시스템 콜의 wrapper 함수를 호출하기만 하면 된다.

 

표준 C 라이브러리

C 언어에는 ISO(* International Organization for Standardization,국제 표준화 기구)에 의해 정해진 표준 라이브러리가 있는데 리눅스에도 이 표준 C 라이브러리가 제공되고 있다. 보통은 GNU 프로젝트가 제공하는 glibc를 표준 C 라이브러리로 사용한다. 대부분의 C 프로그램은 glibc를 링크(link)하고 있다.

glibc는 시스템 콜의 wrapper 함수를 포함한다. 또한 POSIX 규격(유닉스 계열의 OS가 갖추어야 할 각종 기능을 정해둔 귝격)에 정의된 함수도 제공한다.

프로그램이 어떠한 라이브러리를 링크하고 있는가는 'ldd' 명령어를 사용하여 확인할 수 있다.

ppidloop 명령어와 파이썬3을 실행하는 python3 명령어에 대해서 링크된 라이브러리를 확인해보면 libc를 링크하고 있는 것을 확인할 수 있다. 즉, C 언어를 직접 사용하는 개발자가 드물어졌지만 OS 레벨에서는 매우 중요한 언어임을 알 수 있다.

이것뿐만 아니라 시스템에 의존하고 있는 여러 가지 프로그램에 대해 ldd를 실행해보면 매우 많은 프로그램이 libc에 링크되어 있음을 알 수 있다.

 

OS가 제공하는 프로그램

OS가 제공하는 프로그램은 OS가 제공하는 라이브러리와 마찬가지로 대부분의 프로그램이 필요로 한다. OS의 동작을 변경시키는 프로그램도 OS의 일부로써 제공된다. OS가 제공하는 프로그램은 다음과 같고 우리는 잘 모르는 사이 간접적으로 사용하고 있.

  • 시스템 초기화: init
  • OS의 동작을 바꿈: sysctl, nice, sync
  • 파일 관련: touch, mkdir
  • 텍스트 데이터 가공: grep, sort, uniq
  • 성능 측정: sar, iostat
  • 컴파일러: gcc
  • 스크립터 언어 실행 환경: perl, python, ruby
  • 셸: bash
  • 윈도우 시스템: X

'교육 및 책 > 실습과 그림으로 배우는 리눅스 구조' 카테고리의 다른 글

프로세스 스케줄  (0) 2023.08.22
프로세스 관리  (0) 2023.08.04