문제

와일드카드는 다양한 운영체제에서 파일 이름의 일부만으로 파일 이름을 지정하는 방법이다. 와일드카드 문자열은 일반적인 파일명과 같지만, * 나 ? 와 같은 특수 문자를 포함한다.

와일드카드 문자열을 앞에서 한 글자씩 파일명과 비교해서, 모든 글자가 일치했을 때 해당 와일드카드 문자열이 파일명과 매치된다고 하자. 단, 와일드카드 문자열에 포함된 ? 는 어떤 글자와 비교해도 일치한다고 가정하며, * 는 0 글자 이상의 어떤 문자열에도 일치한다고 본다.

예를 들어 와일드 카드 he?p 는 파일명 help 에도, heap 에도 매치되지만, helpp 에는 매치되지 않는다. 와일드 카드 *p* 는 파일명 help 에도, papa 에도 매치되지만, hello 에는 매치되지 않는다.

와일드카드 문자열과 함께 파일명의 집합이 주어질 때, 그 중 매치되는 파일명들을 찾아내는 프로그램을 작성하시오.

입력

입력의 첫 줄에는 테스트 케이스의 수 C (1 <= C <= 10) 가 주어진다. 각 테스트 케이스의 첫 줄에는 와일드카드 문자열 W 가 주어지며, 그 다음 줄에는 파일명의 수 N (1 <= N <= 50) 이 주어진다. 그 후 N 줄에 하나씩 각 파일명이 주어진다. 파일명은 공백 없이 알파벳 대소문자와 숫자만으로 이루어져 있으며, 와일드카드는 그 외에 * 와 ? 를 가질 수 있다. 모든 문자열의 길이는 1 이상 100 이하이다.

출력

각 테스트 케이스마다 주어진 와일드카드에 매치되는 파일들의 이름을 한 줄에 하나씩 아스키 코드 순서(숫자, 대문자, 소문자 순)대로 출력한다.

예제 입력

2 he?p 3 help heap helpp *p* 3 help papa hello

예제 출력

heap help help papa

출처 : https://www.algospot.com/judge/problem/read/WILDCARD

[접근방식]

  1. 완전탐색 가능? ⇒ 범위가 작아서 가능할듯(재귀 / for 문)
  2. 풀이기법 ⇒ 정렬로 풀 경우에 *가 여러자릿수가 된다는 게 문제가 됨 ⇒ 정규식으로는 가능할듯

[풀이코드(정규식)]

import re

#입력값
# wild = 'he?p'
# files = ['help', 'heap', 'helpp']

wild = '*p*'
files = ['help', 'papa', 'hello']

new_wild = ''
for char in wild:
    if char == '?':
        new_wild += '.'
    if char == '*':
        new_wild += '.*'
    else:
        new_wild += char
regExp = re.compile(new_wild)
ans = []
for file in files:
    match = re.match(regExp, file)
    if match:
        #자릿수를 맞춰주기 위해(?만 있을 땐 처음에 길이 확인하면 되지만, *때문에)
        if file == match.group():
            ans.append(file)
print(ans)

[책 풀이(DP)]

#입력값
# wild = 'he?p'
# files = ['help', 'heap', 'helpp']

wild = '*p*'
files = ['help', 'papa', 'hello']

# wild = '*bb*'
# files = ['babbbc']
cache = [[None]*101 for _ in range(101)]

def matchMemoized(w, s, W, S):
    ret = cache[w][s]
    if ret: return ret
    while s < len(S) and w < len(W) and (W[w] == '?' or W[w] == S[s]):
        w += 1
        s += 1
    if w == len(W):
        cache[w][s] = (s == len(S))
        return cache[w][s]
    if W[w] == '*':
        for skip in range(len(S)-s+1):
            if matchMemoized(w+1, s+skip, W, S):
                ret = True
                return ret
    ret = False
    return ret

ans = []
for file in files:
    if matchMemoized(0, 0, wild, file):
        ans.append(file)
print(ans)

[책 풀이에서 Cache 없앴을 때(완전탐색)]

#입력값
# wild = 'he?p'
# files = ['help', 'heap', 'helpp']

wild = '*p*'
files = ['help', 'papa', 'hello']

# wild = '*bb*'
# files = ['babbbc']

def matchMemoized(w, s, W, S):
    while s < len(S) and w < len(W) and (W[w] == '?' or W[w] == S[s]):
        w += 1
        s += 1
    if w == len(W):
        return (s == len(S))
    if W[w] == '*':
        for skip in range(len(S)-s+1):
            if matchMemoized(w+1, s+skip, W, S):
                return True
    return False

ans = []
for file in files:
    if matchMemoized(0, 0, wild, file):
        ans.append(file)
print(ans)

참고 : https://junho-one.tistory.com/13

[느낀점/의문점]

  1. ‘책 풀이’에서 아래의 부분에서 여러개가 불러져서 가고 또 거기서 *로 끝나면 또 부를거니까 memoization을 적용시킨다고 생각했음
if W[w] == '*':
    for skip in range(len(S)-s+1):
        if matchMemoized(w+1, s+skip, W, S):
            ret = True
            return ret
  1. 그런데, 어차피 w+1로 *을 뛰어넘어서 줄테고 매칭이 안 되면 False를 반환하게 되고 앞서서 for구문에서 skip 적용해서 돌리기 때문에 캐싱을 사용하고 있다기 보다는 완전탐색에 가깝다고 사료됨
  2. 왜 메모이제이션을 여기서 썼는지 잘 모르겠음. 입력값이 아주 크거나 복잡한 값이 들어올 경우에 메모이제이션을 쓰게되나?
  3. 문자열 parsing 문제에서는 정규식 만큼 강력한 도구가 없는 것 같음

문제

땅따먹기를 하다 질린 재하와 영훈이는 땅따먹기의 변종인 새로운 게임을 하기로 했습니다. 이 게임은 그림과 같이 n*n 크기의 격자에 각 1부터 9 사이의 정수를 쓴 상태로 시작합니다. 각 차례인 사람은 맨 왼쪽 윗 칸에서 시작해 외발로 뛰어서 오른쪽 아래 칸으로 내려가야 합니다. 이 때 각 칸에 적혀 있는 숫자만큼 오른쪽이나 아래 칸으로 움직일 수 있으며, 중간에 게임판 밖으로 벗어나면 안 됩니다.

균형을 잃어서 다른 발로 서거나 넘어져도 게임에서 집니다만, 재하와 영훈이는 젊고 활기차기 때문에 외발로 뛰어다니는 것은 아무것도 아닙니다. 다만 걱정되는 것은 주어진 게임판에 시작점에서 끝점으로 가는 방법이 존재하지 않을 수도 있다는 것입니다. 예를 들어 그림 (a)의 게임판에서는 사각형으로 표시된 칸들을 통해 끝에 도달할 수 있지만, 숫자가 하나 바뀐 그림 (b)에서는 그럴 수가 없습니다.

게임판이 주어질 때 왼쪽 위의 시작점에서 오른쪽 아래의 시작점에 도달할 수 있는 방법이 있는지 확인하는 프로그램을 작성하세요.

입력

입력의 첫 줄에는 테스트 케이스의 수 C(C <= 50)가 주어집니다. 각 테스트 케이스의 첫 줄에는 격자의 크기 n(2 <= n <= 100)이 주어지고, 그 후 n줄에 각 n개의 숫자로 왼쪽 위부터 각 칸에 쓰인 숫자들이 주어집니다. 오른쪽 아래 있는 끝 점 위치에는 0이 주어집니다.

출력

각 테스트 케이스마다 한 줄로, 시작점에서 끝 점으로 도달할 수 있을 경우 "YES"를, 아닐 경우 "NO"를 출력합니다. (따옴표 제외)

예제 입력

2 7 2 5 1 6 1 4 1 6 1 1 2 2 9 3 7 2 3 2 1 3 1 1 1 3 1 7 1 2 4 1 2 3 4 1 2 3 3 1 2 3 4 1 1 5 2 9 4 7 0 7 2 5 1 6 1 4 1 6 1 1 2 2 9 3 7 2 3 2 1 3 1 1 1 3 1 7 1 2 4 1 2 3 4 1 3 3 3 1 2 3 4 1 1 5 2 9 4 7 0

예제 출력

YES NO

출처 : https://algospot.com/judge/problem/read/JUMPGAME

[접근방식]

  1. 완전탐색? ⇒ 재귀로 가능하나, 단순 완전탐색은 시간복잡도 때문에 불가능
  2. 메모이제이션? ⇒ 2차원배열 cache 사용 but 실제로 사용해보니, dfs탐색할 때 visited처리랑 똑같은듯
  3. 풀이방식 dfs로 탐색해주면서 cache(visited)라는 2차원 배열에 업데이트하고 만일 cache에 이미 있으면 넘기고 없을때만 cache에 업데이트하는 방식으로 진행 이 때, 도달할 수 없다면 같은 셀을 계속 돌게되고 그 때마다 cache 값이 존재할 것이므로 dfs가 추가적으로 불리지 않고 끝나게 될 것 마지막에 cache[n-1][n-1]이 여전히 초기값(0)이면, 도달할 수 없는 것이고 0보다 크면 도달가능

[풀이코드]

#입력값
n = 7
# board = [
#     [2, 5, 1, 6, 1, 4, 1],
#     [6, 1, 1, 2, 2, 9, 3],
#     [7, 2, 3, 2, 1, 3, 1],
#     [1, 1, 3, 1, 7, 1, 2],
#     [4, 1, 2, 3, 4, 1, 2],
#     [3, 3, 1, 2, 3, 4, 1],
#     [1, 5, 2, 9, 4, 7, 0]]

board = [
    [2, 5, 1, 6, 1, 4, 1],
    [6, 1, 1, 2, 2, 9, 3],
    [7, 2, 3, 2, 1, 3, 1],
    [1, 1, 3, 1, 7, 1, 2],
    [4, 1, 2, 3, 4, 1, 3],
    [3, 3, 1, 2, 3, 4, 1],
    [1, 5, 2, 9, 4, 7, 0]]

dir = [(1,0), (0, 1)]
cache = [[0]*n for _ in range(n)]

def dfs(r, c):
    global board, dir, cache, n
    if r == n-1 and c == n-1:
        return
    steps = board[r][c]
    for i in range(2):
        nxt_r = r + steps*dir[i][0]
        nxt_c = c + steps*dir[i][1]
        if nxt_r < 0 or nxt_r >= n or nxt_c < 0 or nxt_c >= n:
            continue
        if cache[nxt_r][nxt_c] > 0:
            continue
        else:
            cache[nxt_r][nxt_c] = steps + cache[r][c]
            dfs(nxt_r, nxt_c)

