[한 권으로 읽는 컴퓨터 구조와 프로그래밍]5장 컴퓨터 아키텍처와 운영체제
컴퓨터는 어떻게 프로그램과 메모리를 조직적으로 관리할까
서론
- 컴퓨터 아키텍처: 컴퓨터의 여러 구성요소를 배치하는 방법
- 멀티태스킹 ⇒ 여러 프로그램 실행(또는 실행하는 것처럼 보이게) ⇒ 프로그램 실행 제어를 위한 감독 프로그램 필요 ⇒ 운영체제(operating system, OS)
기본적인 구조 요소들
- 가장 흔한 두가지 컴퓨터 구조
- 두 구조의 유일한 차이는 메모리 배열
- 폰 노이만 구조
- 데이터 버스와 주소 버스가 하나뿐 ⇒ 메모리에서 동시에 명령어와 데이터를 가져올 수 없어 약간 더 느림
- 하버드 구조
- 동시에 명령어와 데이터를 가져올 수 있어 좀 더 빠름
- 두 번째 메모리를 처리하기 위한 버스가 추가로 필요
프로세서 코어
- 가장 흔한 두 컴퓨터 구조는 모두 CPU가 하나뿐
- CPU는 ALU, 레지스터, 실행 장치의 조합
- 멀티프로세서 시스템
- 단일 CPU보다 더 좋은 성능을 얻어내기 위한 방법
- 여러 CPU를 활용할 수 있도록 프로그램을 병렬화 하는건 어려움
- 그래픽 사용자 인터페이스(GUI)를 제공하는 초기 워크스테이션은 여러 프로그램 동시 실행 가능이 큰 이점이었기에 어렵지만 그래도 이를 이용
- 전력 장벽(power wall)
- 반도체 회로 크기가 줄어들고 기계는 빨라지며 전역을 더 많이 소모하고 단위 면적당 열 발생이 많아진 현상
- 열로 인해 회로가 녹지 않으면서 소형화, 고성능화 하기 어려워짐
- 전력 장벽을 극복한 해결책이 프로세스 코어이다.
- 예전에 CPU라고 부르던 것이 요즘은 프로세서 코어
- 코어가 여러개면 멀티코어 프로세서
마이크로프로세서와 마이크로컴퓨터
- 물리적인 패키징에 따른 구조
- 마이크로프로세서
- 메모리와 I/O가 프로세서 코어와 같은 패키지에 들어있지 않는 경우
- 칩 안에서 메모리가 차지하는 영역이 크기 때문에 일반적으로 마이크로컴퓨터보다 강력
- 보통 큰 시스템에 들어가는 부품
- 마이크로컴퓨터
- 모든 요소를 한 칩 안에 패키징하는 경우
- 마이크로컨트롤러: 마이크로컴퓨터가 가리키는 대상
- 식기세척기 등에서 찾을 수 있는 단일 칩으로 된 작은 컴퓨터
- 마이크로프로세서
- 단일 칩 시스템(System on a Chip, SoC)
- 더 복잡한 마이크로 컴퓨터
- 상대적으로 간단한 온칩(on-chip, 칩 내부에 존재하는) I/O를 제공하는 대신, WiFi 회로 등 더 복잡한 장치 포함
- 핸드폰 등에서 볼 수 있다.
프로시저, 서브루틴, 함수
- 프로시저, 서브루틴, 함수 모두 코드를 재사용하는 주요 수단을 뜻하는 같은 말이다.
- 함수를 실행하려면 함수를 호출하는 부분에서 함수를 실행하고 다시 원래 자리로 돌아와야 한다 ⇒ 어디서 함수로 들어갔는지 기억해야 한다 ⇒ 이 위치가 프로그램 카운터의 값
- 함수 실행 예시
- 함수 실행 후 돌아와야 하는 주소 계산
- 주소 계산과 다른 여러 명령을 위해 차지하는 주소를 감안하여 반환 주소 결정
- 계산된 주소를 함수에서 돌아올 주소 저장을 위해 미리 확보해둔 메모리 위치에 저장
- 함수 호출
- 함수 실행
- 간접 분기(반환)
- 함수 반환 뒤 실행을 이어감
- 함수 실행 후 돌아와야 하는 주소 계산
- 함수 실행 예시
- 함수 호출에는 많은 작업이 필요해 일반적으로 이런 과정을 돕는 명령어가 존재한다.
- ARM 프로세서에는 링크 레지스터를 사용해 분기(Branch with Link, BL) 명령어가 존재
- 함수로 호출하는 명령어와 현재 명령어의 다음 위치를 저장하는 명령어를 합친 것
- ARM 프로세서에는 링크 레지스터를 사용해 분기(Branch with Link, BL) 명령어가 존재
스택
- 재귀적 분할로 JPEG를 압축하는 경우를 예로 든다면,
- 정사각평 안의 픽셀이 모두 같은 색이 될 때까지 분해
- 이를 트리로 표현할 수 있다.
- 트리: 유향 비순환 그래프(directed acyclic graph, DAG)
- 화살표가 있으면 거슬러 올라갈 수 없기 때문에 유향
- 쿼드 트리: 각 노드에서 가지가 4개 뻗어나가는 트리로 공간 데이터 구조(spatial data structure)에 속한다.
- 트리: 유향 비순환 그래프(directed acyclic graph, DAG)
- 위 방법을 사용하는 경우, 함수의 반환값을 저장할 위치는 한 군데 ⇒ 이러한 재귀 함수는 이미 들어 있던 반환값을 덮어써서 돌아갈 위치를 잃어버려 자기 자신을 호출할 수 없음
- 따라서 반환 주소를 여럿 저장할 수 있어야 하고, 호출된 지점으로 반환할 때 어떤 주소를 선택할지 결정할 수 있어야 한다.
- 이를 위해 사용하는게 스택
- Last In, First Out
- push해 넣고 pop 해서 제거
- 스택 오버플로: 더 넣을 공간이 없는데 push하는 경우
- 스택 언더플로: 빈 스택에서 pop하는 경우
- 한계 레지스터: 스택 오버플로를 항상 검사하지 않도록 도움
- 스택 프레임: 함수가 호출될 때마다 스택에 저장되는 데이터의 모음
- 반환 주소 뿐만 아니라 지역 변수도 스택에 함께 저장
인터럽트
- 순서도: 작업이 이뤄지는 순서를 표현하는 다이어그램
- 폴링: 하나의 작업을 더 작은 하위 작업으로 나누고 각 하위 작업 사이에 다른 과정(주의를 기울여야 하는 외부 요소)을 수행
- 다른 과정을 자주 수행해 원하는 결과를 얻을 수는 있겠지만 매번 다른 과정을 수행해 시간이 오래 걸림
- 따라서 실행 중인 프로그램을 잠깐 중단(interrupt)시켜서 주의를 기울여야 하는 외부의 요소에 대응할 수 있는 방법이 필요
인터럽트 시스템
- 적절한 신호가 들어오면 CPU 실행을 잠깐 중단시킬 수 있는 핀이나 전기 연결을 포함
- 핀: 칩에 연결된 전기적 접점
- 많은 프로세서 칩(특히 마이크로컴퓨터)에는 통합 주변장치(integrated peripheral 또는 on-chip I/O 장치)이 들어있고 이러한 장치는 내부적으로 인터렙트 시스템에 연결됨
- 작동 방식
- CPU가 주의를 기울여야 하는 주변 장치는 인터럽트 요청을 생성
- 프로세서는 보통 현재 실행 중인 명령어를 끝까지 실행
- 프로세서는 현재 실행 중인 프로그램을 잠시 중단시키고 인터럽트 핸들러라는 프로그램을 실행
- 인터럽트 핸들러는 함수다.
- 인터럽트 핸들러가 필요한 작업을 다 마치면 원래 실행중이던 프로그램이 중단된 위치부터 다시 실행
- 고려해야 할 사항
- 인터럽트에 대한 응답 시간(response time)
- 서비스: 인터럽트에 대응하는 것
- 인터럽트 처리를 정해진 시간 안에 끝내야 한다.
- 인터럽트를 서비스한 뒤 돌아오기 위해 현재 상태 저장
- 인터럽스 시스템은 서비스 후 돌아올 프로그램 위치를 스택에 저장
- 자신이 덮어쓸 레지스터를 모두 저장해야 한다.
- 이러면 저장해야 하는 요소를 인터럽트 핸들러가 최소화해서 가장 빨리 인터럽트를 서비스할 수 있다.
- 인터럽트 핸들러가 인터럽트 당하는 프로그램과 인터럽트 하는 프로그램 사이의 데이터를 모두 처리하니까 저장해야 하는 요소가 최소화 되는것
- 인터럽트에 대한 응답 시간(response time)
- 일반적으로 인터럽트 핸들러 주소가 저장될 메모리 주소가 존재
- 이 주소에는 여러 인터렙트 벡터가 들어 있고 각 인터럽트 벡터는 CPU가 지원하는 각 인터럽트에 대한 핸들러 주소를 지정
- 인터럽트 벡터: 메모리 위치를 가르키는 포인터
- 인터럽트가 일어나면 컴퓨터는 인터럽트 벡터에 저장된 주소를 보고 제어를 그 주소로 옮김
- 프로그램 카운터를 인터럽트 벡터에 저장된 값으로 설정
- 많은 기계는 물리적인 주소를 벗어나는 주소를 사용하려 하거나, 스택 오버플로가 일어나는 등의 예외 상황에 대해 인터럽트 벡터 제공
- 이 주소에는 여러 인터렙트 벡터가 들어 있고 각 인터럽트 벡터는 CPU가 지원하는 각 인터럽트에 대한 핸들러 주소를 지정
- 인터럽트를 중단시킬 수 있는 마스크(mask)가 있을 수 있음
- 인터럽트간에는 우선순위가 있을 수 있음
- 우선순위가 낮은 인터럽트를 서비스하는 인터럽트 핸들러는 높은 우선순위의 인터럽트가 발생시 일시 중단될 수 있음
- 일정 시간이 지나면 또는 일정 간격으로 인터럽트를 발생하는 내장 타이머가 있을 수 있음
- 운영체제는 다른 일반 프로그램이 접근 못하는 물리적(하드웨어) 인터럽트에 접근 가능
- 운영체제는 가상 인터럽트나 소프트웨어 인터럽트 시스템을 제공하기도 함
- 유닉스 운영체제는 시그널 매커니즘 제공
- 최근 개발된 시스템은 인터럽트 시스템을 이벤트라 함
상대 주소 지정
- 운영체제 또는 운영체제 커널: 여러 프로그램을 동시에 실행할 때 각 프로그램을 서로 전환시켜줄 수 있는 관리자 프로그램
- OS를 시스템 프로그램, OS가 관리하는 다른 모든 프로그램을 사용자 프로그램 또는 프로세스라고 한다.
시분할(time slicing)
- 사용자 프로그램의 실행 시간을 조절하는 스케줄링 기법
- 시간을 정해진 간격으로 나누고 정해진 시간 간격 동안 사용자 프로그램 실행
- 사용자 프로그램 상태 또는 문맥: 레지스터의 상태와 프로그램이 사용 중인 메모리 상태
- 메모리에는 스택도 포함
- 프로그램을 메모리로 불러들이려면 시간이 걸리기 때문에 아주 느리다.
- 따라서 각 프로그램에게 각기 다른 공간을 허용하면 더 빠르게 시분할 실행 가능
인덱스 레지스터
- 1000번지에서 실행되도록 만들어진 프로그램이 있다고 가정하자. 절대 주소 지정을 사용하는 컴퓨터로 이 프로그램을 2000번지에서 읽어 들이면 제대로 실행하지 않는다.
- 인덱스 레지스터를 사용하면 인덱스 레지스터의 값을 명령어에 들어있는 주소와 더해서 유효 주소를 계산한다.
- 사용자 프로그램이 2000번지에서 실행되도록 만들어졌다면 OS는이 프로그램을 3000번지에서 실행하기 위한 인덱스 레지스터를 2000으로 설정할 수 있다.
- 인덱스 레지스터 2000과 1000을 더하면 3000번지가 되니.
상대 주소 지정
- 명령어 주소를 기준으로 하는 상대적인 주소
- 이러한 상대 주소 계산은 프로그래밍 언어 도구가 알아서 함
- 상대 주소 지정을 사용하면 프로그램을 원하는 위치로 자유롭게 재배치할 수 있다.
메모리 관리 장치(memory management unit, MMU)
배경
- 통신작업은 백그라운드에서 실행돼야 하기 때문에 멀티태스킹이 필수
- 문제가 생겨 메모리가 덮여 써지거나 누군가 다른 실행중인 프로그램을 훔쳐 본다거나 하는 일을 막아야 함
- 인덱스 레지스터와 상대 주소 지정은 멀티테스크에 도움이 되지만 이를 막을 순 없음
MMU
- MMU가 들어있는 프로그램은 가상주소 (virtual address)와 물리 주소(physical address)를 구분
- 프로그램은 가상주소를 사용해 작성되고 MMU는 가상주소를 물리주소로 변환
- MMU로 인해 폰 노이만 구조와 하버드 구조 구분 사라짐
- 단일 메모리 버스만 사용하는 폰 노이만 구조의 시스템도 명령어 메모리와 데이터 메모리를 분리해 제공 가능
MMU와 인덱스 레지스터의 차이
- 보통 MMU의 가상 주소 범위는 물리적 메모리 주소보다 크다
- MMU는 가상 메모리 주소를 두 부분으로 나눔
- 주소 하위(LSB 쪽)부분은 물리적 주소범위와 같음
- 주소 상위(MSB 쪽)부분은 페이지 테이블이라는 RAM 영역을 통해 주소를 변환
- 페이지: 메모리를 일정한 바이트 크기로 분할 한 것
- MMU는 가상 메모리 주소를 두 부분으로 나눔
페이지 테이블
- 각 페이지가 물리 메모리상에서 차지하는 실제 위치정보로, 이를 통해 가상메모리에서 시작하는 프로그램을 물리메모리에 넣을 수 있다.
- 모든 내용은 페이지 경계 안에 들어있어야 한다.
- 프로그램 입장에서 가상 메모리는 연속적인 것 처럼 보이지만 실제 물리 메모리 상 위치는 연속적일 필요 없다.
- 프로그램 실행 중에 프로그램이 위치한 물리적 메모리 공간이 바뀔수도 있다.
- 페이지 테이블의 내용은 프로그램 문맥의 일부이다.
- 페이지 크기를 줄이면 페이지 테이블 크기가 늘어난다.
- 공유 메모리 기능: 프로그램들이 협력하는 경우 여러 프로그램의 가상 메모리 중 일부가 같은 물리메모리를 함께 사용
- 페이지 테이블 항목(page table entry)
- 현대 프로세서의 MMU는 페이지 테이블 크기 고정
- 주 메모리에 저장되거나 디스크에 저장
- 일부를 필요할 때에만 자신의 페이지 테이블로 읽음
- 페이지 테이블 제어 비트
- 일부 MMU 설계에서 제공
- 실행 불가 비트(no execute bit)
- 어떤 페이지에 이 비트가 설정되어 있는 경우 CPU가 페이지의 명령어 실행 불가
- 프로그램이 실수로 자기 데이터를 실행하는 경우 방지
- 데이터 부분을 실행할 수 있으면 프로그램을 실행하는 시점에 마음대로 바꿔쓸 수 있어 보안 문제도 발생
- 어떤 페이지에 이 비트가 설정되어 있는 경우 CPU가 페이지의 명령어 실행 불가
- 읽기 전용으로 만드는 비트
- 페이지 폴트(page fault) 예외
- 프로그램이 물리적 메모리에 연관되지 않은 주소에 접근할때 발생하는 예외
- 스택 오버플로 등에 유용하게 사용
가상 메모리
- OS는 프로그램들 사이의 자원 분배를 관리하며 메모리는 이 자원에 포함된다 ⇒ OS는 MMU를 사용해 사용자 프로그램에게 가상 메모리를 제공한다.
요구불 페이징(demand paging)
- 스왑 아웃과 스왑 인 과정을 통해 페이지를 처리하는 방식
- 스왑 아웃(swap out): 프로그램이 요청한 메모리가 사용 가능한 물리적 메모리보다 더 큰 경우 OS는 현재 필요하지 않은 메모리 페이지를 더 느리지만 더 용량이 큰 대용량 저장장치인 디스크로 옮김
- 스왑 인: 스왑 아웃한 페이지에 프로그램이 접근하면 OS가 다시 메모리로 불러들임
최소 최근 사용(least recently used, LRU)
- 스와핑이 일어나면 시스템 성능이 저하되지만 프로그램을 실행 못하는 것보단 낫다.
- 최소 최근 사용은 성능 저하를 막기 위해 페이지 접근을 추적해 스왑 아웃할 페이지를 결정하는 알고리즘
- 최근에 가장 자주 사용된 페이지는 물리 메모리에 남겨두고 최근에 가장 덜 사용된 페이지를 스왑 아웃함
시스템 공간과 사용자 공간
문제
- 사용자 프로그램이 OS가 맞춘 인터럽트 타이머를 변경하거나, MMU의 설정을 바꿔버린다면 MMU는 프로그램을 서로 격리할 수 없다.
해결
- CPU에는 컴퓨터가 시스템 모드에 있는지 사용자 모드에 있는지 결정하는 비트가 어떤 레지스터 안에 들어 있다.
- I/O를 처리하는 명령어 등 일부 명령어는 특권(privileged) 명령어라서 오직 시스템 모드에서만 실행할 수 있다.
- 트랩, 시스템 콜과 같은 특별한 명령어를 이용하면 사용자 모드에서 실행중인 프로그램이 시스템 모드 프로그램(즉 운영체제)에게 요청을 보낼 수 있다.
장점
- 사용자 프로그램으로부터 운영체제를, 사용자 프로그램을 다른 사용자 프로그램으로부터 보호한다.
- 사용자 프로그램이 MMU 등 요소에 손을 댈 수 없어 운영체제가 프로그램에 대한 자원 할당을 전적으로 제어할 수 있다.
- 하드웨어 예외는 오직 시스템 공간에서만 처리된다.
메모리 계층과 성능
문제
- 이제는 CPU가 너무 빨라졌지만 메모리는 그러지 못해 CPU가 메모리를 기다려야 한다.
사용 메모리 종류
- 레지스터: 프로세서에 들어있는 빠르고 비싼 메모리
- 공간이 제일 작지만 빨리 접근 가능
- 주 메모리: 프로세서가 주로 통신하는, 일반적으로 RAM으로 이뤄진 메모리
- 프로세서보다 느리다
- CPU와 주 메모리 사이에는 작은 덩어리가 오간다.
- 디스크 드라이브 등의 대량 저장장치: 프로세서보다 훨씬 느림
- ROM
- 공간이 제일 크지만 제일 느림
- CPU와 디스크 사이에는 큰 덩어리가 오간다.
캐시
- 아주 빠른 온칩 메모리로써 CPU에 추가되는 하드웨어
- 주 메모리보다는 작지만 더 빠르고, 프로세서와 같은 속도로 작동
- 주 메모리에서 데이터를 가져와 캐시에 채운다.
- CPU 메모리 컨트롤러 하드웨어는 메모리에서 연속된 열에 있는 데이터를 한꺼번에 가져와 채운다.
- 열에 있는 데이터를 가져오는 이유
- 열로 DRAM을 읽는게 행으로 읽는 것보다 더 빠르기 때문
- 분기가 없는 경우 프로그램 메모리를 순서대로 읽기 때문
- 프로그램이 사용하는 데이터가 한데 모여 있는 경우가 많기 때문
- 대부분은 연속된 위치에 있는 데이터가 필요하기 때문
- 열에 있는 데이터를 가져오는 이유
- CPU 메모리 컨트롤러 하드웨어는 메모리에서 연속된 열에 있는 데이터를 한꺼번에 가져와 채운다.
- 캐시 실패(cache miss): CPU가 캐시에서 어떤 내용(어떤 주 메모리 주소에 들어있는 내용)을 찾았는데 캐시에 그 데이터가 없어 메모리를 읽어야 하는 경우
- 캐시 실패가 일어나도 CPU는 고속 메모리 접근 모드가 가능해서 캐시를 사용하는게 유리
- 캐시 적중(cache hit): CPU가 원하는 내용을 캐시에서 찾은 경우
캐시의 계층
- 캐시가 CPU에서 멀어질수록 더 느려지고 더 커진다.
- 여러 구성 요소
- 디스패처: 여러 크기의 데이터 덩어리를 가져오고, 채워 넣는 일을 담당하는 논리 회로
- 분기 예측(branch prediction): 캐시를 프리페치해 준비하기 위해 조건 분기 명령어 결과를 예측하는 회로
- 프리페치(prefetch): 미리 페치해 가져옴
- 순서를 벗어나는 실행(out-of-order execution) 처리 회로
- 캐시 일관성
- 어떤 메모리 위치의 데이터를 한 프로세서나 코어가 변경했을 때 이를 다른 프로세서 또는 코어가 어떻게 알 수 있는가? 캐시 일관성을 통해.
- 라이트 스루(write through): 데이터를 캐시에 기록하는 동시에 메모리에도 기록
- 단점: 캐시를 사용하는 장점 대부분을 없앰
코프로세서
- 몇 가지 연산을 코프로세서 회로에 위임하면 프로세서 코어가 더 많은 공간을 확보할 수 있다.
직접 메모리 접근(direct memory access, DMA)
- 주 메모리와 디스크 사이에 단순한 데이터 복사가 자주 일어난다.
- 프로그램이 디스크 드라이브 등의 너 느리고 값싼 메모리에 들어있어 이를 불러올 때
- 가상 메모리 시스템이 스와핑을 하며 디스크에 메모리 내용을 저장하거나 디스크에서 메모리로 데이터나 프로그램을 읽어올 때
- 디스크는 바이트 단위로 접근이 불가능하므로
- 일부 코프로세서는 이러한 데이터 접근(복사 등)을 담당하고 이러한 방식을 DMA라 한다.
메모리상의 데이터 배치
정적 데이터
- 메모리에 담긴, 필요한 크기를 알고있는 데이터
- MMU가 없을 때 폰 노이만 구조와 하버드 구조의 메모리 배치 차이
- 하버드 구조에서는 명령어가 별도의 메모리에 존재
동적 데이터
- 프로그램 실행 전까지는 크기를 알 수 없는 데이터
- 정적 데이터가 차지하는 영역 위 영역에 쌓이며 이를 힙이라 함
- 스택은 아래로 자라며 힙은 위로 자란다.
- 스택과 충돌하지 않게 해야한다.
메모리 배치 변형
- 일부 프로세서는 메모리의 시작 부분이나 끝 부분에 인터럽트 벡터를 저장하는 영역이나 온칩 I/O 장지를 제어하기 위한 레지스터를 확보
MMU가 쓰이는 경우
- 마이크로컴퓨터는 MMU가 없는 경우가 많아 위와 같은 메모리 배치가 흔하다.
- MMU가 쓰이는 경우에는 명령, 데이터, 스택이 각기 다른 물리적 메모리 페이지에 매핑
- 필요에 따라 할당된 크기 변경 가능
- 프로그램이 바라보는 메모리(가상 메모리)는 여전히 위와 같은 메모리 배치 사용
프로그램 실행
- 라이브러리: 목표와 관련된 함수의 모임
여러 조각으로 나뉜 프로그램
- 여러 사람이 한 프로그램을 같이 개발할 수 있다는 장점이 있다.
- 여러 조각을 하나로 엮거나 연결(link)하기 위해 링커(linker)를 이용해 실행
- 각 프로그램은 링크하기 편한 형식의 매개 파일(intermediate file)로 나뉨
- 실행과 링크가 가능한 형식(executable and linkable format, ELF) 형식이 가장 유명
- 여러 섹션으로 나누는 방식
링크
- 정적 링크: 라이브러리를 프로그램의 나머지 부분과 직접 연결해 실행 파일 생성
- 동적 링크: 공유 라이브러리를 사용
- 여러 프로그램이 같은 라이브러리를 사용하는 경우가 많아 정적 링크를 쓰면 메모리 낭비
- 공유 라이브러리 함수를 작성할 때에는 호츨하는 프로그램의 스택과 힙을 사용할 수 있도록 설계되어야 한다.
런타임 라이브러리
- 진입점(entry point): 프로그램의 첫 번째 명령어가 위치한 주소
- 프로그램의 조각이 모두 합쳐져 하나의 실행파일(executable)을 이룰 때 런타임 라이브러리가 추가
- 이 런타임 라이브러리에 있는 명령어가 먼저 실행된 뒤 진입점 명령어가 실행됨
- 역할
- 메모리 설정을 책임짐 ⇒ 스택과 힙 영역을 설정
- 데이터의 초깃값 설정
- 이런 값은 실행 파일에 들어 있고 시스템에서 메모리를 할당받은 직후 실행 파일에서 메모리로 복사되어야 함
메모리 전력 소비
- 데이터를 메모리에서 옮기면 전력이 소비되며 모바일 장치에서 이는 중요
This post is licensed under CC BY 4.0 by the author.