Tips2020. 9. 26. 03:18

npm을 통해 typescript 설치 시 -g 옵션을 통해 globally 하게 typescript를 설치했다 하더라도, 이를 vs code에서 실행할 때 아래와 같은 문제가 발생하는 경우가 있다.

+ tsc
+ ~~~
    + CategoryInfo          : 보안 오류: (:) [], PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

이는 해당 커맨드에 대한 접근 권한이 부여되지 않은 경우로써, 해결방법은 다음과 같다.

  1. 관리자 권한으로 Power Shell 실행.
  2. Get-ExcutionPolicy 명령 실행.
  3. "RemoteSigned" 권한이 아닐 경우 (보통 위와 같은 문제가 발생한다면 "Restricted" 로 나올 것이다), 아래의 명령 실행.
    1. Set-ExecutionPolicy RemoteSigned
  4. 다시 Get-ExecutionPolicy 명령을 실행하여 "RemoteSigned" 로 바뀌었는지 확인.

명령을 실행하면 보안 문제가 발생할 수 있다고 경고가 나올 것이다. 이를 다시 Restricted로 바꿀 경우, 마찬가지로 tsc 명령이 먹히지 않는다. (vs code가 아닌 일반 커맨드 프롬프트에서는 정상적으로 실행된다. vs code 와 같은 타 애플리케이션에 권한을 주지 않는 것으로 보인다.) RemoteSigned 권한일 경우, vs code에서도 정상적으로 tsc와 같은 커맨드가 실행된다.

아래의 포스팅을 참고하였다.

dog-developers.tistory.com/183

Posted by 곰푼
Tips2018. 8. 29. 02:38


Docker는 컨테이너의 일종으로써, 리눅스, MacOS, 윈도우즈와 같은 많은 OS에서 동작한다. 

본 포스트는 리눅스 기반의 docker를 기준으로 작성된 포스트이므로, 다른 OS에는 해당사항이 있을 수도 있고 없을 수도 있다.


Docker도 결국 주어진 머신을 최대한 잘 활용하기 위해 사용되는 것이다.

최근 각광받는 기술이긴 하지만 Docker와 같은 컨테이너들은 역사 자체는 오래되었다. 

가상화 얘기가 한창 나오던 십수년전부터 OS 수준 가상화(OS-level virtualization)라는 이름으로 연구되었던 영역이다. 

이러한 카테고리에 속하는 가상화 기술의 주 목적은 다음과 같다.


  • OS 및 런타임(JAVA, Python 등)과 같은 코드 기반들의 공유.
  • CPU, 메모리와 같은 물리적 자원 및 네트워크 연결, 파일시스템 등의 논리적 자원의 분배 및 고립(isolation). 


OS 및 런타임과 같은 요소들까지 서로 고립시키는 전가상화(full-virtualization) 내지 반가상화(para-virtualization) 기술에 비해 오버헤드가 상대적으로 덜한 기술이라 할 수 있다.

따라서, 유사한 시스템 및 런타임 기반을 가진 애플리케이션을 구동시킬 경우, Docker와 같은 컨테이너 서비스는 아주 좋은 선택이라 할 수 있다.


리눅스 환경에서 동작하는 Docker의 경우 메모리와 같은 자원을 분배하기 위해 cgroup이라는 매커니즘을 활용한다. 

cgroup은 control group의 약자로써, 특정 프로세스 혹은 프로세스 그룹이 사용할 수 있는 자원의 상한을 정해두고, 그 이상으로 사용하지 못하도록 커널 수준에서 제어하는 매커니즘의 명칭이다. 

cgroup은 여러 자원들에 대한 컨트롤러를 제공하는데, Linux 4.5 버전이 되며 덩달아 버전 업 된 cgroup v2에서는 CPU, Memory, I/O의 세 가지 컨트롤러를 지원한다. 

상대적으로 역사가 오래된 cgroup v1은 이보다 많은 컨트롤러를 지원하는데, 이로 인해 아직까지 docker에서는 cgroup v1을 활용한다고 한다. (명시적으로 표기된 공식 문서는 발견하지 못했지만, cgroup v2로 커널을 설정할 경우 Docker가 cgroup을 인식하지 못한다.)


