모던C – 추상상태기계

값과 데이터

C프로그램은 표현방식(representation)보다는 값(value)을 중심으로 처리한다.

특정한 값에 대한 표현은 크게 중요하지 않은 경우가 많다. 값과 표현 사이의 변환 관계는 컴파일러가 처리한다.

표현에 얽매이는 경우 오류가 발생할 가능성도 배제할 수 없다.

예를들면, 8비트 정수로 -1을 표현한다고 생각했을 때 1111 1111이라는 표현을 생각할 수 있지만, 모든 머신이 2의 보수 방식을 이용해서 값을 나타낸다고 할 수 있을까? 또는 바이너리 머신이 아니라 추후에 개발될 수 있는 양자컴퓨터가 사용하는 큐비트에서도 동일한 표현으로 처리할까?

또한 동일한 표현이 서로 다른 값을 나타낼 수 있다. 1111 1111은 255인가 혹은 -1인가?

따라서 특정 머신, 구현에 얽매이지 않기 위해 C에서 데이터의 처리는 값으로 처리해야한다.

추상 상태 기계

C프로그램은 값을 처리하는 기계라고 볼 수 있다. 프로그램에서 사용하는 변수는 실행시점마다 특정한 값을 가지는데, 이 값은 최종 표현식으로 이어지는 중간값일 수 있다.

double x = 5.0;
double y = 3.0;
// ... 
x = (x * 1.5) - y;
printf("x is %g\n", x);

x의 값은 프로그램 실행 시점에 따라 5.0 -> 4.5로 변화한다.

프로그램에서 연산의 수행 과정과 결과가 항상 관측 가능(observable)한 것은 아니다.

주소기반메모리(addressable memory)에 저장하거나 출력 장치에 쓸 때만 볼 수 있다.

중간 계산 과정 중 (x*1.5)의 결과인 7.5를 관측할 수 있는가?

이러한 관측 불가능한 값에 대해서 컴파일러는 최적화(optimization)을 수행할 수도 있다.

컴파일러는 최종 결과의 정확성을 해치지 않는다면 겉으로 드러나지 않는 계산 과정을 얼마든지 축약할 수 있다.

즉, 위의 프로그램을 아래처럼 바꾼다면 프로그램의 동작에 차이가 있는가?

printf("x is 4.5\n");
double x = 4.5;
double y = 3.0;

이 계산 이후로 x, y를 더이상 사용하지 않는다면 어떨까? 다음과 같이 프로그램을 변경한다면 프로그램의 동작에 이상이 생길까?

printf("4.5\n");

이렇게 최적화를 한다고 해도 프로그램은 여전히 동작할 것이며, 프로그래머가 원했던 동작을 정확히 수행할 것이다.

오류없이 최적화할 때 중요한 단 한 가지 사항은, C컴파일러가 관측 가능한 상태(observable state)를 재현하는 실행 파일을 만들어내는지 여부다.

즉, 최적화 이전의 관측 가능한 상태(몇 가지 변수 및 프로그램 실행 중 출력되는 내용)가 동일하게 재현되는 변경이라면, 컴파일러는 자유롭게 최적화를 수행할 수 있다.

또한 이런 관측가능한 상태가 변경되는 전반적인 매커니즘을 추상 상태 기계(abstract state machine)이라고 부른다.

이때 “추상”이라는 용어가 의미하는 것처럼, C언어는 프로그램이 표현하는 추상 상태 기계를 다양한 플랫폼에서 각자의 능력과 필요에 맞게 구현할 수 있는 매커니즘을 제공한다.

추상 상태 기계에 대해 더 자세하게 살펴보기 위해 값, 타입, 표현에 대해 알아야 한다.

값value

C에서 값은 프로그램의 구체적인 구현 방식이나 프로그램 실행중에 표현되는 방식과는 독립적인 추상적인 개체다. 즉 0이라는 값은 모든 플랫폼에서 동일한 효과를 내야한다.(실제 세부적인 구현이 어떻게 되었든, 0을 다른 값x에 더하는 경우 프로그래머는 결과가 x일 것으로 기대할 것이다.)

C의 핵심 속성 중 하나는 이것이다. 값은 모두 숫자이거나 숫자로 변환된다.

타입type

타입은 값에 적용되는 속성이다.

값에는 타입이 있으며 컴파일타임에 결정된다.

값에 적용할 수 있는 연산은 타입에 의해 결정된다.

값의 타입에 따라 연산의 결과가 달라진다.

나중에 더 입력하는 것으로… 졸림…

C++20 import

2023. 08. 11

C++ 20에서 추가된 import 

import <iostream>;

int main()
{
    std::cout << "Hello, World!" << std::endl;
    return 0;
}

기존에 사용하던 #include 전처리기를 대체하는 C++20의 신기능이라고 한다.

g++ 13버전을 이용하여 컴파일 했을 때 2가지의 오류를 만났다…

