VNC와 RDP

기술 2018. 9. 12. 22:10 Posted by 아는 개발자


원격 데스크톱을 연결하는 프로토콜은 여러 가지가 존재하는데 이중 대표적인 방법이 VNC와 RDP다. 두 프로토콜 모두 단순히 커맨드 라인 창만 띄우는 ssh를 넘어서서 원격 PC의 그래픽 인터페이스까지 연결해준다는 점은 동일하지만 둘의 세부 동작 방식과 주 사용 용도는 조금 다르다. 이번 포스트에서는 VNC와 RDP의 원리를 분석하고 각각의 차이점을 간단히 정리해보고자 한다.


VNC (Virtual Network Computing)


VNC는 RFB(Remote Frame Buffer)프로토콜 방식을 이용해 서버에서 보낸 화면 정보를 클라이언트에 설치된 그래픽 라이브러리를 이용해 그리는 방식이다. 사용하기 위해선 원격으로 접속하려는 PC에서는 지속적으로 화면 정보를 RFB의 형태로 변환해 전송하는 서버가 실행되고 있어야 하고 원격 PC를 조종하려는 PC에서는 받은 RFB 데이터를 변환하고 그래픽 드라이버를 이용해 화면에 뿌려줄 수 있는 클라이언트 소프트웨어가 설치되어 있어야 한다.




VNC는 주로 클라우드 상에서 동작하는 서버 VM을 동작하기 위해서 사용하는 프로토콜이며 QEMU 같은 가상화소프트웨어에서는 VNC를 디스플레이 옵션으로 기본으로 제공하고 있다. VM을 VNC로 연결하려는 경우 Hypervisor에서 VNC 서버만 실행해준다면 서버 VM에는 별도의 소프트웨어 설치가 필요 없으며 원격에서 접속하는 PC도 VNC Client 프로그램만 설치하면 Guest의 화면을 볼 수 있다.


하지만 장점 만큼이나 단점도 있다. VNC를 사용하면 직접 접속했을 때보다 화질이 좋지 않고 화면의 움직임도 더디고 자연스럽지 않다. 실제 원격 데스크톱은 더 빠르게 움직이고 있지만 데이터 전달 시간과 클라이언트 소프트웨어가 변환하는 시간이 갉아 먹으면서 실제 사용자에게는 느리게 보이게 되는 것이다. 아래 그림은 Ubuntu 18.04를 VM으로 띄우고 Host 컴퓨터에서 VNC로 접속한 것인데 보다시피 화면의 그래픽 화질이 낮다. 


화질 문제는 더 좋은 VNC software를 사용하면 어느정도 해결이 가능하다.


그리고 VNC는 원격에서 화면을 볼 수 있는 기능만 지원 할 뿐 클라이언트 별로 사용자 세션을 가질 수 있는 기능은 없다. 그래서 하나의 원격 PC에 접속한 다수의 클라이언트는 모두 동일한 화면과 마우스, 키보드 인터페이스를 공유한다. 즉 내가 마우스를 움직이면 다른 클라이언트의 화면에서도 똑같이 움직이고 다른 클라이언트가 키보드를 입력하면 내 화면에서도 입력이 되는 셈이다. 아래 그림처럼 Ubuntu 18.04 VM을 두 개의 VNC 클라이언트로 접속해보니 모두 동일한 Youtube 동영상을 실행하게 된다. VNC 프로토콜 본래 목적이 네트워크를 이용한 가상 컴퓨팅에 맞춰 설계됐기 때문에 그렇다.


화질이 매우 지저분하다


RDP (Remote Desktop Protocol)


RDP는 VNC의 한계점들을 개선하기 위해 새로 만든 프로토콜은 아니고, 마이크로소프트에서 자체적으로 만든 그래픽 공유 프로토콜이며 VNC와 동일하게 인터넷 연결을 통해 원격 데스크탑 PC를 사용할 수 있다. 마이크로스프트에서 개발한 만큼 초창기에는 Windows에서만 사용할 수 있는 소프트웨어 였으나 지금은 Mac, Linux, Android 등 다양한 운영체제에서도 서버 및 클라이언트로 이용이 가능하다. 


RDP와 VNC의 차이점은 사용자 세션 관리다. VNC는 여러 개로 접속한 Client가 모두 동일한 화면을 공유하게 되지만 RDP는 사전 설정에 따라서 Client 세션을 여러개 생성 할 수 있고 접속한 Client는 각자의 화면을 가지고 동작하게 된다. VNC보다 보안적으로 안전하고 사용성도 좋다. RDP 이름 대로 원격 데스크톱으로 사용하는데 초점을 맞춘 소프트웨어 인 것 같다. 만약 집에 있는 컴퓨터를 원격으로 사용하고 싶다면 RDP를 사용하는 것을 추천한다. VNC를 사용한다면 내가 하는 작업을 다른사람이 모니터로 볼 수도 있다.


freerdp 공식 홈페이지 소개 자료. 우분투에서 원격 윈도우 PC에 접속했다


프로토콜 방식이 다른 만큼 둘의 성능차이도 나는데 다른건 모르겠고 그래픽 쪽에서는 VNC보다 RDP가 조금더 우수한 것 같다. RDP에서 VNC의 RFB에 해당하는 전송 포맷이 알고리즘 적으로 조금 더 우수해서 그런것 같은데 정확한 원리는 잘 모르겠다. 나중에 자료조사해서 나오면 좀더 읽어봐야지. 아무튼 VNC처럼 못 쓸 정도는 아니어서 나처럼 우분투랑 윈도우를 동시에 사용하는 개발자들이 많이 사용하는 툴이다.




728x90

'기술' 카테고리의 다른 글

JAVA 파일 생성/읽기/쓰기  (0) 2018.11.25
스택, 힙, 코드, 데이터영역  (6) 2018.11.10
VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
  1. 양정호 2019.10.14 11:03  댓글주소  수정/삭제  댓글쓰기

    안녕하세요. 글 잘 봤습니다.
    다름이 아니라 궁금한게 있어서 글 남깁니다.

    VNC서버에서 보내는 그래픽데이터를 간단한 옵션같은 것을 통해서 제가 지정한 이미지 파일로 보내는게 가능한지 궁금합니다.
    VNC가 Xserver에서 출력?하는 이미지를 땡겨서 클라이언트 쪽으로 보내는 걸로 알고 있는데
    Xserver소스를 고치는건 일이 너무 커지는 것 같아서 혹시나 더 간단한 방법이 있는지 알고 싶어서 글 남깁니다.

    혹시나 알고 계시다면 꼭 도움주시면 감사하겠습니다.

  2. 이응이응 2021.01.11 15:05  댓글주소  수정/삭제  댓글쓰기

    배우고 갑니다 ~_~

Q-Network

기술/인공지능 2018. 9. 12. 22:10 Posted by 아는 개발자


Q-Network


앞선 포스트에서 다룬 Q-Learning은 초기 학습 데이터 없이 주변을 탐사해가며 보상값을 최대화 하는 방향으로 환경 데이터의 파라미터의 값을 지속적으로 업데이트 하는 알고리즘이다. 그런데 만약 4x4처럼 작고 단순한 환경이 아니라 5000x5000이고 각 환경별로 선택 할 수 있는 경우의 수가 수십가지가 된다면 기존 알고리즘으로는 시간도 오래 걸릴 뿐만 아니라 모든 환경 데이터 값을 관리하는 것도 불가능하다.


이 문제는 Linear/Logical Regression에서도 겪어보았다. 학습에 사용되는 이미지의 크기가 커질수록 Feature의 값이 많아지게돼 기존의 방식 대로는 학습이 불가능했었다. 당시엔 이 문제를 해결하기 위해 Feature단위로 값을 분석하지 않고 인간의 뇌 구조를 본뜬 뉴럴 네트워크에 데이터 값을 몽땅 넣은 다음에 추이를 살펴보는 방법을 택했었는데 다행이도 Q-Learning도 이와 동일한 접근으로 해결 할 수 있다. Network를 사용한 Q-Learning은 Q-Network라고 부른다.


그런데 Linear/Logical Regression과 달리 지금까지 Q-Learning에서는 Cost Function이 없었다. 왜 없었을까? 그런데 조금만 생각해보면 있는 것이 오히려 이상하다. Q-Learning에서는 학습에 사용하는 실제 데이터가 없기 때문에 학습 결과물이 예측한 값과 라벨 값을 비교할 일이 없었다. 그래서 다소 비효율적이지만 모든 값을 돌아다니며 당시에 주어진 보상 값을 업데이트하는 방식을 사용해야 했다. 하지만 뉴럴 네트워크에 적용하려면 죽이되든 밥이되든 Cost Function이 필요하다. 그래서 Q-network에서는 학습데이터 없이 기존과 동일하게 보상 값을 할 수 있는 Cost Function을 새로 만들었다. 


별도로 논문에 첨부된 수식 보다는 코드로 보는것이 더 직관적일 것 같다. tensorflow를 해본 사람은 40, 41번째 줄을 제외하면 술술 넘어갈 것이다.

# Deep Q-Network lecture slide from http://hunkim.github.io/ml/RL/rl07-l1.pdf
# Original source code repository is https://github.com/awjuliani/DeepRL-Agents

import numpy as np
import tensorflow as tf
import random
from collections import deque
import gym 

class DQN:
    def __init__(self, session, input_size, output_size, name="main"):
        self.session = session
        self.input_size = input_size
        self.output_size = output_size
        self.net_name = name
     
        self._build_network()
     
    def _build_network(self, h_size = 10, l_rate = 1e-1):
        with tf.variable_scope(self.net_name):
            # Input data type
            self._X = tf.placeholder(
                tf.float32, [None, self.input_size], name="input_x")
     
            W1 = tf.get_variable("W1", shape=[self.input_size, h_size],
                             initializer=tf.contrib.layers.xavier_initializer())
            layer = tf.nn.tanh(tf.matmul(self._X, W1))
     
            W2 = tf.get_variable("W2", shape=[h_size, self.output_size],
                                initializer=tf.contrib.layers.xavier_initializer())
     
            # Predicted reward value
            self._Qpred = tf.matmul(layer, W2) 
     
        # Label data type
        self._Y = tf.placeholder(
            shape=[None, self.output_size], dtype=tf.float32)

        # Cost function
        self._loss = tf.reduce_mean(tf.square(self._Y - self._Qpred))
        self.train = tf.train.AdadeltaOptimizer(learning_rate=l_rate).minimize(self._loss)

    def predict(self, state):
        x = np.reshape(state, [1, self.input_size])
        return self.session.run(self._Qpred, feed_dict = {self._X: x}) 

    def update(self, x_stack, y_stack):
        return self.session.run([self._loss, self.train], 
                feed_dict={self._X: x_stack, self._Y: y_stack})

위 코드의 40번째 줄에 해당하는 부분이 Q-Network에서 사용하는 Cost Function이다. 여기서 self._Qpred는 현재 위치에서 예측했을 때 각 행동별 reward 값이다. 그리고 그것과 비교하는 self._Y는 아래 update 함수에서 y_stack으로 대입해주는 값인데 여기서 대입 해주는 값은 학습하면서 얻게된 실제 reward 값이다. 이 말은 즉슨 지금 내가 학습한 결과물과 동시에 시도하면서 얻어낸 reward 값을 비교해보면서 맞춰 나가겠다는 뜻이다. 발상의 전환이다. 방금 Learning중에 얻어낸 유의미한 데이터 값을 바로 학습에 적용했다. 특별한 사전 학습 데이터가 없이도 이런 방식으로 Cost Function을 만들어 낼 수 있다니 신기하다.


728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Network  (0) 2018.09.12
Q-Learning in Non-Deterministic World  (0) 2018.08.19
Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10

CPU pinning과 taskset

기술/컴퓨터사이언스 2018. 8. 27. 22:49 Posted by 아는 개발자


Big/Little로 이루어진 멀티코어 하드웨어 환경에서는 소프트웨어의 성능을 높이기 위해 하드웨어적인 트릭을 사용하는데 이중 가장 대표적인 것이 프로세스를 처리할 수 있는 CPU 종류를 설정하는 CPU pinning 방법이다. CPU affinity라고도 부른다.


기본적으로 스케줄러는 큐에 대기중인 프로세스를 하드웨어상에서 idle 상태에 있는 CPU에 최우선적으로 작업을 할당한다. 그런데 idle 상태에 있는 CPU가 Little라면 처리하는 속도가 Big Core에 비해서 확연히 차이가 날 수 있다. 칩에 따라서 다르지만 일반적으로 Big Core와 Little Core의 성능은 1.5배정도 차이가 난다. 만약 오래 걸리는 작업이라면 Little Core에 할당하는 것보다 Big Core가 idle이 되길 기다렸다가 여기서 처리하도록 하는 것이 성능적으로 우수 할 수 있다.


물론 시스템 내에 존재하는 모든 프로세스가 Big Core에서만 동작하려고 한다면 성능의 효과를 보지 못한다. CPU pinning은 시스템 전반의 효율화를 위해 만들어진 스케줄러의 빈틈을 노리는 것이기 때문에 다른 프로세스들은 모든 코어에서 정상적으로 돌고 있는 환경에서 효과를 극대화 할 수 있다. 그래서 일반적인 소프트웨어에서는 사용하지 않고 여러 개의 Virtual Machine이 동작하는 환경에서 VM의 성능을 조정할 때 주로 이용된다.



taskset은 Linux 환경에서 CPU pinning을 지원하는 커맨드다. 현재 프로세스에 pinning된 CPU 정보를 볼 수 있고 역으로 설정 할 수도 있다. 사용방법은 아래의 코드로만 봐도 될 정도로 간단하다.

// taskset -pc {pid} : pinning 정보 보기
// taskset -pc {Masking ex) 0-3 or 1,2,3,4} {pid} : pinning 설정하기
kwony@kwony:/proc/4007/task/4007$ taskset -pc 4007
pid 4007's current affinity list: 0-3
kwony@kwony:/proc/4007/task/4007$ taskset -pc 1,2,3 4007
pid 4007's current affinity list: 0-3
pid 4007's new affinity list: 1-3


728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

RCU (Read-Copy Update)  (0) 2018.10.30
Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27
스핀락  (0) 2018.07.23
eventfd  (0) 2018.07.18
workqueue 사용법  (0) 2018.07.16

Q-Learning in Non-Deterministic World

기술/인공지능 2018. 8. 19. 13:11 Posted by 아는 개발자


그림 1.


Non Deterministic은 한가지 행동이 여러가지 결과를 만들 수 있는 환경을 의미한다. 그림1에서 빨간선은 Agent가 이동하기로 의도한 방향이고 파란선은 에이전트가 실제로 이동한 방향이다. Deterministic한 상황에서는 에이전트가 의도한 대로 항상 오른쪽으로만 이동한다. 그런데 그림 1과 같은 상황에서는 에이전트가 그림처럼 오른쪽으로도 이동할 수도 있고 위로도 이동할 수도 있다. 이처럼 한가지의 행동(Action)이 여러가지 결과를 내는 환경을 Non-Deterministic이라 부른다.


앞선 포스트에서 설명한 Q-Learning은 예전에 알고리즘 시간에 배웠던 Dynamic Programming(이하 DP)과 여러모로 유사한데 DP는 에이전트가 취한 행동이 항상 고정된 결과를 가져올 때는(Deterministic) 최적의 해를 빠르게 낼 수 있는 알고리즘이지만 여러 가지 결과를 낼 수 있는 상황(Non-Deterministic)에서는 적용할 수 없는 문제점이 있다. 마찬가지로 Q-Learning도 환경이 고정될 때는 100%에 가까운 확률로 예측을 하지만 여러가지 결과를 내는 환경에서는 학습률이 1~2% 이하로 급격하게 저하된다. 


다행이도 DP와 달리 Q-Learning 수식을 조금만 수정하면 Non-Deterministic 환경에서도 어느 정도 괜찮은 성능을 볼 수 있다. 알고리즘 설명에 앞서 우리의 현실 세계를 바라보자. 회사에 이제 막 입사한 신입 사원은 자신의 직속 상사뿐만 아니라 고과권자, 임원 그리고 다른 회사에서 일하는 학교 선배 등등 무수히 많은 멘토들로부터 조언을 듣게 된다. 이중 몇몇은 지금 당장 실행에 옮길 수 있는 조언도 있고 지금은 아니어도 미래에 해야할 조언도 있으며 감동적인 얘기지만 요즘 시대의 정서와는 맞지 않는 (꼰대 소리) 이야기도 있다. 그래서 현명한 신입 사원이라면 멘토의 조언을 곧이곧이 받아들이지 않고 필요한 것들만 적절히 취사 선택 한다.