dfs(0, 0)
if cache[n-1][n-1] > 0:
    print('Yes')
else:
    print('No')

여기서는 도착여부만 판단하므로 cache(visited)의 초기값을 False로 두고 True/False로 표기해도 될 것으로 판단됨

[책 풀이]

#입력값
n = 7
board = [
    [2, 5, 1, 6, 1, 4, 1],
    [6, 1, 1, 2, 2, 9, 3],
    [7, 2, 3, 2, 1, 3, 1],
    [1, 1, 3, 1, 7, 1, 2],
    [4, 1, 2, 3, 4, 1, 2],
    [3, 3, 1, 2, 3, 4, 1],
    [1, 5, 2, 9, 4, 7, 0]]

# board = [
#     [2, 5, 1, 6, 1, 4, 1],
#     [6, 1, 1, 2, 2, 9, 3],
#     [7, 2, 3, 2, 1, 3, 1],
#     [1, 1, 3, 1, 7, 1, 2],
#     [4, 1, 2, 3, 4, 1, 3],
#     [3, 3, 1, 2, 3, 4, 1],
#     [1, 5, 2, 9, 4, 7, 0]]

cache = [[-1]*n for _ in range(n)]

def jump(r, c):
    global n, board, cache
    if r >= n or c >= n: return 0
    if r == n-1 and c == n-1: return 1
    if cache[r][c] != -1: return cache[r][c]
    steps = board[r][c]
    ret = jump(r+steps, c) or jump(r, c+steps)
    cache[r][c] = ret
    return ret

if jump(0, 0) == 1:
    print('Yes')
else:
    print('No')

책에서도 그래프로 모델링시, 아주 간단한 문제가 된다고 하니 이 문제의 경우에는 dfs 그래프 문제로 해결하면 될듯

문제

너비가 같은 N개의 나무 판자를 붙여 세운 울타리가 있습니다. 시간이 지남에 따라 판자들이 부러지거나 망가져 높이가 다 달라진 관계로 울타리를 통째로 교체하기로 했습니다. 이 때 버리는 울타리의 일부를 직사각형으로 잘라내 재활용하고 싶습니다. 그림 (b)는 (a)의 울타리에서 잘라낼 수 있는 많은 직사각형 중 가장 넓은 직사각형을 보여줍니다. 울타리를 구성하는 각 판자의 높이가 주어질 때, 잘라낼 수 있는 직사각형의 최대 크기를 계산하는 프로그램을 작성하세요. 단 (c)처럼 직사각형을 비스듬히 잘라낼 수는 없습니다.

판자의 너비는 모두 1이라고 가정합니다.

입력

첫 줄에 테스트 케이스의 개수 C (C≤50)가 주어집니다. 각 테스트 케이스의 첫 줄에는 판자의 수 N (1≤N≤20000)이 주어집니다. 그 다음 줄에는 N개의 정수로 왼쪽부터 각 판자의 높이가 순서대로 주어집니다. 높이는 모두 10,000 이하의 음이 아닌 정수입니다.

출력

각 테스트 케이스당 정수 하나를 한 줄에 출력합니다. 이 정수는 주어진 울타리에서 잘라낼 수 있는 최대 직사각형의 크기를 나타내야 합니다.

예제 입력

3 7 7 1 5 9 6 7 3 7 1 4 4 4 4 1 1 4 1 8 2 2

예제 출력

20 16 8

출처 : https://www.algospot.com/judge/problem/read/FENCE

분할정복 풀이

[접근방식]

  1. 가장 단순하게 푸는 방법? ⇒ 울타리 하나씩 돌아가며 기준으로 잡고 전체 울타리 탐색(완전탐색)
  2. 완전탐색 가능? ⇒ O(N^2)라 불가능
  3. 풀이기법 ⇒ 분할정복
  4. 분할정복에 쓰인 아이디어⇒ 가장 큰 사각형은 3가지 경우가 있음 1) 왼쪽에서 가장 큰 것, 2) 오른쪽에서 가장 큰 것, 3) 겹치는 것 중 가장 큰 것
  5. 3)번의 경우에서 중앙의 2개를 포함하게 되어있음
  6. 1)과 2)의 경우 재귀로 넘겨주고 3)의 기저조건은 울타리가 하나일 때로 설정

[어려웠던 점]

  1. 분할정복 자체를 생각해내기가 어려웠음
  2. 나는 1), 2), 3)을 각각의 함수로 생각했고 1)과 2)를 재귀로 넘겨준다는 것을 생각하지 못했음
  3. 3)의 경우에 중앙의 2개를 무조건 가지게 된다는 조건에 대해 잘 생각하지 못했던 것 같음

[분할정복 풀이코드(책 코드 참조)]

#입력값
n = 7
h = [1, 4, 4, 4, 4, 1, 1]
def dfs(left, right):
    global h
    if left == right: return h[left]
    mid = (left+right)//2
    lo = mid
    hi = mid+1
    ret = max(dfs(left, lo), dfs(hi, right))
    height = min(h[lo], h[hi])
    ret = max(ret, height*2)
    while left < lo or hi < right:
        if hi<right and (h[lo-1]<h[hi+1] or lo == left):
            hi += 1
            height = min(height, h[hi])
        else:
            lo -= 1
            height = min(height, h[lo])
        ret = max(ret, height*(hi-lo+1))
    return ret

print(dfs(0, n-1))

스위핑 풀이

[스위핑이란?]

스위핑 알고리즘이란, 이미 연산이 진행된 이전 공간에 대해서는 연산을 하지않는다는 개념

[접근방식]

  1. 사각형을 구하기 위해 필요한 정보는 해당 사각형의 왼쪽 끝과 오른쪽 끝 그리고 높이임
  2. 높이가 오른쪽 끝점의 높이라고 했을 때, 왼쪽으로 연속된 판자들 중 이 높이를 포함하는 가장 낮은 지점이 왼쪽 끝점이 될 것
  3. 위의 사항을 이용하기 위해 index(폭 구할 때 이용)를 stack에 저장하고 stack에서 빼내면서 폭을 구하고 해당 index로 높이 정보에 접근도 가능
  4. index를 꺼내는 시점의 경우, 현재 울타리 높이가 stack의 마지막 울타리 높이보다 낮을 때 현 울타리 보다 낮은 울타리 전까지 하나씩 빼면서 넓이를 업데이트 해주면 됨
  5. 단, 스텍에서 뺄 때, 스택이 비어있는지 확인이 필요함
  6. 추가적으로 마지막 울타리의 높이가 굉장히 높을 경우가 있으므로 울타리 높이 배열에 0을 추가해주면 이 경우 처리 가능
  7. 해당 문제를 푸는 알고리즘 요약 (O(N) 풀이 가능)

[(참고) 스텍 생성 울타리를 저장하는 배열에 0 추가 0부터 n까지(n+1 번) 다음을 순회]

  1. 스텍이 비지 않고 현재 스택에 가장 나중에 저장된 위치의 울타리 높이가 현 울타리 높이 이상일때까지 다음을 반복 a. 스텍에 가장 나중에 저장된 위치 가져옴 b. 너비는 스텍이 비었을 땐 현재위치 i를, 비지 않았다면 i-다음으로 나중에 저장된 위치-1 c. 넓이를 구하고 최대값 갱신
  2. 현재 idx를 스텍에 넣어줌

[어려웠던 점]

  1. 스텍까지는 떠올렸었는데, 울타리의 idx를 스텍에 넣는다는 것을 떠올리지 못해서 풀이로 연결시키기 어려웠던 것 같다.
  2. 스텍을 넣고 빼고하는 지점의 조건에 대해 정당성을 입증하기가 어려웠던 것 같다. 정작 그림을 그려가며 생각해보니, 모든 조건에서 정상작동하였다.
  3. 울타리의 idx가 0부터 시작한다는 점에서 너비의 조건이 i 혹은 i-다음으로 나중에 저장된 위치-1가 된다는 것을 잘 연결시키지 못했다.

[느낀 점]

  1. 분할정복 보다 스위핑이 시간복잡도도 낮은데다, 스위핑은 재귀를 부르지 않기 때문에 이해만 잘 한다면 스위핑이 훨씬 간단하고도 강력한 알고리즘이 될 것이다.
  2. 앞으로 스위핑과 스텍을 이용하는 방법에 대해 항상 유념하고 비슷한 문제를 만나면 잘 활용해봐야겠다.

[스위핑 풀이코드]

#입력값
n = 7
h = [1, 4, 4, 4, 4, 1, 1]

#마지막 울타리 처리를 위해서 0높이의 울타리 추가
h.append(0)
#울타리 idx 담을 stack
stack = []
#업데이트할 결과값
ret = 0

for i in range(n+1):
    #현재 울타리가 stack의 마지막 울타리보다 낮으면
    #현 울타리보다 작은 울타리 나오기 전까지 뺴주면서 넓이 업데이트
    while stack and h[stack[-1]] >= h[i]:
        #높이를 stack 마지막걸로 업데이트해가며 진행
        height = h[stack[-1]]
        stack.pop()
        #stack이 비었다면(idx가 0부터 시작)
        if not stack:
            width = i
        #stack이 남아있으면(idx가 0부터 시작)
        else:
            width = i-stack[-1]-1
        #넓이 업데이트
        ret = max(ret, height*width)
    #stack에 넣어주기
    stack.append(i)

print(ret)

참고출처 https://gurumee92.tistory.com/90 https://doctcoder.tistory.com/27

 

문제

그림과 같이 4 x 4 개의 격자 형태로 배치된 16개의 시계가 있다. 이 시계들은 모두 12시, 3시, 6시, 혹은 9시를 가리키고 있다. 이 시계들이 모두 12시를 가리키도록 바꾸고 싶다.

시계의 시간을 조작하는 유일한 방법은 모두 10개 있는 스위치들을 조작하는 것으로, 각 스위치들은 모두 적게는 3개에서 많게는 5개의 시계에 연결되어 있다. 한 스위치를 누를 때마다, 해당 스위치와 연결된 시계들의 시간은 3시간씩 앞으로 움직인다. 스위치들과 그들이 연결된 시계들의 목록은 다음과 같다.

제목 없음