자 그럼 왜 이번 포스팅의 주제인 MongoDB와 Docker 얘기를 꺼내기전에 왜 이런 장황한 이야기를 늘어놨는지 밝히도록 하겠다. 

cgroup은 결국 논리적으로 자원을 분배한다. 그렇기 때문에 docker 컨테이너를 생성한후, 컨테이너에 접속하여 cat /proc/meminfo와 같은 명령을 입력하게 되면 호스트 머신이 가진 자원이 그대로 보이는 것을 확인할 수 있을 것이다. 

예를 들자면 이런 것이다.

16G의 메모리를 탑재한 머신에 네 개의 컨테이너를 생성한다고 가정하자. 

메모리를 최대한으로 허용한다면, 각각의 컨테이너에게 4G씩의 메모리를 할당할 수 있을 것이다. (물론 호스트의 OS 및 서비스를 위해 가용 메모리를 남겨둬야 한다. 이것은 단순히 예시일 뿐이다.)

즉, 우리가 기대하는 것은 각 컨테이너가 최대 4G의 메모리를 사용하는 것이다.

그리고 그 컨테이너에서 MongoDB의 인스턴스를 구동시켜보자. 

MongoDB의 WiredTiger 스토리지 엔진은 자체적으로 데이터베이스의 레코드들을 캐시해두기 위한 in-memory cache를 생성하게 된다. 

이때 in-memory cache의 크기는 1GB 혹은 (memory size / 2) - 1G 중 큰 쪽으로 선택된다. 

여기서 저 memory size가 각 컨테이너에게 할당된 메모리 크기가 된다면, (4G / 2) - 1G 이므로 1G 크기의 in-memory cache를 가질 것이라 예측할 수 있다. 

그리하여 MongoDB를 구동시키고 시간이 지난다면... 짜잔! 무언가 잘못되었다는 것을 느낄 수 있을 것이다. (물론 운이 좋으면 혹은 나쁘다면 잘못되지 않고 계속 실행된다.)


그 이유는 단순한데, 컨테이너에서 구동되고 있는 MongoDB가 컨테이너에게 할당된 메모리가 아닌 호스트 머신이 가지고 있는 메모리를 인식하여 in-memory cache를 생성하기 때문이다. 

MongoDB에 리퀘스트가 도달할 때마다 MongoDB는 I/O를 줄이기 위해 레코드들을 캐싱해둘 것이고, 캐싱된 데이터의 크기가 현재 할당된 in-memory cache 사이즈를 넘을 경우 MongoDB는 메모리를 추가로 요청하게 된다. 

만약, 미리 설정된 메모리 크기를 넘어설 경우 (예를 들어, (memory size / 2) -1G의 크기를 넘어설 경우), MongoDB는 캐시에서 오래된 레코드들을 찾아 디스크에 저장하고 메모리 공간을 회수한 뒤 회수된 메모리 공간에 새로운 레코드를 캐싱하게 된다.

안넘어섰을 경우에는 미리 설정된 사이즈까지 메모리를 추가로 요청하게 된다.

그리고 바로 이 시점이 오류가 발생하는 시점이다. 


컨테이너에 할당된 메모리 사이즈와 애플리케이션이 인식한 메모리 사이즈 사이의 정보 격차로 인해, 컨테이너에 할당된 메모리 사이즈 이상의 메모리 요청이 발생할 경우 해당 요청은 거부되고 애플리케이션이 오동작을 일으키는 것이다. 

경우에 따라서 OOM killer가 발생할 수도 있고, 이것을 옵션으로 막아두었다면 컨테이너가 deadlock 상태에 빠지거나 그냥 꺼질 수 있다 (여기까지는 직접 발견한 증상이다). 


이를 방지하기 위해서는 wiredtiger의 캐시 사이즈를 제한할 필요가 있다. 

이는 --wiredTigerCacheSizeGB  라는 옵션을 통해 설정할 수 있다. 

MongoD 인스턴스를 실행시킬 때, 다음과 같은 옵션을 덧붙이면 된다.

$mongod --wiredTigerCacheSizeGB  2G --config <config path>

그럼 in-memory cache의 크기는 2G로 제한이 되고, 위에서 발생가능한 오류들을 사전에 방지할 수 있다. 


사실 이러한 semantic gap으로 인한 버그들은 굉장히 많을 것이다. 

이때문에 verification이라던지, gap을 없애려는 노력들을 하는 것일 거고...