첫 번째는 import 기능을 사용할 수 없다는 에러 (아니,,, C++20 표준기능이라면서!)

두 번째는 <iostream> 모듈을 찾을 수 없다는 에러(….? 표준 라이브러리인데?)

첫 번째는 친절하게 해결 방법을 컴파일러가 알려준다. import를 사용하기 위해서는 -fmodules-ts 옵션을 사용할것

두 번째는 모듈을 찾을 수 없다는 건데… 스택오버플로를 조금 찾아보니 다음과 같은 방법으로 표준라이브러리를 컴파일된 모듈로 준비를 해야하는 것 같다.

> $ g++ -std=c++20 -fmodules-ts -xc++-system-header iostream
> $ g++ -std=c++20 -fmodules-ts -o hello helloworld.cc

iostream 모듈을 컴파일하고 나면 iostream을 import해서 사용하는 코드는 정상적으로 컴파일 되는 것을 확인할 수 있다.

ps. 예전엔 윈도우 VS에서 C++03 정도의 기능만 사용해서 이런 옵션 설정과 관련된 부분은 하나도 건들지 않았었는데, 시작부터 힘들다;;

docker & k8s 스터디

회사에서 도커 및 k8s 환경을 올해 도입할 예정이라, 기존에 컨테이너 환경에서 작업을 해보지 않았던 사람들끼리 교재를 선정해 실습 스터디를 진행해보기로 하였다.

사내에서 사용중인 환경은 M1 맥을 사용하고 있고, 현재 유료 서비스로 전환된 도커데스크탑을 사용할 수 없어(소프트웨어 구매 신청은 하였지만 아직 승인이 되지 않은 상태이다..) 우선은 스터디 진행을 위해 colima를 이용하여 도커 및 쿠버네티스 환경을 마련하기로 하였다.

colima는 맥 환경에서 사용가능하도록 컨테이너 환경(Runtime)을 제공해주는 프로젝트인데, 시작시 나오는 메시지를 봐서는 내부적으로 QEMU를 이용하여 리눅스를 구동하고 있는 것으로 보인다. (깃허브 문서를 확인해보니 정확히는 Lima에서 동작하는 Containerd를 구현해놓은 프로젝트이고, Lima는 맥에서 리눅스를 운용하는 프로젝트로 보인다. 즉, QEMU 머신 자체는 lima에서, containerd 구동은 colima에서 담당하는 것으로 보인다)

기본적인 도커 사용법 스터디는 이전에 진행을 하였고, 이번에는 실습/모범사례 위주로 진행하는 스터디가 될 것으로 보인다.

앞으로 빠르게 스터디를 진행해서 팀 내에서 컨테이너 환경으로 이주할 때 원활하게 진행될 수 있도록 해야겠다.

리눅스 공부

요즘 웹 개발보다는 시스템쪽에 관심이 가고 있어서…. 리눅스 시스템 프로그래밍부터 차근차근 진행해보기로 했다. 시스템콜의 경우 리눅스 플랫폼 전반적으로 공통으로 사용될 거니까 실습은 간단하게 라즈베리파이 위에서 진행하는 것으로 결정했다. (SSH를 이용한 원격 작업)

진행하는 교재는 오라일리(한빛미디어)에서 출판된 “리눅스 시스템 프로그래밍”. 책에서 설명하는 커널 자체는 3.0 버전을 기준으로 하고 있어 상당이 오래전의 버전이지만, 리눅스 커널의 경우 대격변(?)이 2.6 버전에서 이뤄지기도 했고, 구버전이라도 전반적인 동작 자체는 크게 변하지 않았을 거라 생각해서 우선 진행중이다. 추가적으로 필요한 기능의 경우 따로 공부하면 될 것이니까…

책은 크게 5~6가지로 목차를 구분해뒀다.

  1. 입출력 (기본, 버퍼, 메모리맵핑)
  2. 프로세스 & 스레드
  3. 파일 & 디렉터리
  4. 메모리
  5. 시그널
  6. 시간

현재 입출력 부분을 공부하였고 빠른시간안에 나머지 부분들을 공부해본 후 ARM 커널을 설명하는 책을 이용해 커널에 대해서 심층적으로 공부를 해볼 예정이다.

UTM 가상환경에 도커 설치

기기를 새로 구매한 것을 기회로 새로운 기기를 도커 머신으로 사용하기로 결정하였다.

새 컴퓨터의 경우 ARM환경의 Mac OS를 사용하고 있기 때문에 로컬 환경을 리눅스로 변경하기 힘든 상황이어서 (사실 변경하고자 하면 가능하지만 앱등이라서 굳이 애플 기기에 설치하고 싶지는 않았다) 가상환경에 리눅스를 올려서 도커를 설치해 사용하기로 하였다.

간단히 검색해 본 결과 리눅스를 올리기 위해 가장 적합한 가상머신의 경우 UTM으로 보여 UTM을 이용한 도커 환경을 구축하는 것으로 진행하였다.

