프로세스와 스레드의 차이
프로세스
실행중인 프로그램으로, 메모리에 적재되어 CPU의 할당을 받을 수 있는 것을 말함.
OS로부터 주소공간, 파일, 메모리등을 할당받으며, 이것들을 총칭하여 프로세스라고한다.
할당받는 메모리 공간
- 프로세스 스택 : 함수의 매개변수, 복귀주소, 로컬 변수 같은 임시자료를 저장
- 데이터 섹션 : 전역변수들을 수록
- 힙 : 프로세스 실행 중에 동적으로 항당되는 메모리
프로세스 제어 블록(Process Control Block, PCB)
PCB는 특정 프로세스에 대한 중요한 정보를 저장하고 있는 OS의 자료구조이다. OS는 프로세스를 관리하기 위해 프로세스의 생성과 동시에 고유한 PCB를 생성한다.
프로세스는 CPU를 할당받아 작업을 처리하다가도, 시간이 지나면 진행하던 작업을 저장하고 CPU를 반환해야하는데, 이때 작업의 진행상항은 PCB에 저장하게 된다. 그리고 다시 CPU를 할당 받으면 PCB에 저장된 내용을 불러와 이전에 종료된 시점부터 다시 수행한다.
PCB에 저장되는 정보
- 프로세스 식별자(process ID, PID)
- 프로세스 상태 : new, ready, running, waiting, terminated 등의 상태를 저장
- 프로그램 카운터 : 프로세스가 다음에 실행할 명령어의 주소
- CPU 레지스터
- CPU 스케쥴링 정보 : 프로세스의 우선순위, 스케쥴 큐에 대한 포인터 등
- 메모리 관리 정보 : 페이지 테이블 또는 세그먼트 테이블 등과 같은 정보를 포함
- 입출력 상태 정보 : 프로세스에 할당된 입출력 장치들과 열린 파일 목록
- 어카운팅 정보 : 사용된 CPU 시간, 시간제한, 계정번호 등
스레드 (Thread)
프로세스의 실행단위. 한 프로세스 내에서 동작되는 여러 실행흐름으로 프로세스 내의 주소 공간이나 자원을 공유할 수 있음.
스레드는 스레드 ID, 프로그램 카운터, 레지스터 집합, 스택으로 구성된다. 같은 프로세스에 속한 다른 스레드와 코드, 데이터 섹션, 그리고 열린파일이나 신호와 같은 운영체제 자원들을 공유한다.
하나의 프로세스를 다수의 실행단위로 구분하여 자원을 공유하고 자원의 생성과 관리의 중복성을 최소화하여 수행 능력을 향상 시키는 것을 멀티 스레딩이라고 한다. 이 경우 각각의 스레드는 독립적인 작업을 수행해야 하기 때문에 각자의 스택과 PC 레지스터 값을 갖고 있다.
스택을 스레드마다 독립적으로 할당하는 이유
스택은 함수 호출시 전달되는 인자, 되돌아갈 주소값 및 함수 내에서 선언하는 변수 등을 저장하기 위해 사용되는 메모리 공간이므로, 스택 메모리 공간이 독립적이라는 것은 독립적인 함수 호출이 가능하다는 것이고, 이는 독립적인 실행 흐름이 추가되는 것이다. 따라서 스레드의 정의에 따라 독립적인 실행 흐름을 추가하기 위한 최소 조건으로 독립된 스택을 할당한다.
PC 레지스터를 스레드마다 독립적으로 할당하는 이유
PC 값은 스레드가 명령어의 어디까지 수행하였는지를 나타나게 된다. 스레드는 CPU를 할당 받았다가 스케쥴러에 의해 다시 선점당한다. 그렇기 때문에 명령어가 연속적으로 수행되지 못하고 어느 부분까지 수행했는지 기억할 필요가 있다. 따라서 PC 레지스터를 독립적으로 할당한다.
멀티 스레드
멀티 스레딩의 장점
여러 프로세스를 이용하여 동시에 처리하던 일을 스레드로 구현할 경우, 메모리 공간과 시스템 자원 소모가 줄어들게 된다. 스레드간의 통신이 필요한 경우에도 전역 변수의 공간 또는 Heap 영역을 사용하여 데이터를 주고받을 수 있다. 그렇기 때문에 프로세스 간 통신보다 통신방법이 훨씬 간단하다. 심지어 스레드의 context switch는 프로세스 context switch와는 달리 캐시 메모리를 비울 필요가 없기 때문에 더 빠르다. 따라서 시스템의 throughput이 향상되고 자원소모가 줄어들며, 따라서 프로그램은 더욱 빨라진다.
문제점
공유하는 자원이 있다. 따라서 서로 다른 스레드가 데이터와 힙 영역을 공유하기 때문에 어떤 스레드가 다른 스레드에서 사용중인 변수나 자료구조에 접근하여 엉뚱한 값을 읽어오거나 수정할 수 있다.
따라서 멀티스레딩 환경에서는 동기화 작업이 필요하다. 동기화를 통해 작업처리 순서를 컨트롤 하고 공유자원에 대한 접근을 컨트롤 하는 것이다. 하지만 이로인해 병목 현상이 발생하여 성능이 저하될 가능성이 높다. 그러므로 과도한 락으로 인한 병목현상을 줄여야한다.
멀티 스레드 vs 멀티 프로세스
멀티 스레드
- 멀티 프로세스보다 적은 메모리 공간 차지. context switch가 빠름.
- 공유된 자원으로 인한 오류로 전체 스레드가 영향을 받을 수 있음. 동기화에 시간이 걸림
멀티 프로세스
- 하나의 프로세스가 죽어도, 다른 프로세스에는 영향을 끼치지 않음.
- 많은 메모리, CPU 차지.
두 가지가 서로 상반된 장/단점을 가지고 있기에, 동작하는 시스템에 따라 적합/부적합이 구분된다. 따라서 시스템의 특징에 따라 적합한 동작 방식을 선택하고 적용해야한다.
스케쥴러
프로세스를 스케쥴링 하기 위한 3가지 종류의 Queue
- Job Queue : 현재 시스템 내에 있는 모든 프로세스의 집합
- Ready Queue : 현재 메모리 내에 있으면서 CPU를 잡아서 실행되기를 기다리는 프로세스의 집합
- Device Queue : Device I/O 작업을 대기하고 있는 프로세스의 집합
각각의 Queue 에 프로세스들을 넣고 빼주는 스케쥴러에도 크게 세 가지 종류가 존재함
**장기 스케줄러(Long term scheduler or job scheduler) **
많은 프로세스들이 한번에 메모리에 올라올 경우, 대용량메모리(디스크)에 임시로 저장된다. 이 pool에 저장되어있는 프로세스 중 어떤 프로세스에 메모리를 할당하여 ready queue로 보낼지 결정하는 역할을 함
- 메모리와 디스크 사이의 스케줄링을 담당
- 프로세스에 메모리를 할당
- degree of multiprogramming 제어 (실행중인 프로세스 수 제어)
- 프로세스의 상태 : new -> ready
단기 스케줄러 (short-term scheduler or CPU scheduler)
- CPU와 메모리 사이의 스케줄링을 담당.
- Ready queue에 존재하는 프로세스 중 어떤 프로세스를 running 시킬지 결정
- 프로세스에 CPU를 할당 (scheduler dispatch)
- 프로세스의 상태 : ready -> running -> waiting -> ready
중기 스케줄러(Medium-term scheduler or Swapper)
- 여유 공간을 마련하기 위해, 프로세스를 통째로 메모리에서 디스크로 쫓아냄(swapping)
- 프로세스에게서 메모리를 deallocate
- degree of multiprogramming 제어
- 프로세스의 상태 : ready -> suspended
- suspended 상태
- 외부적인 이유로 프로세스의 수행이 정지된 상태로, 메모리에서 내려간 상태를 의미함. 프로세스 전태를 디스크로 swap out 한다.
- blocked 상태는 I/O 작업을 기다는 상태이므로 스스로 ready state로 돌아갈 수 있지만, suspended 상태는 스스로 돌아갈 수 없음
- suspended 상태
CPU 스케줄러
Ready Queue에 있는 프로세스들 대상
FCFS(First come First serve)
특징
먼저 온 순서대로 처리해줌
비선점형(non-preemptive) 스케줄링 : 한번 CPU을 잡으면 CPU burst가 완료될때까지 CPU를 반환하지 않음.
- 할당 되었던 CPU가 반환될 때만 스케줄링이 이루어진다.
문제점
- convoy effect
- 소요시간이 긴 프로세스가 먼저 도달하면 효율성이 낮아진다.
SJF(shortest job first)
- CPU burst time이 짧은 프로세스가 먼저 할당됨
- non-preemptive 스케줄링 : 한번 CPU에 올라간 프로세스는 끝까지 수행됨
문제점
- starvation : 어떤 프로세스 A보다 CPU burst time이 짧은 프로세스가 계속 들어오면 A는 영원히 실행되지 않음
SRTF (shortest Remaining time first)
- SJF 에서 선점형 스케줄링 추가
- 현재 수행중인 프로세스보다 남은 burst time이 짧은 프로세스가 있으면, 현재 프로세스를 빼고 그 프로세스를 수행함
문제점
- starvation
- 새로운 프로세스가 도착할때마다 스케줄링을 다시하기 때문에, CPU burst time을 측정할 수가 없음
Priority Scheduling
- 우선순위가 높은 CPU를 먼저 할당하는 방식
- 선점형 스케줄링 방식: 우선순위가 높은 것이 들어오면 CPU를 뺏도록 함
- 비선점형 스케줄링 방식 : 우선순위가 높은 것이 들어오면, Ready queue의 head에 두고 현재 수행중인 프로세스는 끝마침
문제점
- starvation
- aging 기법을 통해, 오래 기다릴수록 우선순위를 높여줌으로써 해결할 수 있음
Round Robin
- 현대적인 CPU 스케줄링
- 각 프로세스는 동일한 크기의 할당 시간(time quantum)을 갖게 됨.
- 할당시간이 지나면 프로세스는 선점 당하고, ready queue의 제일 뒤에 줄을 섬
- CPU 사용시간이 랜덤한 프로세스들이 섞여있을 경우 효율적이다.
- 프로세스의 context를 저장하기에 가능한 방법.
장점
- Response time이 빨라짐. 어떤 프로세스라도 (n-1)q 단위 시간이상 기다리지 않음(q : time quantum, n : 프로세스 수)
- 프로세스가 기다리는 시간이 CPU burst time만큼 증가함. 따라서 공정하게 분배된다.
주의할 점
할당하는 시간이 너무 커지면 FCFS와 같아지고, 너무 작아지면 잦은 context switch로 overhead가 발생한다. 따라서 적절하게 설정해준다.
HRN(Highest Response ratio Next)
SJF의 단점을 개선한 기법. 각 작업의 우선순위로 서비스해주는 스케줄링
우선순위
- (대기시간 + 서비스시간) / 서비스시간
프로세스 동기화
Critical Section(임계영역)
동일한 자원을 동시에 접근하는 작업을 실행하는 코드 영역
ex) 공유하는 변수 사용, 동일 파일 사용
Critical Section Problem
Requirement
- Mutual Exclusion : 상호배제
- 프로세스 P1이 critical section을 실행중이라면, 다른 프로세스들은 P1이 가진 Critical section을 수행할 수 없도록 함
- Progress
- Critical Section을 실행중인 프로세스가 없고, 별도의 동작이 없는 프로세드들만 Critical Section진입 후보로서 참여할 수 있음
- Bounded waiting(한정된 대기)
- P1이 critical section에 진입 신청 후 받아들여지기 전까지, 다른 프로세스들이 Critical section에 진입하는 횟수는 제한이 있어야한다.
해결책
Lock
- 하드웨어 기반 해결책으로써, 동시에 공유 자원에 접근하는 것을 막기 위해 Critical Section 에 진입하는 프로세스는 Lock 을 획득하고 Critical Section 을 빠져나올 때, Lock 을 방출함으로써 동시에 접근이 되지 않도록 한다.
- 문제점 : 다중처리기 환경에서는 시간적인 효율성 측면에서 적용할 수 없다
Semaphores
소프트웨어상에서 Critical Section 문제를 해결하기 위한 동기화 도구
- 종류
- Counting Semaphore
- 가용한 개수를 가진 자원에 대한 접근 제어용으로 사용되며, 세마포는 그 가용한 자원의 개수로 초기화 된다. 자원을 사용하면 세마포가 감소, 방출하면 세마포가 증가 한다.
- Binary Semaphore
- MUTEX 라고도 부르며, 상호배제의 (Mutual Exclusion)의 머릿글자를 따서 만들어졌다. 이름 그대로 0 과 1 사이의 값만 가능하며, 다중 프로세스들 사이의 Critical Section 문제를 해결하기 위해 사용한다.
- Counting Semaphore
- 단점
- Busy waiting
- Spin lock이라고 불리는 Semaphore 초기 버전에서 Critical Section 에 진입해야하는 프로세스는 진입 코드를 계속 반복 실행해야 하며, CPU 시간을 낭비했었다. 이를 Busy Waiting이라고 부르며 특수한 상황이 아니면 비효율적이다. 일반적으로는 Semaphore에서 Critical Section에 진입을 시도했지만 실패한 프로세스에 대해 Block시킨 뒤, Critical Section에 자리가 날 때 다시 깨우는 방식을 사용한다. 이 경우 Busy waiting으로 인한 시간낭비 문제가 해결된다.
- Busy waiting
모니터
고급 언어의 설계 구조물로서, 개발자의 코드를 상호배제 하게끔 만든 추상화된 데이터 형태이다.
공유자원에 접근하기 위한 키 획득, 자원사용 후 해제 를 모두 처리함.(세마포어는 직접 키 해제와 공유자원 접근 처리가 필요함)
Deadlock
- 둘 이상의 프로세스가 Critical Section 진입을 무한정 기다리고 있고, Critical Section에서 실행되는 프로세스는 진입 대기 중인 프로세스가 실행되어야만 빠져나올 수 있는 상황을 지칭함
- 즉, 원형으로 서로를 기다리고 있는 상황
메모리 관리 전략
용어
Swapping
메모리의 관리를 위해 사용되는 기법.
표준 Swapping 방식으로는 round-robin 과 같은 스케줄링의 다중 프로그래밍 환경에서 CPU 할당 시간이 끝난 프로세스의 메모리를 보조 기억장치(e.g. 하드디스크)로 내보내고 다른 프로세스의 메모리를 불러 들일 수 있다.
주기억장치로 불러들이는 것을 swap-in 이라하고, 보조기억장치로 내보내는 것을 swap-out이라 한다.
작업이 오래 걸리기에 현재는 메모리 공간이 부족할때만 수행됨
단편화
메모리에 남은 공간들이 있으나, 프로세스에 끼여 사용하지 못하는 현상
- 외부 단편화 : 프로세스 사이에 끼여 남은 공간.
- 내부 단편화 : 프로세스에 할당 된 것보다 작은 공간을 사용할때, 그 남는 공간
압축
외부단편화의 해소를 위해, 프로세스가 사용하는 메모리 공간을 모두 한 곳에 모아, 남는 공간을 한곳에 모으는 방법
작업효율이 안좋다.
Paging
물리 메모리를 Frame이라는 고정 크기로 분리하고, 논리 메모리(프로세스가 점유하는)를 페이지라 불리는 고정 크기의 블록으로 분리함.
하나의 프로세스가 사용하는 공간은 여러개의 페이지로 나뉘어서 논리메모리에서 관리되고, 개별 페이지는 순서에 상관없이 물리메모리에 있는 프레임에 맵핑되어 저장됨
페이징 기법을 사용하여 논리메모리는 물리메모리에 저장될때, 연속되어 저장될 필요가 없고, 물리 메모리의 남는 프레임에 적절히 배치 됨으로써 외부 단편화를 해결할 수 있다.
단점
- 내부단편화 문제가 크게 발생할 수 있다. 예를들어 페이지 크기가 1,024B 이고 프로세스 A 가 3,172B 의 메모리를 요구한다면 3 개의 페이지 프레임(1,024 * 3 = 3,072) 하고도 100B 가 남기때문에 총 4 개의 페이지 프레임이 필요한 것이다. 결론적으로 4 번째 페이지 프레임에는 924B(1,024 - 100)의 여유 공간이 남게 되는 내부 단편화 문제가 발생하는 것이다.
Segmentation
논리메모리와 물리메모리를 같은 크기의 블록이 아닌, 서로 다른 크기의 논리적 단위인 세그먼트로 분할하는 방법.
사용자는 두개의 주소를 지정(세그먼트 번호 + offset)하고, 세그먼트 테이블에는 각 세그먼트의 시작 물리주소와 길이를 저장
단점
- 서로 다른 크기의 세그먼트들이 메모리에 적재되고 제거되는 일이 반복되다 보면, 자유공간들이 많은 수의 작은 조작들로 나뉘어져 못쓰게 될 수도 있다. (외부 단편화)
가상 메모리
프로세스 전체가 메모리 내에 올라오지 않더라도 실행이 가능하도록 하는 기법으로, 프로그램이 물리 메모리보다 커도 된다는 장점이 존재.
개발 배경
프로그램의 사이즈가 커짐에 따라 물리메모리에 프로세스 전체를 올릴 수 없거나, 올릴 수 있는 프로세스의 수가 줄어들게 되었다. 또한 가끔씩만 사용되는 코드가 메모리를 차지하게 되는 문제가 발생하였다.
프로그램의 일부분만 메모리에 올릴 수 있게 되면 다음과 같은 장점이 있다.
물리 메모리 크기에 프로그램의 사이즈가 제약받지 않는다.
더 많은 프로그램을 동시에 실행할 수 있게 된다. 이에따라
응답시간
은 유지되고,CPU 이용률
과처리율
은 높아진다.swap이 필요한 입출력이 줄어들기 때문에 프로그램들이 빠르게 실행된다.’
가상 메모리가 하는 일
가상메모리는 실제의 물리메모리 개념과 사용자의 논리메모리 개념을 분리한 것으로 정리할 수 있다. 이로써 작은 메모리를 가지고도 얼마든지 큰 가상 주소 공간
을 프로그래머에게 제공할 수 있다.
가상주소공간
- 한 프로세스가 메모리에 저장되는 논리적인 모습을 가상메모리에 구현한 공간.
- 프로세스가 요구하는 메모리 공간을 가상메모리에서 제공함으로써 현재 직접적으로 필요치 않은 메모리 공간은 실제 물리 메모리에 올리지 않는 것으로 물리 메모리를 절약할 수 있다.
- 예를 들어, 한 프로그램이 실행되며 논리 메모리로 100KB 가 요구되었다고 하자. 하지만 실행까지에 필요한 메모리 공간
(Heap영역, Stack 영역, 코드, 데이터)
의 합이 40KB 라면, 실제 물리 메모리에는 40KB 만 올라가 있고, 나머지 60KB 만큼은 필요시에 물리메모리에 요구한다고 이해할 수 있겠다.
프로세스간 페이지 공유
- 가상메모리는
시스템 라이브러리
가 여러 프로세스들 사이에 공유될 수 있도록 한다. 각 프로세스들은공유 라이브러리
를 자신의 가상 주소 공간에 두고 사용하는 것처럼 인식하지만, 라이브러리가 올라가있는물리 메모리 페이지
들은 모든 프로세스에 공유되고 있다. - 프로세스들이 메모리를 공유하는 것을 가능하게 하고, 프로세스들은 공유 메모리를 통해 통신할 수 있다. 이 또한, 각 프로세스들은 각자 자신의 주소 공간처럼 인식하지만, 실제 물리 메모리는 공유되고 있다.
fork()
를 통한 프로세스 생성 과정에서 페이지들이 공유되는 것을 가능하게 한다.
Demand Paging(요구 페이징)
프로그램 실행 시작시에 필요한 것만 메모리에 적재하는 것을 의미. 가상 메모리는 대개 페이지로 관리되고, 요구 페이징을 사용하는 가상 메모리에서는 실행과정에서 필요해질 때만 페이지들을 적재한다. 한번도 접근되지 않은 페이지는 물리메모리에 적재되지 않는다.
프로세스 내의 개별 페이지들은 페이저(pager)
에 의해 관리된다. 페이저는 프로세스 실행에 실제 필요한 페이지들만 메모리로 읽어옴으로써, 사용되지 않을 페이지를 가져오는 시간낭비와 메모리 낭비를 줄일 수 있다.
페이지 교체
요구 페이징에서 필요한 페이지가 물리메모리에 없는 상태를 page fault(페이지부재)
라고 한다. 이때 필요한 페이지는 불러오게 되는데, 만약 물리메모리가 꽉 찬 상태라면, 이미 들어있는 페이지를 빼고 새로운 페이지를 넣어야한다. 이때의 프로세스는 다음과 같다.
- 디스크에서 필요한 페이지의 위치를 찾는다.
- 빈 페이지 프레임을 찾는다.
페이지 교체 알고리즘
에 따라 희생될 페이지를 고른다.- 희생될 페이지를 디스크에 기록하고, 관련 페이지 테이블을 수정한다.
- 새롭게 비워진 페이지 테이블 내 프레임에 새 페이지를 넣고, 프레임 테이블을 수정한다.
- 사용자 프로세스 재시작.
페이지 교체 알고리즘
- FIFO
- 장점 : 쉽다.
- 단점
- 활발하게 사용중인 페이지를 빼버릴 수도 있다.
- belady의 모순 : 페이지를 저장할 수 있는 페이지 프레임 갯수를 늘려도, 되려 페이지의 부재가 많이 발생하는 모순이 발생할 수 있다.
- 최적 페이지 교체(optimal page replacement)
- 앞으로 가장 오랫동안 사용되지 않을 페이지를 찾아 교체하는 것. belady의 모순이 발생하지 않음.
- 장점 : 가장 낮은 page fault rate를 가진다.
- 단점 : 구현이 어렵다. 모든 프로세스의 메모리 참조 계획을 미리 파악할 순 없기때문
- LRU(Least Recently used)
- 가장 오랫동안 사용되지 않은 페이지를 선택하여 교체한다.
- 최적근사알고리즘으로, 대체적으로 FIFO보다는 우수하고, OPT 보다는 덜 우수하다.
- LFU(Least Frequently used)
- 가장 적게 사용된 페이지를 교체하는 것.
- 어떤 프로세스가 특정 페이지를 집중적으로 사용하다, 다른 기능을 사용하게되면 더 이상 사용하지 않아도 계속 메모리에 머물게 되어 초기 가정에 어긋나는 시점이 발생할 수 있다
- 잘 안쓰임
- MFU(Most frequently used)
- 가장 적게 사용된 페이지는 최근에 올라왔고, 따라서 앞으로도 계속 사용할 것임을 가정한 알고리즘
- 잘 안쓰임
Locality of cache
캐시의 지역성 원리
캐시의 hit rate를 극대화시키기위해 locality의 원리
를 사용한다. 지역성의 전제조건으로 프로그램은 모든 코드나 데이터를 균등하게 access하지 않는다는 특성을 기본으로 한다. 즉, locality란 기억장치 내의 정보를 균일하게 access하는 것이 아닌 어느 한 순간에 특정 부분을 집중적으로 참조하는 특성인 것이다.
- temporal locality : 최근에 참조된 주소의 내용은 다시 참조될 확률이 높다.
- spatial locality : 최근에 참조된 주소와 인접한 주소의 내용은, 참조될 확률이 높다.
caching line
캐시에 저장된 데이터가 어느 곳에 저장되어 있는지 몰라 모든 데이터를 순회해야 한다면 시간이 오래 걸리게 된다. 즉, 캐시에 목적 데이터가 저장되어 있다면 바로 접근하여 출력할 수 있어야 캐시가 의미 있어진다.
따라서 캐시에 데이터를 저장할때 특정 자료구조를 사용하여 묶음
으로 저장하게 되는데, 이를 캐싱라인
이라고 한다.
프로세스는 다양한 주소에 있는 데이터를 사용하므로 빈번하게 사용하는 데이터의 주소 또한 흩어져있다. 따라서 캐시에 저장하는 데이터에는 데이터의 메모리 주소 등을 기록해 둔 태그를 달아놓을 필요가 있다. 이러한 태그들의 묶음을 캐싱 라인이라고 하고, 메모리로부터 가져올 때도 캐싱라인을 기준으로 가져온다. 종류로는 대표적으로 세가지 방법이 있다.
- Full associative : set의 개수가 1인 것. 즉, 데이터가 전체 블록에서 어디에나 들어갈 수 있음.
- set associative : 전체 블록을 n개의 set으로 나누어 저장. 데이터는 할당된 set에서 어떠한 블록에도 들어갈 수 있음.
- direct map : 데이터가 정해진 위치에만 들어갈 수 있도록 한 것.
출처
https://github.com/JaeYeopHan/Interview_Question_for_Beginner/tree/master/OS#%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%98-%EC%B0%A8%EC%9D%B4