시계들은 맨 윗줄부터, 왼쪽에서 오른쪽으로 순서대로 번호가 매겨졌다고 가정하자. 시계들이 현재 가리키는 시간들이 주어졌을 때, 모든 시계를 12시로 돌리기 위해 최소한 눌러야 할 스위치의 수를 계산하는 프로그램을 작성하시오.

입력

첫 줄에 테스트 케이스의 개수 C (<= 30) 가 주어진다.각 테스트 케이스는 한 줄에 6개의 정수로 주어지며, 각 정수는 0번부터 15번까지 각 시계가 가리키고 있는 시간을 12, 3, 6, 9 중 하나로 표현한다.

출력

각 테스트 케이스당 한 줄을 출력한다. 시계들을 모두 12시로 돌려놓기 위해 눌러야 할 스위치의 최소 수를 출력한다. 만약 이것이 불가능할 경우 -1 을 출력한다.

예제 입력

2 12 6 6 6 6 6 12 12 12 12 12 12 12 12 12 12 12 9 3 12 6 6 9 3 12 9 12 9 12 12 6 6

예제 출력

2 9

출처 : https://www.algospot.com/judge/problem/read/CLOCKSYNC

[접근]

  1. 완전탐색 가능? ⇒ 최대 4**10이므로 가능
  2. 문제의 특징? 스위치를 4번 누르면 시계가 원복됨 ⇒ 스위치는 0~3번까지 누를 수 있음
  3. 풀이기법 : dfs
  4. 각 스위치마다 돌아가면서 0~3번 누르고 dfs에 넘겨줌
  5. 이 때, 스위치 num += 1 을 해주는 식으로 넘길 것이므로 스위치 num이 최대 num+1이 되었을 때 clock이 모두 12인지 검사하고 answer 업데이트
  6. 스위치 누르고 시간을 더하거나 뺄 때, 시계는 12시가 기점인 것을 항상 유념!

[느낀 점]

이 문제는 각 스위치마다 0~3번까지 누를 수 있다는 특징을 잡아내야 풀 수 있는 문제였음

각 문제마다 짧게라도 시뮬레이션 해보면서 특징(제한할 수 있는 조건 등)을 잡아내는 것이 중요!

import sys
sys.setrecursionlimit(10**6)

def is_finished(clocks):
    for clock in clocks:
        if clock != 12:
            return False
    return True

def set_clock(switch, clocks, switch_clocks, time):
    for clock in switch_clocks[switch]:
        clocks[clock] += time
        if clocks[clock] < 0 : clocks[clock] += 12
        if clocks[clock] > 12: clocks[clock] -= 12
        if clocks[clock] == 0: clocks[clock] = 12

answer = int(1e9)

def dfs(switch, clocks, switch_clocks, cnt):
    global answer
    if switch == 10:
        if is_finished(clocks): answer = min(answer, cnt)
        return
    #안 누르고 그냥 넘기기
    dfs(switch+1, clocks, switch_clocks, cnt)
		#1~3번 누른 결과 dfs로 넘기기
    for i in range(1, 4):
        set_clock(switch, clocks, switch_clocks, 3*i)
        dfs(switch+1, clocks, switch_clocks, cnt+i)
        set_clock(switch, clocks, switch_clocks, (-3)*i)
    return

dfs(0, clocks, switch_clocks, 0)
print(answer)

[(참조) 문제 풀 때 입력값]

clocks = [12, 9, 3, 12, 6, 6, 9, 3, 12, 9, 12, 9, 12, 12, 6, 6]
# clocks = [12, 6, 6, 6, 6, 6, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12]
switch_clocks = [[0, 1, 2],
        [3, 7, 9, 11],
        [4, 10, 14, 15],
        [0, 4, 5, 6, 7],
        [6, 7, 8, 10, 12],
        [0, 2, 14, 15],
        [3, 14, 15],
        [4, 5, 7, 14, 15],
        [1, 2, 3, 4, 5],
        [3, 4, 5, 9, 13]]

문제

H*W 크기의 게임판이 있습니다. 게임판은 검은 칸과 흰 칸으로 구성된 격자 모양을 하고 있는데 이 중 모든 흰 칸을 3칸짜리 L자 모양의 블록으로 덮고 싶습니다. 이 때 블록들은 자유롭게 회전해서 놓을 수 있지만, 서로 겹치거나, 검은 칸을 덮거나, 게임판 밖으로 나가서는 안 됩니다. 위 그림은 한 게임판과 이를 덮는 방법을 보여줍니다.

게임판이 주어질 때 이를 덮는 방법의 수를 계산하는 프로그램을 작성하세요.

입력

력의 첫 줄에는 테스트 케이스의 수 C (C <= 30) 가 주어집니다. 각 테스트 케이스의 첫 줄에는 2개의 정수 H, W (1 <= H,W <= 20) 가 주어집니다. 다음 H 줄에 각 W 글자로 게임판의 모양이 주어집니다. # 은 검은 칸, . 는 흰 칸을 나타냅니다. 입력에 주어지는 게임판에 있는 흰 칸의 수는 50 을 넘지 않습니다.

출력

한 줄에 하나씩 흰 칸을 모두 덮는 방법의 수를 출력합니다.

예제 입력

3 3 7 #.....# #.....# ##...## 3 7 #.....# #.....# ##..### 8 10 ########## #........# #........# #........# #........# #........# #........# ##########

예제 출력

0 2 1514

출처 : https://www.algospot.com/judge/problem/read/BOARDCOVER

[접근]

  1. 문제 단순화/순서 강제 ⇒ 왼쪽 상단부터 검사한다는 순서강제
  2. 왼쪽상단부터 비어있는 칸을 찾아내서 검사
  3. 편의를 위해 데이터 전처리(리스트화, 숫자화)
  4. 블록방향의 경우, 상대적 위치를 정해두고 타겟 위치에 따라 검사진행
  5. dfs 사용

[주의사항]

  1. 파이썬의 경우, 다중 for문에서 한꺼번에 break하는 기능이 없음⇒ flag사용해서 상위 for문 break해주는 기법 사용
  2. 파이썬은 기본 recursion limit이 10**3이므로, 재귀 사용 시에 limit 풀어주기

[기타 참고사항]

  1. 처음에 is_fulfilled함수를 따로 빼서 검사를 해주었는데, 책처럼 빈칸을 찾는 완전탐색에서 찾지 못하면 fulfilled로 간주하는 것이 더 효율적
  2. 파이썬 다중for문에서 break를 걸 때는 항상 주의하자!
import sys
sys.setrecursionlimit(10**6)

L_type = [[(1, 0), (1, 1)], [(0, 1), (1, 1)], [(1, 0), (1, -1)], [(1, 0), (0, 1)]]

new_board = []
for i in range(H):
    temp = board[i].replace('#', '1')
    temp = temp.replace('.', '0')
    new_board.append(list(map(int, temp)))

def dfs(new_board, L_type, H, W):
    ret = 0
    startH = -1
    startW = -1
    #다중 for문 break를 위한 flag처리
    flag = False
    for h in range(H):
        for w in range(W):
            if new_board[h][w] == 0:
                #빈칸찾으면 변수 지정해주고 멈추기
                startH = h
                startW = w
                flag = True
                break
        if flag: break
    #변수가 지정되지 않았다는 것은 모두 채워졌다는 의미이므로 return
    if startH == -1 and startW == -1: return 1
    for i in range(4):
        h1 = startH + L_type[i][0][0]
        w1 = startW + L_type[i][0][1]
        h2 = startH + L_type[i][1][0]
        w2 = startW + L_type[i][1][1]
        if 0<= h1 < H and 0<= h2 < H and 0<= w1 < W and 0<= w2 < W and new_board[h1][w1] == 0 and new_board[h2][w2] == 0:
            new_board[h1][w1] = 1
            new_board[h2][w2] = 1
            new_board[h][w] = 1
            ret += dfs(new_board, L_type, H, W)
            new_board[h1][w1] = 0
            new_board[h2][w2] = 0
            new_board[h][w] = 0
    return ret

print(dfs(new_board, L_type, H, W))

[재귀 관련 참고사항]

모든 재귀함수는 ‘더 이상 쪼개지지 않는’ 최소한의 작업에 도달했을 때 답을 곧장 반환하는 조건문 포함필요(기저 사례 base case)

기저사례를 선택할 때는 존재하는 모든 입력이 항상 기저 사례의 답을 이용해서 계산될 수 있도록 신경써야 함

또한, 입력이 잘못되거나 범위에서 벗어난 경우도 기저사례로 택해서 맨 처음 처리하면 함수 호출 시점에 이런 오류를 검사할 필요가 없음

⇒ 재귀함수는 보통 한군데 이상에서 호출되기 때문에 이 습관은 반복적 코드 제거에 큰 도움이 됨

 

문제

안드로메다 유치원 익스프레스반에서는 다음 주에 율동공원으로 소풍을 갑니다. 원석 선생님은 소풍 때 학생들을 두 명씩 짝을 지어 행동하게 하려고 합니다. 그런데 서로 친구가 아닌 학생들끼리 짝을 지어 주면 서로 싸우거나 같이 돌아다니지 않기 때문에, 항상 서로 친구인 학생들끼리만 짝을 지어 줘야 합니다.

각 학생들의 쌍에 대해 이들이 서로 친구인지 여부가 주어질 때, 학생들을 짝지어줄 수 있는 방법의 수를 계산하는 프로그램을 작성하세요. 짝이 되는 학생들이 일부만 다르더라도 다른 방법이라고 봅니다. 예를 들어 다음 두 가지 방법은 서로 다른 방법입니다.

  • (태연,제시카) (써니,티파니) (효연,유리)
  • (태연,제시카) (써니,유리) (효연,티파니)

입력

입력의 첫 줄에는 테스트 케이스의 수 C (C <= 50) 가 주어집니다. 각 테스트 케이스의 첫 줄에는 학생의 수 n (2 <= n <= 10) 과 친구 쌍의 수 m (0 <= m <= n*(n-1)/2) 이 주어집니다. 그 다음 줄에 m 개의 정수 쌍으로 서로 친구인 두 학생의 번호가 주어집니다. 번호는 모두 0 부터 n-1 사이의 정수이고, 같은 쌍은 입력에 두 번 주어지지 않습니다. 학생들의 수는 짝수입니다.

출력

각 테스트 케이스마다 한 줄에 모든 학생을 친구끼리만 짝지어줄 수 있는 방법의 수를 출력합니다.

예제 입력