UTM 가상환경에 우분투 설치

UTM에 올릴 리눅스 배포판은 Ubuntu를 이용하기로 하였다. Ubuntu를 지금까지 개인적으로 사용했었고, 윈도우 WSL 환경에서도 Ubuntu를 이용하고 있어 굳이 다른 배포판을 설치하지는 않았다.(회사에서는 서버용으로 CentOS를 사용하고 있긴 하지만…)

Ubuntu는 현재 기준(2023.01.08) 가장 최신의 LTS 버전(22.04.1 LTS)을 사용하였으며, 굳이 GUI 환경을 사용하지 않기에 Server 버전을 다운로드 받았다. (https://ubuntu.com/download/server)

리눅스 설치 중 설정은 전부 default로 진행하였고 ssh의 경우에만 별도 옵션 활성화를 통해 외부에서 ssh를 이용한 접속이 진행되도록 설정하였다.

또한 가상환경의 기본 네트워크 설정은 초기에 Shared Network로 되어있는데 이 경우 게스트OS와 호스트OS 간 통신이 가능하도록 VLAN을 구성하게 된다. 이 경우 게스트OS에 외부 접근이 되지 않으므로 Bridged 모드로 네트워크 모드를 변경하여 집에서 사용하는 LAN(공유기의 사설 네트워크)에서 주소를 할당받을 수 있도록 설정하였다. (네트워크 설정과 관련된 더 자세한 정보는 공식 문서를 참고. https://docs.getutm.app/settings-qemu/devices/network/network/)

Docker 설치

Ubuntu를 설치한 후 docker를 설치하였다.

이미 많은 사람들이 UTM환경에 도커를 설치하는 작업을 진행하였고, 공유된 문서들이 많아 해당 문서들을 참고하여 설치를 진행하였다.

(참고: https://breezymind.com/silicon-m1-mac-utm-docker-desktop-alternative/)

다만, 참고한 문서의 경우 현재 사용할 수 없는 명령으로 이뤄진 부분이 있어 공식문서를 참고하여 해당 부분만 설치 명령어를 변경하였다. (https://docs.docker.com/engine/install/ubuntu/)

1) 도커 설치

sudo apt update
sudo apt-get install apt-transport-https ca-certificates curl gnupg-agent software-properties-common

# Add docker's official GPG key
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg

# command to set up the repository
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# update apt package index
sudo apt update

# install the latest version of docker
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin

# (Optionally) verifying docker engine installation
sudo docker run hello-world

2) Docker Daemon의 Remote API 설정

sudo vim /lib/systemd/system/docker.service

ExecStart에 -H tcp://0.0.0.0:2375 옵션을 추가

3) HOST OS에서 원격 도커 접속 설정

환경변수 DOCKER_HOST에 게스트OS(우분투) 설정 (tcp://guest.ip.add.ress)

Docker 환경 테스트

게스트 OS에 도커를 설치한 후 원격으로 도커 엔진 제어가 정상적으로 이뤄지는지 가장 만만한 아파치(httpd) 컨테이너를 이용하여 확인하였다. Bridged 모드로 네트워크를 구성하고 외부에서 접속할 수 있도록 공유기에서 포트포워딩 설정을 해주었기에 공인IP를 통해서도 정상적으로 접속이 되는 것을 확인할 수 있었다.

부팅시 리눅스 이미지가 실행되도록 설정

이제 남은 것은 항상 리눅스 이미지가 실행될 수 있도록 설정하는 부분이다. 컴퓨터를 사용 중 굳이 UTM을 종료할 생각은 없어 부팅시에만 UTM 이미지가 실행만 되면 되는 상황으로 UTM의 실행을 자동화 하는 방법을 찾던 중 Automator를 이용하여 UTM을 부팅시 자동으로 실행하는 방법이 있어 그대로 적용하였다.

UTM의 경우 utm 프로토콜로 시작되는 URL을 이용하여 게스트OS의 실행을 제어할 수 있는 방법을 제공한다. Automator를 이용하여 UTM을 실행하고 utm://start URL을 열어 리눅스를 실행하는 앱을 작성할 수 있었다. (https://github.com/utmapp/UTM/wiki/URL-Scheme-–-Automation-for-UTM-app-&-VMs)

끝으로

지금까지 UTM 가상환경을 통해 리눅스를 설치한 후 Docker Engine을 설치하여 호스트(Mac)에서 원격으로 Docker를 제어하는 환경을 구축하고, 부팅시 자동으로 UTM의 리눅스를 자동으로 실행할 수 있도록 구성하였다.

곧 회사에서 Docker 및 k8s를 기반으로 프로젝트를 구성한다고 하여 도커를 공부해야하는 상황이라 M1 기반의 맥에 도커를 설치해보는 것을 시작으로 컨테이너 환경의 개발에 대해 공부를 진행할 예정이다.

(여담이지만 이 글이 작성되는 워드프레스 환경은 Docker를 이용하여 구축되었 :D)