앞으로 이와 관련된 연구를 좀 해볼 수 있을려나...?

Posted by 곰푼
Tips2018. 4. 27. 09:21

보통 make 혹은 cmake 등으로 빌드를 수행할 때, undefined reference 에러가 발생하는 경우가 있을 것이다. 

필요한 라이브러리를 -l 옵션을 사용하여 링커에게 전달하지 않았거나, 라이브러리 간의 order가 맞지 않을 때 발생하는 문제이다. 예를 들어 pthread를 사용한다면, 다음과 같이 옵션을 주면 된다.

$ gcc a.c -o a -lpthread

코드가 pthread를 사용하여 구현되었을 경우, 해당 실행 파일(여기서는 a) 내에는 pthread관련 함수의 바이너리가 없기 때문에 실제 코드가 구현된 pthread 라이브러리를 링크시켜주어야 한다. 이 역할을 수행하는 것이 바로 링커(리눅스에서는 보통 ld)이며, 링커에게 링크 해야하는 라이브러리를 알려주는 지시자가 바로 -lpthread 가 되겠다.

물론, 이걸로 해결안되는 경우도 많다. 사실 이 경우 때문에 이 포스팅을 남기기도 하는 것이고.

내가 작성한 라이브러리를 사용하여 다른 애플리케이션을 수정할 일이 생겼다. 그래서 애플리케이션 코드를 수정하고, cmake 스크립트를 수정하여 라이브러리를 링크하도록 만들어 놓고 빌드를 수행하였는데, undefined reference 에러가 발생하는게 아닌가?

여러 삽질을 해봤는데, 결과는 영 꽝이었고, 혹시나 싶어 nm을 통해 코드에서 심볼들이 어떻게 정의되어 있는지 보았다. 애플리케이션도 라이브러리로 컴파일 된 뒤, 다른 실행 파일에서 이를 링크하여 사용하는 방식으로 되어 있기 때문에, 여기서는 애플리케이션이 작성한 라이브러리를 확인하였다.

$ nm app.a | c++filt | grep pthread

                 U pthread_cond_broadcast

                 U pthread_cond_init

                 U pthread_cond_signal

                 U pthread_cond_wait

                 ...

$ nm app.a | c++filt | grep my_lib_func

                 U my_lib_func1(my_struct*)

                 U my_lib_func2(unsigned long, my_struct*, int)

                 U my_lib_func3(unsigned long, my_struct*)


차이가 눈에 띄는가?

pthread 함수의 경우, argument에 대한 정의가 심볼에 포함되어 있지 않지만, 내가 작성한 라이브러리의 경우 심볼에 argument의 정의가 포함되어 있다.

이런 이유가 발생한 이유는 따지고 보면 간단한데(물론 찾는 것은 간단하지 않았다.), 나는 C언어를 사용하여 라이브러리를 작성하였고, 애플리케이션은 C++로 구현되어 있기 때문이었다. 

C++는 오버로딩과 같은 특성을 지원하기 때문에, 심볼을 다룰 때 간결한 C언어와 차이점을 보이게 되는 듯 하다. 

해결책은 간단하다. 

헤더파일의 선언문들을 extern "C" { } 로 감싸주면된다. 물론, C++의 경우에만 이를 적용하면 되므로 #ifdef __cplusplus와 같은 매크로를 이용해 구분을 해주면 더욱 좋겠다.,

이렇게 헤더파일을 수정할 경우, 실행파일의 심볼 테이블에서 argument에 대한 정의가 제거된다. 

$ nm app.a | c++filt | grep my_lib_func

                 U my_lib_func1

                 U my_lib_func2

                 U my_lib_func3

빌드해본 결과, 에러없이 잘 빌드되는 것을 확인할 수 있었다.

Posted by 곰푼
Writings/Linux kernel2017. 12. 11. 05:22

다음으로 소개할 방법은 cgroup을 이용하는 방법이다.

Cgroup (control group)은 리눅스 커널이 제공하는 시스템의 자원 사용률을 그룹별로 제어하기 위한 방법이다.