수정된 Q-Learning에서도 동일한 방식이 적용된다. 에이전트는 행동의 보상을 그대로 업데이트 하지 말고 여러가지 결과물의 보상값을 볼 수 있도록 기존에 학습한 정보를 고집할 필요가 있다. 수학자들은 고집하는 정도를 수학 수식의 형태로 간단하지만 예술적으로 표현 해뒀다. 이미 인공지능을 공부해본 사람들은 아마 예측 했을 수도 있겠다. Linear Regression을 공부 할 때 귀에 박히도록 배운 학습률(learning rate)을 적용하면 된다. 학습률이 높을 수록 방금 취한 action의 보상 값을 많이 반영하고 낮을 수록 보상값을 무시한다. 


1. 모든 환경 데이터 값, Q(s, a)을 초기화 한다.

2. 현재 상태를 확인한다.

3. 그리고 다음의 작업을 반복한다.

- 동작을 선택할 때 랜덤 값을 포함한다.

= argmax(Q(s, a) + ramdom_value)

- 보상 값을 받는다.

- 새로운 상태 값(s')을 확인한다.

- 환경 데이터 값을 업데이트 한다

  Q(s, a) = (1 - learning_rate) * Q(s, a) + learning_rate * (gamma * reward + Max Q(s', a'),)

# 0 < learning_rate < 1


* 이 알고리즘을 사용하면 예전처럼 100%까지는 아니어도 50-60% 정도의 정확도는 보인다. 여전히 아직은 아쉬운 결과 값이다.


* 어찌보면 단순한 수식의 변형이 학습률을 이정도로 끌어올리니 신기하다. 인간의 두뇌도 생각보다 단순하게 이뤄져 있을 것 같다는 생각이 든다.

728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Network  (0) 2018.09.12
Q-Learning in Non-Deterministic World  (0) 2018.08.19
Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10

Q-Learning

기술/인공지능 2018. 8. 19. 09:50 Posted by 아는 개발자

강화학습은 주변의 상태를 지속적으로 탐사해가며 미래에 주어지는 '보상' 값을 활용해 최적의 알고리즘을 찾는 학습법이다. 초기 학습데이터가 필요한 딥러닝과는 달리 초기의 학습데이터가 필요 없으며(물론 학습 데이터를 활용해서 성능을 높이기도 한다) 대신 어떤 조건에서 보상을 줄 것인지에 대한 정책과 탐사할 환경이 필요한 알고리즘이다. 국내에선 이세돌과 커제를 무참히 무찌른 알파고의 학습 알고리즘으로 알려져있다.


Q-Learning은 대표적인 강화 학습 알고리즘으로 '특정 상태에서 어떤 결정을 내리는 것이 미래 보상을 가장 높여줄 것'인지에 대한 정책 데이터를 지속적으로 업데이트 하는 알고리즘이다. 강화학습을 배울 때 가장 먼저 접하는 알고리즘이며 수학 수식들이 난무해 이해하고 싶지 않고 할 수도 없을 것 같은 위키피디아 페이지와 달리 예상외로 쉽고 차근차근 학습해나가면 누구나 배울 수 있는 알고리즘이다. 유튜브에 쉽게 풀어 쓴 강의도 많으니 심도 있게 공부하고 싶은 분들은 여기서 공부하면 좋을 것 같다.


이번 포스트에서는 Q-Learning을 아주 쉽게 풀어 쓰지는 않고 대략적인 동작 방법과 의사 코드를 정리해 수강한 강의 내용들을 정리하는데 초점을 맞추고자 한다.


그림 1.


Q-Learning의 현재 상태를 표현하면 위의 그림 처럼 테이블의 형태로 표현 할 수 있다. 현재 에이전트(학습하는 주체를 에이전트라고 부른다)가 있는 곳은 S로 표시된 곳이고 가야할 곳은 D이며 이곳에 도달 했을 때 보상을 받게 된다. H는 진입하면 죽는 곳이다.


그림 2.


처음 환경에 진입했을 때는 아무런 정보가 없기 때문에 이곳 저곳 다 다녀본다. 에이전트가 선택할 수 있는 방향은 동서남북중 하나인데 어떠한 정보가 없기 때문에 에이전트는 어느 곳을 선택해도 돌아오는 보상 값은 동일하다고 보고 랜덤하게 선택한 방향으로 이동하게 된다. 이렇게 다니면서 다니면서 홀에도 빠져 보고 이전에 지나친 경로들을 다시 다녀보기도 한다. 한마디로 삽질의 연속이다.


그림 3.


이렇게 우왕좌왕하면서 우연히 D 값에 도달 하기도 하는데 이때 에이전트는 처음으로 보상(reward)값을 받는다. 그리고 앞으로 움직이는 행동들은 앞서 초기화한 보상값을 활용해서 업데이트가 이뤄진다. 즉 이 상태에서는 특정 방향이 목적지에 도달할 확률이 높다고 가중치를 주는 작업이다. 지금까지를 의사코드로 정리하면 다음과 같다.

1. 모든 환경 데이터 값, Q(s, a)을 초기화 한다.

2. 현재 상태를 확인한다.

3. 그리고 다음의 작업을 반복한다.

- 동작을 선택하고 실행한다.

- 행동에 따른 보상 값을 받는다. reward

- 새로운 상태 값(s')을 확인한다.

- 아래의 수식으로 환경 데이터 값을 업데이트 한다

  Q(s, a) = reward + Max Q(s', a')


Q(s, a)의 의미는 상태 현재 상태가 's'이고 'a' 행동을 취했을 때 얻게 될 보상 값이다. 위 그림에서는 총 25개의 상태가 있고 각 상태별로 가능한 행동이 4개가 있으니 총 100개의 환경 데이터 값이 필요하다.


그림 4. 


위의 알고리즘은 최적의 값은 찾기는 하지만 보상값을 업데이트한 이후에는 똑같은 경로로 이동하게 되는 단점있다. 현재 상태의 보상 값을 바로 다음 상태의 보상 값으로 입력시키기 때문에 파란선으로 지나온 상태에서 최적의 보상을 주는 값이 이미 정해져 버리고 또 각 상태별 행동은 가장 높은 보상 값을 주는 것으로 선택하기 때문에 매번 동일한 경로로 이동하게 된다. 그래서 그림 4의 빨간선을 이용하면 더 빠르게 갈 수 있지만 이 경로는 보지 못하고 이전에 학습한 경로대로만 다니게 되는 단점이 있다.


이런 오류를 막기위해서 알고리즘에 두가지 노이즈를 추가한다. 미래의 보상은 시간이 지날 수록 낮춰지는 것과 행동을 선택할 때 보상값과 랜덤값을 더하는 방식이다. 두 방법 모두 에이전트에게 돌출 행동을 유도하는 일종의 트릭이다.


1. 모든 환경 데이터 값, Q(s, a)을 초기화 한다.

2. 현재 상태를 확인한다.

3. 그리고 다음의 작업을 반복한다.

- 동작을 선택할 때 랜덤 값을 포함한다.

a= argmax(Q(s, a) + ramdom_value)

- 보상 값을 받는다.

- 새로운 상태 값(s')을 확인한다.

- 환경 데이터 값을 업데이트 한다

  Q(s, a) = gamma * reward + Max Q(s', a'), # 0 < gamma < 1


노이즈를 준 부분을 굵은 글씨로 표시해뒀다. 행동을 선택 할 때 최적의 보상값 + random 값으로 선택하도록 했는데 이는 동서남북을 선택 할 때 각각의 보상값과 ramdom값을 더한 값중에서 가장 높은 값을 선택하도록 하는 것이다. 초기 알고리즘은 그림 4에서 빨간선과 파란선이 갈라지는 지점(2행 3열)에서 가장 높은 보상값을 주는 파란선을 무조건 선택하게 되는데 수정한 알고리즘은 빨간선을 선택 했을 때의 랜덤값이 더 높으면 빨간선을 선택하게 된다.


그리고 미래에 주어지는 보상은 지속적으로 감소하게 만들었다. 처음으로 받게되는 보상은 D에 도달하는 지점이었으니 빨간선과 파란선이 갈라지는 지점에서는 보상값이 꽤 많이 감가상각돼 빨간선을 택하는 것과 파란색을 택할 때의 보상값 차이가 얼마 나지 않을 것이다. 이 두가지 방법으로 에이전트는 기존 학습 데이터 값을 크게 벗어나지 않는 선에서 돌출된 행동을 유도 할 수 있다.


참고문헌


- 홍콩과기대 교수님 유튜브 강의: https://www.youtube.com/channel/UCML9R2ol-l0Ab9OXoNnr7Lw

728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Network  (0) 2018.09.12
Q-Learning in Non-Deterministic World  (0) 2018.08.19
Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10

kvm ioeventfd

기술/가상화 2018. 8. 11. 23:10 Posted by 아는 개발자

* 개인 공부용으로 정리한 것이라 부정확한 내용이 있을 수 있으니 참고용으로만 사용하길 바랍니다.


IOEVENTFD


eventfd를 응용해서 guest에 interrupt를 보낼 수 있는 기능을 만든 irqfd것과 비슷하게 ioeventfd도 eventfd를 이용해서 guest에게 mmio 기능을 전달 할 수 있는 매커니즘을 만들었다. 초기화 작업도 irqfd와 거의 비슷하다.


1. QEMU에서 kvm으로 mmio 값 전달.


QEMU에서 장치가 사용할 주소 값과 flag, eventfd 값을 세팅하고 kvm에 ioctl 을 날린다. 이때 전달인자 fd는 MemoryRegionIoeventfd 라는 구조체의 EventNotifier 값을 뽑아낸 것이다. 구조체 이름이 매우 와닿지 않는다.

// qemu/accel/kvm-all.c
static int kvm_set_ioeventfd_pio(int fd, uint16_t addr, uint16_t val, 
                      bool assign, uint32_t size, bool datamatch)
{
    struct kvm_ioeventfd kick = {
        .datamatch = datamatch 
                       ? adjust_ioeventfd_endianness(val, size) : 0, 
        .addr = addr,
        .flags = KVM_IOEVENTFD_FLAG_PIO,
        .len = size,
        .fd = fd,
    };   
    r = kvm_vm_ioctl(kvm_state, KVM_IOEVENTFD, &kick);

2. 받은 값을 이용해 _ioeventfd 자료구조 생성 및 초기화


_ioevent라는 자료구조를 선언하고 qemu 에서 전달한 값을 세팅한다. args는 QEMU에서 전달받은 인자 값이고 eventfd는 MemoryRegionIoeventfd 의 EventNotifer의 파일 디스크립터 값이다. VirtQueue와 비슷하게 선언된 것 같다.

// virt/kvm/eventfd.c
static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
                enum kvm_bus bus_idx,
                struct kvm_ioeventfd *args)
{
    // HK: Define ioeventfd data structure
    INIT_LIST_HEAD(&p->list);
    p->addr    = args->addr;
    p->bus_idx = bus_idx; // HK: bus_idx is defined by ioeventfd flag
    p->length  = args->len;
    p->eventfd = eventfd;
}

3. IO Device 초기화


초기화한 _ioeventfd에 ioeventfd 작업인 write와 destructor 함수를 세팅한다.. kvm ioeventfd 기반에서 사용할 수 있는 함수들을 설정하는 작업으로 보면 될 것 같다. ioeventfd_write 함수는 쓰려는 값이 range 안에 있는지 확인하고 있으면 eventfd_signal을 보낸다. 값을 업데이트해서 QEMU에 알림을주려는 용도다. 

// virt/kvm/eventfd.c
static int kvm_assign_ioeventfd_idx(struct kvm *kvm,
                enum kvm_bus bus_idx,
                struct kvm_ioeventfd *args)
{
    // HK: Set ioeventfd operation for this device
    kvm_iodevice_init(&p->dev, &ioeventfd_ops);

static const struct kvm_io_device_ops ioeventfd_ops = {
    .write      = ioeventfd_write,
    .destructor = ioeventfd_destructor,
};

/* MMIO/PIO writes trigger an event if the addr/val match */
static int
ioeventfd_write(struct kvm_vcpu *vcpu, struct kvm_io_device *this,
      gpa_t addr, int len, const void *val)
{
    struct _ioeventfd *p = to_ioeventfd(this);

    if (!ioeventfd_in_range(p, addr, len, val))
        return -EOPNOTSUPP;
            
    eventfd_signal(p->eventfd, 1);
    return 0;
}

4. KVM IO bus 등록


mmio로 사용할 주소와 bus index, mmio 동작 작업을 kvm io bus에 등록한다. 버스에서는 등록된 mmio 장치의 주소 값을 보고 event를 날려주는것 같다. bus쪽은 공부가 좀더 필요한 부분이다.

    ret = kvm_io_bus_register_dev(kvm, bus_idx, p->addr, p->length,
                      &p->dev);
    if (ret < 0) 
        goto unlock_fail;
    
    kvm->buses[bus_idx]->ioeventfd_count++;


728x90

'기술 > 가상화' 카테고리의 다른 글

Virtio Block 성능 벤치마크  (0) 2018.12.23
QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08
virtio  (0) 2018.07.08

kvm irqfd

기술/가상화 2018. 8. 11. 14:02 Posted by 아는 개발자

* 개인 공부용도로 정리한 것이라 부정확한 정보가 있을 수도 있으니 참고용으로만 사용하세요.


IRQFD


irqfd는 QEMU에서 정의한 eventfd와 GSI(Global System Interrupt) 값을 이용해서 Guest에 바로 interrupt를 바로 쏘아 줄 때(irqfd_inject) 사용하는 kvm interrupt 라이브러리다. 간단한 event를 만들 때 사용하는 eventfd 메커니즘을 응용한 대표적인 예다. 


좀더 디테일한 동작 내용을 이해하기 위해 초기화 코드를 순서대로 분석해보자. 


1. QEMU 장치 정보 세팅 후 kvm으로 ioctl 전달


QEMU에서는 장치가 사용하려는 GSI(Global system interrupt)와 상태에 대한 정보를 저장하는 flag 값 그리고 qemu/VM 간 데이터를 주고받는 통로인 virtqueue의 file descriptor 값을 kvm에게 ioctl로 전달한다.

// qemu/accel/kvm/kvm-all.c
static int kvm_irqchip_assign_irqfd(KVMState *s, int fd, int rfd,
                                               int virq, bool assign)
{
    struct kvm_irqfd irqfd = {
        .fd = fd,
        .gsi = virq,
        .flags = assign ? 0 : KVM_IRQFD_FLAG_DEASSIGN,

    return kvm_vm_ioctl(s, KVM_IRQFD, &irqfd);
}

2. irqfd 자료구조 생성 및 동작 함수 세팅


QEMU로부터 전달받은 정보들을 토대로 kvm은 현재 VM 이 사용할 irqfd 자료구조를 할당하고 값을 세팅한다. args로 참조하는 값은 QEMU에서 받아온 값이고 eventfd는 QEMU에서 전달받은 VirtQueue의 file descriptor 값을 이용해 추출한 주소다. VirtQueue가 eventfd 기반으로 만들어진 자료구조 인 것 같다. irqfd->inject,shutdown은 irqfd가 runtime에 수행할 함수들이다. irqfd_inject는 인터럽트를 guest에 주입하는 함수고 irqfd_shutdown은 irqfd 자료구조를 제거할 때 사용된다.

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    irqfd->kvm = kvm;
    irqfd->gsi = args->gsi;
    INIT_LIST_HEAD(&irqfd->list);
    INIT_WORK(&irqfd->inject, irqfd_inject);
    INIT_WORK(&irqfd->shutdown, irqfd_shutdown);
    seqcount_init(&irqfd->irq_entry_sc);
...
    irqfd->eventfd = eventfd;


3. poll table, waitqueue 초기화


irqfd의 poll table의 콜백 함수와 waitqueue의 콜백 함수를 설정한다. 설정 목적은 eventfd 메커니즘으로 전달받은 이벤트를 irqfd로 쏘아주기 위함이다. eventfd부터 최종적으로 irqfd 함수가 불리기 까지의 동작은 마지막에 설명하도록 하자. 여기서는 poll_table이라는 것을 사용했다는 것에 주목하자

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    init_waitqueue_func_entry(&irqfd->wait, irqfd_wakeup);
    init_poll_funcptr(&irqfd->pt, irqfd_ptable_queue_proc);


4. kvm irq routing 정보 입력


Guest로부터 넘겨받은 GSI 값에 대한 irq routing entry 정보를 받고 업데이트한다. 이부분은 배경 지식이 부족해 정확하게 이해하지 못한 부분이기는 한데 코드만 보면 대충 짐작하기로는 kvm 자료구조에서 irq routing에 대한 정보가 있으며 이 값은 GSI값에 따라 처리할 수 있는 entry 정보가 별도로 존재하는 것 같다. 추출한 entry 값을 irqfd 자료구조에 설정한다.

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    irqfd_update(kvm, irqfd);
static void irqfd_update(struct kvm *kvm, 
                             struct kvm_kernel_irqfd *irqfd)
{
    // HK: Get the entries of irq_rt->map[gsi]
    // Set mapping between irqfd(kernel) to user space
    n_entries = kvm_irq_map_gsi(kvm, entries, irqfd->gsi);
// linux/virt/kvm/irqchip.c
int kvm_irq_map_gsi(struct kvm *kvm,
            struct kvm_kernel_irq_routing_entry *entries, int gsi)
{
    irq_rt = srcu_dereference_check(kvm->irq_routing, &kvm->irq_srcu,
                    lockdep_is_held(&kvm->irq_lock));

    // HK: Define kvm_irq_routing_entry
    //     Each entry properties depend on gsi value...
    if (irq_rt && gsi < irq_rt->nr_rt_entries) {
        hlist_for_each_entry(e, &irq_rt->map[gsi], link) {
            entries[n] = *e; 
            ++n;


5. irqfd 리스트 추가 및 pending된 인터럽트 처리


지금까지 세팅한 irqfd 자료구조를 kvm irqfds 리스트에 추가한다. 장치가 여러개 있으니 VM별로 여러개의 irqfd를 가질 수 있다. 그리고 지금까지 초기화 하는 도중에 인터럽트가 발생했는지 확인하고 있으면 스케줄링으로 저리한다. 여기서 poll이라는 콜백함수로 이벤트가 있는지 확인하는데 poll 함수는 eventfd_poll이다. eventfd 값이 변경되면 새로운 event가 발생한 것으로 보고 interrupt를 날리려는 용도다.

// linux/virt/kvm/eventfd.c
static int kvm_irqfd_assign(struct kvm *kvm, struct kvm_irqfd *args) 
{
    /* HK: Add new irqfd to kvm irqfd list */
    list_add_tail(&irqfd->list, &kvm->irqfds.items);
    events = f.file->f_op->poll(f.file, &irqfd->pt);

    if (events & POLLIN)
        schedule_work(&irqfd->inject);


초기화 작업을 바탕으로 분석한 irqfd의 동작 과정은 다음과 같다


eventfd_poll => irqfd->pt (poll table) => (callback) irqfd_ptable_queue_proc

=> add_wait_queue(wqh, &irqfd->wait) => irqfd_wakeup => schedule_work(&irqfd->inject);

=> kvm_set_irq(kvm, KVM_USERSPACE_IRQ_SOURCE_ID, irqfd->gsi, 1,


eventfd_poll 함수에서는 irqfd가 갖고 있는 eventfd 값이 변경되길 기다리며 event 발생시 poll table에 물려있는 콜백함수(irqfd_ptable_queue_proc)함수가 불리고 이 함수에서는 waitqueue의 콜백 함수로 설정한 irqfd_wakeup 함수를 부른다. 이 함수에서는 interrupt를 날리는 함수인 irqfd_inject 함수가 불리고 이 함수에서 최종적으로 guest에게 interrupt를 쏴주게 된다. 휴 어렵네;


kvm_set_irq 함수 내의 while문에서 콜백 함수로 set이 불리는데 이 함수는 아키텍처별로 설정되는 함수다. ARM의 경우에는 vgic의 vgic_irqfd_set_irq 함수가 불리며 x86 에서는 irqchip 타입에 따라서 콜백 함수가 다르다.

// virt/kvm/irqchip.c
int kvm_set_irq(struct kvm *kvm, int irq_source_id, u32 irq, int level,
        bool line_status)
{
    while (i--) {
        int r;
        // HK: kvm_set_msi architecture specific callback!
        //     set -> vgic_irqfd_set_irq (vgic-irqfd.c)
        r = irq_set[i].set(&irq_set[i], kvm, irq_source_id, level,
                   line_status);


사용 예시


Guest가 virtio 드라이버를 사용하는 경우, QEMU에서 에뮬레이션된 virtio backend 드라이버가 요청 받은 작업을 마치고 Guest에 VirtQueue를 이용해서 알림을 주게 된다. 이때 QEMU에서 VirtQueue에 결과 값을 입력하면 커널에서는 이전에 초기화한 VirtQueue의 eventfd 값이 변경된 것을 감지하고하고 초기화된 함수들이 연달아 불리게 된다. 최종적으로 interrupt_inject 함수가 불리게 되면서 Guest에게 interrupt가 전달된다.

728x90

'기술 > 가상화' 카테고리의 다른 글

QEMU를 이용해 커널 이미지 바꿔서 부팅해보기  (0) 2018.12.20
kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08
virtio  (0) 2018.07.08
VFIO, Passthrough  (0) 2018.06.30

파이썬으로 인공지능을 개발할 때 자주 사용하는 패키지 세가지를 정리해봤다.


1. Pandas


csv형태로 된 데이터 파일을 일어올 수 있는 라이브러리를 제공. 엑셀 파일을 함수 호출 한번으로 파이썬에서 사용할 수 있는 객체로 변환할 수 있다. 데이터 셋이 엑셀의 형태로 되어있는 경우가 많아 데이터 분석을 할 때 자주 사용하는 패키지중에 하나다.


import pandas pd
df_data = pd.read_excel('data.xlsx', encoding='utf-8') # 엑셀 데이터 읽어오기


리턴된 객체인 df_data에는 행을 추가하거나 삭제할 수 있는 기본적인 기능뿐만 아니라 새로운 속성을 추가하고 다른 데이터와 합칠 수 있는 기능도 제공한다. 꽤 많은 기능이 있으니 직접 사용하면서 익혀보는게 좋다


2. Numpy




파이썬에서 차트 형태로 있는 데이터를 행렬로 바꾸고 필요한 데이터만 쪽쪽 뽑아내야 할 때 numpy를 사용하면 쉽게 해결 할 수 있다. 리턴 객체 안에서 간단한 행렬 연산 뿐만 아니라 선형대수에서 자주 사용하는 행렬 연산을 제공해서 인공지능 뿐만 아니라 수치해석, 행렬연산, 기술통계에서도 자주 사용되는 패키지다. 거의 matlab에 맞먹는 사용성을 지니고 있다고 하기도.


import numpy as np
data = np.array(df_data) # df_data를 행렬 형태로 변환
select_data = data[:,5:6] # 5열만 데이터를 뽑음
transpose_data = data.transpose() # Transpose 형태로 변환


상대적으로 라이브러리가 제공이 잘되어있는 Java에서도 행렬을 이용한 작업은 언제나 까다롭기 마련인데 파이썬에는 numpy라는 유용한 패키지가 있어 비전공자들에게도 사랑 받는 것 같다.


3. Matplot



엑셀 차트만큼 강력한 파이썬 데이터 시각화 툴. 처음에는 사용하기 낯설지만 함수 몇개만 좀 외워두고 나면 갖고 있는 데이터를 몇줄 안되는 코드로 그래프로 나타낼 수 있다. 주로 Machine Learning 단계에서 자주 사용하는 툴이다. 


import matplotlib.pyplot as plt
plt.figure(figsize=(10, 10))
plt.scatter(x_test, y_test, color="black") # Test 데이터는 검은 점으로 표현한다
plt.scatter(x_train, y_train, color="red") # Train 데이터는 붉은 점으로 표현한다
plt.plot(x_test, model.predict(x_test),
            color="blue", linewidth=3) # 예측 결과는 파란 선으로 표현한다.
plt.show()


다섯줄의 코드로 Linear Regression의 결과를 시각화 할 수 있다.



728x90

'기술 > 인공지능' 카테고리의 다른 글

Q-Learning in Non-Deterministic World  (0) 2018.08.19
Q-Learning  (0) 2018.08.19
파이썬으로 AI 개발할 때 유용한 패키지 모음  (0) 2018.08.07
뉴럴네트워크(Neural Network)  (0) 2017.08.18
오버피팅(Overfitting)  (0) 2017.08.10
숫자인식 코드 분석해보기  (2) 2017.08.03

jupyter notebook 소개

기술 2018. 8. 4. 11:58 Posted by 아는 개발자

파이썬은 라이브러리가 많다는 것이 무궁무진한 장점인데 정작 개발할 때는 어떤 함수가 있고 그 함수를 사용하려면 어떤 인자가 필요한 건지 알지 못해 난감한 경우가 있다. 물론 구글 검색을 통해서 알 수 있지만 궁금한 함수가 한 두개가 아닌 경우에는 매번 검색으로 알아보는 것도 꽤 일이다.


이럴 때는 jupyter-notebook을 사용하면 편리하다. jupyter notebook은 C를 처음 배울때 사용했던 비주얼 스튜디오처럼 객체에서 사용가능한 함수들을 쭉 나열해주고 함수가 어떤 역할을 하며 사용할 때 필요한 인자는 무엇인지 뽑아준다. 매번 선언이 안된 함수를 사용해서 (나처럼) 붉은 에러 메시지를 뿜어내는 사람에게는 필수 소프트웨어 툴이다. 


pandas 모듈에서 사용가능한 함수들을 출력하고 필요한 인자 정보를 확인 할 수 있다.


사진을 보면 여러개의 칸으로 구분되어 있는데 이 칸은 코드의 실행을 구분하는 단위가된다. 하나의 파일을 여러 개의 칸으로 나눠 놓으면 코드 조각 별로 실행이 가능하다. 코드에 문법상 오류가 없는지, 출력하려는 결과가 예상한 것과 일치하는지 바로바로 확인할 수 있어서 간편하다.


tensorflow 더하기 예제를 여러 코드 조각으로 나눠서 실행 할 수 있다.


Jupyter의 또다른 장점은 마크다운 문서를 코드 중간에 삽입 할 수 있다는 것이다. 파이썬 코드에서 문서를 만들수 있는 방법은 주석을 추가하던가 아니변 별도로 문서를 만드는 만들어야 하는데 jupyter 노트북을 사용하면 코드 칸을 마크다운 용도로 바꿔서 문서를 작성 할 수 있다. 마크다운 문법만 익숙하다면 문서 만드는건 순식간이다. 


쉽게 문서를 만들 수 있다는 장점 덕분에 대부분의 파이썬 강의들은 jupyter로 하는 것 같다;


알록달록한 GUI 환경임에도 불구하고 다양한 단축키를 제공해 마우스가 거의 필요하지 않으며(개발자에게는 필수 제외 템이다) 단축키도 vim에서 사용하는 것과 거의 흡사해 Terminal 환경에서 쭉 개발해온 사람들도 쉽게 적응 할 수 있다. 단축키를 직접 수정할 수 있는 기능도 제공하니 자신의 환경에 맞게 단축키를 변경해서 사용하는 것도 가능하다.


스크롤바를 보면 눈치 채겠지만 이게 전부가 아니다.


+) 브라우저에서 동작하는 소프트웨어라 설치에 부담이 없고 가벼워서 실행이 빠르다.

728x90

'기술' 카테고리의 다른 글

스택, 힙, 코드, 데이터영역  (6) 2018.11.10
VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
libgdx - Renderer  (0) 2018.06.22

conda tensorflow 설치 및 jupyter notebook 연결

개발/삽질 기록 2018. 8. 3. 23:20 Posted by 아는 개발자


Conda 환경에서 tensorflow를 설치하는 방법


0. Prerequsite


- Anaconda를 설치한다.

- GPU를 사용할 경우 tensorflow를 사용할 수 있도록 CUDA, cuDNN 라이브러리를 설치한다.


1. tensorflow 가상 환경 생성


tensorflow를 사용하는 가상 환경을 하나 생성한다. 별도로 설정하고 싶은 환경이 있으면 생략하고 아니면 만들자.

kwony@kwony:~$ conda create -n tensorflow pip \ 
python={ python 버전 입력 ex) 2.7, 3.4

2. 가상환경 활성화


1에서 만들어둔 가상 환경을 활성화한다. activate 명령어가 안먹히는 경우는 anaconda의 바이너리가 export가 제대로 안된것이니 확인해보도록 하자.

kwony@kwony:~$ conda activate tensorflow
(tensorflow)$  # tensorflow로 명령어 프롬프트가 변형된다

3. tensorflow 설치


활성화된 명령어 프롬프트에서 tensorflow를 설치한다. tensorflow 공식 홈페이지에서 설치하려는 tensorflow의 종류와 파이선 버전에 해당하는 저장소를 입력하도록 하자.

(tensorflow) kwony@kwony:~$ pip install --ignore-installed \
 --upgrade { URL }

4. jupyter 설치


원래 아나콘다를 설치할 때 기본으로 포함된 jupyter-notebook에서 tensorflow를 쓰려고 하면 없는 모듈이라고 나온다. 방금전에 설치한 tensorflow는 현재 사용중인 가상 환경에만 적용되기 때문이다. 현재 가상 환경에서 사용하는 jupyter notebook을 설치하면 방금 전에 설치한 tensorflow를 사용 할 수 있다.

(tensorflow) kwony@kwony:~$ pip install jupyter

5. jupyter notebook 실행 후 테스트


jupyter 노트북으로 tensorflow 모듈을 사용해보면 문제없이 동작하는 것을 확인 할 수 있다.

(tensorflow) kwony@kwony:~$ jupyter-notebook 



728x90

git rebase 를 이용해 중간 커밋 수정하기

개발/삽질 기록 2018. 7. 25. 21:54 Posted by 아는 개발자

최상위 커밋은 git commit --amend로 수정이 가능한데 중간에 있는 커밋은 수정하기가 참 난감하다. 가장 간단하면서 무식한 방법은 git format-patch로 수정하려는 커밋 전까지 패치 파일을 만들어둔 후 reset으로 수정하려는 커밋까지 쌓인 커밋들을 모두 지운 뒤 git commit --amend로 수정한 다음, reset 하기 전에 패치로 만들어둔 커밋을 다시 적용하는 방법이 있다. 이 방법은 매우 번거롭고 복잡하며 이미 Pull Request가 진행중인 브랜치에서는 적용할 수 없다는 문제가 있다.


이런 경우에는 git rebase의 interactive 옵션을 이용하면 Pull Request가 진행중인 브랜치의 중간 커밋을 수정 할 수 있다. 사용 방법은 간단하다.



위 그림에서 맨 아래 "appear action bar and use customized one in user activity"라는 커밋만 수정해보고 싶다고 해보자. 그러면 내가 수정하려는 커밋의 번호를 복사한 후 command에 아래와 같이 입력하자


// git rebase -i {커밋 id}^
git rebase -i 1372d858b7d84708f54da356035cec35a4375f07^


실행하고 나면 nano 에디터가 열리면서 아래와 같은 메시지가 나온다.


pick 1372d85 appear actionbar and use customized one in user activity
pick 0ba4172 cut side space in actionbar
pick 64d006d Fix bug: Change error message on dialog user name input
pick 5be8b91 Replace textview to imageview in sliding tab layout
pick c6d7a5e Add postfix string in actionbar title to show detailed information
pick 624baa3 upgrade version to 1.20

# Rebase 382448a..624baa3 onto 382448a (6 command(s))
#
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell
# d, drop = remove commit


메시지에는 수정하려는 커밋부터 최신 커밋까지 "명령" "커밋 번호" "커밋 메시지"의 형태로 쭉 나열 돼어 있다. 현재 상태는 어떤 커밋에 무슨 작업을 할 것인지 설정 할 수 있는 단계다. 아래 Commands의 설명이 나와 있는 것 처럼 'pick'은 commit을 그대로 사용하는 것이고 'edit'은 커밋을 사용하면서 수정하기 위해 멈춘다는 뜻이다. 기본으로는 모두 변경하지 않음인 'pick' 설정이 돼있다.


개발자는 "명령"에 해당하는 문자열 값을 바꿔서 해당 커밋에서 원하는 작업을 수행 할 수 있다.  우리는 가장 처음에 있는 커밋(1372d85)을 수정해야하니 이 문장에 pick을 edit으로 변경하고 빠져나오자. 그럼 아래와 같은 메시지가 뜬다.



해당 커밋에 멈춰 있으니 git commit --amend로 수정을 한 다음에 git rebase --continue를 하라는 뜻이다. git rebase --continue는 현재 커밋에 대한 작업을 수행한 후 다음 커밋의 작업으로 넘어가는 명령어다. 테스트를 위해 README.md 파일만 수정하고 넘어가본다. 그러면 숫자들이 빠르게 움직이고 나서 아래와 같은 메시지가 나온다.



반영이 모두 완료돼었다는 뜻이다. 그런데 이런 완료 메시지가 나오지 않고 충돌이 떴다는 메시지가 나오는 경우도 있다. 중간 커밋의 수정사항 때문에 기존 커밋을 적용하지 못해 충돌이 발생하는 경우다. 이런 경우에는 중간 커밋의 수정사항을 따를 수 있도록 기존 커밋도 바꿔주도록 한다.

728x90

스핀락

기술/컴퓨터사이언스 2018. 7. 23. 22:23 Posted by 아는 개발자

멀티프로세서 환경에서는 시스템에서 생성된 프로세스가 하드웨어에 설치된 CPU의 수만큼 동시에 실행된다. CPU에서 동작 중인 프로세스는 메모리 영역에 할당된 데이터중 연산이 필요한 값을 읽고 쓰는 작업을 수행하게 되는데 이때 여러 개의 프로세스가 같은 데이터 영역을 공유하게 되는 경우가 있다. 여러 개의 프로세스가 공통된 전역 변수의 값을 읽는 경우가 대표적인 예다.




데이터 값이 고정되고 모든 프로세스가 고정된 값을 읽기만 하면 별 다른 문제는 없다. 그런데 누군가가 쓰게 되는 경우부터 싱크 문제가 생긴다. A랑 B 프로세스가 있다고 해보자. A 프로세스는 C와 D의 데이터 값을 수정하게 되어있고, B 프로세스는 A프로세스보다 조금 늦게 생성되어 A 프로세스가 수정한 값을 읽도록 짜여져 있다. 싱글코어에선 A 프로세스 다음에 B 프로세스가 스케줄링돼 수정 작업과 읽는 작업이 순차적으로 이뤄진다. 그런데 멀티코어에서는 B프로세스가 A 프로세스의 작업이 끝나기 전에 스케줄링 될 수도 있다. 이런 경우에는 수정된 C, D 값을 읽지못하고 예전 값을 읽게 될수도 있다.


static __always_inline void spin_lock(spinlock_t *lock)
static __always_inline void spin_unlock(spinlock_t *lock)


이런 경우를 막기위해 리눅스에서는 spin_lock이라는 API를 제공한다. spin_lock은 매개변수(spinlock_t)의 값을 이용해 다른 프로세스의 공유 영역에 대한 접근을 막을 수 있는 함수다. spin_lock을 부르면 매개 변수의 값을 보고 다른 프로세스의 점유 유무를 확인한다. 점유한 상태가 아니면 매개변수의 값을 점유 상태로 바꾸고 공유 영역에서 수행할 작업을 수행하고 그렇지 않으면 다른 프로세스가 놓을 때까지 (busy waiting으로)기다린다. 수행한 작업을 완료한 후 spin_unlock 함수를 통해 점유 상태를 취소한다. 취소 후에는 대기중이던 프로세스가 매개 변수의 값을 읽고 재점유한다.


다시 예제로 돌아와보자. A 프로세스가 값을 수정하기 전에 B 프로세스가 읽는 것을 막기 위해선 값을 수정하는 A 프로세스가 값을 수정하는 작업과 B 프로세스가 값을 읽는 작업 앞에 spin_lock을 걸어두면 된다. 수정 작업이 먼저 실행되기 때문에 A 프로세스가 먼저 lock을 획득하게 되고 중간에 읽기 작업을 수행하는 B 프로세스는 lock을 획득하지 못해 busy waiting 상태로 기다린다. 수정작업이 모두 끝난 A는 다른 프로세스가 접근 할 수 있도록 spin_unlock을 수행하게 되고 이 이후에 B 프로세스는 lock을 획득해 수정된 값을 읽을 수 있게 된다.




여기서 더 나아가 하드웨어에서 인터럽트가 들어온 상황을 생각해보자. 프로세스가 lock을 획득해서 작업하는 도중에 인터럽트가 들어오면 CPU는 하던일을 중단하고 인터럽트 서비스 루틴을 처리한다. 그런데 만약 인터럽트를 처리하는 작업중에서 인터럽트가 들어오기전 프로세스가 획득한 lock을 갖기 위해 spin_lock을 부르는 코드가 있다고 해보자. 인터럽트 서비스 루틴도 일반 프로세스와 마찬가지로 lock을 획득 할 수 있을 때까지 대기 상태에 있다. 그런데 lock을 놓아줘야하는 프로세스는 여전히 인터럽트에 걸린 상태에서 다시 스케줄링 되지 못하고 있다. 서로가 서로의 진행을 막고있는 셈이다. 이런 경우 프로세스는 교착 상태(Dead Lock)에 빠진다.


static __always_inline void spin_lock_irq(spinlock_t *lock)
static __always_inline void spin_unlock_irq(spinlock_t *lock)


이를 해결하기 위해 리눅스에서는 lock을 획득한 상태에서는 cpu의 인터럽트를 막을 수 있는 함수를 제공한다. spin_lock_irq 함수를 사용하면 해당 lock을 획득하고 다시 놓을 때까지 프로세스의 인터럽트를 막는다. 이 시간 동안 들어오는 인터럽트는 pending 상태로 대기하고 있다가 spin_unlock_irq가 불리면 다시 재실행 된다.


그런데 spin_unlock_irq는 disable 되어있던 인터럽트를 모두 enable 시키는데 이러면 함수의 depth가 깊어지는 경우 문제가 발생 할 수 있다. 어떤 함수가 spin_lock_irq를 부르고 lock 내부 루틴을 타는 함수가 또 spin_lock_irq를 부르면 정상적으로 함수가 종료 될 때 spin_unlock_irq가 두번 불리게 될 것이다. 그런데 spin_unlock_irq는 예전에 인터럽트가 몇번 disable 됐는지 상관없이 모두다 enable하게돼 첫번째 spin_unlock_irq와 두번째 spin_unlock_irq가 호출되는 사이 지점은 인터럽트가 disable이 되지 않는다. 즉 spin_lock_irq를 못쓰고 spin_lock을 쓰는것과 마찬가지인 셈. 이 사이에 하드웨어 인터럽트가 들어와 똑같은 lock에 spin_lock을 걸면 예전처럼 deadlock이 걸린다.


#define spin_lock_irqsave(lock, flags)              \
do {                                \
    raw_spin_lock_irqsave(spinlock_check(lock), flags); \
} while (0)
static __always_inline void spin_unlock_irqrestore(spinlock_t *lock,
                                 unsigned long flags)
{
    raw_spin_unlock_irqrestore(&lock->rlock, flags);
}


이런 경우를 해결하고자 spin_lock_irqsave라는 함수가 있다. 잘 보면 이 함수는 매개변수로 flags라는 값을 하나 더 주는 것을 볼 수 있다. flag에다가 현재 interrupt의 상태를 보존해 놓는 것이다. 이 값을 활용해 더 안전하게 스핀락을 처리할 수 있도록 한다.



728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

Cgroup (Control Group)  (0) 2018.09.15
CPU pinning과 taskset  (0) 2018.08.27
스핀락  (0) 2018.07.23
eventfd  (0) 2018.07.18
workqueue 사용법  (0) 2018.07.16
tasklet 사용법  (0) 2018.06.17

eventfd

기술/컴퓨터사이언스 2018. 7. 18. 21:37 Posted by 아는 개발자

eventfd는 리눅스기반 운영체제에서 파일 디스크립터를 통해 프로세스들끼리 통신 할 수 있는 API중 하나다. IPC로 가장 유명한 pipe는 read와 write용으로 채널을 따로 만드는 것과는 달리 eventfd에서는 읽고 쓸 파일 디스크립터 하나면 충분하기 때문에 번거로운 세팅작업 없이 간단하게 구현 할 수 있다. 그러나 단 한개의 채널을 사용하는만큼 사용할 수 있는 기능은 제한적이다. 이 파일 디스크립터로 교환할 수 있는 값은 1 이상의 64비트 정수 하나가 전부라 데이터 전송보다는 이름처럼 프로세스간에 event를 전달하는 용도로 사용한다.


1. eventfd(uint64 initval, int flag)


프로세스들끼리 통신 할 수 있는 파일 디스크립터를 만드는 함수다. 종료후에는 채널로 사용 할 수 있는 64비트 정수형 파일 디스크립터가 리턴된다.


첫번째 인자는 counter 값이다. counter 값은 eventfd의 고유한 값 정도로 보면 될 것 같다. 이 값은 write로 추가 할 수 있고 read로 읽을 수 있다. 그런데 flag에 따라서 동작이 달라지니까 유의하도록 한다.


2. read(int fd, void* buf, size_t count)


eventfd의 counter 값을 읽는 함수다. 단 counter 값은 무조건 1 이상이 되야 하고 counter 값이 0이면 되면 읽지 못하고 에러를 리턴한다. 1이상이면 가지고 있던 counter 값을 리턴한다. 그런데 리턴한 후에는 flag 값에 따라서 counter 값을 변형한다.


- EFD_SEMAPHORE가 세팅된 경우에는 counter 값을 1만큼 감소시킨다. 세마포어 기능을 한다고 보면 된다. 여러개 프로세스가 읽는 경우를 생각한 것 같다.


- EFT_SEMAPHORE가 세팅이 안된 경우에는 counter 값을 0으로 리셋한다.


3. write(int fd, const void *buf, size_t count)


counter 값을 추가할 수 있는 함수다. counter 값은 uint64 범위 내에서 자유롭게 추가 할 수 있으며 만약 최대값을 넘는 경우에는 막히고 에러를 리턴한다. 별거 어려운게 없다.


나중에는 실제로 프로세스들이 어떻게 사용하고있는지 분석해봐야겠다.




728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

CPU pinning과 taskset  (0) 2018.08.27
스핀락  (0) 2018.07.23
eventfd  (0) 2018.07.18
workqueue 사용법  (0) 2018.07.16
tasklet 사용법  (0) 2018.06.17
tasklet과 workqueue의 차이점  (1) 2018.06.15

workqueue 사용법

기술/컴퓨터사이언스 2018. 7. 16. 20:30 Posted by 아는 개발자

workqueue를 사용하는 작업은 크게 queue와 work를 만들고 작업을 스케줄 하는 것으로 나뉜다.


1. Queue 생성


workqueue도 이름에서 짐작 할 수 있듯이 queue이기 때문에 자료구조시간에 지겨울 정도로 봤던 queue를 만들어야 한다. 하지만 push나 pop처럼 queue를 사용하는 함수 API까지 만들어줄 필요는 없고 내가 짓고 싶은 Queue의 이름만 하나만 생각하고 생성 매크로만 호출하면 된다

#define create_workqueue(name)                      \
    alloc_workqueue("%s", __WQ_LEGACY | WQ_MEM_RECLAIM, 1, (name))

리턴인자는 workqueue_struct * 다. 이 포인터를 이용해서 queue에 넣는 작업을 할 수 있다.


2. Work 생성


Work를 생성할 때는 queue처럼 이름까지는 필요 없고 스케줄 될 때 실행할 함수만 있으면 된다. queue처럼 work도 이미 구현된 매크로를 호출해서 간단히 초기화 할 수 있다.

#define INIT_WORK(_work, _func)                     \
    __INIT_WORK((_work), (_func), 0)

void acpi_scan_table_handler(u32 event, void *table, void *context) {
...
INIT_WORK(&tew->work, acpi_table_events_fn);
}

첫번째 인자는 struct work_struct 자료구조의 포인터이고 두번째 인자는 void로 선언된 함수의 포인터다. 일반 콜백 함수를 등록하는 것과 비슷하다. 그 밑의 코드는 실제로 사용하는 예시다. tew->work인 구조체에다가 acpi_table_events_fn 함수를 호출하도록 했다.


3. 스케줄링


delay해서 실행하려는 작업(work)를 만든 queue에다가 넣는 작업이다. 함수 이름만 봐도 대강 무슨 일을 하는지 짐작 할 수 있다.


static inline bool queue_work(struct workqueue_struct *wq,
                  struct work_struct *work)
extern bool queue_work_on(int cpu, struct workqueue_struct *wq,
            struct work_struct *work);


queue_work는 첫번째 인자로 workqueue_struct의 포인터를 받고 두번째 인자로는 실행할 작업인 work_struct를 받는다. queue에다가 작업을 넣는 함수다. 바로 밑의 함수인 queue_work_on은 cpu라는 인자가 있는데 이 인자를 이용해서 스케줄링할 cpu를 선택 할 수 있다.


queue를 선언하지 않고 work만 선언해서 바로 스케줄 할 수 있다. 이때는 queue를 사용하지 않는건 아니고 시스템 부팅때 초기화된 queue(system_wq로 선언돼있다)를 사용해서 스케줄링 한다. 

static inline bool schedule_work(struct work_struct *work)
static inline bool schedule_work_on(int cpu, struct work_struct *work)



728x90

'기술 > 컴퓨터사이언스' 카테고리의 다른 글

스핀락  (0) 2018.07.23
eventfd  (0) 2018.07.18
workqueue 사용법  (0) 2018.07.16
tasklet 사용법  (0) 2018.06.17
tasklet과 workqueue의 차이점  (1) 2018.06.15
ARM64 리눅스 부팅 초기 어셈블리 코드 분석(head.S) (2/2)  (0) 2018.01.27

vhost

기술/가상화 2018. 7. 8. 14:19 Posted by 아는 개발자

Vhost는 Virtio를 이용한 장치 가상화의 성능을 개선하는 모듈이다. 일반적으로 Virtio를 이용하는 장치들은 모두 virtqueue 기반의 킥 메커니즘으로 backend와 frontend가 통신한다. 그런데 이때 virtqueue를 처리하는 주체는 QEMU에서 만든 유저 프로세스이기 때문에 다른 우선순위가 높은 작업들이 처리 될 때 까지 연기되며 실제 Host의 장치 드라이버를 사용하기까지 오랜 시간이 걸린다. 이것 뿐만아니라 장치를 에뮬레이션 하는 작업 또한 유저 스페이스에서 이뤄지기 때문에 커널내의 작업보다 미뤄게되고 실제로 장치를 사용하는 Native 드라이브와 통신하기 위해선 여러번 context switch가 일어나게돼 오버헤드가 발생한다. 


그림 1. virtio 장치 구조


이런 오버헤드를 해결하기 위해 창안된 구조가 vhost다. vhost는 QEMU처럼 유저스페이스위에 돌아가는 virtqueue를 Host의 커널에서 직접 접근 할 수 있고 이를 바탕으로 backend 드라이버와 emulation 코드가 native driver가 있는 kernel 영역에서 동작하도록 한다. 이런 구조면 virtqueue로 주고 받는 통신을 qemu에서 처리할 필요가 없어 지연 될리가 없고 또한 이미 kernel내로 내려왔기 때문에 그림 1 처럼 장치 에뮬레이션을 하기 위해 번거롭게 여러번 context switch를 할 필요도 없다.


그림 2. vhost 구조


vhost를 사용한 대표적인 장치는 vhost-net, 네트워크다. 아무래도 네트워크를 사용하면 Host와 Guest가 패킷을 주고 받는 일이 많은데 이때 기존의 킥 매커니즘을 사용하면 오버헤드가 많아 이를 손보기 위해 가장 먼저 나온 것 같다. 


그런데 몇몇 드라이버의 경우에는 user space에서 존재하는 드라이버를 사용하는 경우가 있다. 이때는 backend를 커널 내에서 돌리는 것보다 유저단에서돌아가야 하며 user space에서 돌아가는 드라이버 프로세스와 통신해야 한다. vhost-user는 이런 경우를 대비해서 만들어졌다. vhost의 구조는 그대로 따라가는 대신 user space에서 돌아가는 에뮬레이션 구조로 보면 된다



아래 참고사이트를 이용하면 이해하는데 훨씬 도움이 될 것 같다.


1. VMSPLICE 블로그

2. Vhost-User Feature for QEMU

3. xiaorg




728x90

'기술 > 가상화' 카테고리의 다른 글

kvm ioeventfd  (0) 2018.08.11
kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08
virtio  (0) 2018.07.08
VFIO, Passthrough  (0) 2018.06.30
QEMU 성능 문제 - 개론  (0) 2018.05.30

virtio

기술/가상화 2018. 7. 8. 12:12 Posted by 아는 개발자

QEMU를 이용해서 Full virtualization으로 VM을 돌릴 경우 Guest OS에 별다른 수정 사용 할 수 있다는 장점이 있으나 매번 Guest의 명령어를 트랩해서 장치를 에뮬레이션 해줘야 하므로 느리다는 단점이 있다. 그래도 과거와 달리 요즘에는 하드웨어가 좋아져서 마우스나 키보드를 사용할 때 버벅거리지 않아 거의 성능에 문제가 없어 보이는 착각이 들지만 실제로 벤치마크 툴을 이용해 Guest 장치의 성능을 Host와 비교해보면 심각할 정도로 낮다.


이런 문제를 해결하고자 가상화 개발자는 virtio라는 구조를 창안해냈다. 이 구조의 주요 개념은 일부 장치에 대해서는 매번 트랩해서 emulation 하지 말고 Hypervisor와 Guest가 바로 통신 할 수 있는 채널을 만들어 불필요한 오버헤드를 줄이자는 것이다. 이를 위해 Guest에는 특정 장치가 Host와 통신하기 위한 frontend 드라이버가 있어야하고 마찬가지로 Hypervisor에도 Guest와 특정 장치가 통신 할 수 있는 backend 드라이버가 필요하다. 


그림 1. Virtio 구조.


그림 1의 왼쪽 구조는 기존에 Guest OS에 아무런 수정 없이 Full Virtualization을 사용 했을 때고 오른쪽 구조는 Guest OS와 Hypervisor에 front/backend 드라이버를 추가 했을 때다. 왼쪽 구조에서는 Guest가 장치를 사용하려는 경우 매번 트랩을 해야하는데 오른쪽 구조에서는 front/backend에서 직접 통신을 하기 때문에 별도의 트랩이 필요 없어 효율적이다. Virtio-Backend에서는 Frontend에서 받은 요청을 바로 Hypervisor에게 Emulation을 요청한다.


virtio는 Guest의 일부 수정이 필요하다는 단점이 있지만 채택시 얻게될 성능상 이득을 고려하면 이정도 불편함은 감수해야 할 것 같다. 다행히도 Linux인 경우에는 몇몇 장치들에 대해선 virtio frontend 드라이버 코드가 이미 커밋돼 있고 Windows도 주요 하이퍼바이저 커뮤니티에서는 드라이버 코드를 올려둔 상태니 적절히 찾아서 사용하면 될 것 같다.


참고문헌


1. Virtio: An I/O virtualization framework for Linux IBM Developer works

728x90

'기술 > 가상화' 카테고리의 다른 글

kvm irqfd  (0) 2018.08.11
vhost  (0) 2018.07.08
virtio  (0) 2018.07.08
VFIO, Passthrough  (0) 2018.06.30
QEMU 성능 문제 - 개론  (0) 2018.05.30
KVM - ARM  (0) 2018.01.01

URI (Uniform Resource Identifier)

기술 2018. 7. 2. 22:34 Posted by 아는 개발자

URI (Uniform Resource Identifier)


현재 URI을 간략하게 소개하는 글을 쓰기 위해 접속한 티스토리 에디터의 주소는 이렇다.


http://kwony91.tistory.com/admin/entry/post/?type=post&returnURL=/manage/posts/


위 주소를 가지고 URI을 이루는 요소에 대해서 간단히 알아보자


1. 스키마(Scheme)


주소 맨 앞에 있는 "http://" 은 많이들 봤을 것이다. 이것 말고도 https도 있고 개발하는 사람들은 ftp, ssh도 봤을 것이다. 일할 때는 특별한 명칭 없이 "http에요? https에요?"라고 말하곤 했는데 전문적인 용어로 스키라(Scheme)라고 부른다. 스키마의 역할은 웹브라우저나 스마트폰 같은 일종의 클라이언트가 서버에게 resource에 어떤 방식으로 접근 할 지를 결정하는 역할을 한다. 여기에서 이제껏 몰랐던 무수히 많은 스키마들을 확인 해볼 수 있다


2. 호스트(Host)


지금은 도메인 서버를 써서 selffish-developer.com을 사용하는데 티스토리 계정으로 로그인해서 글을 쓸 때는 나의 원래 주소(kwony91.tistory.com)을 이용해야지만 가능하다. 호스트는 리소스의 주소를 의미하며 클라이언트는 이 값을 이용해서 리소스를 찾을 수 있다. 내 블로그 진짜 주소 외에도 www.google.com, www.naver.com 이것들도 모두 호스트다. 식상한 얘기지만.


한가지 흥미로운건 mailto라는 스키마를 사용하면 개인 이메일 주소도 호스트로 본다.


3. 경로(Path)


호스트 뒤에 슬래쉬로 붇는 문장은 경로를 의미한다. 호스트 위치까지는 잘 찾아왔고 여기서 더 세분화된 리소스를 찾기 위한 세부 주소를 의미한다. /admin/entry/post 의 뜻은 호스트 주소에서 관리자(admin) 페이지의 내부(entry)에서 글을 작성(post)하는 리소스를 말한다. 현재 글을 쓰고 있는 에디터 페이지와 쿵짝이 맞는다. 쉽게 이해할 수 있을 것 같다.


물음표 뒷부분은 질의(query)다. 질의의 역할은 리소스를 찾은 서버에 전달할 정보를 의미한다. 질의는 '?'를 이용해서 시작하고 '&'를 이용해서 구분하며 각각의 값들은 key=value로 표현한다. 현재 접속한 페이지의 질의값은 총 두 개이며 type은 post이고 returnURL은 /manage/posts라고 전달했다. 이름만으로 역할을 유추해보면 현재 에디터의 타입은 post이고 마치고 돌아올 경로는 /manage/posts 라고 볼 수 있을 것 같다.

728x90

'기술' 카테고리의 다른 글

VNC와 RDP  (2) 2018.09.12
jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
libgdx - Renderer  (0) 2018.06.22
Libgdx - 소개 및 주요함수 정리  (0) 2018.06.20

VFIO, Passthrough

기술/가상화 2018. 6. 30. 13:40 Posted by 아는 개발자

VFIO (Virtual Function I/O)


일반적으로 유저 애플리케이션에서 특정 장치를 이용하기 위해선 먼저 Host OS에서 해당 장치를 전담하는 유저 서비스(안드로이드는 surface flinger 같은게 있다)에게 요청하고, 유저 서비스는 커널 단의 장치 드라이버에 작업을 전달하며 장치 드라이버는 전달 받은 요청에 따라 장치를 실제로 움직이게 된다. 이러한 형태는 유저 앱의 입장에서 꽤 단순한 작업을 해도 커널 단의 장치드라이버에 불필요한 작업이 많다면 이에 비례해서 처리하는 시간이 늘어나게돼 유저앱의 성능이 저하되는 일이 발생한다.


이를 해결하고자 리눅스 커널에서는 유저 영역에서 직접 장치에 접근 할 수 있는 플랫폼을 만들었다. 간단히 말해 장치 드라이버를 커널에 두지 않고 유저 영역에 드라이버를 둘 수 있는 형태다. 이런 구조는 불필요한 커널 스택을 거치지 않고 사용 목적에 따라 드라이버를 만들 수 있기 때문에 효율적이고 최적화된 코드를 짤 수 있으며 혹시나 만든 드라이버가 죽더라도 커널 영역이 아니기 때문에 시스템 전체가 크러쉬되는 부담은 줄어드는 장점이 있다. 그리고 무엇보다 회사 입장에서는 유저 영역에서 코드를 작성하기 때문에 GPL 라이센스를 따르지 않아 코드를 공개하지 않아도 되는 메리트가 있기도 하다. (물론 오픈소스 철학과는 상반되지만)


Passthrough


애초에 Type2 하이퍼바이저를 목표로두고 설계한 구조는 아니지만 구글에 VFIO를 치면 QEMU와 연관된 자료들이 무수히 많이 나오는데 아마 VFIO를 가장 유용하게 사용할 수 있는 대표적인 예가 가상화이기 때문에 그런것 같다. QEMU에서 만들어준 장치들은 이미 VM내의 커널 드라이버를 거치고 왔기 때문에 그 이후 이뤄지는 Host 커널 영역에서 이뤄지는 작업은 어찌보면 같은 일은 두번 반복하는 불필요한 작업이기도 하다. 이때 VFIO를 사용하면 불필요한 작업 없이 에뮬레이션 장치의 결과물을 그래도 실제 장치에 전달 할 수 없어 성능을 대폭 향상 시킬 수 있다. VM이 직접 장치에 접근 할 수 있는 구조가 되는 것이다.


이처럼 Guest가 Host의 중재 없이 직접 장치에 접근 할 수 있는 구조를 Passthrough라고 한다. 여기서 VFIO는 Type2 하이퍼바이저에서 Passthrough를 할 수 있는 일종의 플랫폼 역할을 하는 것이며 Xen과 같은 Type1 하이퍼바이저에서는 장치를 front/backend의 형태로 안쓰고 Guest가 native driver를 사용할 수 있도록 변형해서 Passthrough를 사용한다.



그림 1. VFIO 개념도

  • 간단하게 그래픽 장치에 바로 접근한다고 설명했지만 실제로 고려해야할 일들은 무수히 많다. 벤더에 따라서 해야하는 일이 천차만별.

  • 주로 그래픽카드와 네트워크 장치에 사용한다.

보안 위험성

대부분의 장치가 MMIO로 사용하기 때문에 VM이 직접 장치를 사용하기 위해선 장치의 물리 메모리 주소에 VM이 접근 할 수 있어야 한다.  그런데 VM이 실제 물리 주소를 알고 변환 과정 없이 물리주소로 사용하는 경우 보안상 위험이 존재한다. 실제 물리 주소를 알게 되면 물리 메모리 영역의 전반적인 주소 값을 추측 할 수 있게 되고 (물론 정확하진 않지만) 추측한 값을 통해 할당된 장치 뿐만 아니라 주변 장치 또는 메모리 영역까지 접근 할 수 있게 된다. 악의적인 목적을 가지면 Host나 다른 VM의 메모리 값을 오염시키는 것도 가능하다. 

위와 같은 보안상의 위험을 막기 위해선 VM이 자신에게 할당된 장치 영역 이외의 물리 주소는 접근 하지 못하도록 막아야 한다. iommu를 사용하면 page table을 사용할 때처럼 VM의 가상 주소를 실제 장치의 물리주소와 매핑하기 때문에 VM으로부터 장치의 주소의 정보를 숨기면서도 접근은 가능하게 되고 뿐만 아니라 할당 되지 않은 메모리에 접근하려고 하는 경우에는 fault를 발생시켜서 보안 위협을 막을 수 있다. 

참고자료


- Virtual Open System, vfio에 대해서 전반적인 소개를 하는 자료. 

- Platform Device Assignment to KVM-on-ARM Virtual Machines via VFIO 논문, 그림을 가져왔다. 자세하게 설명해주고 있어 많은 도움이 됐다.

728x90

'기술 > 가상화' 카테고리의 다른 글

vhost  (0) 2018.07.08
virtio  (0) 2018.07.08
VFIO, Passthrough  (0) 2018.06.30
QEMU 성능 문제 - 개론  (0) 2018.05.30
KVM - ARM  (0) 2018.01.01
QEMU와 KVM - 2  (0) 2017.11.11

Kubernetes 소개

기술/클라우드컴퓨팅 2018. 6. 23. 14:30 Posted by 아는 개발자


Kubernetes



공식 홈페이지에서는 Kubernetes를 이렇게 정의한다.


"Kubernetes is an open-source system for automating deployment, scaling, and management of containerized applications"


직역하면 컨테이너화된 앱들의 배치(deployment), 확장(scaling), 관리(management)를 하는 오픈소스 시스템이라는데 이것만 봐서는 왜 kubernetes가 무슨 역할을 하는지 감이 안온다. 어차피 컨테이너는 Docker에서 생성하고 관리 할 수 있는데 왜 kubernetes가 필요한 걸까?


설명에 앞서 docker만 사용할 때의 위험성을 생각해보자. docker는 특정 Host OS 위에서 사용자의 요청을 받아 container app을 만들어주는 소프트웨어다. 그런데 요청 받은 서비스를 운영하는 도중 host OS가 고장나는 경우가 생긴다면? docker가 만들어준 모든 container도 같이 죽는다. 


(사실 기본적인 내용이지만) 안정적으로 서비스를 돌리기 위해선 하나의 host에서만 돌리면 안되고 여러 컴퓨터에 서버를 동시에 돌려야 한다. kubernetes는 여러 대의 서버로 갖춰진 하드웨어 환경의 network, computing, storage 요소를 관리해 container 서비스가 안정적으로 돌아갈 수 있도록 관리하는 툴이다.



Openstack이랑 비슷한 역할을 하는데 open stack은 virtual machine 을 생성해 클라우드 서비스를 관리하는데 목적을 두고 kubernetes는 컨테이너 서비스에 강점을 둔다는 점에서 차이가 있는 것 같지만 공식 홈페이지 말고 다른 회사의 소개 자료를 보면 kubernetes를 이용해 container 말고도 다양한 서비스를 만들고 있으니 꼭 container를 초점으로 만든 기술이라고는 볼 수 없는것 같기도하다. 서비스 자체는 자유자재로 할 수 있으며 하드웨어 자원을 효율적으로 관리하는데 초점을 두는것 같다. 공식 홈페이지에서도 많은 기능을 제공 하고 있으며 이를 활용해 언제든 새로운 기술이 만들어 질 수 있다고 하고 있으니 얼마든지 변형해서 만들어 낼 수 있나보다.


공부하기에 앞서 활용 사례를 몇가지 더 찾아봐야겠다.


728x90

'기술 > 클라우드컴퓨팅' 카테고리의 다른 글

SDN과 NFV  (0) 2019.04.21
Kubernetes 소개  (0) 2018.06.23
오픈스택 구조 분석  (0) 2017.03.07
오픈스택이란?  (0) 2017.03.07
클라우드 가상화와 Docker  (2) 2017.03.05
클라우드 컴퓨팅(Cloud Computing)  (0) 2017.02.25

libgdx - Viewport

기술 2018. 6. 22. 21:49 Posted by 아는 개발자

Viewport 정의/종류


Renderer 클래스를 통해 화면에 그려진 결과물을 게임 스크린의 크기에 맞춰 비율을 조정할 수 있는 객체다. 여러 가지 종류가 있는데 이번 포스트에서는 대표적인 세개만 다루고자 한다.


1. StretchViewport


렌더링 결과물을 화면 크기에 맞춰 재조정 해주는 객체다. 주로 윈도우 그림 파일의 가로/세로 크기를 임의 조정할 때 볼 수 있는데 글로 설명하는 것보다는 그림을 보는게 훨씬 이해하기 쉬울 것 같다.



코드상에서는 화면 중앙에 동그란 원을 그렸는데 게임 스크린의 가로 길이가 세로보다 길어 화면 비율에 맞추다보니 원이 타원형으로 보여지고있다. 사각형도 정사각형을 그렸는데 실제로 보이는건 가로가 더 긴 직사각형이다. 


2. FitViewport


화면 비율에 따른 재조정 없이 렌더링한 코드 그대로를 화면에 보여주는 객체다. FitViewport를 사용하면 위의 그림이 아래처럼 바뀌게 된다.



가로 길이가 더 긴 게임 스크린이라도 원본 코드 그대로 렌더링을 유지하고 표시할 수 없는 부분은 검은색 배경으로 남겼다. 휴대폰을 돌리지 않고 사진을 촬영했을 때 양쪽 여백이 나오는 것과 같은 원리다.


3. ExtendViewport



Fitviewport와 달리 검은 배경은 한쪽으로 몰빵한다. 왜 이런 Viewport를 만들었는지 아직 이해가 되진 않지만... 그래도 이런 Viewport가 있다는 것도 기억은 해두자 언젠간 써먹을 일이 있겠지


Viewport 사용법


Viewport는 주로 create, resize, render 함수에서 사용된다.

@Override
    public void create() {
        /// ...
        viewport = new ExtendViewport(WORLD_WIDTH, WORLD_HEIGHT);
    }

    @Override
    public void resize(int width, int height) {
        /// ...
        viewport.update(width, height, true);
    }

    @Override
    public void render() {
        // ...
        viewport.apply();
        renderer.setProjectionMatrix(viewport.getCamera().combined);
    }

각 함수에서 viewport의 역할을 간단히 정리해보자.


create() : viewport 객체를 초기화하는 작업이다. 사용하려는 Viewport의 종류에 맞춰서 선언하고 두개의 인자로 렌더링하려는 화면 크기를 전달한다.


resize() : 변경된 게임 스크린의 크기에 맞춰서 viewport의 크기도 변경해야 한다. 이때 세번째 인자로 true 값을 전달하는데 이건 렌더링한 화면의 중심으로 보겠다는 의미다. true로 설정하지 않으면 코드상에서는 열심히 그렸는데 초점은 다른데를 보고 있어 스크린상에 안보이는 경우가 발생한다.


renderer() : apply 함수로 viewport를 render 상에서 적용하고 렌더링 객체와 연결한다. 항상 잊지말고 선언하도록 하자.

728x90

'기술' 카테고리의 다른 글

jupyter notebook 소개  (0) 2018.08.04
URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
libgdx - Renderer  (0) 2018.06.22
Libgdx - 소개 및 주요함수 정리  (0) 2018.06.20
objdump 를 이용한 바이너리 깨보기  (0) 2018.05.29

libgdx - Renderer

기술 2018. 6. 22. 21:12 Posted by 아는 개발자

ShapeRenderer


컴퓨터 그래픽 소프트웨어에서 '렌더링'은 이진화된 데이터를 컴퓨터 내에 영상을 만들어내는 과정을 뜻한다. 게임 엔진인 libgdx에서도 렌더링 개념을 사용하며 객체 지향적인 관점으로 활용하기 위해 ShapeRenderer라는 클래스를 만들었다. ShapeRenderer 는 화면을 그리기 위한 객체로 개발자가 만들어둔 데이터를 다양한 API를 이용해화면에 출력해주는데 사용된다.

    ShapeRenderer renderer;
    @Override
    public void create() {
        renderer = new ShapeRenderer();


실제 클래스의 내부를 보면 public으로 선언된 함수가 많은데 이중 대부분이 화면에 그리기 위해 사용 할 수 있는 API 들이다. 목적에 따라서 함수를 세가지 종류로 나눌 수 있을 것 같다.


1. 도형함수

public void point (float x, float y, float z) {}
public final void line (Vector3 v0, Vector3 v1) {}
public void rectLine (Vector2 p1, Vector2 p2, float width) {}
public void arc (float x, float y, float radius, float start, float degrees, int segments) {}
public void circle (float x, float y, float radius, int segments) {}

말 그래도 도형을 그리는 함수다. 상위 렌더링 클래스(ImmediateModeRenderer)로부터 받은 기본적인 렌더링 함수를 확장해(점하나 찍는 기능밖에 없지만) 기본적인 도형을 그릴 수 있는 함수로 만들었다. 함수명을 통해서 대강 무슨 기능을 하는지 짐작 할 수 있을 것이다.


강의 들을 때는 이것 만으로도 과제를 다 할 수 있었는데 실제 개발 할 때는 턱없이 부족할 것 같고 새로운 클래스를 만들어 위 도형 함수를 추상화해야 캐릭터도 만들고 배경도 만들 수 있을 것 같다. 정말 순수한 코딩이 많이 필요한 게임 엔진 인 것 같다.


2. 세팅함수

public void setColor (Color color) { };
public void setProjectionMatrix (Matrix4 matrix) { }
public void setTransformMatrix (Matrix4 matrix) { }
public void set (ShapeType type) { }

세팅함수에서는 도형을 그리기 전에 어떤 색으로 그릴 것인지(setColor), 도형 내부 공간은 채울 것인지 말것인지(set) 그리고 만든 완성물을 무엇으로 볼 것인지(setProjectionMatrix, setTransformMatrix)를 결정한다. setProjectionMatrix, setTransformMatrix 함수 모두 인자로 Matrix인데 이 변수는 유저가 게임을 바라보는 시선 결정한다고 보면 될 것 같다. 


3. 시작/종료 함수

    public void render(float delta) {
        shapeRenderer.begin();
        // ...
        shapeRenderer.end();

렌더링의 시작과 끝을 알리는 함수다. begin 함수는 지금부터 이 객체를 이용해서 렌더링을 하겠다는 의미고, end 함수는 이 객체의 렌더링을 끝낸다는 의미다. begin 함수 전이나 end 이후에는 renderer 함수를 호출해도 그림이 그려지지 않으니 주의하도록 하자. 


728x90

'기술' 카테고리의 다른 글

URI (Uniform Resource Identifier)  (0) 2018.07.02
libgdx - Viewport  (0) 2018.06.22
libgdx - Renderer  (0) 2018.06.22
Libgdx - 소개 및 주요함수 정리  (0) 2018.06.20
objdump 를 이용한 바이너리 깨보기  (0) 2018.05.29
그래픽 소프트웨어, 라이브러리 정리  (0) 2018.05.06

Libgdx - 소개 및 주요함수 정리

기술 2018. 6. 20. 20:52 Posted by 아는 개발자

Libgdx는 오픈소스로 운영되는 게임엔진이다. 주로 2D용 게임을 개발하는데 사용하며 Mac과 Windows에서만 사용할 수 있는 Unity와는 다르게 Ubuntu 환경에서도 사용할 수 있어 개발하기 편리하고 개발한 코드가 Android, IOS, Desktop 환경에서도 동시에 사용 할 수 있는 cross platform 툴이다. 다만 Unity처럼 강력한 GUI 기능을 제공하지는 않고 직접 코드를 이용해서 일일이 그리는 원시적인 방법을 채택해 초심자들에겐 어려울 수 있으나 반대로 생각하면 무거운 기능을 줄이고 가능한 가볍게 만들었기 때문에 성능이 좋고 새로운 기능을 자신의 입맛에 맞게 추가 할 수 있어 능숙한 개발자들에겐 높은 자유도를 주기도 한다.


공식 홈페이지에 올라온 libgdx 게임은 아래와 같은 것들이 있다. 아마 우리나라 사람들은 대부분 모르는 게임들이다


링크를 클릭하면 더 많은 게임을 볼 수 있다. 


처음 게임을 만들려고 할때는 간단히 Unity를 이용해서 만들려고 했으나 대부분의 개발을 Ubuntu로 하고 있는 현재 컴퓨터에 Unity를 설치할 수가 없고 하루 내일 하고 있는 윈도우 10 노트북에다가 설치할 수 없는 관계로 반강제적으로 libgdx를 채택했는데 생각보다 공부하는 과정은 재밌었다. 오랜만에 수학 공간 도형과 물리 공식을 써보니까 고등학교 추억도 새록새록 나는 것 같기도 하고 또 이론으로만 배웠던 것들을 실제로 시뮬레이션 해보니 신기하기도 했다. 역시 수학/물리 이론은 현실 세계를 반영한 것이었어!


운좋게도 udacity에서 libgdx 강의가 있어 인터넷 블로그 돌아다니며 삽질하는 시간을 줄이고 체계적으로 배울 수 있었다. 강의는 무료인데 듣다보면 왠만한 유료 강의보다 훨씬 좋아서 돈내고 들어야할 것 같은 죄책감이 든다. 혹시나 libgdx에 대해서 공부하고 싶은 분은 꼭 이 강의를 통해 시작하길 바란다. libgdx를 이용해 점, 선을 그리는 것부터 똥피하기 게임을 만드는 것 까지 배울 수 있다. 강의 주소 링크 


사진 속 아저씨 두분이 강사인데 오른쪽 아저씨는 매 class 마지막에 등장해 경험담만 풀어주고 수업은 왼쪽 아저씨가 다한다.


공부한 걸 복습을 위해 Libgdx의 주요 함수들을 정리해보자. libgdx도 안드로이드의 Activity 처럼 기본적으로 채워줘야 하는 오버라이드 함수가 있다. 기본적으로 사용되는 ApplicationListener 인터페이스의 오버라이드 함수를 살펴보자.

    @Override
    public void create() {    }
    @Override
    public void resize(int width, int height) {    }
    @Override
    public void dispose() {    }
    @Override
    public void render() {    }
    @Override
    public void pause() {    }
    @Override
    public void resume() {   }

각 함수의 성질은 안드로이드의 오버라이드 함수랑 비슷하다. 간단히 정리해보자.

  • create() : 이름에서도 짐작 할 수 있듯이 게임이 만들어 질 때 기본적으로 불리는 함수다. 안드로이드의 onCreate()와 같다. 클래스의 변수를 초기화하는데 사용된다.
  • resize(int width, int height) : 게임의 크기가 변할 때 실행되는 함수며 이때 전달 인자인 width와 height는 변경되는 게임 스크린의 크기다. 게임 개발을 안해본 사람들은 크기가 변하면 무슨일을 해야할 지 감이 안올 것이다. Libgdx에서는 주로 이 함수에 viewpoint라는 변수를 재정의 해주는데 이건 다음 포스트에서 정리해야겠다.
  • dispose(): 메모리가 부족할 때 불리는 함수다. 게임 실행중에 특정 변수가 불필요하게 메모리를 많이 잡고 있다면 dispose 함수 내에서 처리해주도록 하자.
  • render(): Libgdx에서 가장 중요한 함수다. 이 함수는 1초에 60번 불리며 실제로 화면을 그려주는 일을 담당한다. 60fps를 적어도 보장하려는 모양이다. 개발자는 이 함수에 자신의 그림을 그린다.
  • pause(): 안드로이드의 onPause()처럼 게임이 중간에 멈추거나 종료될 때 불리는 함수다.
  • resume(): 이것 또한 안드로이드의 onResume()처럼 Pause 상태에서 다시 시작 될 때 불리는 함수다.

이 외에 인터페이스에서도 별도의 오버라이드 함수가 있으나 성격은 위의 함수를 크게 벗어나지 않는다. 이걸 보니까 플랫폼이 애초 객체 지향적 관점으로 설계됐다면 안드로이드든 libgdx든 어디든 통용되는 지식이 있는 것 같다. 공통된 감각을 가지고 있으면 어디든 적용하는건 어렵지 않을 것 같다.

728x90

'기술' 카테고리의 다른 글

libgdx - Viewport  (0) 2018.06.22
libgdx - Renderer  (0) 2018.06.22
Libgdx - 소개 및 주요함수 정리  (0) 2018.06.20
objdump 를 이용한 바이너리 깨보기  (0) 2018.05.29
그래픽 소프트웨어, 라이브러리 정리  (0) 2018.05.06
Yocto 내부 파일 분석  (1) 2016.10.02

tasklet 사용법

기술/컴퓨터사이언스 2018. 6. 17. 09:48 Posted by 아는 개발자

tasklet을 사용하는 과정은 tasklet을 생성하는 작업과 스케줄하는 작업으로 구분된다.


1. tasklet 생성


include/linux/interrupt.h 라는 파일을 보면 tasklet_struct 라는 구조체가 존재한다. 구조체 내의 속성 값을 입력해서 tasklet이 수행할 작업을 설정 할 수 있는데 일일이 속성 값을 호출할 필요는 없이 tasklet_init 이라는 함수를 이용해서 간단히 선언 할 수 있다.

// include/linux/interrupt.h
struct tasklet_struct
{
    struct tasklet_struct *next;
    unsigned long state;
    atomic_t count;
    void (*func)(unsigned long);
    unsigned long data;
};
// kernel/softirq.c
void tasklet_init(struct tasklet_struct *t, 
          void (*func)(unsigned long), unsigned long data)
{
    t->next = NULL;
    t->state = 0;
    atomic_set(&t->count, 0); 
    t->func = func;
    t->data = data;
}

tasklet_init 함수에서 짐작할 수 있듯이 속성은 다섯개지만 이중에서 사용자가 입력해야 할 값은 *func과 data다. *func는 tasklet이 실행해야하는 함수고 data는 function 실행시 이용 할 수 있는 argument다. data를 일반 상수 값을 전달하기도 하지만 주소 값을 전달해서 포인터로 받아 낼 수도 있다.


아래는 리눅스 드라이버에서 사용하는 예다. *func 함수에서 data 값을 주소로 보고 포인터를 이용해 구조체 값들을 읽는다. 여러 개의 데이터를 전달해야 하는 경우에 사용하면 유용하다.

// linux/driver/firewire/ohci.h
static int ar_context_init(struct ar_context *ctx, struct fw_ohci *ohci,
               unsigned int descriptors_offset, u32 regs)
{
// ...
    ctx->regs        = regs;
    ctx->ohci        = ohci;
    tasklet_init(&ctx->tasklet, ar_context_tasklet, (unsigned long)ctx);
// ...
}
static void ar_context_tasklet(unsigned long data)
{
    struct ar_context *ctx = (struct ar_context *)data;
//...
}

2. tasklet 스케줄


tasklet_schedule, tasklet_hi_schedule이란 함수를 이용해서 앞서 선언한 tasklet 구조체를 실제 CPU에 스케줄링 할 수 있다. 두 함수는 매우 비슷하나 softirq number가 다르다. irq number가 낮을수록 더 우선적으로 처리되니 더 높은 우선적으로 처리하고 싶은 작업이라면 tasklet_hi_schedule을 사용하자

// kernel/softirq.c
void __tasklet_schedule(struct tasklet_struct *t)
{
    // ...
   raise_softirq_irqoff(TASKLET_SOFTIRQ);
}
EXPORT_SYMBOL(__tasklet_schedule);

void __tasklet_hi_schedule(struct tasklet_struct *t)
{
    // ...
   raise_softirq_irqoff(HI_SOFTIRQ);
}
EXPORT_SYMBOL(__tasklet_hi_schedule);

3. kill 또는 disable


tasklet을 이미 스케줄 했는데 사용해선 안되거나, 스케줄링 queue에서 빼야할 때가 있다. queue에는 남겨두나 사용하고 싶지 않은 경우에는 tasklet_disable 함수를 사용하면 되고 반대로 다시 살리고 싶으면 tasklet_enable을 쓰면 된다. 아예 queue에서 빼버리고 싶으면 tasklet_kill 함수를 사용한다.

// include/linux/interrupt.h
static inline void tasklet_disable(struct tasklet_struct *t)
static inline void tasklet_enable(struct tasklet_struct *t)
extern void tasklet_kill(struct tasklet_struct *t);

4. 추가 


- 설명한것 외에도 다른 함수가 있지만 이정도만 알아도 사용하는데는 지장이 없을 것 같다. 이외의 함수들은 드라이버 단에서는 거의 사용하지 않는다.


- 분석하면서 토발즈가 남긴 주석을 읽어봤는데 되도록이면 softirq를 새로 짜지 말고 가능한 tasklet을 사용하라고 한다. 기존 동작 루틴을 망칠까봐 그런건가. 하긴 모든 드라이버들이 너도나도 softirq 새로 추가하겠다고 하면 슬롯이 부족해서 안될 것 같기도 하다.


- 스케줄링 후 콜백 함수가 실행되기 까지 세부 동작 과정이 궁금하신 분은 아래의 링크를 참조하면 좋을 것 같다.


Schicao's NotesIBM Developer 사이트 두 포스트의 내용이 비슷한데.. 누가 누구껄 보고 쓴건지 궁금하다.

728x90

tasklet과 workqueue의 차이점

기술/컴퓨터사이언스 2018. 6. 15. 20:04 Posted by 아는 개발자


 커널 내의 코드를 짜다보면은 특정 작업을 다른 CPU에서 처리해야 하기도하고 어떤 작업은 몇미리 후에 처리 할 필요가 있는데 이런 경우 리눅스 커널에서는 tasklet과 workqueue라는 API를 사용해서 간단히 해결 할 수 있다. booklet, piglet 단어처럼 기존보다 작은 단위를 표현 할 때 let을 쓴다는 점으로 추측해볼 때 tasklet은 작은 일을 처리할 때 사용하는것 같고 같은 논리로 workqueue는 접미사로 queue가 있다는 점에서 미루어보아 작업(work)을 queue에 넣어서 처리하는 API인 것 같다. 


tasklet과 workqueue 모두 특정 작업을 미룰 수 있다는 점에선 동일하나 동작하는 매커니즘은 다르다. 먼저 tasklet은 softirq를 이용해서 동작한다. 세부 과정은 이렇다. tasklet이 스케줄 되면 스케줄러는 이 tasklet을 처리할 수 있는 CPU에게 softirq를 날리고 softirq를 받은 CPU는 하던 일을 멈추고 tasklet에서 설정된 작업을 수행한다. interrupt를 받은 상태로 동작하기 때문에 더 우선순위가 높은 interrupt가(ex. HW interrupt) 오지 않는 이상 tasklet의 작업을 모두 수행하기 전까지 다른 프로세스가 끼어들 수 없다. 만약 tasklet에서 처리하는 작업이 길고 남발하면 다른 작업들은 그만큼 뒤로 밀리게 돼 시스템 전반의 성능 저하가 올 수도 있다. 따라서 tasklet을 사용할 때는 가능한 짧게 수행 할 수 있는 작업을 써야한다. 이름을 참 잘지은 것 같다.


workqueue는 일반 프로세스가 스케줄링 되는 것과 비슷한 원리로 동작한다. workqueue를 관리하는 handler는 일반 프로세스처럼 CPU의 스케줄링을 받기 때문에 workqueue는 tasklet과 달리 작업이 끝나지 않았더라도 sleep에 들기도 한다. 작업을 모두 끝내기 전까지 CPU를 독점하는 tasklet에 비해서 처리하는데 시간이 오래 걸리지만(higher latency) 시스템에 무리를 줄 수 있는 요소가 없어 시간을 두고 처리해야하는 작업을 사용할 때 유용하다. 자유도가 더 높은 덕분인지 API의 기능도 tasklet에 비해서 많다.


 

tasklet 

workqueue 

 동작방식

 Interrupt

 Schedule 

 장점

 처리가 빠르다 

 시스템에 무리가 없다 

 단점

 시스템에 무리를 줄 수 있다 

 처리가 느리다 



보다시피 tasklet과 workqueue는 수행하는 일 자체는 동일할 지 모르나 사용하는 상황은 다르다. 짧고 빠르게 수행해야 하는 작업(low latency)일 경우에는 tasklet을 사용해야 하는 반면 긴 시간을 두고 처리해야하는(high latency) 작업인 경우에는 workqueue를 사용하는 것이 좋다. 본인 작업의 성격에 따라서 tasklet과 workqueue를 잘 구분해서 사용하도록 하자.


다음 포스트에서는 tasklet과 workqueue의 사용법에 대해서 다루도록 하겠다.


* tasklet은 버전 2.3에, workqueue는 버전 2.5에 mainline에 포함됐다. 둘다 역사가 오래된 API이다.


* 참고사이트


https://www.ibm.com/developerworks/library/l-tasklets/index.html IBM 블로그에서 정리를 아주 잘해뒀다. 돈내고 읽어야 할 것 같은 글인데.

https://stackoverflow.com/questions/18321858/what-is-the-difference-between-tasklet-and-workqueue

728x90
  1. 1234 2020.04.15 21:55  댓글주소  수정/삭제  댓글쓰기

    안녕하세요 커널을 이제막 공부하고 있는 학생입니다. 블로그에서 게시한글들이 정말 많은 도움이됩니다. 감사합니다. 혹시 근데, 이번 글보면서 궁금한저미 tasklet의 동작방식이 인터럽트라고 하셨는데, 워크큐랑 비슷하게 스케쥴도 사용하지 않나요?? 그 처음엔 인터럽트처럼 tasklet이 수행되다가, 근데 거기서 수행소요시간이 오래걸리면 포기하고, ksoftirqd라는 커널태스크 (우선순위-나이스 밸류19)가 스케쥴링돼서 나중에 tasklet 을 다시 실행하는걸로 알고있는데.. 혹시 그럼 tasklet은 반반이라고보면될까요? 인터럽트 반 스케쥴반..? 워크큐는 스케쥴

QEMU 성능 문제 - 개론

기술/가상화 2018. 5. 30. 23:54 Posted by 아는 개발자

Host OS의 유저 앱으로 구동되는 Guest OS는 Hypervisor가 만들어 준 가상 장치로 동작하고 있기 때문에 실제 물리 장치를 이용하는 Host OS에 비해서 성능이 확연히 낮다. Hypervisor가 만들어준 가상장치도 결국에는 실제 하드웨어에서 동작하게 되는 것이니 이론상으론 시스템 하드웨어 성능이 높아지면 가상 장치로 동작하는 Guest OS도 좋은 성능을 가질 수는 있다. 그러나 아무리 좋은 하드웨어를 사용해도 Host OS처럼 직접 접근해서 사용하는 것과는 확연하게 차이가 난다. 배틀그라운드의 권장사양을 훨씬 뛰어넘는 그래픽카드와 CPU를 장착해도 Guest OS로 실행되는 윈도우에서는 배틀그라운드는 커녕 스타크래프트도 온전하게 플레이하기 어렵다.


qemu를 이용해 스타크래프트를 하는 유튜브. 사진을 클릭하면 실제 영상을 볼 수 있다


성능 문제를 해결하기 위해선 가능한 Guest OS에서 장치를 실행하는 명령과 실제 하드웨어의 동작 사이의 오버헤드를 줄이는 것이 관건이다. 앞선 포스트에서도 이미 설명 했듯 인텔과 ARM과 같은 하드웨어 제조사들은 이점을 고려해 Guest OS가 일반 연산에 사용하는 장치(CPU, Memory, Interrupt)를 직접 또는 좀더 빨리 접근 할 수 있는 구조를 만들었고 KVM처럼 위 구조를 적절하게 활용한 소프트웨어를 사용한 Guest OS의 CPU와 메모리 성능이 Host OS에 못지 않다.


CPU와 메모리의 성능이 Host와 비슷하니 시스템 전반의 성능도 비슷하지 않을까 싶지만 실제로는 그렇지 않다. 일반 연산 장치를 제외한 장치는 여전히 가상 장치로 사용하기 때문에 여러 개의 장치를 동시에 사용하는 소프트웨어는 Host OS처럼 부드럽게 사용하기가 어렵다. 쉬운 예로 Guest OS에서 곰플레이어를 이용해 영화를 본다고 해보자. 영상 파일을 모니터에 출력하고 소리를 내기 위해선 CPU와 메모리 뿐만 아니라 GPU와 사운드카드도 필요하다. 그런데 GPU와 사운드카드는 여전히 가상 장치다. Guest OS의 CPU 속도가 높아도 정작 화면 출력을 담당하는 가상 GPU 장치를 사용하면 화면 출력이 느려지고 Guest OS의 메모리 처리 속도가 높아도 가상의 오디오를 사용하면 음악이 끊길 수 밖에 없다. 이런 상황에서 키보드, 마우스같은 인풋 장치를 빈번하게 사용하는 FPS 게임을 실행한다면 말할 것도 없다. 총알 한방 쏘는것도 쉽지 않을 것이다.


Passthrough, VFIO 는 위와 같은 문제를 다룬 대표적인 솔루션이다. 꽤 오랜 기간 연구했으며 논문도 풍성하다. 안타깝게도 새로 나오는 것은 없지만. 앞으로 포스트에서는 이것들이 무엇이며 어떻게 동작하는지 그리고 이것들을 사용할 경우 어떤 문제가 발생하는지에 대해서 차근차근 공부해보려고 한다. 



728x90

'기술 > 가상화' 카테고리의 다른 글

virtio  (0) 2018.07.08
VFIO, Passthrough  (0) 2018.06.30
QEMU 성능 문제 - 개론  (0) 2018.05.30
KVM - ARM  (0) 2018.01.01
QEMU와 KVM - 2  (0) 2017.11.11
QEMU와 KVM - 1  (0) 2017.11.01

objdump 를 이용한 바이너리 깨보기

기술 2018. 5. 29. 23:02 Posted by 아는 개발자

개발하다보면 보안이나 라이센스의 이유로 코드는 없고 빌드된 바이너리만 가지고 있는 경우가 간혹 있다. 그런데 이 바이너리의 의사코드도(pseudo code)나 사용한 API 문서도 없고 바이너리를 만든 사람으로부터 어떠한 지원도 받을 수 없는 극한의 상황에서 반드시 바이너리에 포함된 API를 이용해서 무언가를 만들어야 한다면 개발자로선 참 난감한 상황일 것이다.


이런 개발자를 위해서(?) 컴파일러에서는 objdump라는 옵션이 있다. 바이너리에서 원래 코드를 볼 수 있는 환상적인 옵션은 아니고, 바이너리에 있는 기계어를 어셈블리 코드로 변환해주는 옵션이다. 어셈블리 코드는 컴퓨터를 처음 배웠을 때 다들 경험 했을 것이다. 프로그래밍 언어와 기계어 사이의 중간 언어이며, 바이너리를 수행하려는 아키텍처(x86, arm)에 따라서 다르고 직관적이지 못해 사용하기 매우 껄끄러워 배우면 금방 머릿속에서 잊혀지는 언어. 컴퓨터 시스템 수업시간에만 잠깐 배울 것 같았던 이 언어를 가지고 바이너리가 어떤 동작을 하는지 대강 짐작 해볼수 있다. 


간단한 예제를 보자. 아래의 코드는 두 인자의 값을 출력해주는 add 함수를 구현하고 이를 main 함수에서 호출한 C 파일이다.

// aarch64-linux-gnu-objdump -o add.out add.c
int add(int a, int b) {
    return a + b;
}

int main() {
    int val1 = 1;
    int val2 = 2;

    int sum = add(val1, val2);

    return 0;
}

주석 처리한 명령어를 사용하니 add.out이라는 바이너리가 나왔다. 그러면 이제 objdump를 이용해서 어셈블리 값들을 출력해보자. 그러면 아래와 같은 출력물이 나올 것이다.

// aarch64-linux-gnu-objdump -D test.out
hello_world_arm.out:     file format elf64-littleaarch64
....
00000000004005c0 :
  4005c0:	d10043ff 	sub	sp, sp, #0x10
  4005c4:	b9000fe0 	str	w0, [sp,#12]
  4005c8:	b9000be1 	str	w1, [sp,#8]
  4005cc:	b9400fe1 	ldr	w1, [sp,#12]
  4005d0:	b9400be0 	ldr	w0, [sp,#8]
  4005d4:	0b000020 	add	w0, w1, w0
  4005d8:	910043ff 	add	sp, sp, #0x10
  4005dc:	d65f03c0 	ret

00000000004005e0 
: 4005e0: a9be7bfd stp x29, x30, [sp,#-32]! 4005e4: 910003fd mov x29, sp 4005e8: 52800020 mov w0, #0x1 // #1 4005ec: b90017a0 str w0, [x29,#20] 4005f0: 52800040 mov w0, #0x2 // #2 4005f4: b9001ba0 str w0, [x29,#24] 4005f8: b9401ba1 ldr w1, [x29,#24] 4005fc: b94017a0 ldr w0, [x29,#20] 400600: 97fffff0 bl 4005c0 400604: b9001fa0 str w0, [x29,#28] 400608: 90000000 adrp x0, 400000 <_init-0x3f0> 40060c: 911b0000 add x0, x0, #0x6c0 400610: b9401fa1 ldr w1, [x29,#28] 400614: 97ffff93 bl 400460 400618: 52800000 mov w0, #0x0 // #0 40061c: a8c27bfd ldp x29, x30, [sp],#32 400620: d65f03c0 ret 400624: 00000000 .inst 0x00000000 ; undefined ...

특별한 옵션을 주지 않고 gcc로 바로 빌드하면 바이너리에 각종 라이브러리가 자동으로 들어가기 때문에 이것들이 변환된 어셈블리 코드도 무수히 출력된다. 예상보다 너무 많아 당혹스럽지만 자세히 보면 어셈블리 코드들이 <함수명 처럼 생긴 문자열>의 그룹으로 이뤄져 있는 것을 볼 수 있다. 이것은 컴파일러가 함수별로 나눠서 변환했기 때문이다. 우리가 실제로 작성한 add와 main 함수만 어셈블리를 추려봤다. 이제 작성한 코드가 어떻게 어셈블리 언어로 변환되는지 살펴보자.


add함수에선 stack에 메모리 공간을 할당하고, 스택에 w0, w1 값을 넣었다가 빼는 무의미한 작업을 한 뒤, w0에 w0, w1의 두 값을 더하는 작업을 하고 스택을 복원하고 리턴한다. 좀 불필요한 작업이 섞여 있지만 의미상으로 보면 인자 둘(w0, w1)을 더한 값을 리턴 값으로 저장한 후(ARM은 리턴값을 X0레지스터에 저장한다) ret 명령어를 실행해 함수를 종료한다. add 함수라고 볼 수 있다.


메인 함수는 add 함수보다 앞의 전처리 부분이 복잡하니 앞부분은 무시하고 중간에 bl 4005c0 명령어부터 보자. bl 뒤의 값은 점프할 주소를 의미하고 여기서 0x 4005c0 값은 add 함수의 위치니 결과적으로 add 함수를 호출하는 작업이라고 볼 수 있다. 그리고 호출하기 직전에 w0, w1 값을 x29주소 부근에서 읽어온다. add 함수를 호출하기 전에 인자의 값들을 세팅하는 작업이다. 이정도만 봐도 main 함수의 어셈블리 코드로 봐도 무관 할 것 같다.


실제 세계에서는 구현한 함수의 라인이 많고 한 함수 내에서 호출하는 함수가 무수히 많아 분석하는데 꽤 오랜 시간이 소요된다. 이럴 때는 가능한 objdump 옵션에서 제공해주는 여러가지 기능을 활용하고 함수의 이름을 통해 기능을 유추해가면서 분석하는 것이 가장 효율적이다. -D 옵션을 사용하면 바이너리에서 볼 수 있는 모든 정보를 모두 출력하니 가장 먼저 훑어보고 objdump로 나온 함수 이름은 최초 코드를 작성 할 때 그대로이니 '개발자가 함수명을 막 짓지 않았을 것'이라는 가정 하에 함수명으로 의미를 유추해서 어셈블리 코드를 분석하자. C파일을 분석할 때보다 시간은 더디지만 그래도 어느정도 분석은 된다.


그런데 몇몇 회사에선 이런 꼼수로부터 바이너리를 보호하기 위해 심볼테이블을 없애버려 함수의 이름조차 안나오게 하는 경우도 있다. 이러면 모든 어셈블리 코드가 함수별로 나눠져 있지 않고 다닥 다닥 붙어있게 되는 그야말로 어셈블리 코드만 있는 출력물이 나온다. 이런 극악의 상황에서는 ret 명령어가 주로 함수의 끝 부분에 나온다는 점을 활용해 직접 함수의 시작 지점이 될 것 같은 곳을 찾고 함수 별로 나눠가며 파악하기도 하는데... 가능하면 포기하는 것을 추천한다. 개인적으로 개발자가 할 짓이 못된다고 생각한다.


* 이 작업을 역공학(Reverse Engineering)이라고 부르기도 한다. 



728x90

'기술' 카테고리의 다른 글

libgdx - Renderer  (0) 2018.06.22
Libgdx - 소개 및 주요함수 정리  (0) 2018.06.20
objdump 를 이용한 바이너리 깨보기  (0) 2018.05.29
그래픽 소프트웨어, 라이브러리 정리  (0) 2018.05.06
Yocto 내부 파일 분석  (1) 2016.10.02
Yocto 작동방식  (0) 2016.10.01

리눅스 디스크, 폴더 용량 확인하기

개발/삽질 기록 2018. 5. 26. 14:29 Posted by 아는 개발자

매번 구글창에 디스크/파일 용량을 확인하는 단축키를 찾기 번거로워서 이번 포스트로 정리하고 머릿속에 기억해두려고 한다.


1. 디스크 용량 확인, df


시스템상에서 FILE이 있는 곳의 파일시스템에 대한 정보를 보여준다. 즉 시스템에 마운트되어 있는 모든 것들에 대한 사용 정보를 출력한다는 뜻.

그냥 df를 검색하면 아래처럼 나온다. 여기서 1K-blocks는 파일시스템의 전체 용량, Used는 사용중인 크기, Available은 사용가능한 메모리의 크기를 의미한다.



그런데 위 그림은 바이트 단위라서 보기가 어렵다. -h 옵션을 넣어서 human readable하게 변환하자. 훨씬 직관적으로 보인다.



df --help 로 검색하면 다양한 옵션을 발견할 수 있으니 이것도 적극적으로 활용하자. 디스크 용량 검색하는 것은 이걸로 끝. 다른 것들도 있는데 하나만 정확하게 기억하는게 중요할 것 같다.


2. 파일, 폴더내 총용량 확인, du


파일 크기같은 경우 ls를 통해서 용량 정보도 나오고 여기에 -h 옵션만 넣으면 사람이 읽을 수 있는 용량 크기로 나온다. 그런데 폴더내 총용량을 확인할 때는 폴더 디렉토리 자체의 크기만 볼 수 있어서 사용할 수가 없다. 이럴때는 du라는 명령어를 사용하면 편하다. 


du 명령어를 이용해 Pictures 폴더를 검색해봤다.



앞의 416 은 Pictures 폴더내에 있는 파일들의 총 용량 크기다. 단위가 없어서 읽기 불편하니 -h 옵션을 붙여 human readable하게 변형하자. 

이것도 df처럼 용량이 커지면 M, G 단위로 변환해준다.



폴더 내의 파일들이 궁금하면 -a 옵션을 붙이면 된다. 반대로 출력하고 싶지 않다면 -s 옵션을 붙여서 제거한다.


    


728x90

그래픽 소프트웨어, 라이브러리 정리

기술 2018. 5. 6. 10:27 Posted by 아는 개발자

내가 일하는 곳 옆 소파트가 그래픽 관련된 일을 하는 곳이라서 같이 회의할 때마다 wayland, opengl, surface flinger과 같은 그래픽 라이브러리 용어를 자연스럽게 흘려 듣게 된다. 처음에는 생소했지만 어느정도 시간이 지나면 이들의 역할과 각각의 상관관계를 파악하게 될 줄 알았는데 만 2년이 지나도 용어 자체만 익숙해질뿐 이해 수준은 그대로인걸 보니 의도적인 노력 없이는 안되는 것 같다. 그래서 이번 포스트를 통해 이제껏 귀동냥으로 들은 용어와 그래픽의 기초적인 동작을 정리해보려고한다.


그래픽을 공부하기전에 잠깐 화가가돼서 그림을 그린다고 생각해보자. 지금처럼 태블릿을 이용해서 웹툰을 그리는 화가가 아니라 10년전 팔레트와 붓을 이용해서 그리는 화가 말이다. 그림을 그리기 위해선 어떤 소도구가 필요할까?


그림 1. 화가 '김원'의 캐리커처? 라고 한다.


먼저 자신의 작품을 담을 도화지가 필요하다. 그리려는 그림의 사이즈에 맞는 적당한 크기를 골라야 하고 이왕이면 여러개의 작품을 동시에 관리 할 수 있는 스케치북이면 더 좋겠다. 좀더 편안하게 작업하기 위해 이젤도 있으면 좋겠지만 필수는 아니다. 스케치북에 옮기기 전 물감을 담고 새로운 색을 조합하기 위한 팔레트도 필요하다. 많은 색을 담을 수 있으면 좋겠지만 그렇다고 너무 무거워지면 한손에 쥐고 작업하기 어려울 수 있으니 본인이 편리하다고 느끼는 것이면 충분하다. 마지막으로 팔레트에 담은 물감을 스케치북 화폭에 옮길 수 있는 붓과 물감이 필요하다. 작품에 독특한 질감을 주고 싶다면 특별한 붓과 물감이 필요 할 수 있으니 이점을 잘 고려해서 선정하도록 하자. 스케치북, 팔레트, 붓과 물감만 있으면 그림을 그리기 위한 기본적인 소도구는 준비된 셈이다.


컴퓨터 디스플레이 화면에 UI를 그리는 것도 이와 비슷하다. GUI 개발자가 화면에 Widget 하나를 띄우려고 한다고 해보자. GUI 개발자는 자신의 작품을 컴퓨터 화면에 표현하고 싶은 화가라고 볼 수 있다. 개발자는 자신의 widget을 표현 할 수 있는 공간(스케치북)이 필요하다. 이 공간은 디스플레이와 연결이 되어 있어 자신의 출력물이 디스플레이 화면에 나와야 한다. 이때 이런 역할을 하는 것이 X-Server(Ubuntu 계열) 또는 Surface Flinger(Android 시스템)와 같은 디스플레이 서버다. 이 소프트웨어는 사용자가 값을 입력할 수 있는 버퍼를 제공하고 개발자는 이 버퍼에 값을 씀으로써 화면에 표현할 수 있게 된다.


그런데 사용자가 직접 display server에서 메모리를 할당 받고 이곳에 값을 쓰는 것은 꽤나 번거로운 일이다. 그래서 그래픽 소프트웨어 개발자들은 이 일을 대신 하는 다양한 툴킷(GTK, QT 등등)을 만들었다. 그림 그린 것으로 치면 팔레트라 볼 수 있는데 스케치북을 관리 할 수 있는 특별한 기능이 있는 팔레트라고 보면 된다. 사용자가 공간을 요청하면 툴킷은 직접 시스템에서 사용되는 디스플레이 서버로부터 메모리 공간을 대신 받아 온다.


툴킷별로 glib(GTK용 그래픽 라이브러리), xlib(X윈도우용 라이브러리)과 같은 특정 라이브러리를 참조해서 사용하는데 이것들은 해당 할당받은 디스플레이 서버에 값을 쓸 수 있는 함수들이다. 툴킷은 이 함수들을 적절하게 이용해 할당 받은 공간에 값을 쓸 수있는 기능도 제공한다. 심플하게 시스템상에 쩜하나 찍는 것 뿐만 아니라 맨 위창에 뜨는 제목을 설정하고 취소 버튼을 추가하는 것처럼 소프트웨어에서 통상적으로 사용하는 UI도 툴킷을 이용해서 쉽고 간결하게 만들 수 있다. 툴킷 사용자는 공간 배치와 색, 그리고 화면에 표현할 그림을 입력 하기만 하면 툴킷이 대신해서 메모리에 값을 입력하고 화면에 그려준다. 툴킷이 시스템 환경에 맞는 붓을 제공하는 것이라고 볼 수 있다.



그림 2. 그래픽 라이브러리 스택


지금까지 설명한 내용을 위의 그림처럼 표현 할 수 있다. 여러 가지 소프트웨어가 나오는데 여기서 이 포스트에서 다룬 내용들만 추출하고 공부한 내용을 정리해보자.

개발자는 GTK, QT, EFL와 같은 툴킷(주황색 블록)을 이용해서 X-Server, Wayland, SF와 같은 디스플레이 서버(하늘색 블록)로 부터 버퍼를 할당 받고 툴킷이 제공해준 라이브러리를 이용해 2D/Media/3D Application(파란색 블록)이란 작품을 만들었다. 


참고자료


그림 1 : http://dh.aks.ac.kr/Encyves/wiki/index.php/%EA%B9%80%EC%9B%90(%ED%99%94%EA%B0%80) 

그림 1 : By Shmuel Csaba Otto Traian, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=27799196

728x90

'기술' 카테고리의 다른 글

Libgdx - 소개 및 주요함수 정리  (0) 2018.06.20
objdump 를 이용한 바이너리 깨보기  (0) 2018.05.29
그래픽 소프트웨어, 라이브러리 정리  (0) 2018.05.06
Yocto 내부 파일 분석  (1) 2016.10.02
Yocto 작동방식  (0) 2016.10.01
Yocto 튜토리얼  (2) 2016.10.01

Ubuntu 16.04에서 GPU 가속화를 지원하는 Tensorflow를 설치하는 방법

이 포스트는 Tensorflow 공식 가이드 페이지를 읽고 글쓴이의 컴퓨터에 설치한 과정을 정리한 것이다. PC 환경에 따라 차이가 있을 수 있으므로 포스트를 읽고 설치하기 전에 개인 PC환경이 글쓴이와 어떤점이 차이가 나는지 먼저 확인해두길 바란다. 똑같은 instruction을 사용해도 어떤 PC는 되고 어떤 거는 안되는 경우가 많다. 글쓴이의 PC 환경은 다음과 같다.

CPU: Intel(R) Core(TM) i5-6600 CPU @ 3.30GHz, Quad Core

GPU: GEFORCE GTX1060 (그래픽 드라이버 설치 완료)

RAM: 8GB

Kernel: 4.4.0-122-generic


* 혹시 우분투 커널 버전을 업데이트 했다면 중요한 자료들을 미리 백업해두길 바란다. 글쓴이는 처음에 4.9로 업데이트한 커널로 설치해봤는데 재부팅하니 갑자기 컴퓨터가 안켜지는 문제가 있었다. 아무리 찾아도 해결책을 찾을 수 없어 별 수 없이 커널을 밀고 재설치를 했었다.


1. CUDA Toolkit 다운로드 및 설치

Nvidia 홈페이지에서 tensorflow를 지원하는 cuda 설치 패키지를 다운 받는다. 현재 tensorflow는 cuda 9.0만 지원하고 있으니 이에 유의하도록 하자. 만약 현재 사용중인 NVIDIA driver가 cuda 9.0가 호환하지 않으면 삭제하고 재설치해야 하는 경우도 있다. 이런 경우에는 runfile을 받으면 호환되는 그래픽 카드 드라이버도 같이 받을 수 있다.

* CUDA는 GPGPU에서 사용하는 병렬 처리 알고리즘 기술을 C언어를 비롯한 여러가지 언어에서 사용할 수 있도록 지원하는 기술이다. GPGPU를 사용하기 위한 인터페이스로 볼 수 있다.




2. cuDNN 설치

이것 또한 Nvidia 홈페이지에서 자신의 PC 환경에 맞는 CUDA를 설치해야한다. 그런데 CUDA와 달리 cuDNN 부터는 따로 계정을 생성해야 한다. 가입하고 난 뒤에는 cuDNN 7.0.5 버전을 받도록 한다.

* cuDNN은 CUDA를 딥러닝에 사용할 수 있도록 만든 API다.


설치 가능한 목록에서 자신의 환경을 꼼꼼히 확인하고 다운 받아야 한다. Ubuntu 16.04에서는 Runtime Library와 Developer Library가 있는데 나는 Runtime을 선택해서 설치했다.


3. 그래픽 드라이버 설치

본인이 갖고 있는 그래픽 카드에 맞는 그래픽 드라이버를 설치한다. NVIDIA 홈페이지에서 그래픽 카드 드라이버를 받을 수 있다. 미리 설치 했으면 할 필요 없다. 설치 방법이 apt-get 처럼 단순하지 않다. 설치 방법은 추후 별도의 포스트로 올리겠다.


4. Tensorflow 설치 

Tensorflow 홈페이지에서는 총 네가지 방법(Virutalenv, native, Docker, anaconda)을 지원하는데 이중에서 가상 환경을 만들어서 사용하는 방법(Virtualenv)을 권장하고 있다. Virtualenv는 특정 폴더에 python 환경을 독립적으로 꾸릴 수 있는 환경을 제공하는데 이곳에 tensorflow를 설치하면 다른 파이선 프로그램의 디펜던시가 걸릴 우려가 없어 관리하기가 편하다. 글쓴이는 virtualenv를 이용해서 설치했다.

  1. 먼저 python 패키지 관리 시스템과 virtual env를 apt-get으로 설치한다.

    $ sudo apt-get install python-pip python-dev python-virtualenv # for Python 2.7
    $ sudo apt-get install python3-pip python3-dev python-virtualenv # for Python 3.n

    파이썬 2.7과 3.n 버전이 엄격히 구분되니, 앞으로 사용할 파이선 버전에 따라서 명령어를 잘 선택한다.

  2. python 환경을 독립적으로 꾸릴 폴더에 가상 환경을 설정한다.

    $ virtualenv --system-site-packages targetDirectory # for Python 2.7
    $ virtualenv --system-site-packages -p python3 targetDirectory # for Python 3.n
  3. 다음 명령어중 하나로 Virtualenv 환경을 활성화 한다.
    세가지 명령어를 다 시도하면 virtualenv 환경에서 튕기니까 주의한다.

    $ source ~/{targetDirectory}/bin/activate # bash, sh, ksh, or zsh $ source ~/{targetDirectory}/bin/activate.csh # csh or tcsh $ . ~/{targetDirectory}/bin/activate.fish # fish

  4. 정상적으로 됐으면 아래와 같이 활성화된 창이 뜬다.

    (targetDirectory)$

  5. Virtualenv 환경 내에서 pip가 설치 됐는지 확인해본다.

    (targetDirectory)$ easy_install -U pip
  6. pip 버전에 따라서 tensorflow를 설치한다

    (targetDirectory)$ pip install --upgrade tensorflow-gpu  # for Python 2.7 and GPU
    (targetDirectory)$ pip3 install --upgrade tensorflow-gpu # for Python 3.n and GPU
    

5. 설치 완료 메시지 확인

아래와같은 메시지가 나오면 정상적으로 tensorflow가 설치된 것이다


728x90
  1. CB 2018.08.01 22:49  댓글주소  수정/삭제  댓글쓰기

    안녕하세요,
    runtime library 와 developer library 간의 차이를 알 수 있을까요?
    검색 해본다고 해봤는데 잘 모르겠습니다.

    • 아는 개발자 2018.08.01 23:09 신고  댓글주소  수정/삭제

      Runtime Library: cnDNN을 돌릴 때 필요한 기본적인 API를 설치합니다. Window에서 일반 프로그램 설치 옵션중 하나인 '기본 설치'와 비슷하다고 보시면 됩니다. 이것만 설치해도 Tensorflow를 실행하는데 전혀 지장 없습니다.

      Developer Library: 헤더파일과 일부 코드만 제공하고 나머지는 개발자가 직접 빌드해야 합니다. Tensorflow에서 cuDNN을 사용하는 코드를 변경해서 돌려보고 싶은 경우에 사용하는 라이브러리입니다.

      요약: tensorflow를 API만 사용하는 경우에는 Runtime Library를, 직접 코드를 수정하는 경우에는 Developer Library를 사용하시면 됩니다.

      출처: https://stackoverflow.com/questions/48784645/which-nvidia-cudnn-release-type-for-tensorflow-on-ubuntu-16-04

Ubuntu 안드로이드 스튜디오 설치하기

개발/삽질 기록 2018. 5. 1. 09:55 Posted by 아는 개발자

Ubuntu 16.04에서 안드로이드 스튜디오 설치하기


Ubuntu에서 안드로이드 스튜디오를 설치하는 방법은 두가지가 있다.

  • 공식 홈페이지에서 .zip 파일로 받아온 후 압축을 풀어서 바이너리를 링크거는 방법. 안드로이드 스튜디오를 개발자가 유연성있게 관리 할 수 있다는 장점이 있지만 환경변수를 일일이 설정하고 추적하는게 번거롭다.
  • ppa 저장소를 추가하고 apt-get으로 받기. vim, terminator 같은 프로그램을 받을 때처럼 원격 저장소에서 모두 받을 수 있는 방법이다. 유연성있게 관리할 수는 없지만 설치 방법이 간단하다.

이 포스트는 두번째 방법에 대해서만 다룬다.


1. 저장소 추가 및 apt-get을 이용한 안드로이드 스튜디오 설치

터미널을 열고 아래의 명령어를 입력한다


sudo apt-add-repository ppa:paolorotolo/android-studio

sudo apt-get update

sudo apt-get install android-studio


2. 재부팅후 Android Studio 설치 확인

시작창을 열고 Android Studio를 검색해보면 아이콘이 나오면 설치가 정상적으로 이뤄진 것이다.


728x90