Julie의 Tech 블로그

리눅스 - (1) 기본 구조와 프로세스에 대해 본문

Tech

리눅스 - (1) 기본 구조와 프로세스에 대해

Julie's tech 2021. 5. 12. 23:30
728x90

OS와 하드웨어에 대한 기초 공부를 하기 위해서, 참고 도서를 참조하여 정리해보고자 한다.

개인적으로 대학과정 중 운영체계 수업에서 배웠던 내용을 복습할 수 있어 신선했다.

(참고 도서 : 실습과 그림으로 배우는 리눅스 구조, 다케우치 사토루)

요즈음 다양한 타입의 컴퓨터 시스템이 존재한다 : 컴퓨터, 스마트폰, 태블릿 등

전반적으로 컴퓨터 시스템은 아래와 같은 구조로 되어 있다.

출처 : https://wan-blog.tistory.com/32

우측 I/O device를 입출력 장치라고 부르는데, 입출력 장치를 통해 컴퓨터로의 request가 들어오게 되면, 명령어를 메모리에서 읽어 CPU에서 실행하고, 산출된 값을 다시 메모리에 기록하게 되는 구조이다.

(이처럼 디바이스는 디바이스 드라이버를 두고 OS와 소통하게 되는데, 디바이스 제조업체가 디바이스 드라이버를 OS마다 별도로 구현하여 제공하고 있다. OS 회사는 디바이스 제조업체들에게 스펙을 제공하여 자신의 OS와 맞게 동작가능한 가이드라인을 준다.)


OS의 개념

위 구조를 반복하여 하나의 기능으로 정리된 것을 프로그램이라고 한다.

프로그램의 종류 중 하나인 OS(운영체제)는 하드웨어를 직접 조작하여 프로세스, 기억장치 및 입출력장치 등을 관리하는 역할을 한다. 즉 어플리케이션과 하드웨어 사이에 위치하여 중간 관리자 역할을 하는 것이다. 다시 말해 하드웨어 리소스(CPU, 메모리, 디스크 등)을 관리하고 프로그램들을 지원하는 것이다.

우리가 어플리케이션을 개발/사용할 때 CPU나 메모리 등에 대해 상관하지 않는 것도 역시 OS 덕분이다.

(* 추가 - 미들웨어는 어플리케이션 개발시 OS에 접근하여 처리해야하는 공통적인 프로세스들을 처리가능하도록 서비스화한 것이다. 예를 들어 웹 서비스시에 웹 캐시를 개발해야할 경우 미들웨어를 구매하여 연결하기만 하면 됨)

우리에게 잘 알려진 OS는 윈도우와 리눅스 이다.

이 둘의 차이는 근본적으로 시스템에 몇 명의 유저가 접근하여 사용가능한지이다.

리눅스는 멀티유저 시스템이고, 윈도우는 싱글유저 시스템이다.

멀티유저일 경우 싱글과는 다르게, 메모리를 어떻게 분배할 것인지, 보안을 어떻게 관리할 것인지가 화두가 된다.

이 배경에 따라 윈도우는 자연스럽게 PC(Personal Computer)의 등장에 따라 채택되었다.

윈도우는 GUI(Graphic User Interface)를 기반으로 하고 있어, 리눅스와는 다르게 사용성 편의가 증대되었다. 유틸리티들이 모두 화면에서 아이콘 형태로 제공되기 때문이다.

반면 리눅스는 CLI(Command Line Interface) 기반이기 때문에, 쉘을 통해 명령어를 수행하게 된다.

출처 : https://medium.com/pocs/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%95%EC%9D%98%EB%85%B8%ED%8A%B8-1-d36d6c961566


OS의 구성요소

OS는 여러 가지 프로그램으로 구성되어 있다.

대표적으로 커널, 쉘, 유틸리티라는 C 프로그램들을 예시로 들 수 있다.

여기서 커널은 OS의 구성요소 중 하나인데, 커널 모드에서 동작하는 처리를 담당하는 프로그램이다.

커널은 CPU나 메모리 등의 리소스를 관리하며, 프로세스에 각 자원을 할당하는 역할을 한다.

이 역할에 따라 커널은 운영체제를 구성하는 다른 프로그램들과는 달리 메모리에 항상 상주하고 있다.