이 또한 sysfs 인터페이스를 통해 사용 가능한데, 기존 cgroup v1과 Linux 4.5.x 때부터(정확한지는 모르겠지만 이때쯤) 지원하기 시작한 cgroup v2 두 종류가 있다. 사용방법이 사뭇 다르므로 정확한 용법은 커널 도큐먼트(Documentation/cgroup-v2.txt)를 확인해 보는 것이 좋다. 

일단 아무데나 원하는 위치에 디렉토리를 만들고, 다음과 같이 cgroup sysfs를 마운트하자.


# mkdir cgroup

# mount -t cgroup2 none ./cgroup


해당 마운트포인트에서 ls -l 명령을 실행시키면 다음과 같은 결과가 나올 것이다.


-r--r--r-- 1 root root 0 Dec  9 03:20 cgroup.controllers

-rw-r--r-- 1 root root 0 Dec  9 03:20 cgroup.procs

-rw-r--r-- 1 root root 0 Dec  9 03:21 cgroup.subtree_control


여기서 cgroup.controllers는 현재 cgroup v2가 사용가능한 컨트롤러들을 뜻한다. 부트 파라미터를 통해 cgroup v1을 효과적으로 disable 했다면, (혹은 애초에 cgroup v1을 사용하지 않는다면) 다음과 같은 결과를 볼 수 있다.


# cat cgroup.controllers

io memory pids


I/O와 memory, 그리고 pid에 대한 컨트롤러를 제공하며, 이 말은 cgroup 인터페이스를 통해 원하는 그룹에 저 세 가지 자원에 대한 제한을 걸 수 있다는 뜻이 된다.

cgroup.procs 는 현재 해당 컨트롤 그룹에 속해있는 프로세스를 의미하며, 현재 시스템에 등록되어 동작하고 있는 대부분의 프로세스가 여기에 등록되어 있을 것이다. 즉, 마운트포인트의 루트 디렉토리는 시스템의 루트 컨트롤 그룹에 대한 정보가 등록되어 있는 것이다. 

cgroups.subtree_control 은 하위 컨트롤 그룹에 대해 어떤 컨트롤러를 사용할 것인지 지정하는 것이다. 현재는 아무런 정보도 없을 것이다.


이제 새로운 컨트롤 그룹을 생성할 시간이다. 아래의 명령을 입력해보자.


# mkdir cgroup_child


생성된 디렉토리 아래로 가게 되면, 부모 디렉토리와 마찬가지로 cgroup.controllers, cgroup.subtree_control, cgroup.procs, 그리고 cgroup.events 파일이 존재할 것이다. 

생성된 그룹에 프로세스를 추가하기 위해서는, 해당 프로세스의 pid를 알아내어 다음과 같이 입력하면 된다.


# echo "pid" > ./cgroup/cgroup_child/cgroup.procs


그리고 원하는 컨트롤러를 "부모" 디렉토리의 cgroup.subtree_control에 입력하자. 여기서는 페이지 캐시 사용량을 제한하려고 하니, memory 컨트롤러를 입력하면 된다.


# echo "+memory" > ./cgroup/cgroup.subtree_control


여기서 +는 해당 컨트롤러를 추가한다는 의미이며, -로 입력할 경우 해당 컨트롤러를 제거한다는 의미가 된다. 

해당 명령을 수행하면, 자녀 디렉토리에 다음과 같은 파일들이 추가로 생성된다.


-r--r--r-- 1 root root 0 Dec  9 03:21 cgroup.controllers

-r--r--r-- 1 root root 0 Dec  9 03:21 cgroup.events

-rw-r--r-- 1 root root 0 Dec  9 03:21 cgroup.procs

-rw-r--r-- 1 root root 0 Dec  9 03:21 cgroup.subtree_control

-r--r--r-- 1 root root 0 Dec  9 03:21 memory.current

-r--r--r-- 1 root root 0 Dec  9 03:22 memory.events

-rw-r--r-- 1 root root 0 Dec  9 03:21 memory.high

-rw-r--r-- 1 root root 0 Dec  9 03:22 memory.low

-rw-r--r-- 1 root root 0 Dec  9 03:21 memory.max

-r--r--r-- 1 root root 0 Dec  9 03:21 memory.stat

각각의 의미는 다음과 같다.


 File 

Description 

 memory.current

컨트롤 그룹의 현재 메모리 사용량 

 memory.high

메모리 사용량의 soft limit 

 memory.low

메모리 사용량의 하한 

 memory.max

