ROS, 셸, 커널, 터미널의 상호작용 이해

ROS, 셸, 커널, 터미널의 상호작용 이해

ROS 사용하기 전에 셸, 커널, 터미널의 개념 이해하기

이 글에서는 Ubuntu에서 ROS2를 사용하는 과정에서, 사용자가 터미널에 명령을 입력하면 어떤 방식으로 셸, 커널, 그리고 터미널이 상호작용하는지를 설명합니다. 또한, Unix/Linux/Ubuntu의 차이와 각각의 개념이 ROS2 사용 환경에서 어떤 역할을 하는지에 대해 알아보겠습니다.

1. 서론

ROS2와 Ubuntu의 기본 개념 : ROS2는 로봇 운영 체제(ROS)의 최신 버전으로, Ubuntu에서 가장 자주 사용되는 운영체제입니다. 하지만, ROS2를 사용하다 보면 터미널에 명령을 입력하고, 시스템이 어떻게 동작하는지에 대한 이해가 부족한 경우가 많습니다.

많은 경우 터미널에 명령어를 복사, 붙여넣기하여 빌드 및 설치 과정을 따르지만, 정작 명령어를 입력하는 이유나 그 과정의 배경을 깊이 생각할 시간은 부족할 수 있습니다.

터미널 일러

그래서 앞으로 설명할 셸, 커널, 터미널이라는 용어를 정확히 구분하고, ROS2와의 상호작용을 설명하여 여러분들이 ROS2를 사용하고 있는 환경에 대한 명확한 이해를 가질 수 있도록 도움이 되었으면 좋겠습니다.

2. Unix / Linux / Ubuntu의 구분

Unix는 1969년에 시작된 운영체제의 시초로, 다중 사용자, 다중 작업을 지원하는 철학과 설계 원칙을 가지고 있습니다.

  • Linux의 등장 : Unix의 철학을 따르며 1991년에 개발된 오픈소스 커널로, 전 세계의 개발자들이 참여하는 커뮤니티 기반 프로젝트입니다. Linux는 Unix와 유사한 기능을 제공하며, 다양한 배포판으로 분기되었습니다.

  • Ubuntu란? : Ubuntu는 Linux 배포판 중 하나로, 사용자 친화적인 인터페이스와 ROS2와 같은 로봇 애플리케이션에서 주로 사용됩니다.

Unix는 철학과 구조의 뿌리, Linux는 그 철학을 따르는 커널, Ubuntu는 Linux 커널을 사용하는 운영체제입니다.

커널에서 터미널까지 상호작용

이러한 Unix / Linux / Ubuntu의 관계를 요약하자면,

Unix: Unix-like 운영체제 전체를 포함하는 가장 넓은 개념

Linux: Unix-like 운영체제 중 하나로, 오픈소스 커널을 기반

Ubuntu: Linux 배포판 중 하나로, Linux 커널을 사용

따라서 Unix > Linux > Ubuntu의 순서로 위와 같이 관계를 정리할 수 있으며, 각 단계에서 더 구체적이고 특화된 개념으로 좁혀진다고 생각하면 됩니다.

3. 셸(Shell)이란?

셸은 사용자와 운영체제 커널 간의 인터페이스입니다. 사용자가 터미널에 명령을 입력하면, 셸이 그 명령을 해석해 커널에 전달하고, 커널이 그 명령을 실행하는 방식으로 작동합니다.

bash, zsh, csh 등 여러 종류의 셸이 있으며, Ubuntu는 기본적으로 Bash 셸을 사용합니다. 그래서 ROS에서 bash를 사용한 셸스크립트를 많이 보셨을 겁니다.

셸 스크립트는 여러 명령을 자동으로 실행할 수 있도록 작성된 프로그램입니다. Ubuntu에서 .bashrc 파일을 통해 환경 변수를 설정하거나, ROS2 환경을 로드하는 스크립트를 작성할 수 있습니다.

사용자가 터미널에 명령어(예: ls, echo, ros2 run 등)를 입력하면, 셸은 이 명령어를 해석(파싱)합니다. 명령어의 형식, 옵션, 인자 등을 분석하여 어떤 작업을 수행해야 하는지 결정합니다.