즉 시스템을 처음 부팅하면 커널이 가장 먼저 메모리에 올라가게 된다. 커널 실행파일이 가장 먼저 로딩(Load)이 된다는 뜻이다.

다른 프로그램들은 디스크에 상주하고 있으며, 사용자로부터 요청이 있을 때마다 메모리에 잠시 올라온 뒤 종료되면 다시 디스크로 내려가게 된다. 이들을 유틸리티라고 통칭하기도 하며, Command라고도 부르기도 한다.

(이러한 여러 Command들의 교통정리 역할을 하는 것이 Shell이다)

출처 : https://medium.com/pocs/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%95%EC%9D%98%EB%85%B8%ED%8A%B8-1-d36d6c961566


OS의 모드와 시스템 콜

OS는 사용자 모드와 커널 모드로 나뉘게 된다. 리눅스는 특정 프로세스의 악의적인 조작 시도를 사전에 차단하기 위해 프로세스는 사용자 모드로 동작하게끔 하였고, 디바이스 드라이버만이 커널 모드로 동작할 수 있게 구성하였따. 이렇게 써두면 이해가 잘 안간다. 그럴 경우 잠시 잊어두고 아래 글을 다시 읽기 시작해보자.

아까 리눅스가 멀티 유저 시스템이라는 것을 다시 상기해보면, 두 명의 유저가 각자의 쉘에서 프로그램을 실행하고 있을 때, 어느 한 쪽에서 버그가 생겨 다른 유저의 영역에 데이터를 write하게 되면 오류가 발생할 수 있다. 이 경우를 방지하기 위해 리눅스는 커널을 제외한 모든 프로그램에 I/O instruction을 할 권한을 배제하였다.

(I/O instruction 외에 다른 유저에게 영향을 줄 수 있는 권한들은 배제되어 있다. ex. special register access)

따라서 이러한 배제된 권한 영역을 수행해야할 경우 (= 프로세스에서 커널의 역할이 필요한 경우) '시스템 콜'이라는 것을 통해 커널에 처리 요청을 내린다. 즉 시스템 콜은 OS가 제공하는 API와 같은 것이라 이해하면 된다.

함수 형태로 구현되어 있으며, 커널이 들어온 시스템콜을 파악하여 입출력 명령에 대해 검증한 뒤 수행하게 된다.

시스템콜의 예로는 프로세스 생성/삭제, 메모리 확보/해제, 프로세스간 통신(IPC), 네트워크, 파일 시스템 다루기 등이 있다.

(OS는 시스템 콜을 호출하는 담당 함수를 제공하는데, 이를 Wrapper라고 부른다. high-level 언어로 작성된 프로그램은 이 Wrapper함수를 통해 시스템 콜을 처리할 수 있다.)

여기서 의문점이 생길 수 있다. 우리는 보통 다른 프로그램 내에서 작성된 함수로의 접근권한이 없다. 동일 프로그램 내에서만 접근이 가능하기 때문이다. 이 때 앞에서 등장한 OS의 모드 변경이 일어나게 된다.

(리눅스 설계자는 CPU에 binary bit를 두었다. 0일 경우 사용자 모드이고, 1일 경우 커널 모드로 셋팅된다. 커널 모드로 셋팅될 경우 CPU는 모든 영역의 메모리에 접근이 가능하다. 하지만 0일 경우 메모리에서 자신에게 할당된 주소 공간에만 접근이 가능하다.)

OS는 여러 프로그램들을 제공한다. 이 중 몇 가지는 이미 많은 사람들이 무의식적으로 사용하고 있을 것이다.

- 시스템 초기화 : init

- 파일 관련 : mkdir

- 컴파일러 : gcc

- 셸 : bash

- 스크립트 언어 실행 환경 : perl, python, ruby

** 참고 : 리눅스 터미널에서 아래와 같이 strace 또는 sar 명령어를 통해 시스템 콜 히스토리와 프로세스에 소요된 모드 비율 등을 확인할 수 있다.

strace -o name.py.log python3 ./name.py //프로그램의 시스템콜 히스토리 sar -P ALL 1 //1초 단위로 측정


커널의 프로세스의 생성 및 삭제