메모리 사용량의 hard limit 

 memory.events 

low, high, max, oom 에 대한 이벤트 횟수

 memory.stat

컨트롤 그룹의 메모리 사용량 통계 


메모리 사용량이 soft limit(memory.high)에 도달할 경우, 해당 그룹의 프로세스들을 throttling 하며, memory 회수를 빈번하게 수행시키기 위한 압박을 주게 된다. 만약, 메모리 사용량이 hard limit (memory.max)를 넘어서게 될 경우, 커널은 OOM killer를 호출하여 해당 프로세스를 종료시키게 된다.

두 항목에 적절한 값을 설정하고, free 명령을 통해 메모리 사용량 변화의 추이를 살펴보자.

root@ubuntu:/home/ubuntu# free -c 10

              total        used        free      shared  buff/cache   available

Mem:       65722960      410052    59913708        9736     5399200    64511200

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410612    59913580        9736     5398768    64510436

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410908    59913108        9736     5398944    64510268

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      411000    59913268        9736     5398692    64510304

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410824    59913176        9736     5398960    64510368

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410348    59913612        9736     5399000    64510896

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410980    59913340        9736     5398640    64509924

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410580    59913672        9736     5398708    64510560

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      411080    59912944        9736     5398936    64509680

Swap:      33326076           0    33326076


              total        used        free      shared  buff/cache   available

Mem:       65722960      410504    59913820        9736     5398636    64510612

Swap:      33326076           0    33326076


모든 가용영역을 캐시용도로 쓰던 과거에서 벗어나, 제한된 만큼 사용하는 것을 확인할 수 있다.


엄밀히 따지자면, 이 방법은 페이지 캐시를 제한하기 위해 존재하는 방법은 아니다.

하지만 내 용도가 페이지 캐시를 제한하는 것 이었던 만큼 이 항목으로 포스팅을 남긴다.

'Writings > Linux kernel' 카테고리의 다른 글

페이지 캐시의 크기 제한걸기 (1/2)  (0) 2017.12.10
Enabling Cgroup v2  (0) 2017.12.09
Posted by 곰푼
Writings/Linux kernel2017. 12. 10. 00:10

리눅스 커널은 I/O 성능을 높이기 위해 페이지 캐시를 제공한다. 


페이지 캐시는 파일 시스템의 각 오퍼레이션들에 삽입되어 (엄밀히 말하자면 VFS 인터페이스겠지만,) 블록 디바이스의 데이터 페이지를 메모리에 캐싱해 둔다. 

이후 요청이 들어왔을 때 해당 블록 어드레스에 대한 페이지가 메모리 내에 있을 경우, 캐시 히트로 간주하여 디바이스에 대한 I/O를 발생시키지 않고 메모리에 캐싱되어 있는 페이지를 제공한다. 

Write가 발생했을 때도 이를 바로 블록 디바이스로 보내는 것이 아니라  메모리에 캐싱해두며, 특정 시점 혹은 fsync() 호출이 발생할 때까지 동일한 블록 어드레스에 대한 모든 write를 흡수한다. 


이런 캐시는 I/O가 빈번이 발생하는 경우에는 도움이 되겠지만, 커널의 메모리 사용량을 늘리는 결과를 야기시키기도 한다.


커널은 이를 제한할 수 있는 인터페이스를 sysfs를 통해 제공하게 되는데, 그 안을 보면 꽤 많은 항목들이 있다. 일단 경로는 다음과 같다.


/proc/sys/vm


이 디렉토리 중, 다음의 두 항목을 살펴보자.


/proc/sys/vm/drop_caches

/proc/sys/vm/vfs_cache_pressure


drop_caches는 입력에 따라 서로 다른 수준으로 시스템의 캐시를 비워주게 된다. 


1) echo 1 > /proc/sys/vm/drop_caches // 페이지 캐시만을 비운다.

2) echo 2 > /proc/sys/vm/drop_caches // dentry 캐시와 inode 캐시를 비운다.

3) echo 3 > /proc/sys/vm/drop_caches // inode, dentry, 페이지 캐시 모두를 비운다.


페이지 캐시는 실제 파일에 저장되는 데이터 페이지를 캐싱하며, dentry 및 inode 캐시는 파일시스템에서 사용하는 메타데이터를 캐싱한다. 