셸은 입력된 명령어를 해석한 후, 그에 맞는 실행 파일을 찾습니다. 아주 간단한 예시로 ls 명령어를 입력했을 때 일어나는 일들을 한 번 천천히 살펴보겠습니다.

3.1. ‘ls 명령어’ 입력시 일어나는 일

우리가 ls 명령어를 입력했다고 가정하겠습니다. 그럼 셸은 시스템의 파일 시스템에서 ls 프로그램을 찾습니다. Ubuntu라면 /usr/bin/ 디렉토리에 ls라는 파일이 있을 겁니다. 해당 디렉토리에 여러분이 입력했을 sudo, systemctl, mount 등등이 파일로 존재하고 있답니다.

명령어 파일들

다양한 명령어들이 파일로 존재하는 것을 위와 같은 경로에서 확인

이때, PATH 환경 변수를 참고하여 실행 파일이 저장된 경로를 탐색하여 파일(지금 예시에서는 ls 파일)을 찾아냅니다. 셸은 해당 작업을 실행하기 위해 시스템 콜을(System Call)을 사용하여 커널에게 요청합니다.

시스템 콜 : 커널과 사용자 프로그램 간의 인터페이스로 커널에 특정 작업을 요청하는 함수 호출입니다.

대표적인 시스템 콜에는 fork(), exec(), open(), read(), write() 등의 함수가 있습니다.(Linux API 기초 등의 개념을 접하셨다면 들어봤을 함수일 겁니다.)

시스템콜 구조

👉 System Call Mechanism - Linux Magazine

추가적으로 사족을 달면 해당 함수는 C 언어 계열의 함수가 아니라 운영 체제의 커널에서 제공하는 기능이지만 소스코드로써 라이브러리 함수를 통해 사용할 수 있긴 합니다. 이에 대한 상세 설명은 생략하고 시스템 콜을 통해 ls 프로그램을 로드하고 CPU, 메모리, 파일 시스템 등의 리소스를 관리하여 프로그램이 실행될 수 있도록 합니다.

커널은 하드웨어와 직접 상호작용하여 프로그램이 올바르게 실행될 수 있도록 지원합니다. 커널이 명령어를 처리한 이후 결과를 생성하면, 셸은 그 결과를 받아 터미널에 출력합니다.

예시상 ls 명령어는 파일 목록을 출력하겠죠?

그래서 ls 명령어를 실행한 경우, 현재 디렉토리의 파일 목록이 터미널 화면에 출력되는 겁니다. 작업이 완료되면 셸은 다시 프롬프트로 돌아와 사용자로부터 새로운 명령어 입력을 기다립니다.

3.2. source /opt/ros/humble/setup.bash는 왜 하는걸까?

ROS2를 실행하기 위해서는 환경 변수가 올바르게 설정되어 있어야 합니다. 보통 source /opt/ros/humble/setup.bash를 통해 ROS2의 환경 변수가 셸에 설정됩니다.

이러한 환경변수를 토대로 ROS2와 관련한 패키지가 정상적으로 인식하여 노드를 실행할 수 있을 겁니다.

터미널 창을 새로 열면 환경 변수 설정이 풀리는 걸 경험해보신 적이 있을 겁니다.

그래서 아래와 같은 명령어를 입력해두고 조금이나마 명령어 입력 반복 작업을 줄였던 경험이 있을 겁니다.

echo "source /opt/ros/humble/setup.bash >> ~/.bashrc
source ~/.bashrc

왜 위와 같은 입력만으로 터미널 창을 열 때마다 입력할 필요가 없는 걸까요?

이를 위해 로그인 셸 / 비로그인 셸 개념을 알아두면 좋습니다.

비로그인 셸 : 터미널 에뮬레이터를 통해 새로운 터미널 창을 열 때, 또는 이미 로그인된 상태에서 셸을 새롭게 시작할 때 실행되는 셸입니다.

로그인 셸 : 사용자가 시스템에 로그인할 때 시작되는 셸입니다. SSH로 원격 접속하거나, 콘솔을 통해 로그인할 때 /etc/profile, ~/.bash_profile, ~/.bash_login, ~/.profile과 같은 파일들을 순차적으로 읽습니다.

