일러두기
해당 글은 다케우치 사토루의 책 '실습과 그림으로 배우는 리눅스 구조'의 chapter 2 '사용자 모드로 구현되는 기능'을 읽고 배운 부분을 정리하기 위한 글입니다.
사용자와 하드웨어, 그리고 운영체제
컴퓨터 시스템에서 하드웨어와 사용자의 관계는 사뭇 오묘하다. 컴퓨터 시스템에서의 사용자는 인간을, 하드웨어는 컴퓨터 내부의 CPU, 메모리, 입출력장치, 저장장치 등을 의미하는데 일반적인 경우 컴퓨터의 하드웨어는 사용자인 인간의 편의를 위해 다양한 계산을 하는 방식으로 활용된다. 즉, 컴퓨터가 단순한 계산기에 불과했던 과거에서부터, 고사양의 게임을 돌린다거나 동영상, 사진 편집과 같은 작업을 수행하는 현재까지 컴퓨터의 하드웨어는 사용자의 요구를 입력받고 필요한 작업을 수행하여 결과물을 제출하는 일종의 생산자(혹은 도구) 역할을 하는 것이다. 이런 관점에서 사용자는 필요한 업무를 할당하는 갑(甲), 하드웨어는 그러한 업무를 처리하는 을(乙)의 입장을 갖는다.
그런데, 첫 번째 문단의 첫 번째 문장에서 표현했던 것처럼 하드웨어와 사용자의 관계는 단순히 갑과 을의 관계라고 보기에는 사뭇 오묘한데, 그 이유는 하드웨어 관점에서 사용자라는 존재는 다소 추상적인 존재이거나 신뢰할 수 없는 존재이기 때문이다.
하드웨어를 사용자가 직접 조작하는 행위는 사실 굉장히 어렵다. 특히 현대의 컴퓨터 시스템의 경우 컴퓨터에 들어가는 CPU가 멀티코어인 경우가 허다하기 때문에 하드웨어를 사용자가 직접적으로 제어하려는 행위는 많은 실수와 그로 인한 버그에 노출될 확률이 높다.
또한 컴퓨터 시스템을 사용하려는 사용자가 항상 건전한 행위를 하리라는 보장이 없는데, 가령 해커와 같은 악의적인 의도를 가진 사용자가 직접 하드웨어를 조작할 경우 시스템에 다양한 문제를 일으킬 수 있다. (운영체제를 밀어버린다던지..)
따라서 컴퓨터 시스템은 사용자 이외의 다른 요소, 즉 운영체제가 하드웨어를 조작하고 사용자는 운영체제를 통해 컴퓨터 시스템의 하드웨어에 간접적으로 접근하도록 하는 방법을 사용하는데 여기에서 등장하는 개념이 바로 사용자 모드와 커널 모드이다.
사용자 모드와 커널 모드
사용자 모드는 간단하게 컴퓨터 시스템을 사용하는 사람이 작업하는 환경이라고 생각하면 되겠다. 하드웨어에 접근할 수 있는 부분은 사용자가 직접적으로 제어할 수 없도록 제한되어 있기 때문에 사용자 모드에서 하드웨어를 조작하는 일은 불가능하다. 이는 앞서 설명한 바와 같이, 사용자가 직접적으로 하드웨어를 조작하는 일은 어렵고 실수의 여지가 많기 때문에, 그리고 사용자라는 대상이 컴퓨터 시스템에 있어 항상 신뢰가 가능한 대상이 아니기 때문이다. 사용자 모드에서는 사람이 java, python, c 등과 같은 고급 언어 등을 통해 코드를 작성해 프로그램을 만들고, 해당 프로그램을 실행해 프로세스로 구동하는 행동이 가능하다. 쉽게, 우리가 컴퓨터를 쓸 때 하는 모든 행동이 이 사용자 모드에 들어간다고 이해하면 쉬울 것 같다.
커널 모드는 컴퓨터를 사용하는 사람의 눈에는 보이지 않게, 컴퓨터 시스템 하부에서 직접 하드웨어를 조작하는 행위를 하는 모드라고 생각하면 될 것 같다. 우리는 프로세스를 실행할 때, 가령 크롬과 같은 웹 브라우저를 실행할 때 크롬 실행파일을 더블클릭해 실행한다. 그러면 컴퓨터 내부에서 일련의 작업이 거쳐진 후 모니터 화면을 통해 크롬 브라우저가 실행되어 출력된다. 여기서 크롬 파일을 실행한 주체는 사용자인데, 크롬 파일을 출력 장치인 모니터를 통해 사용자에게 보여주는 것은 커널이 담당한다. 시스템 내부에서 여러 연산처리를 통해 직접 하드웨어에 접근해 유저의 요청을 처리하는 작업이 커널 모드에서 행해진다.
CPU의 모드 변경 : 시스템 콜
사용자 모드에서 실행된 프로세스가 하드웨어 조작 등의 작업이 필요한 경우, 시스템은 시스템 콜을 활용해 CPU 모드를 커널 모드로 전환한다. 시스템 콜은 사용자 모드에서 커널로 작업을 요청할 때 수행되는 명령을 일컫는데 다음은 시스템 콜의 종류이다. 시스템 콜에 의해 호출된 커널은 하드웨어 조작 뿐 아니라 프로세스 혹은 메모리를 관리하는 작업도 수행할 수 있다.
- 프로세스 생성, 삭제
- 메모리 확보, 해제
- 프로세스 간 통신(IPC)
- 네트워크
- 파일시스템 다루기
- 파일 다루기(디바이스 접근)
그럼 사용자 모드에서 커널 모드로 변경되거나 커널 모드에서 사용자 모드로 변경되는 과정은 어떻게 진행이 될까? 우선 사용자 모드로 실행되고 있는 프로세스를 생각해보자. 프로세스는 사용자 모드에서 실행되다 커널의 작업이 필요해지면 시스템 콜을 호출한다. 그러면 CPU에서는 인터럽트 이벤트를 발생해 모드를 사용자 모드에서 커널 모드로 변경하고 커널을 동작시킨다. 이후 커널 모드에서 커널의 작업이 종료되면 시스템 콜 처리가 종료되고, 다시 사용자 모드로 돌아가 프로세스의 동작을 진행 시킨다.
strace 명령어를 통한 시스템 콜 호출 확인
실습을 위해 hello.c라는 파일을 리눅스 시스템에 생성하고 아래와 같이 코드를 작성한다.
#include <stdio.h>
int main(void) {
puts("hello world");
return 0;
}
이후 다음과 같은 명령어로 파일을 컴파일 한 후 파일을 실행해보자. 콘솔창에 hello world라는 출력이 나오는 것을 확인할 수 있다.
cc -o hello hello.c
./hello
이번에는 아래와 같은 명령어를 통해 작성된 프로그램에서 시스템 콜이 어떻게 호출되는지 확인해본다. 명령어를 통한 결과물은 실행 환경에 따라 다소 달라질 수 있다.
strace -o hello.log ./hello
cat hello.log
조금 보기는 불편하나, strace 결과의 각각의 줄은 1개의 시스템 콜을 보여준다. 가령 밑에서 3번째 줄의 write() 함수는 데이터를 화면이나 파일 등에 출력하는 시스템 콜을 의미한다. 해당 결과는 위에서 작성한 c 파일을 실행시켰을 때 출력되는 시스템 콜들로 write()를 제외한 대부분의 시스템 콜은 hello.c에 있는 main 함수를 실행시키기 위한 것들이다.
자바로 된 코드나 파이썬으로 된 코드를 strace로 실행해보면 마찬가지로 write() 함수가 출력된 것을 확인할 수 있는데 write() 함수는 시스템 콜 함수이기 때문에 언어와는 독립적인 것임을 확인할 수 있다.
sar 명령어를 통한 사용자 모드와 커널 모드의 비율 확인하기
다음으로는 sar 명령어를 통해 각각의 CPU 코어가 어떤 종류의 처리를 어느 정도의 비율로 하고 있는지 확인 할 수 있다. 처음 보면 이게 뭐지 싶을 수 있는데 크게 어렵지 않으니 천천히 따라와 보자.
우선 아래와 같이 리눅스 환경에서 명령어를 작성한 후 1 - 2초 후 ctrl + c를 눌러 sar를 종료한다.
sar -P ALL 1
필자의 환경에서는 CPU 코어가 1개라 all과 0번 코어 두개의 결과만 출력되지만 CPU 코어가 여러개인 경우 출력이 all, 0, 1, 2, ... 로 여러개일 수 있다.
한번 11:44:47 AM의 0번 cpu의 결과를 확인해보자. %user가 0.99이고 %idle이 99.01임을 확인할 수 있다. %user와 %nice의 합계가 사용자 모드이고 %system이 커널 모드를 나타낸다. %idle의 경우 CPU 코어 상에 프로세스도, 커널도 움직이지 않는 상태를 의미하는데, 쉽게 말해 CPU가 놀고 있다 정도로 해석하면 될 것 같다.
위의 경우를 보니 11시 44분 47초에 0번 CPU는 0.99%의 비율로 사용자 모드를 실행했고 나머지 99.01%의 비율로는 아무 행동도 하지 않았다는 것을 알 수 있다. 만약 loop문을 도는 코드를 실행한 후 다시 sar를 실행하면 user, nice, system의 비율이 급격하게 올라가는 것을 확인할 수 있는데 한번 sar 명령어를 통해 사용자 모드와 커널 모드가 어떤 코드에서 어떤 비율로 실행되는지 확인해보자.
참고로 아래는 부모 프로세스의 PID를 얻는 getppid() 함수를 루프문 안에 넣어 무한 반복하는 코드를 실행시킨 후 sar 명령어를 실행한 결과이다. 해당 코드를 실행할 때는 꼭 & 키워드를 붙여 pid를 확인한 후 sar 명령어 이후 프로세스를 kill 하도록 하자. 그렇지 않으면 계속 프로세스가 돌아가 CPU 점유율을 차지할 수 있다.
#include <sys/types.h>
#include <unistd.h>
int main(void) {
for (;;)
getppid();
}
cc -o ppidloop ppidloop.c
./ppidloop &
(pid 출력)
sar -P ALL 1
sar 명령어를 통해 확인해보면 이번에는 %idle이 전혀 없고 %user와 %system이 각각 40% 대의 비율과 60% 대의 비율로 나뉘어져 있는 모습을 확인할 수 있다. ppidloop라는 프로세스가 실행되는 동안 CPU는 사용자모드를 40% 대로, 커널 모드를 60% 대로 사용한 것임을 확인할 수 있다.
'운영체제' 카테고리의 다른 글
러프한 운영체제 기초 4편 | 가상메모리 (0) | 2022.06.05 |
---|---|
러프한 운영체제 기초 3편 | 메모리 (0) | 2022.04.13 |
러프한 운영체제 기초 2편 | CPU 스케줄링과 교착 상태 (0) | 2022.03.18 |
러프한 운영체제 기초 1편 | 프로세스와 약간의 쓰레드 (0) | 2022.03.09 |