앞서 시스템을 부팅할 때 가장 먼저 메모리에 로드가 되는 것이 커널이라고 하였다.

사용자가 그 뒤 터미널을 실행할 경우 커널은 쉘이라는 Child process를 생성하게 된다.

자식 프로세스를 생성하였으니 커널은 이제 부모 프로세스가 되는 것이다.

이 때 어떻게 자식 프로세스를 생성하며, 자식 프로세스는 부모 프로세스와 어떻게 다를까?

리눅스에서는 프로세스의 생성 목적에 따라 fork()와 execve() 함수를 사용한다.

fork() 함수는 단일 프로그램의 처리를 여러 프로세스로 나누어 처리할 때 사용한다. 블록체인이 포크를 떠서 새로운 분기를 생성하듯이, 프로세스를 fork하여 부모 프로세스와 자식 프로세스로 나누게 된다.

부모 프로세스는 자식 프로세스의 ID를 출력하게 된다. 반면 자식 프로세스는 0을 출력한다.

이 과정을 좀 더 자세히 살펴보면, fork()를 통해 PCB(Process Control Block) 공간을 생성하게 된다.

이 PCB의 초기상태는 부모 프로세스의 PCB와 동일하다. 부모가 사용하던 리소스(터미널, 키보드, 스크립트)를 자식 프로세스도 동일하게 사용하게 된다.

그 후 부모 프로세스와 동일한 소스코드(image)도 구성하게 된다.

위의 경우와는 달리 전혀 새로운 프로세스를 생성할 때는 execve() 함수를 사용하게 된다.

여기서 기존 프로세스를 별도의 프로세스로 덮어쓰는 방식으로 구현되기 때문에 프로세스의 수에는 차이가 없게 된다.

실행할 프로그램 파일의 데이터 영역에 대한 정보 및 메모리 주소 등을 읽어 기존 프로세스 데이터에 엎어쓰게 된다.

위 두 가지 함수를 응용하여 전혀 다른 새로운 프로세스를 생성할 때는 fork()함수를 수행한 뒤 자식 프로세스에 대해 exec() 함수를 실행하면 된다.

(** 참고 : 프로세스란 프로그램이 명령어와 데이터가 함께 메모리에 적재된, 실행중인 프로그램을 의미한다. 내가 크롬이라는 프로그램을 두 개 실행하게 되면 2개의 프로세스가 생성된다.)


프로세스 스케줄러와 프로세스의 상태 변화

리눅스 커널은 프로세스 스케줄러를 통해 여러 프로세스를 동작하게 된다.

하나의 CPU는 동시에 하나의 프로세스만 처리할 수 있지만, 여러 프로세스를 수행해야할 때는 각 프로세스를 잘게 쪼개어 우리 눈엔 동시에 처리하는 것 처럼 보이게끔 한다.

실제로 sched 프로그램을 통해 위 사실을 확인해볼 수 있다.

아래 명령어 taskset으로 프로그램을 수행할 CPU 번호를 변경하며 확인해볼 때,

경과 시간동안 동작한 프로세스의 번호와, 여러 프로세스의 각 진행도를 프로세스 별로 plotting하였을 때, CPU가 동시에 여러 프로세스를 처리하지 않는다는 것을 확인할 수 있다.

taskset -c 0 ./sched <n> <total> <resol>

위 실험을 통해 한 CPU에는 여러개의 프로세스가 라운드로빈 방식으로 처리되고 있다는 점도 확인할 수 있다.

프로세스의 status를 확인할 수 있는 명령어가 있다. 흔히 프로세스 리스트를 보고자 실행하는 아래 명령어를 입력하면, 프로세스 별로 STAT 필드를 확인하여 상태를 알 수 있다.

ps ax

- R : 실행 혹은 실행 대기

- S 또는 D : 슬립 상태

- Z : 좀비 상태

대부분 프로세스는 S로 슬립 상태로 나타날 것이다.

일반적으로 프로세스가 생성되면 실행 대기 상태로 진입하게 되고, 이벤트가 발생하기 전까지는 슬립 상태로 있다. 이벤트가 발생하여 CPU를 실행할 수 있는 권한을 얻게 되면, 실행상태로 변환하게 되고 수행을 마무리하였을 때는 좀비 상태로 돌입하게 된다.