즉, 1번 명령은 데이터 캐시를 비우는 명령이며, 2번 명령은 메타데이터 캐시를 비우는 명령인 것이다.

참고로, 위의 명령들은 캐시를 디스크에 flush 시키는 명령이 아니기 때문에 먼저 sync를 실행시킨 뒤에 실행 시킬 것을 권장하고 있다. 


두번째는 vfs_cache_pressure 이다.

해당 항목에 대한 커널 도큐먼트의 설명을 살펴보면, 메모리 회수를 위해 inode 및 dentry 캐시에 압박을 준다고 되어 있다. 

100을 기준으로 그 위의 값은 일반적인 것보다 더 큰 압박을 주는 것이고, 그 아래의 값은 압박을 덜 줌으로써 좀더 메타데이터를 캐싱할 수 있도록 하는 것이다.

특별히, 0은 절대 주지 말라고 한다. 해당 메모리가 회수가 안되서 OOM 킬러에 의한 오작동이 나타날 수 있다고 한다.

'Writings > Linux kernel' 카테고리의 다른 글

페이지 캐시의 크기 제한걸기 (2/2)  (0) 2017.12.11
Enabling Cgroup v2  (0) 2017.12.09
Posted by 곰푼
Writings/Linux kernel2017. 12. 9. 03:29

Cgroup을 사용할 일이 생겨서 뒤적거리던 중, cgroup v2가 나왔단 사실을 알게 되었다.


지금 사용중인 커널 (4.8.x) 버전에서는 두 cgroup이 모두 탑재되어 있는데, 그냥 부팅하면 커널이 알아서 모든 cgroup 컨트롤러를 기존 cgroup에 줘버린다. 따라서 cgroup v2로는 할 수 있는 일이 없다.


이를 해결할 수 있는 방법은, 커널 파라미터를 통해 컨트롤러의 권한을 해제하는 것이다.


/etc/default/grub 파일을 열어서, GRUB_CMDLINE_LINUX_DEFAULT= 항목에 다음과 같이 파라미터를 작성하자.

GRUB_CMDLINE_LINUX_DEFAULT="cgroup_no_v1=all"


혹은 cpu, memory 와 같은 특정 컨트롤러를 지정해도 무방한 듯 하다.


참고자료

https://lkml.org/lkml/2016/2/11/603



Posted by 곰푼
Tips2017. 10. 20. 01:32

재밌는 현상을 발견해서 포스팅을 남긴다.


Python으로 class를 지정해서 코딩할 때, 한 클래스 내에서 리스트를 사용할 일이 있어서 다음과 같은 형태로 클래스를 정의했다.


class foo:
    name    = None
    foo_list = []


해당 클래스의 객체를 여러개 만든 뒤, 들어오는 input에 대해 name을 확인하여 각기 다른 foo 객체의 foo_list에 삽입하도록 했다. 

이후 여러 foo 객체를 오가며 foo_list에 삽입된 항목들을 출력하도록 했더니... 오잉? 모든 항목이 동일한 리스트에 들어가 있는 것이 아닌가?


열심히 디버깅 하다가 미심쩍은 부분이 있어서 다음과 같이 변경하였다.


class foo:
    name    = None
    foo_list = None
    def __init__ (self):
        foo_list = []


그러고 나니 각 객체 별로 별도의 리스트를 잘 출력하는 것이었다.

그러니까, class의 멤버 변수를 정의할 때, 초기값을 바로 때려넣어버리면 그게 일종의 static type이 되어서 모든 객체에서 공유하는 멤버 변수가 되는 것이었다. 수정한 코드와 같이 init을 통해 객체가 생성될 때 리스트를 선언하여 사용하면 각 객체 별로 별도의 리스트를 가질 수 있게 되는 것이다.

뭐, 저 용어들이 엄밀하게 따져서 맞는 용어인지는 모르겠다. 어쨌든 내 전문 분야는 C 언어이고, C 언어의 지식으로 해석해봤을 때 저게 static type (혹은 전역변수)으로 보일 뿐이다.



Posted by 곰푼
Writings/Operating System2017. 10. 18. 15:31