3 2 1 0 1 4 6 0 1 1 2 2 3 3 0 0 2 1 3 6 10 0 1 0 2 1 2 1 3 1 4 2 3 2 4 3 4 3 5 4 5

예제 출력

1 3 4

출처 : https://www.algospot.com/judge/problem/read/PICNIC

[접근법]

  1. 완전탐색으로 풀 수 있을까? ⇒ 현 문제 상의 데이터크기라면 가능
  2. 조합으로 다 뽑아서 검사할까? ⇒ 너무 무식해서 하고싶지 않음
  3. 문제의 단순화/순서강제 ⇒ 순서대로 뽑는 걸로 강제하면 선택지가 줄어듬
  4. 짝들은 그래프로 데이터 전처리하고 dfs로 접근
  5. 모두 짝을 이뤘는지는 visited(set)의 length로 체크

[주의사항]

visited를 넘겨줄 때, deepcopy로 새 visited만들어서(new_visited) 넘겨줘야함

그렇지 않으면 그 전 방문기록이 쌓여서 결과값에 오류 발생

[기타 refactoring]

deepcopy로 visited를 다 복사하지 말고 책의 풀이처럼, visited처리 후 dfs 넘긴다음에 unvisited하는 것이 더 효율적일 수도 있을듯

[내가 푼 코드]

from collections import defaultdict
from copy import deepcopy

#데이터 전처리
graph = defaultdict(list)
for pair in friends:
    student1, student2 = pair
    graph[student1].append(student2)
    graph[student2].append(student1)

answer = 0
visited = set()

#dfs
def dfs(visited, graph, n):
    global answer
    if len(visited) == n:
        answer += 1
        return
    for i in range(n):
        if i not in visited:
            visited.add(i)
            for friend in graph[i]:
                if friend not in visited:
                    new_visited = deepcopy(visited)
                    new_visited.add(friend)
                    dfs(new_visited, graph, n)
            break
    return

dfs(visited, graph, n)
print(answer)

[책에 나온 스타일대로 풀어본 코드]

visited를 True/False를 가진 배열(taken)로 처리함

해당 값들을 넘겨주고 visited 정보를 다시 초기화하기 위한 작업들을 눈여겨 볼 것(다시 False 처리해줌)

def are_friends(i, j, graph):
    if j in graph[i]: return True
    return False

def is_finished(taken):
    for i in range(n):
        if taken[i] != True:
            return False
    return True

taken = [False]*n

def count_pairs(graph, taken, n):
    if is_finished(taken): return 1
    ret = 0
    target = -1
    for i in range(n):
        if taken[i] == False:
            target = i
            break
    for j in range(n):
        if taken[j] == False and are_friends(target, j, graph):
            taken[target] = True
            taken[j] = True
            ret += count_pairs(graph, taken, n)
            taken[target] = False
            taken[j] = False
    return ret

print(count_pairs(graph, taken, n))

 

네트워크 계층 구조와 네트워크 기기

애플리케이션 계층 : L7 스위치

인터넷 계층 : 라우터, L3 스위치

데이터링크 계층 : L2 스위치, 브리지

물리 계층 : NIC, 리피터, AP

스위치?

1. 정의?

네트워크 스위치는 컴퓨터와 같은 2개 이상의 IT 디바이스가 서로 통신하도록 허용하는 장비

  • IT 디바이스는 네트워크상에서 "패킷"을 교환하는 방식으로 통신함
    • 기본 스위치는 특정 디바이스의 패킷을 다른 디바이스로 전달함
    • 패킷을 지정된 목적지에 보내도 될지 결정하는 등 좀 더 복잡한 작업은 보통 다른 유형의 네트워크 디바이스가 담당
  • 스위치는 전용 어플라이언스의 형태일 수도 있고, 네트워크 라우터나 무선 액세스 포인트(AP) 등과 같이 데이터 패킷에 작업을 수행하는 다른 장비의 구성 요소일 수도 있음
  • 기본적인 스위칭 기술은 수십 년 전부터 사용되어 왔으며 인터넷을 포함한 오늘날 모든 IT 네트워크의 기본 요소 중 하나임

2. 스위치를 왜 쓰는거야?

  • 허브와의 차이점을 통해 이해해보자

⇒ 허브가 뭐야?

기본적으로 허브는 이더넷 케이블을 위한 많은 연결(소켓)이 있는 상자.

허브는 수신한 모든 메시지를 연결된 모든 노드에 반복하고 이러한 노드는 자신을 위한 메시지만 필터링함(이 필터링은 이더넷 수준에서 발생)

들어오는 메시지는 목적지인 수신자의 이더넷 네트워크 주소를 전달함

⇒ 이 접근 방식의 문제는 허브가 특히 대규모 네트워크에서 많은 트래픽을 생성한다는 것임

⇒ 이 트래픽의 대부분은 하나의 노드만을 위한 것이지만 네트워크의 모든 노드로 전송되기 때문에 낭비됨

⇒ 해결하기 위한 방법?

오늘날 일반적으로 사용되는 솔루션은 스위치

스위치는 여전히 허브처럼 모든 노드를 서로 연결하지만 메시지가 어느 노드로 전달되는지에 있어 더 지능적

스위치는 들어오는 이더넷 메시지를 검사하여 어떤 노드가 의도된 수신자인지 확인한 다음 메시지를 해당 노드에 직접 전달

⇒ 이렇게 하면 다른 노드가 불필요하게 모든 트래픽을 수신하지 않음

스위치는 허브보다 비싸기 때문에 네트워크의 트래픽이 적은 부분은 허브를 사용하여 설정할 수 있으며 트래픽이 많은 노드는 스위치에 상호 연결되고 그림 2와 같이 허브 세그먼트도 스위치에 연결됨

라우터?

1. 정의?

라우터는 컴퓨팅 디바이스와 네트워크를 다른 네트워크에 연결하는 네트워킹 디바이스

라우터는 주로 3가지 기본 기능을 수행

  • 경로 결정

라우터는 소스에서 대상으로 이동하는 데이터의 경로를 결정

(지연, 용량 및 속도와 같은 네트워크 지표를 분석하여 최상의 경로를 찾으려고 시도함)

  • 데이터 전달

라우터는 선택한 경로의 다음 디바이스로 데이터를 전달하여 최종적으로 대상에 도달하도록 함

(디바이스와 라우터는 동일한 네트워크에 있거나 서로 다른 네트워크에 있을 수 있음)

  • 로드 밸런싱

경우에 따라 라우터가 여러 경로를 사용하여 동일한 데이터 패킷의 여러 사본을 전송할 수도 있음

⇒ 이 방법을 통해 데이터 손실로 인한 오류를 줄이고 이중화를 구현하고 트래픽 볼륨을 관리

2. 라우터는 왜 쓰는거야?

네트워크가 분할된 경우에도 모든 노드는 여전히 서로 통신할 수 있는데, 이 때 네트워크를 연결하기 위해 라우터 또는 게이트웨이 가 사용함

3. 라우터 vs. 게이트웨이?

라우터는 두 개의 서로 다른 네트워크에 연결되어 있으며, 그림 4와 같이 두 네트워크 간에 패킷을 전달함

일반적인 홈 네트워크에서 라우터는 네트워크와 인터넷 간의 연결을 제공

게이트웨이는 하나의 네트워크 시스템 또는 프로토콜과 다른 네트워크 시스템 간에 변환을 시행한다는 점을 제외하고는 라우터와 동일

예를 들어 NAT 프로토콜은 NAT 게이트웨이를 사용하여 사설 네트워크를 인터넷에 연결함

스위치 vs. 라우터?

1. 주요 차이점

스위치는 주로 레이어 2 연결을 제공하는 기능을 하고 라우터는 레이어 3 연결을 제공함

2. 그게 어떤 뜻이야?

  • 상위 단계에서 이는 스위치를 통해 호스트들이 공통 네트워크(예: 근거리 통신망 – LAN)에 있는 한 호스트들이 통신이 가능함을 의미
  • 반면, 라우터는 서로 다른 네트워크가 서로 통신할 수 있도록 하고 서로 다른 호스트가 서로 다른 원격 네트워크에 연결되어 있어도 통신을 허용