로그인 셸은 우리가 부팅을 하면서 시작됩니다. 터미널 앱을 실행해 터미널 창을 열면서(새로운 세션 활성화) 비로그인셸들이 활성화되며 여기에 .bashrc 파일 같은 존재가 있습니다.

.bashrc 파일에는 사용자 환경을 설정하기 위한 다양한 명령어와 설정이 포함될 수 있습니다. 예를 들어, 명령어 별칭(alias), 프롬프트 설정, 경로 설정 등이 여기에 포함되는 것이죠.

로그인셸 중 ~/.profile 파일이 초기화 스크립트 실행 역할을 하게 됩니다.

if [ -n "$BASH_VERSION" ]; then
    # Source the .bashrc file if it exists
    if [ -f "$HOME/.bashrc" ]; then
        . "$HOME/.bashrc"
    fi
fi

위와 같이 HOME 환경 변수를 체크해(보통 “/home/유저이름/”으로 설정된) .bashrc 실행하도록 합니다.

그럼 .bashrc 파일이 실행되고 echo 명령어로 작성해둔 “source /opt/ros/humble/setup.bash” 명령어도 실행하면서 유저 입장에서 자동적으로 설정이 이루어지는 것처럼 되는 것이죠.

이런 방식에 의거해서 .bashrc가 자동적으로 실행 안된다면 .profile 파일을 추가해서 .bashrc가 터미널 창을 열면 알아서 실행되도록 한다든지 .bashrc에 특정 명령이나 시퀀스를 알아서 해주도록 작성하여 터미널 창을 열 때마다 입력하는 번거로움을 줄여줄 수 있는 것이죠.


세밀하게 살펴보니 생각보다 복잡하죠? 처음엔 이 흐름을 짚기엔 부족한 개념과 경험이 적다보니 와닿지 않겠지만 터미널을 통한 몇몇 작업 경험이 있다면 이 흐름을 유추하는데 조금 도움이 되리라 생각합니다.

4. 커널(Kernel)이란?

커널은 운영체제의 핵심으로, 하드웨어와 소프트웨어 간의 중재자 역할을 합니다. 프로세스 관리, 메모리 관리, 장치 드라이버 제어 등의 작업을 수행합니다.

용자가 터미널에 명령을 입력하면, 셸은 커널에게 하드웨어 자원을 요청합니다. 커널은 이 요청을 처리하여 프로그램이 실행될 수 있도록 합니다.

Ubuntu는 Linux 커널 위에서 동작하므로, ROS2의 노드나 프로세스들이 커널에 의해 관리됩니다. 커널이 ROS2의 리소스 사용, 프로세스 간 통신 등을 제어합니다.

커널에서 터미널까지 상호작용

여러분이 로봇이 주행하도록 모터 드라이버에 데이터를 송수신하는 프로그램을 실행했다면 프로그램의 프로세스는 운영체제 특성상 셸을 통해 시스템 콜을 호출하고 커널로 전달하고 커널은 하드웨어(=모터 드라이버)에 연결된 장치로 데이터를 주고 받도록 명령을 수행할 겁니다.

그래서 PC에 연결된 장치가 저장되는 /dev 디렉토리를 알고 장치가 파일로 저장된다는 것을 인지했다면?

하드웨어와 상호작용

위의 상호작용들이 Ubuntu에서 어떤 계층에서 인터페이스가 활성화하는지를 이미 알고 있으니 곁가지 정보는 빠르게 차단하거나 털어낼 수 있습니다.

데이터가 어떤 과정을 거쳐서 이동하는지를 파악할 수 있다면 방대한 소스코드 속에서 필요한 구조만 잡아내 의도하는대로 데이터를 보내거나 입력하면 되는 것이죠.

만약 소스코드 구조가 잡혀 있지 않아도 어떻게 연결되는지를 이해하고 있으니 빠르게 구조를 잡고 일련의 장치들을 동작하게 할 수 있는 것이죠.

5. 터미널(Terminal)이란?

  • 터미널의 역할 : 터미널은 사용자가 운영체제와 상호작용하는 인터페이스입니다. 셸을 통해 명령어를 입력하고, 결과를 출력하는 창입니다.

  • 터미널 에뮬레이터 : 실제 터미널 장치가 아닌 소프트웨어로 구현된 터미널 에뮬레이터(예: GNOME 터미널, Terminator)에서 명령을 입력하여 셸을 통해 시스템에 전달됩니다.