클락업을 통한 싱글 코어의 발전이 power wall에 가로막힌 이후, CPU의 발전 방향은 코어의 갯수를 늘리는 쪽으로 선회하였다. 그에 따라 멀티코어, 매니코어에 이르는 다양한 변종들이 등장하게 되었으며, 이로 인해 애플리케이션 및 운영체제의 확장성(scalability)이 중요한 문제로 대두되게 되었다. 


확장성이란 사전적인 의미로 보았을 때 늘어난 수요에 맞춰 얼마나 유연하게 대응할 수 있는지를 나타내는 척도라고 할 수 있다. 애플리케이션을 뒷 받침 하는 시스템의 입장에서 확장성이란 단어를 협의적으로 해석하면 늘어난 자원에 맞추어(e.g., 코어 수) 성능이 얼마나 향상되는 지를 나타내는 척도라고 할 수 있다. 자원이 늘어남에 따라서 성능이 비례하여 향상되면 확장성이 좋다 할 수 있고, 성능이 그대로이거나 혹은 더 떨어지게 된다면 확장성이 나쁘다라고 할 수 있다.


이 포스팅에서는 운영체제를 주로 다룰 것이므로 운영체제와 관련된 문제만을 살펴보도록 할 것이다. 확장성을 저해하는 요소는 운영체제 내에 다양하게 산재해 있지만, 그 중에서도 주된 요인을 꼽아보자면 단연 동기화(synchronization) 메커니즘이라고 할 수 있다.


비단 멀티코어 환경이 아니더라도 CPU의 시분할을 통한 멀티 프로그램 기법으로 인해 하나의 공유 자원에 대해 여러 프로세스가 동시에 접근할 수 있게 되었다. 각 프로세스는 특정 코드를 실행시키는 동안 자신이 접근하는 자원이 최소한 그 코드 내에서는 독점적으로 사용할 것을 요구하며, 이 요구사항이 지켜지지 않을 경우 일관성(consistency) 문제가 발생하게 된다. 


다음의 예시를 보자.


int a = 0, b = 0;

void func (void) {
    b = a + 1;  
    a = b; 
}

void proc_a (void) {
    func();
}

void proc_b (void) {
    func();
}


8번째 줄의 proc_a와 12번째 줄의 proc_b가 각각 별개의 프로세스에서 실행되는 코드라고 가정하자. proc_aproc_b가 동시에 실행된 뒤 a와 b를 출력하면 어떤 결과가 나올까? 총 두 가지의 결과를 예측해 볼 수 있다. 첫째로는 (a = 2, b = 2), 둘째로는 (a = 1, b = 1).  func()이 진행 중일 때 타이머 인터럽트가 발생하여 5번째 줄을 실행하기 전에 컨텍스트 스위치가 발생할 경우 두 번째의 결과가 발생하게 된다. 이러한 결과를 얻어내기 까지 발생할 수 있는 절차의 가지 수는 더욱 많다. 


  • proc_a -> proc_b
  • proc_b -> proc_a
  • proc_a -> (scheduling) proc_b -> proc_a
  • proc_b -> (scheduling) proc_a -> proc_b


위와 같은 상황이 발생할 경우, 사용자 혹은 애플리케이션의 입장에서는 같은 코드를 실행시켰음에도 불구하고 나오는 결과가 매번 달라질 수 있게된다. 따라서, 운영체제에서는 위와 같은 상황을 방지하기 위해 동기화 메커니즘을 제공한다. 대부분의 경우 동기화 메커니즘은 특정 영역(이를 critical section이라 부른다)에 대한 접근을 직렬화(serialize)하는 것으로 이러한 문제를 해결한다. 


void proc_a (void) {
    synch_region {
        func();
    }
}

void proc_b (void) {
    sync_region {
        func();
    }
}

<caller-side synchronization>

직렬화란, 동시에 오직 한 프로세스만 특정 영역에 접근시키도록 하는 것을 의미한다. 동시에 여러 프로세스가 접근할 경우, 뒤늦게 접근하는 프로세스는 앞서 진입한 프로세스가 작업을 마칠 때 까지 기다려야만 한다. 코드에서 synch_region으로 표시된 영역이 바로 이러한 영역이 되겠다.

위와 같이 호출하는 코드에서 동기화 영역을 표시하는 경우도 있으며, 반대로 호출되는 코드에서 동기화를 시키는 경우도 있을 것이다. 

void func (void) {
    synch_region {
        b = a + 1;  
        a = b; 
    }
}