3. 엔터프라이즈 네트워크에서 인기있는 한 네트워크 토폴로지를 바탕으로 비교해보자

  • 이 다이어그램은 L2/L3 스위치와 라우터에 대해 비교하기 좋음

  • Layer2 스위치
    • 가장 일반적인 스위치 유형(데이터 링크 레이어)
    • Layer2와 Layer3 모두에서 작동 가능한 고급 스위치도 있음
    • 시나리오를 통해 이해해보자
    • 호스트 A 가 TCP/IP 네트워크에서 다른 호스트 B 와 통신하려고 할 때 대상 호스트 B의 MAC 주소를 찾기 위해 ARP(주소 결정 프로토콜, Address Resolution Protocol) 요청을 보냄
    • 호스트 A는 호스트 B의 IP 주소를 알고 있지만 해당 호스트에 도달하는 방법을 정확히 알지 못함(MAC 주소를 모름)
    • ARP 요청은 스위치의 다른 모든 호스트로 브로드캐스트되어 이런 질문을 함 “나는 IP 주소가 abcd 인 호스트와 통신하고 싶어. 이 호스트의 MAC 주소가 뭐야?”
    • 호스트 B가 호스트 A와 동일한 스위치(또는 Layer2 브로드캐스트 도메인)에 있는 경우 호스트B는 ARP에 응답하고 호스트A에 MAC 주소 제공
    • 스위치에 연결된 호스트는 동일한 스위치의 다른 호스트 및 인터페이스와 함께 Layer2 브로드캐스트 도메인을 구성함
    • 쉽게 이해하기 위해, 브로드캐스트 도메인을 단일 LAN 연결로 생각해보자. 스위치는 연결된 모든 호스트의 모든 MAC 주소를 학습하고 모든 MAC 주소에 도달 가능한 물리적 포트도 알고있음
    • 스위치를 사용하면 이러한 브로드캐스트 도메인을 분할 가능. 하나의 브로드캐스트 도메인에 너무 많은 호스트가 있으면 네트워크에 적합하지 않은 브로드캐스트 트래픽이 많이 발생할 수 있음
    • 이로인해, 지연이 발생할 수 있고 이를 확인하지 않으면 서비스 중단 및 손실 발생 가능. 스위치는 어떤 LAN 인터페이스가 속해있는지, 즉 그것이 속한 브로드캐스트 도메인을 선택할 수 있는 기능이 있음
    • 가상 LAN 또는 VLAN을 만들어 이를 수행. 단일 스위치에는 수천개의 VLAN이 동시에 실행될 수 있음
    • 스위치가 직면한 문제는 호스트를 서로 다른 VLAN으로 분리할 때 스위치에 Layer3 기능이 없으면 장치가 VLAN 간에 통신할 수 없다는 것임 ⇒ 여기서 라우터나 L3 스위치가 등장함
  • Layer3 스위치 기능
    • Layer3 스위치는 Layer2와 Layer3 모두에서 작동하는 콤보 장치임 ⇒ Layer3 스위치는 포트 간에 이더넷 프레임을 전달하지만, 라우팅 테이블과 Layer3 IP 주소를 기반으로 라우팅 결정도 내릴 수 있음
    • 예를 들어, 3개의 서로 다른 VLAN이 구성된 Layer2 스위치가 있다고 가정했을 때, VLAN 2의 호스트가 VLAN3(다른 Layer3 서브넷에 속함)의 호스트와 통신하려는 경우, L2 스위치는 VLAN간에 트래픽을 라우팅할 수 없음
    • 이제 3개의 서로 다른 VLAN이 있는 Layer3 스위치가 있다고 가정해보자. 이제 이 유형의 스위치는 Layer3 서브넷과 IP 주소를 알고있고 이러한 세그먼트 간 패킷을 라우팅할 수 있기 때문에 VLAN 간 라우팅도 제공 가능
    • 위의 다이어그램에서도 볼 수 있듯이 Layer3 스위치는 호스트를 직접 연결할 수 있으며 VLAN 간의 라우팅을 제공하기 위해 다른 Layer2 스위치도 연결 가능
  • 라우터의 기능
    • 라우터를 사용하면 서로 다른 LAN 또는 네트워크가 서로 통신할 수 있음
    • 라우터의 메모리에 저장된 라우팅 테이블 내부에는 장치가 알고 있는 모든 네트워크와 거기에 도달하는 방법에 대한 자세한 정보가 있음
    • 위의 그림과 같이 네트워크 스위치는 내부 호스트와 VLAN에 이더넷 연결을 제공하기 위해 대부분 내부 LAN 네트워크에 존재함
    • 반면에, 라우터는 일반적으로 내부 LAN과 외부 WAN(예: 인터넷 또는 다른 WAN 네트워크) 사이의 경계를 제공하기 위해 경계에 연결됨
    • 라우팅 테이블은 동적으로(Dynamic routing protocol) 또는 정적으로(관리자가 장치에서 고정 경로 구성)으로 구축됨
    • 라우터가 특정 대상 IP에 도달해야 하는 패킷을 수신하면 라우팅 테이블에서 일치하는 항목을 찾음. 일치하는 항목이 발견되면, 라우터는 해당 대상 IP에 대한 다음 hop(컴퓨터 네트워크에서 출발지와 목적지 사이에 위치한 경로의 한 부분) 게이트웨이가 무엇인지 확인하고 적절한 물리적 또는 논리적 인터페이스로 패킷을 보냄
    • 컴퓨터와 프린터가 있고 사무실의 공통 서브넷에 IP가 있는 두 개의 장치가 있는 경우 두 장치가 통신하는 데 스위치만 있으면 됨. 공통 VLAN에 배치하고 트래픽을 직접 보낼 수 있음
    • 그러나 다른 네트워크에 있는 프린터에서 멀리 떨어진 사무실에 있는 무언가를 인쇄하기 위해 컴퓨터가 필요하다고 가정했을 때, 컴퓨터에서 패킷을 가져와 별도의 서브넷에 있는 IP에 도달하도록 지시할 수 있는 경로에 라우터가 필요

4. Layer2 스위치 vs. 라우터

  • 스위치
    • 스위치를 사용하면 장치가 공통 네트워크에서 통신할 수 있을 뿐만 아니라 이러한 네트워크를 더 작은 브로드캐스트 도메인으로 나눌 수 있음.
    • 스위치는 레이어 2의 호스트 간에 트래픽을 전달하기 위해 연결된 모든 호스트의 모든 MAC 주소를 학습함.
  • 라우터
    • 라우터를 사용하면 레이어 3에서 서로 다른 네트워크를 사용하고 트래픽을 서로 전달할 수 있음
    • 라우터는 다른 네트워크에 도달하는 방법에 대한 맵("라우팅 테이블")을 구축하고 "트래픽 cops"로 작동하여 어디로 가야 하는지 지시. 먼 목적지에 도달하기 위해 패킷을 보냄.
  • 스위치와 라우터는 몇 가지 하드웨어적 차이점이 존재
    • 스위치 연결은 이더넷 포트(예: electrical RJ45, fiber gigabit ports 등)만 사용하여 호스트를 네트워크에 연결
    • 라우터에는 ADSL, 케이블, 광섬유, 전화 접속 등(이더넷 포함)과 같은 다양한 유형의 포트가 있을 수 있음

5. Layer3 스위치 vs. 라우터

  • Layer3 스위치는 순수한 레이어 2 기능 외에 라우팅 기능도 제공할 수 있음
  • Layer3 스위치와 라우터의 유사점
    • 두 장치 모두 각 IP 패킷이 장치를 통해 전달되는 방법을 결정하기 위해 라우팅 테이블을 가지고 있음
    • 둘 다 각 패킷 헤더에 포함된 대상 IP 주소를 살펴본 다음 각 대상 네트워크에 도달할 수 있는 위치와 관련된 정보를 제공하는 라우팅 테이블을 확인
    • 라우팅 테이블을 구축하기 위해 L3 스위치와 라우터 모두 OSPF , RIP 등과 같은 동적 라우팅 프로토콜 또는 정적으로 구성된 경로를 지원
    • 또한 두 장치 모두 네트워크 간의 트래픽을 허용하거나 차단하기 위해 패킷(일반적으로 액세스 제어 목록 사용)에 대한 트래픽 제어를 시행할 수 있음(이러한 액세스 제어 목록은 일반적으로 TCP 계층 4까지 작동할 수 있으므로 포트 수준에서도 트래픽을 제어할 수 있음(예: 포트 443에서 IP 5.5.5.5에 대한 트래픽 허용))
  • Layer3 스위치와 라우터의 차이점
    • 주요 차이점은 라우터 장치는 다양한 유형의 WAN 인터페이스를 지원하는 반면 스위치는 여러 이더넷 포트(예: RJ45 전기 포트 또는 다중 기가비트 광섬유 포트)로 구성된다는 것
    • 반면에 라우터는 광섬유, ADSL, 케이블, ATM, 프레임 릴레이, 전기 이더넷 등과 같은 다양한 WAN 인터페이스를 지원할 수 있음
    • 또한 스위치의 포워딩 성능은 하드웨어 ASIC 칩을 사용하여 패킷 포워딩을 수행하는 반면 라우터는 일반적으로 소프트웨어 라우팅(일부 고급 라우터 제외)을 사용하기 때문에 라우터보다 훨씬 높음
    • Layer3 스위치는 라우터와 같은 기본 라우팅 기능을 제공할 수 있지만, 이는 스타 토폴로지의 이더넷 물리적 연결(LAN 네트워크)에서만 가능
    • 반면에 라우터는 QoS(트래픽에 대한 서비스 품질), 터널 종료(예: VPN의 경우 GRE 또는 IPSEC), NAT(네트워크 주소 변환), BGP 등과 같은 고급 라우팅 프로토콜과 같은 고급 네트워킹 기능을 지원
  • Layer3 스위치의 사용 사례
    • 레이어 3 스위치는 주로 캠퍼스 LAN 네트워크, 데이터 센터 및 대규모 내부 기업 네트워크에서 VLAN 간의 라우팅을 제공하는 데 사용
    • 포트 밀도가 크기 때문에 여러 내부 호스트를 수용할 수 있으며 기가비트, 10기가비트 등과 같은 초고속으로 작동
    • 대규모 내부 LAN을 여러 VLAN으로 분할하고 VLAN 간에 라우팅을 제공하려는 경우 L3 스위치가 이러한 시나리오에 이상적
  • 라우터의 사용 사례
    • 주요 사용 사례는 위에서 설명한 대로 WAN 연결을 위한 것
    • 특히, WAN redundancy 또는 인터넷 액세스 redundancy를 제공하려는 경우 라우터는 여러 WAN 네트워크에 연결하고 BGP(Border Gateway Protocol의 약자로 서로 다른 AS(망 식별번호 Autonomous System)를 연결해 주는 경계 게이트웨이 프로토콜)를 사용하여 페일오버(컴퓨터 서버, 시스템, 네트워크 등에서 이상이 생겼을 때 예비 시스템으로 자동전환되는 기능) 및 로드밸런싱(애플리케이션을 지원하는 리소스 풀 전체에 네트워크 트래픽을 균등하게 배포하는 방법)을 라우팅하는데 이상적
  • 비교표레이어 3 스위치 라우터
    OSI 모델의 Layer 2 및 Layer 3 모두에서 작동 OSI 모델의 Layer 3에서만 작동
    이더넷 인터페이스만 지원(전기, 광) 이더넷, ADSL, 케이블, 파이버, ATM, E1 등과 같은 다양한 유형의 인터페이스 지원
    더 높은 전달 처리량 낮은 포워딩 처리량
    기본 라우팅 기능 지원 BGP, ISIS, MPLS 지원, VRF 등과 같은 더 많은 프로토콜로 고급 라우팅 기능을 지원
    고급 네트워킹 기능 없음 QoS, VPN, 터널링(GRE, IPSEC), NAT, VRF 등과 같은 고급 네트워킹 기능 지원
    비용 절감 더 높은 비용
    주로 내부 네트워크, 데이터 센터, 캠퍼스 LAN 등에서 사용 ISP 환경 등에서 LAN/WAN 사이의 경계 장치로 주로 사용
    높은 포트 밀도 낮은 포트 밀도
    더 작은 라우팅 테이블 대형 라우팅 테이블
  • 일부 라우터 모델 예
    • 라우터는 다양한 사양과 기능으로 구분할 수 있음
    • 예를 들어, 네트워크 인터페이스의 수와 유형(주로 WAN 및 LAN), 하드웨어 성능(예: 처리할 수 있는 초당 패킷 수), 소프트웨어 기능(예: 지원하는 라우팅 프로토콜) 등
    • 보다 일반적인 범주에는 가정용 라우터, 비즈니스 라우터, 엔터프라이즈 모델, ISP 모델 등이 있음
  • 일부 스위치 모델 예
    • 주로 하드웨어 기능과 가장 중요한 물리적 인터페이스 포트로 구별
    • 거의 모든 최신 스위치는 소형 가정용 모델에서도 최소한 기가비트 이더넷 포트를 지원하며 고급 모델은 10기가비트 포트와 광섬유 포트도 지원함