이처럼 프로세스는 생성되어 종료되기까지 CPU를 계속 사용하는 것이 아니라, 상태를 변화하며 각 상태에 맞게끔 CPU를 사용하게 되어 있다.

CPU가 아무런 프로세스를 처리하고 있지 않을 때는 idle 상태에 돌입하였다고 표현한다.

위에서 살펴본 명령어 sar를 이용하면 %idle이라는 컬럼을 통해 idle상태에 있는 CPU를 확인할 수 있다.


Throughput과 Latency

성능 지표로 자주 활용되는 스루풋과 레이턴시 용어를 각각 짚고 넘어가보자.

- throughput : 단위 시간당 처리된 일의 양

- latency : 시작부터 종료까지 처리하기 위해 소요된 시간

프로세스를 증가할 경우 레이턴시는 늘어나게 되지만, CPU가 idle상태가 없을 경우 프로세스를 증가하더라도 throughput에는 영향을 미치지 않게 된다. (물론 다량의 프로세스가 동시에 수행될 경우 컨텍스트 스위치 (프로세스 상태 변화) 등의 오버헤드가 발생할 수 있다.)

throughput과 latency는 서로 상관관계에 있는 경우가 많다. 이에 따라 실제로 시스템을 설계할 때는 각 두 성능 지표의 목표치를 세우고, 그에 따라 시스템을 고도화하곤 한다.

멀티코어 CPU의 환경에서는 단일 코어 CPU 환경과 마찬가지로 여러 개의 프로세스를 동시에 동작시키지 않게 되면 throughput이 오르지 않는다. 다량의 프로세스가 동시에 수행될 경우 각각 타임 슬라이스를 지정하여 CPU에서 순차적으로 처리하게 된다.

실제 프로세스 별로 각 명령어와 경과

시간, CPU사용시간을 아래와 같은 명령어를 통해 확인할 수 있다.

ps -eo pid, comm, time, etime

보통 로컬에 브라우저를 띄워놓고 있는 경우 사용자와 interactive하는 환경이기 때문에 별도의 입력이 없는 이상 슬립 상태에 돌입하여 경과시간에 비해 CPU 사용 시간이 크지 않을 수 있다.

추가로 다른 프로세스에 비해 특정 프로세스가 CPU 사용 권한 우선순위를 높게 지니게 하기 위해서 nice라는 명령어를 사용할 수 있다.


다음 편에서는 메모리와 파일 시스템에 대해 다뤄볼 것이다 :)

++ 추가

리눅스는 리눅스 커널을 사용하는 운영체제를 일컫기도 한다.

사람들마다 리눅스를 커널만을 지칭하기도 하고, 운영체제를 통틀어 지칭하기도 한다.

리눅스 커널의 배포판으로 Ubuntu, CentOs, Debian 등이 있다.


참고 도서 : 실습과 그림으로 배우는 리눅스 구조, 다케우치 사토루

(https://book.naver.com/bookdb/book_detail.nhn?bid=14524977)

 

실습과 그림으로 배우는 리눅스 구조

<개발자 레벨업 프로젝트> OS의 구조를 들여다 보자!스마트폰에서 클라우드까지, IT 산업 곳곳에서 리눅스가 보입니다. 이렇게 산업과 생활 전반에 걸쳐 사용하는 OS는 어떻게 움직일까요?이 책은 컴퓨터 시스템을 구성하는 운영체제를, 그중 가장 많이 사용하는 리눅스를 예로 들어 설명했습니다. 단순히 읽고 넘어가지 않고 스스로 실습해보며 동작 원리를 익힐 수 있으며, 프로그래머, 시스템 설계자 등 대부분의 개발자에게 꼭 필요한 지식을 담았습니다.이 책을 통해 소프트웨어와 하드웨어의 상호작용을 이해하세요. 그다음, 책에서 배운 운영체제 ...

book.naver.com

참고 자료 : https://medium.com/pocs/%EB%A6%AC%EB%88%85%EC%8A%A4-%EC%BB%A4%EB%84%90-%EC%9A%B4%EC%98%81%EC%B2%B4%EC%A0%9C-%EA%B0%95%EC%9D%98%EB%85%B8%ED%8A%B8-1-d36d6c961566

반응형