<callee-side synchronization>

코드에 표시된 synch_region은 동기화 메커니즘의 일종의 예시인 것을 기억하자. 여러 논문 및 구현들을 통하여 다양한 방식의 동기화 메커니즘이 제안되어 왔으며, 위와 비슷한 형태를 취할 수도, 혹은 다른 형태를 취하게 될 수도 있다.


운영체제는 일반적으로 여러 형태의 locking primitive들을 통하여 동기화 기능을 제공한다. 대표적으로 spinlock과 같은 busy-loop 기반의 locking primitive가 있으며, 이는 효과적인 동기화 방법을 제공하지만 확장성을 저해시킬 수도 있다. 예를 들어 여러 코어에서 동작하는 프로세스들이 임계 영역(critical section - 동기화 메커니즘에 의해 보호받는 영역)을 만날 경우, 앞서 진입한 프로세스가 필요한 작업을 다 수행할 때 까지 기다려야만 한다. 이 임계 영역이 길어질 경우, 그만큼 대기 시간이 길어지게 되므로 자연스럽게 대기중인 프로세스가 점유하고 있는 코어의 시간을 낭비하게 된다. 


따라서 spinlock과 같은 busy-loop 기반의 동기화 방식들은 임계 영역의 길이를 최대한 줄일 것을 전제로 하고 있으며, 여의치 않을 경우 mutexsemaphore등의 block-based locking primitive들을 쓸 것을 추천한다. 이러한 lock들은 기다리는 프로세스를 sleep 상태로 전환함으로써, 해당 프로세스가 점유하고 있는 컴퓨팅 자원을 다른 프로세스에게 양도할 수 있도록 한다. 이를 통해 시스템은 낭비되는 컴퓨팅 자원을 효율적으로 활용할 수 있게 된다.


위와 같은 방식 외에도, 보다 확장성을 고려한 형태의 locking primitive들도 많이 고안되었다. reader-writer lock, ticket lock, queue spinlock, mcs lock, seqlock 등이 바로 확장성을 고려한 locking primitive 들이다.


앞으로 이와 같은 locking primitive들에 대해 한번 알아보려고 한다.

Posted by 곰푼
Reviews/Book logs2017. 9. 11. 03:37


시대정신이란 말이 있는데 대충 시민들이 공유하는 그 시대를 대표하는 의식 정도가 될 것이다. 가령 우리나라를 예로 들자면 적폐청산이 있겠다.


그러면 국경을 넘어선 시대정신은 무엇이 있을까? 나는 감히 불평등의 해소가 가능성이 높을 것이라 본다.
사실 우리는 본능적으로 불평등이 어떠한 형태로 나타나고, 또 어떻게 생성되는지 파악하고 있다. 학자들은 그러한 얼개를 구체화하기 위해 통계자료를 구하고 분해한 뒤 분석한다.
그런 관점에서 이 책은 간결하며 명료하다.


근로소득과 자본소득을 구별한 뒤, 현재는 자본소득이 높은이가 근로소득의 수준 또한 높음을 통계자료를 근거로 보여준다. 이른바 금수저이다.


수많은 경제학 이론이 미래를 예측했으나 대부분 빗나갔으며, 자신 또한 그럴 것이라고 얘기하며 그럼에도 불구하고 미래를 조심스럽게 예측한다. 저자는 경제학자들이 불평등이 비치는 렌즈로 세상을 바라보기 시작했다며 적어도 그 부분은 다를 것이라 얘기한다.


재밌는건 책의 결론인데, 그 부분의 사진을 첨부한다.





'Reviews > Book logs' 카테고리의 다른 글

헤아려본 슬픔 - C.S. 루이스  (0) 2012.02.03
Posted by 곰푼
Memos2014. 5. 22. 14:20


비오는 내일,
젖은 꽃잎을 아스팔트에 아로새기네,
그 길 끝에서,
메마른 가지 사이로, 
뒤돌아 홀로 가는 너.

'Memos' 카테고리의 다른 글

20120806  (0) 2012.08.06
3월 4일 분이사진  (0) 2012.03.04
3월 1일 분이사진  (0) 2012.03.01
東京事変 恋は幻 (GET IT UP FOR LOVE)  (0) 2012.02.09
2월3일  (0) 2012.02.03
Posted by 곰푼