위의 자료는 다소 예전 기술 상의 스위치3, 라우터의 차이점이고 요즘에는 기술이 발전하면서 L3 스위치가 라우터를 거의 대체가능해졌음

(참조 : https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&blogId=nackji80&logNo=220228728915)

6. IP 패킷의 네트워크 흐름을 살펴보자

네트워크를 통해 컴퓨터 A에서 컴퓨터 B로 전송된 IP 패킷은 여러 네트워크 세그먼트를 통과해야 할 가능성이 높음

일부는 이더넷에 연결되고 일부는 WiFi에 연결됨

모든 네트워크 세그먼트는 다른 매체 액세스 방법을 사용하므로 프레임은 다르지만 패킷은 동일하게 유지됨

  • 장치 A는 이더넷 프레임으로 캡슐화된 IP 패킷을 보냄
  • 스위치는 프레임을 라우터인 다음 장치로 전환하여 프레임을 유지
  • 라우터는 IP 헤더를 살펴보고 프레임을 스트립(캡슐화 해제)함
  • 라우팅할 올바른 인터페이스를 선택한 후 패킷은 WiFi 프레임으로 캡슐화됨
  • WiFi 프레임이 장치 B에 들어오면 장치가 프레임을 캡슐화 해제하고 IP 패킷을 해석함

스위치의 작동방식?

모든 장치에는 MAC 주소라는 하드코딩된 물리적 주소가 있음

컴퓨터가 다른 장치로 IP 패킷을 보내면 목적지인 장치 B의 MAC 주소와 자신의 MAC 주소를 소스로 사용하여 패킷을 프레임으로 캡슐화하여 보냄

프레임이 장치 B에 도착하면, 프레임이 제거되고 IP 패킷이 수신되고 이 과정에서 이더넷 스위치를 통과하게됨

1. 스위칭 프로세스

프레임이 스위치에 도착하면 스위치는 올바른 포트를 통해 프레임을 보내야 하며 이 리디렉션을 스위칭 이라고

프레임이 스위치 포트에 들어가면 스위치는 물리적 포트와 MAC 주소 쌍을 저장하는 메모리의 동적 테이블을 확인하여 프레임을 전달하는 데 사용할 포트를 인지함

  • 이 때, 스위치는 IP 패킷을 조사하지 않고 대상 MAC 주소를 기반으로 프레임을 전달함
  • 스위치는 테이블을 어떻게 구성하나?
    • 스위치는 MAC 학습 이라는 프로세스에서 mac 및 포트 쌍을 학습
    • 프레임이 스위치 포트에 처음 도착하면 스위치는 프레임 내의 소스 MAC 주소를 확인하고 수신된 포트 번호 옆에 저장
    • 이 프로세스는 CAM(Content Addressable Memory) 또는 TCAM(Ternary Content Addressable Memory)으로 알려진 테이블을 작성

  • 아직 스위치에 알려지지 않은 대상 MAC 주소는?
    • 아래 그림에서 장치 B의 MAC 주소는 아직 스위치에 알려지지 않았음
    • 이 B장치의 MAC으로 향하는 프레임이 스위치 포트에 도착하면 스위치는 TCAM 테이블을 참조하고 MAC 주소를 찾지 못하면 수신된 포트를 제외한 모든 포트로 보내는 프레임을 복제함
    • 프레임이 의도되지 않은 모든 장치는 프레임을 삭제하고 장치 B만 이 프레임을 올바르게 해석함
    • 장치 B가 장치 A로 프레임을 다시 보낸 후 스위치는 장치 B MAC을 학습하고 테이블에 저장하고 이미 MAC 및 포트가 있기 때문에 프레임을 장치 A로 직접 전달합니다. (1A).

2. 스위치 및 브로드캐스트 트래픽

스위치는 특히 브로드캐스트 트래픽을 처리함

대상 MAC 주소가 모두 "1"이거나 FF:FF:FF:FF:FF:FF(16진수 표기법)인 프레임은 수신된 포트를 제외한 모든 포트에서 무조건 전송됨

브로드캐스트 트래픽은 ARP(Address Resolution Protocol) 와 같은 이더넷 작업에 매우 중요

⇒ 하지만, 브로드캐스트는 브로드캐스트 폭풍이나 원치 않는 트래픽 처리 또는 리소스 과사용과 같은 심각한 네트워크 문제의 원인이 될 수 있음

⇒ 그렇기 때문에 레이어 2의 적절한 트래픽 분할은 네트워크 보안과 안정성을 위해 매우 중요

3. 그런데, 프레임이 뭐야?

프레임은 패킷이 한 장치 인터페이스에서 다른 장치 인터페이스로 특정 매체를 통과할 수 있도록 하는 정보의 일부임

  • 예를 들어 이더넷은 장치가 네트워크에 액세스하는 방법, 케이블 커넥터의 모양, 전송 속도, 마지막으로 비트 및 주소 구성 방법에 대한 많은 기술 매개변수를 설명함
  • 따라서, 레이어 2는 장치의 매체 또는 인터페이스 유형과 엄격하게 연결됨 (레이어 2는 스위치가 작동하는 곳임, 빨간색으로 표시)

4. 이더넷 프레임

모든 IP 장치는 패킷을 생성하고 네트워크 액세스 유형에 관계없이 네트워크를 통해 전달됨

모든 액세스 유형은 자체 구조를 사용하여 해당 환경에서 데이터를 전달함

이더넷은 이더넷 프레임이라는 구조를 사용(프레임은 아래 그림과 같이 패킷을 "둘러싸고" 있음)

이더넷 환경을 통해 IP 패킷을 전송하기 위해서 이더넷 페이싱 장치는 프레임을 만드는 IP 패킷의 앞뒤에 여분의 비트를 추가함(이 비트 추가 프로세스를 캡슐화 라고 함)

프레임 헤더는 특히 소스 및 대상 MAC 주소를 포함함

소스 MAC 주소는 송신 장치의 물리적 주소이고, 대상 MAC 주소는 동일한 이더넷 세그먼트 내에서 대상 장치의 이더넷(물리적 인터페이스) 주소임

프레임은 이더넷 세그먼트에 고유하므로 프레임이 다수의 매개체와 다수의 분리된 이더넷 세그먼트를 횡단하지 못한다는 점을 기억하자

라우터가 아닌 스위치를 쓰는 경우는 왜 그럴까?

포트 밀도를 위해 설계됨

컴퓨터 간 직접 이더넷 연결이 있는 경우 이더넷 스위치가 필요한 이유는 무엇일까?

⇒ 하지만, 여기서 세 번째 장치를 장치 그룹(이더넷 세그먼트)에 연결해야 하는 경우 어떻게 해야 할까?

⇒ 이 때, 특정 논리를 가진 통신 장치가 필요하고 이것이 스위치의 목적임

이제 많은 수의 사용자와 유선 장치를 연결할 수 있는 장치가 필요함

이것은 라우터를 위한 것이 아님

대부분의 상황에서 라우터는 제한된 수의 포트를 가지고 있기 때문에 더 고급 기능으로 작동하며 더 비쌈

아래와 같이, 라우터가 있는 소규모 홈 네트워크(노트북 4대)를 인터넷 제공업체에 연결해야 한다고 가정해 보았을 때, 포트가 충분하지 않아 불가능 함!

스위치는 이러한 요구 사항에 적합한 매체임

스위치는 많은 이더넷 장치를 유선 연결하는 데 가장 적합한 네트워크 장치로 간주됨

라우터의 작동방식?

수십억 개의 컴퓨터 장치 간의 통신을 허용하려면 중간 네트워크 장치가 필요

⇒ 교차로 및 도로 표지판과 같은 라우터는 패킷을 소스에서 목적지로 적절하게 보냄

목적지 IP 주소를 검색하는 IP 패킷 헤더를 조사하고(패킷 헤더에 소스 및 목적지 IP가 포함됨) 로컬 라우팅 테이블을 기반으로 패킷을 목적지를 향하는 다음 홉으로 라우팅함

따라서 라우터는 레이어 3에서 작동함(IP 패킷은 네트워크 레이어 – 레이어 3의 통신 구조).

비트는 기계를 위한 언어의 한 형태이고 패킷은 네트워크를 가로지르는 이러한 비트의 시리즈(그룹)임

이러한 패킷이 네트워크를 통해 이동하는 메커니즘은 인터넷 프로토콜 덕분에 구성될 수 있음(IP는 공통 규칙, 구조 및 설명의 집합).

일반적으로 우리는 BITS 라고 부르는 기호 를 생성하고 보낼 준비가 된 IP 패킷으로 형성하는 엔드 장치  (노트북 또는 스마트폰과 같은)를 가지고  있음

전송할 정보의 양이 많을수록 더 많은 비트를 전송해야 하고 더 많은 패킷이 형성되어야 함

이러한 IP 패킷은 목적지를 향한 적절한 패킷 라우팅을  담당하는 라우터로 구성된 네트워크 의 경로를 따라 전송됨

라우터가 정보(비트)를 라우팅하는 방법을 이해할 수 있도록 하는 IP 패킷 내 정보는 무엇이 있을까?

  • IP 패킷을 송수신하는 모든 장치에는 단순히  IP 주소 라고 하는 고유한 주소가 있음. IP 주소는 우편 집 주소와 같음  예를 들어, 누군가 시카고에서 애틀랜타로 엽서를 보내려면 봉투에 애틀랜타의 목적지 우편 주소를 적어야 함  이러한 봉투가 시카고 우체국에 도착하면 목적지 우편 주소와 대조하여 애틀랜타 우체국으로 전달됨 애틀랜타의 PO는 목적지 우편 주소(애틀랜타 포스트의 지역)를 기반으로 봉투를 배달할 위치를 알고 있음

  • IP 주소의 목적은 우편 주소와 매우 유사하고 네트워크 내에서 발신자와 수신자를 찾음

  • 라우터는 목적지를 향한 경로를 알고 있기 때문에 패킷을 올바른 방향으로 전달할 수 있음  ⇒ 라우터가 우체국과 같다고 생각하면, 라우팅 프로세스를 이해할 수 있음