우리가 일반적으로 Ubuntu, Debian 등의 운영체제를 설치하면서 이미 설치되어 있는 터미널 앱을 보셨을 겁니다.

엄밀하게 말하면 이 터미널 앱은 소프트웨어로 구현된 터미널 에뮬레이터인 “GNOME 터미널”입니다.

커널에서 터미널까지 상호작용

또한 터미널 ‘앱’은 다양한 소프트웨어로 존재하며, 여러분의 상황에 맞게 취사선택하여 명령어를 입력하고 셸은 명령어를 구분해 커널과 대화하는 것이죠.

이걸 인지하고 로봇과 같은 하드웨어를 사용하는 환경에서 소스코드로 실행한 여러분의 프로그램은 애플리케이션 계층(Application Layer)에서부터 과연 어떠한 흐름으로 계층 이동과 데이터 송수신이 이루어지는지 다가가는건 제법 큰 차이가 발생합니다.

터미널에 여러분이 ros2 run이나 colcon build와 같은 명령을 입력하면, 셸이 이를 해석해 커널에 전달하여 실행합니다.

ros2 run을 통해 실행한 프로그램은 main문의 argc, argv** 명령행 인자에서 어떤 일이 일어날까요?

몰라도 프로그램을 작성하고 구현하는데 문제가 있다는 건 아니지만 이해하고 있다는 건 장기적으로 디테일의 차이를 만들 겁니다.

👉 명령행 인자에서 일어나는 일?

6. 명령어 입력시 일어나는 셸, 커널, 터미널 간의 상호작용과 ROS2 동작의 흐름

ROS2 명령어 실행 과정: 사용자가 터미널에 ros2 run을 입력하고 ROS2 노드를 실행하면, 셸이 이 명령을 커널에 전달하고, 커널은 ROS2 노드를 실행합니다. 그 과정에서 프로세스 간 통신, 메모리 할당, CPU 스케줄링 등이 일어납니다.

ROS2 노드를 실행함으로써 일련의 결과가 발생할 것이고 커널은 해당 결과를 셸에 전달합니다. 셸은 그 결과를 받아 터미널에 출력하도록 요청합니다.

그렇게 터미널 창에서 로그 기록들을 확인하고 이는 프로그램 로직에서 발생했을 수도 PC와 연결된 하드웨어 장치의 상태의 기록일 수도 있고 다양할 겁니다.

프로그램을 실행하고 뜨는 에러 로그라든지 Ctrl + C 입력으로 프로그램을 종료했을 때 뜨는 Return은 셸과 커널의 존재를 알고 있다면 놓치고 있던 배경지식들이 연결될 겁니다.

7. 결론

셸, 커널, 터미널 간의 상호작용을 이해하는 것은 ROS와 같은 시스템에서 명령어가 어떻게 처리되는지를 명확하게 이해하는 데 중요합니다. 이를 바탕으로 ROS2를 사용하는 환경에서 더욱 효율적으로 작업할 수 있을 것입니다.

조금이나마 터미널과 셸의 상호작용을 이해하면서 우리가 실행했던 명령어 또는 프로그램을 실행하면서 작용하는 흐름을 구분짓는데 도움이 되리라 생각합니다.

ROS를 하시다 보면 ROS를 사용하는 건 도구를 빌려 구조를 잡는 것일 뿐 실제 프로젝트에서는 ROS를 사용하지 않고 프로세스가 동작하길 원하는 요구사항과 항상 마주하게 됩니다.

ROS의 편의성을 덜어내고 시스템을 구성한다는게 이러한 배경지식과 디테일이 예상 외의 돌파구를 만들어내주는 것 같습니다.

저도 이렇게 글로 정리하더라도 내가 이해한 것과 정리해서 글을 표현해봐도 놓치는 것이 늘 보이고, 제 스스로 다시금 공부하게 만드는 것 같습니다.

이번 글을 통해 터미널과 셸스크립트의 배경지식에 참고가 되었으면 합니다, 감사합니다.