1. 라우터가 패킷을 라우팅할 위치를 확인하는 방법

라우터에는 다른 네트워크 장치와 인터페이스하는 포트 또는 더 정확하게는 인터페이스가 있음

인터페이스는 패킷 전달에 사용되는 링크를 연결함.

링크는 케이블, 광섬유 또는 무선일 수 있음

2. 라우팅 프로세스

  1. 패킷이 인터페이스(입구 인터페이스)로 들어 오면  라우터는 패킷을 보내는 데 사용해야 하는 인터페이스(출구 인터페이스)를 알아야 함
  2. 라우터는 알려진 각 DESTINATION과 관련된 항목이 있는 ROUTING TABLE 에 대해 이를 확인
  3. 각 Routing ENTRY 는 목적지 주소와 NEXT HOP device이라고 부르는 경로상의 다음 장치의 주소를 저장
  4. 이제 NEXT HOP 주소가 있으면 라우터는 나가는 인터페이스를 결정 하고 패킷을 보낼 수 있음
  • 정확히 지정된 주소가 있을 때, 다음과 같은 방식으로 동작함

  1. 장치 A는 주소가 192.168.1.20인 장치 B로 IP 패킷을 보내고 이러한 패킷에는 장치 B의 대상 IP 주소(패킷 헤더에 있음)가 포함됨
  2. 패킷이 라우터 1 인터페이스로 오고 라우터는 192.168.1.20인 패킷의 목적지 IP를 분석함
  • 그런 다음 라우팅 항목을 조회하여 가장 일치하는 대상 을 찾음
  • 장치 B의 IP에 도달하려면 패킷이 라우터 2(10.254.2.1을 통해)를 통해 보내져야 함
  • 그런 다음 라우터 2가 인터페이스 2에서 액세스 가능한지 확인함
  • 이제 라우터는 패킷을 보내기 위한 모든 정보를 가지고 있음

목적지까지의 경로를 따라 모든 라우터에서 동일한 프로세스가 발생함

 

부탁드리는 사항

혹시 잘못된 내용이나, 인용/차용 등에 있어 문제의 소지가 되는 내용이 있다면 언제든 알려주시면 큰 도움이 될 것 같습니다!

긴 글 읽어주셔서 감사합니다 :)

 

출처:
네트워크 계층구조와 기기 이미지
https://junroot.github.io/programming/OSI-7계층/
계층별 기기분류
(책) 면접을 위한 CS 전공지식 노트
스위치의 정의
https://www.juniper.net/kr/ko/research-topics/what-is-a-network-switch.html
라우터의 정의
https://aws.amazon.com/ko/what-is/routing/
스위치와 라우터의 기본적인 이해
https://www.iusmentis.com/technology/tcpip/networks/
스위치 vs. 라우터
https://www.networkstraining.com/router-vs-switch-in-networks/
스위치 작동방식
https://www.grandmetric.com/2018/03/08/how-does-switch-work-2/
라우터 작동방식
https://www.grandmetric.com/blog/2018/01/04/how-does-router-work/
패킷 스위칭
https://www.informit.com/articles/article.aspx?p=27569&seqNum=4

 

'Network' 카테고리의 다른 글

IP주소(ARP, 라우팅 테이블, CIDR, DHCP, NAT, IPv4, IPv6)  (8) 2022.11.12

Docker란?

  • Container의 한 종류로 어플리케이션을 패키징할 수 있는 툴 : Application, SystemTools, Dependencies등을 담아, 어떤 OS든 동작이 가능하도록 하여 배포와 관리가 용이

  • Docker Container Image : 가볍고 독립적이며 실행가능한 소프트웨어 패키지로 어플리케이션에 필요한 모든 것들을 포함(code, runtime, system tools, system libraries, settings.)

그럼 Container는 뭔데?

  • Container는 코드와 모든 의존성들을 패키징한 소프트웨어의 표준 단위로 어플리케이션이 여러 컴퓨팅 환경에서 빠르고 신뢰성 있게 실행 가능하도록 함
  • Containers는 Software를 환경에서 독립적일 수 있도록 하며, 소프트웨어가 일관적으로 동작하는 것을 보장함

Docker의 특징?

  • Standard Docker는 Containers에 대한 산업 표준을 생성하고 그러한 Container들은 어디에서든 이동성을 보장
  • Lightweight Containers는 machine의 OS시스템 커널을 공유하기 때문에, Application별 OS를 요구하지 않음. 덕분에, 높은 서버 효율과 서버 및 라이센싱 비용을 절감할 수 있음
  • Secure Application들은 Containers 안에서 더 안전함. 또한, Docker는 강력한 default 독립성을 제공함

그럼, Docker랑 VM이 다른 건 뭔데?

  • Container는 OS를 포함하지 않기 때문에, 훨씬 가벼움

1. Container

  • app layer에서 추상화 되어있음
  • 여러 containers가 같은 machine에서 동작 가능하며 OS kernel을 다른 containers와 공유하나, 각각은 사용자 공간에서 독립된 프로세스들로 동작함
  • Containers는 VMs보다 공간을 적게 차지하며(container images are typically tens of MBs in size), 더 많은 applications를 다룰 수 있고 더 적은 VMs와 OS를 필요로 함

2. VM

  • 물리적 hardware layer에서의 추상화(하나의 서버에서 여러 서버들로)
  • hypervisor가 여러 VMs가 하나의 machine에서 동작될 수 있도록 함
  • 개별 VM은 필요한 OS, application, binaries, libraries의 full copy를 가짐(taking up tens of GBs)
  • VM은 부팅이 느릴 수 있음
  • 자체 운영 체제(Operating System, OS)를 포함하고 있어 여러 리소스 집약적인 기능을 한 번에 수행할 수 있음
  • VM에서 사용할 수 있는 리소스가 늘어남에 따라 전체 서버, OS, 데스크톱, 데이터베이스, 네트워크의 추상화, 분할, 복제, 에뮬레이션이 가능

3. 그 외 Container와 VM의 차이

  • 하이퍼바이저는 VM에서 다양한 운영 체제를 구동할 수 있도록 허용하지만, 컨테이너는 단일한 유형의 운영 체제만 구동할 수 있음

4. 컨테이너가 HyperVisor를 완전히 대체할 수 있나?

  • 컨테이너는 경우에 따라 하이퍼바이저의 대안으로 여겨지기도 하지만, 컨테이너와 가상화는 각기 다른 요구 사항을 충족하므로 확실히 그렇다고 할 수는 없음

그럼 HyperVisor는 뭔데?

1. 정의

  • 가상머신을 생성하고 구동하는 소프트웨어
  • 가상 머신 모니터(Virtual Machine Monitor, VMM)라고도 불리는 하이퍼바이저는 하이퍼바이저 운영 체제와 가상 머신의 리소스를 분리해 VM의 생성과 관리를 지원

2. 용어

  • 하이퍼바이저로 사용되는 물리 하드웨어를 호스트라고 하며 리소스를 사용하는 여러 VM을 게스트라고 함

3. 역할

  • 하이퍼바이저는 CPU, 메모리, 스토리지 등의 리소스를 처리하는 풀로, 기존 게스트 간 또는 새로운 가상 머신에 쉽게 재배치할 수 있음

4. 동작과정

  • 모든 하이퍼바이저에서 VM을 실행하려면 메모리 관리 프로그램, 프로세스 스케줄러, I/O(입력/출력) 스택, 기기 드라이버, 보안 관리 프로그램, 네트워크 스택과 같은 운영 체제 수준의 구성 요소가 필요
  • 하이퍼바이저는 할당되었던 리소스를 각 가상 머신에 제공하고, 물리 리소스에 대해 VM 리소스의 일정을 관리
  • 물리적 하드웨어는 계속해서 실행 작업을 수행하므로 하이퍼바이저가 일정을 관리하는 동안 CPU가 VM에서 요청한 대로 CPU 명령을 계속 실행

5. HyperVisor의 이점

  • 서로 다른 여러 개의 운영 체제를 나란히 구동할 수 있으며, 하이퍼바이저를 사용해 동일한 가상화 하드웨어 리소스를 공유
  • 바로 이러한 부분이 가상화의 핵심적인 이점입니다. 가상화가 없다면 하드웨어에서 운영 체제를 1개만 구동할 수 있음
  • 기타 참조 : 커널 기반 가상 머신(KVM)은 오픈소스 옵션으로 Linux® 커널에 포함되어 있음

Docker HostOS, GuestOS?

1. Docker의 구조

2. Base Image?

  • Dockerfile example

FROM에 있는 Alpine은 Linux의 distro로, 예를 들어, 다른 distro인 Ubuntu를 대신 사용해도 됨

  • 가령, Redhat Linux (RHEL)을 Host OS로 사용하는 경우에 왜 우리는 alpine을 Base Image로 사용할까? ⇒ 이 경우, 이러한 transform이 일어날 것

3. Linux Kernel과 Linux Distribution에 대해 먼저 이해해보자

  • 모든 distros는 같은 Linux Kernel을 씀
  • 다만, 각 distro의 목적에 최적화하기 위해 약간의 차이가 있을 뿐임(differ only in userland software) ⇒ userland software만 추가적으로 설치하면, 다른 distro 환경을 쉽게 모방이가능
  • 가벼운 가상화는 같은 OS 내부에서 독립적인 공간을 가지는 것임 ⇒ Docker가 FreeBSD나 Windows를 Linux 내부에서 구동할 수 없는 이유
  • Base Image는 Base OS가 아니며, Base Image가 Base OS보가 훨씬 가벼우며 이것이 docker containers가 매우 빠를 수 있는 이유임
  • 오직 distro의 고유(or userland) software만 설치하고 host Lunux kernel을 사용함

4. 그래서 Base Image가 왜 필요한데?

  • docker containers filesystem은 hostOS에서 독립적임 ⇒ docker image 내부의 application은 container로 구동되는 시점에 host filesystem에 접근할 수 없음
  • Container 내부의 application이 다양한 OS 라이브러리들에 의존하고 있을 때, 이 어플리케이션의 구동에 대해 독립성을 유지하기 위해서는 그러한 의존성들을 Docker Image 내부에 패키징할 필요가 있음 ⇒ 이러한 이유로 다양한 Linux distros에 대하여 가능한 base images들이 존재함
    (참조 : 영어원문)
    So an application packaged inside a docker image wont be able to see the host filesystem(unless attached as volume) at the time of running as a container.This is why there are base images available for various Linux distros.
    So, imagine the application you have inside the container depends on various OS libraries, so maintaining the isolation if we want to run the application we will have to package those dependencies too inside the Docker Image.
    The docker containers filesystem is isolated from the host OS.
  • 또한, distro를 가지는 것은 그것의 package management 기능(yum/apt-get 등)을 가능하게 함 ⇒ docker image에 대하여 요구되는 의존성들을 디자인하는 것에 편의성을 제공
  • Base Image 모습에 대해 다시 정리하자면, GuestOS는 완전한 OS가 아니며 단지 userland 혹은 distro 고유 libraries만으로 구성되어있음(essentially a part of "Bins/Libs" box of docker architecture and not a virtualized environment)

Linux Kernel은 그럼 뭔데?

1. Linux Kernel 정의

  • Linux Kernel은 Linux 운영체제의 주요 구성 요소이자 컴퓨터 하드웨어와 프로세스를 잇는 핵심 인터페이스임
  • 커널이라는 이름은 단단한 껍질 안의 씨앗처럼 OS 내에 위치하고 전화기, 노트북, 서버 또는 컴퓨터 유형에 관계없이 하드웨어의 모든 주요 기능을 제어하기 때문에 붙은 이름임

2. Linux Kernel의 기능?

  • 커널의 기능?
    1. 메모리 관리 메모리가 어디에서 무엇을 저장하는 데 얼마나 사용되는 지를 추적
    2. 프로세스 관리 어느 프로세스가 중앙 처리 장치(CPU)를 언제 얼마나 오랫동안 사용할지 결정
    3. 장치 드라이버 하드웨어와 프로세스 사이에서 중재자/인터프리터 역할을 수행
    4. 시스템 호출 및 보안 프로세스의 서비스 요청을 수신
  • 올바르게 구현된 커널은 사용자가 볼 수 없으며 커널 공간이라는 자신만의 작은 작업 공간에서 메모리를 할당하고 저장되는 모든 항목을 추적
  • 웹 브라우저 및 파일과 같이 사용자가 볼 수 있는 것을 사용자 공간이라 하고 이러한 애플리케이션은 시스템 호출 인터페이스(SCI)를 통해 커널과 통신

(비유해보자면,)

  • 커널은 강력한 경영진(하드웨어)을 위해 분주하게 일하는 개인 비서.
  • 비서의 할 일은 직원 및 대중(사용자)으로부터 수신되는 메시지 및 요청(프로세스)을 경영진에게 전달하고, 어디에 무엇이 저장되어 있는지 기억(메모리)하고, 특정한 시간에 누가 경영진을 얼마 동안 만날 수 있는지 결정하는 것

3. OS 내에서 Kernel의 위치

  • 커널과 관련하여 Linux 시스템은 다음과 같은 3개 레이어로 구성
    1. 하드웨어: 시스템의 토대가 되는 물리적 머신으로, 메모리(RAM)와 프로세서 또는 중앙 처리 장치(CPU) 그리고 입출력(I/O) 장치(예: 스토리지네트워킹 및 그래픽)로 구성. CPU는 계산을 수행하고 메모리를 읽고 씀.
    2. Linux 커널: OS의 핵심. 메모리에 상주하며 CPU에 명령을 내리는 소프트웨어임.
    3. 사용자 프로세스: 실행 중인 프로그램으로, 커널이 관리. 사용자 프로세스가 모여 사용자 공간을 구성. 사용자 프로세스를 단순히 프로세스라고도 함. 또한, 커널은 이러한 프로세스 및 서버가 서로 통신(프로세스 간 통신 또는 IPC라고 함)할 수 있도록 해줌.
  • 시스템에서 실행되는 코드는 커널 모드 또는 사용자 모드라는 두 가지 모드 중 하나로 CPU에서 실행
  • 커널 모드에서 실행 중인 코드는 하드웨어에 무제한 액세스가 가능한 반면, 사용자 모드에서는 CPU 및 메모리가 SCI를 통해 액세스하는 것을 제한
  • 메모리도 이와 유사하게 구분(커널 공간 및 사용자 공간). 이러한 두 가지 작은 세부 사항이 보안컨테이너 구축 및 가상 머신을 위한 권한 구분과 같은 복잡한 작업의 토대가 됨
  • 또한, 이는 프로세스가 사용자 모드에서 실패할 경우 손상이 제한적이며 커널에 의해 복구될 수 있음을 의미
  • 그러나 커널 프로세스는 메모리와 프로세서에 액세스할 수 있기 때문에 충돌이 발생하면 시스템 전체가 중지될 수 있음.
  • 안전 장치가 마련되어 있고 경계를 넘기 위해서는 권한이 필요하기 때문에, 사용자 프로세스 충돌은 일반적으로 큰 문제를 유발하지는 않음
  • 또한 Linux 커널은 실시간 패치 적용 중에 계속 작업할 수 있으므로 패치가 보안 수정에 적용되는 동안 다운타임이 발생하지 않음

Linux distro는 그럼 뭐야?

1. 정의

  • A complete Linux system package
  • 대부분의 distributions는 특정 사용자 그룹을 위해 customized되어있음

2. Linux distro가 그래서 뭔데?

  • 리눅스 OS는 하나의 조직에서 생산되지 않음(다른 조직과 사람들이 다른 parts를 작업 : Linux kernel (the core of the operating system), the GNU shell utilities (the terminal interface and many of the commands you use), the X server (which produces a graphical desktop), the desktop environment (which runs on the X server to provide a graphical desktop), and more. System services, graphical programs, terminal commands 등)
  • 원한다면, the Linux kernel, GNU shell utilities, Xorg X server, and every other program on a Linux system의 source 코드를 저장하고 사용자 스스로 조립이 가능
  • 하지만, 이는 굉장히 많은 시간과 노력을 필요로 함(involving with making all the different programs work properly together)
  • Linux distribution이 이러한 일을 대신 해주는 것(taking all the code from the open-source projects and compiling it for you, combining it into a single operating system you can boot up and install)
  • Linux distro는 여러 결정을 대신해줌(default desktop environment, browser, and other software)
  • 또한, 새로운 software를 설치하거나 software의 새 버전을 업데이트하는 것에 대해서도 precompiled, packaged form을 제공하여 쉽고 빠르게 설치가 가능

Container환경에서 Alpine을 왜 많이 써?

  • Alpine Linux? 
    musl libc과 busybox를 기반으로한 security-oriented, lightweight Linux distribution
  • Alpine Linux의 특징 
    • Small
      - musl libc and busybox로 생성되어 작고 리소스 효율적임(A container requires no more than 8 MB and a minimal installation to disk requires around 130 MB of storage.)
      - Binary packages가 축소 및 분할됨으로써, install에 대한 컨트롤 능력을 높여줌 => 환경을 가능한 간단하고 효율적으로 유지
    • Simple
      - Alpine은 고유의 패키지매니저를 사용하고(called apk, the OpenRC init system, script driven set-ups) 이것은 노이즈 없이 간단하고 명확한 Linux 환경을 제공함
      - 사용자는 각자의 프로젝트에 필요한 packages를 추가하기만 하면 됨
    • Secure 
      - Alpine Linux는 security를 염두에 두고 설계됨
      - 모든 userland binaries는  Position Independent Executables (PIE) 로 컴파일됨(stack smashing protection)
      - 이러한 사전 보안 기능들은 zero-day와 다른 취약점들과 관련된 전체 Classes의 악용을 예방할 수 있음

 => Small, Secure라는 특징으로 인해, Alpine을 Container환경에서 많이 사용하는 것으로 고려됨(다소 주관적 의견일 수 있음)

Docker를 쓰려면 알아야하는 것? (간단요약)

  • Dockerfile : 어플리케이션의 실행 환경을 정의
  • Image : 어플리케이션의 실행 환경의 SnapShot
  • Container : Image를 담아서 실행
    • Image가 Class라면, Container는 Instance
    • 이미지는 불변, 각 컨테이너는 변경가능(각 컨테이너 변경사항은 이미지에 영향을 미치지 않음)

Docker의 사용과정? (간단요약)

  • 각 컨테이너는 독립된 존재이므로, host의 port와 컨테이너의 port를 연결해줘야함 : port forwarding

추가적으로 알면 좋은 것

1. Docker Compose

Docker Compose 로 local 개발 환경 쉽게 관리하기

 

Docker Compose 로 local 개발 환경 쉽게 관리하기

앗! local 환경 관리 신발보다 싸다 by 강남언니 블로그

blog.gangnamunni.com

부탁드리는 사항

혹시 잘못된 내용이나 인용과 관련해서 문제되는 내용이 있다면, 그에 대해 알려주시면 정말 감사하겠습니다!

출처:
블로그에 사용한 이미지
https://www.docker.com/
https://www.youtube.com/watch?v=LXJhA3VWXFA&t=573s
https://www.youtube.com/watch?v=EbTJtanJUfE&list=PLuHgQVnccGMDeMJsGq2O-55Ymtx0IdKWf&index=3 HyperVisor
https://www.redhat.com/ko/topics/virtualization/what-is-a-hypervisor
Linux distros
https://www.techtarget.com/searchdatacenter/definition/Linux-distros-Linux-distribution https://www.howtogeek.com/132624/htg-explains-whats-a-linux-distro-and-how-are-they-different/ https://www.geeksforgeeks.org/what-are-linux-distributions/ https://www.suse.com/suse-defines/definition/linux-distribution/
Linux Conainers on Wins
https://www.docker.com/blog/preview-linux-containers-on-windows/ http://www.floydhilton.com/docker/2017/03/31/Docker-ContainerHost-vs-ContainerOS-Linux-Windows.html Conatiner
https://azure.microsoft.com/en-in/resources/cloud-computing-dictionary/what-is-a-container/#overview https://www.docker.com/resources/what-container/
Linux Kernel
https://www.redhat.com/ko/topics/linux/what-is-the-linux-kernel
Docker Compose
https://blog.gangnamunni.com/post/docker-compose-for-local-env/
Container와 Alpine Linux
https://www.alpinelinux.org/
https://velog.io/@dry8r3ad/why-alpine-linux
HostOS, GuestOS
https://www.linkedin.com/pulse/docker-host-os-guest-base-image-etc-abhijit-mazumder https://learn.microsoft.com/en-us/answers/questions/373498/are-containers-independent-of-host-os.html
Docker 입문
https://www.44bits.io/ko/post/easy-deploy-with-docker

 

+ Recent posts