본 포스팅은 공룡책으로 불리는 Abraham Silberschatz, Peter B. Galvin, Greg Gagne의 『Operating System Concept 10th』 기반으로 정리한 글입니다.


Process Concept

프로세스란?

  • 실행 중인 프로그램
  • OS의 작업 단위
  • 하나의 프로세스가 실행되기 위해 자원 필요
    • CPU, memory, files, I/O devices

 

프로세스 상태 정보는 (1) program counter와 (2) contents of the processor's register로 구성

 

 

프로세스 레이아웃

  • Text section ⇒ 명령어 실행 코드
  • Data section ⇒ 전역 변수들
  • Heap section ⇒ 함수 호출시 dynamic하게 저장되는 메모리
  • Stack section ⇒ 함수 파라미터, 리턴되는 주소나 지역 변수를 저장하는 일시적 데이터 저장

 

  • Text와 Data 부분은 고정
  • Stack와 Heap 부분은 동적
    • Stack은 grow down
    • Heap은 grow up

⇒ 겹치지 않게 OS가 조절

 

프로세스 레이아웃 예시

 

Program and Process

  • program : 디스크에 저장된 명령어 리스트를 포함하는 파일 (executable file)
  • process : 다음 실행할 명령어를 나타내는 program counter와 관련 자원 집합을 가짐
    • 두 process가 동일 program과 관련되더라도 두 개의 독립된 실행 순서로 간주됨 이 경우 Text section은 동일하고 나머지는 다름

 

 

Process State

  • New : 프로세스가 막 생성된 상태
  • Running : CPU가 프로세스를 점유하여 프로세스를 로딩하는 상태
  • Waiting : 다른 프로세스가 CPU를 점유할 때 
  • Ready : I/O 대기하고 있다가 점유할 준비, 대기중
  • Terminated : 프로세스 실행 끝남

 

processor core에서 실행될 수 있는 process는 오직 한 번에 한 개뿐이지만, ready나 waiting 상태는 많은 process들이 존재할 수 있다.

 

 

Process Control Block (PCB)

PCB는 구조체 내 프로세스가 가져야하는 모든 정보들을 저장하는 저장소이다.

프로세스마다 각각 PCB를 가지며 이는 doubly linked list로 구현된다.

각 프로세스가 가져야하는 모든 정보들을 저장한다.

 

  • Process state : new/running/ready/waiting/halted 등
  • Program counter : 다음 명령어의 주소
  • CPU registers : interrupt 발생 시 PC와 함께 상태 정보 저장하여 reschedule 후 계속 실행됨
  • Memory-management information 
  • Accounting information
  • I/O status information

 

 

Threads

Single thread of execution

  • Process는 single thread를 실행하는 program
    • 현대 OS 프로세스
  • single thread 제어는 한번에 하나의 task만 실행됨 (ex. 문자 입력과 철자 검사를 동시에 X) 

 

Multiple Thread

  • Process가 여러 실행 thread만 가지고 한 번에 하나 이상의 tasks를 실행함
  • Multi core system인 경우 여러 thread가 병렬로 실행 가능
  • Thread 지원 system에서 PCB가 각 thread에 대한 정보를 포함하도록 확장

 

 

Process Scheduling

Multiprogramming

항상 process들이 실행되도록 하여 CPU 이용률을 최대화한다.

 

 

multiprogramming의 목적 

  • 동시에 여러 프로세스 실행
  • CPU 사용률 증가
  • Time sharing으로 프로세스 내 CPU core를 스위치하여 각 프로그램이 동시에 돌고 있는 것처럼 보이도록 함

 

 

Time sharing

Process 사이에서 CPU core를 자주 switch하여 user와 program이 상호작용한다.

→ core에서 프로그램을 실행하기 위해 process scheduler가 실행 가능한 process를 하나 고른다.

 

Single CPU core를 가진 system에서는 한 번에 한 process만 실행 가능하지만, multicore system에서는 multiple processes들이 동시에 실행될 수 있다.

이때 core의 수보다 process의 수가 더 많은 경우 process들이 wait해야 한다.

 

  • multiprogramming과 time sharing의 균형을 위해 적절히 스케줄링 해야한다.
  • 시간 소비에 따라 I/O bound or CPU bound process로 구분된다.

 

Degree of multiprogramming

현재 메모리 내 process 수

 

 

Scheduling Queues

출처 : https://www.tutorialspoint.com/operating_system/os_process_scheduling.htm

  • Process가 system에 진입하여 Ready queue에 삽입되어 대기한다.
  • IO가 있으면 wait queue로 삽입된다.
  • Ready Queue / Wait Queue는 linked list 형태로 저장된다.
  • queue의 head는 첫 PCB 포인터를 가지며, 각 PCB는 다음 PCB에 대한 포인터를 가진다.

 

 

Queueing Diagram

종료 시점에 queue에서 제거되고 PCB와 자원이 해제된다.

https://www.researchgate.net/figure/Queuing-Diagram-for-Scheduling-in-OS_fig1_337448111

 

CPU Scheduling 

process는 ready queue와 wait queue 사이를 이주한다.

 

 

CPU scheduler의 역할

  • ready queue에서 하나의 process를 선택하고 CPU core를 할당한다.
  • I/O bound process → 잠깐 실행 후 I/O 대기
  • CPU bound process → 오랜 기간동안 CPU core가 필요해도 장기간 할당될 가능성은 없음 (CPU를 뺏어서 다른 process에게 스케줄함)

 

Swapping - 메모리 과부하 상태일 때 process 제거

  • memory에서 process를 제거하여 다중화 정도를 낮추는 것
  • 나중에 다시 memory로 적재되어 중단된 위치에서 계속 실행됨
  • swapped out = 메모리 → 디스크
  • swapped in = 디스크 → 메모리
    • 메모리가 초과 사용되어 해제되어야할 때만 필요

 

 

Context Switch (문맥 교환)

  • CPU가 다른 프로세스와 CPU core를 교환하는 것
  • Context : PCB 내 프로세스가 사용되고 있는 상태, PCB로 보면 됨
  • interrupt 발생
    • 실행 중인 프로세스의 현재 context 저장 (PC, 어디까지 저장했는지)
    • OS는 현재 task로부터 CPU core를 뺏아 kernel 루틴을 실행하게 함
    • 다른 프로세스 로딩시 저장한 context를 restore

화살표 = running 상태 / 검은 선 = wait or ready 상태

 

  • 다른 process에게로 CPU core를 switching하는 것
  • PCB에 old process의 문맥을 저장하여 실행될 new process의 saved 문맥을 load

 

 

Operations on Processes

프로세스는 동시에 수행되고 동적으로 생성/삭제된다.

프로세스는 새로운 프로세스를 생성할 수 있다. 부모/자식 프로세스 by fork()

 

 

Process Creation

  • 실행 중에 process는 새로운 process를 생성한다.
    • 생성한 process = 부모 process
    • 생성된 process = 자식 process
  • 부모/자식 process는 병렬적으로 실행
  • 부모는 자식이 종료될 때까지 기다림
  • 자식이 부모의 복제본이면 부모와 동일한 프로그램과 데이터를 가짐 [fork() system call]
  • 자식 process가 자신에게 load되는 새로운 프로그램을 가짐

 

Process identifier (pid)

  • pid로 process를 식별
  • kernel 내에서 다양한 process의 속성에 접근하기 위한 index로 사용

 

command

  • ps -el : 현재 실행 중인 process들의 list를 보여줌
  • pstree : tree로 보여줌

 

systemd process (pid=1)

  • root parent process
  • 모든 user process들의 부모이며 부트 시 생성되는 첫 user process
  • 부팅되면 systemd가 추가적인 서비스를 제공하는 process를 생성함

 

Child process가 자원을 필요로 할 때

  • 자식 process가 OS로부터 직접 자원을 할당받거나 부모 process 자원의 부분집합으로 제한
  • 부모는 자식들 간 자원을 분할하여 사용하거나 자식들과 자원 공유

한 프로세스가 너무 많은 자식을 생성하여 시스템을 과부하시키는 것을 막음

 

 

Child process에 필요한 data 전달

  • OS가 자식 process에게 자원을 전달
  • 부모 process가 자식 process에게 초기화 data 전달

 

 

자식 process

  • 부모로부터 자원뿐만 아니라 특권, 스케줄링 속성을 물려받음
  • exelip()를 이용해 자신의 주소 공간을 ls 명령어로 덮어씀 

 

 

부모 process

  • wait() system call을 이용하여 자식 process가 끝날 때까지 기다림
  • 자식 process 종료 시, 부모는 wait() 호출로부터 재개

 

 

UNIX OS에서의 process 생성 과정

fork() system call : 새로운 process 생성하는 시스템 콜

  • 새로운 process는 original 주소 공간의 복사본으로 구성됨
  • 부모와 자식이 쉽게 통신하게 함
  • pid = for() : 리턴 = (1) 0 (자식) (2) child의 pid (부모)

 

After a fork() system call

  • fork() 후, 한 process가 exec() system call하여 메모리 공간을 새 프로그램으로 교체
  • binary file을 memory로 적재하고 실행함 
    • 적재 : 자신을 포함한 프로그램의 메모리 이미지를 파괴(덮어쓰기)
  • 자식을 더 생성하거나 자식이 실행되는 동안 할 일이 없으면 자식이 종료될 때까지 ready queue에서 자신을 제거하기 위해 wait() system call을 함 

 

 

Process Termination

return(final Statement)로 종료를 명시하거나 exit()로 중도 out처리한다.

OS 입장에서는 메모리 및 자원 해제, 회수한다.

 

 

스스로 종료

  • 마지막 문장을 실행하여 종료하고 exit() system call로 OS에 자신을 삭제 요청
  • wait() system call을 통해 대기 중인 부모 process에 상태값이 반환됨
  • process의 자원들은 OS에 의해 할당이 해제되고 회수됨

 

다른(부모) process에 의한 종료

  • system call로 다른 process를 종료시킴 (TerminateProcess() in Window)
  • 종료시키는 system call은 부모 process로부터 호출됨
  • process 종료를 위해서 부모는 자식의 pid를 알아야함 
    • process 생성 시 부모에게 pid 전달
  • 이유
    • 자식이 할당된 자원을 초과 사용한 경우
    • 자식에게 할당된 task가 더 이상 필요하지 않은 경우 task 종료
    • 부모가 exit시 자식이 계속 실행되는 것을 OS가 허용하지 않은 경우

 

Cascading termination

  • 부모가 종료되면 자식이 존재할 수 없음 → 모든 자식이 종료되어야 함
  • 자식이 종료될 때 pid가 return되어 부모는 어느 자식이 종료됐는지 식별
    • pid = wait(%status) // exit(1) → 1인 status로 종료

 

 

사용자에 의한 종료

 

 

Process Table

PCB의 array는 현재 존재하는 모든 process의 PCB이다. 

→ process의 exit status가 process table에 저장되므로 process table의 항목은 부모가 wait()를 호출할 때까지 남아있어야 한다.

 

 

Zombie Process → 정상 종료 가능

  • 종료되었거나 부모가 아직 wait()를 호출하지 않은 process
    • 자식 할 일 하게 냅워서 자식은 좀비처럼 계속 남아있음
  • 부모가 wait()를 호출하면 좀비의 process pid와 process table의 항목이 해제됨

 

Orphan Process → 정상 종료 불가능

 

  • 부모가 wait()를 호출하지 않고 종료한 경우 PCB에 정보가 계속 남음
  • 이를 System 선에서 정리해야하는데, init process를 orphan process의 새로운 부모로 지정하여 해결
  • int이 주기적으로 wait()를 호출하여 orphan process의 exit status를 수집하고 orphan process의 id와 process table 항목을 해제함

 

 

Interprocess Communication (IPC)

Process 간 협력 이유

  • Information sharing
  • Computation speed up
  • Modularity

 

IPC Model

  1. shared-memory model → 공유 메모리 주고 받음
    • 공유 메모리 영역을 만들 때만 system call이 일어나므로 빠름
    • 공유 메모리가 만들어지면 모든 접근이 일상적인 메모리 접근으로 취급되고 커널 도움 필요 없음
    • process들은 동시에 동일 위치에 쓰지 않도록 책임져야 함
  2. message-passing model → 메세지 주고 받음
    • 작은 data 교환시 유용
    • 분산 system 환경에서 구현 쉬움
    • 느림

 

 

 

IPC in Shared-Memory Systems

  • 공유 메모리 영역은 공유 메모리 segment를 생성하는 process의 주소 공간에 위치
  • 공유 메모리 segment를 사용해 통신하기 원하는 process들은 자신의 주소 공간에 추가
  • process들은 동시에 동일 위치에 쓰지 않도록 책임져야함

 

A solution using shared-memory

producer(정보 생산)와 consumer(정보 소비)는 동시에 실행되고, CPU를 잘 나눠서 점유한다.

  • Complier → assembly code 생산, assembler → assembly code 소비
  • client-server 패러다임
  • 동시에 실행하기 위해 producer는 buffer를 채우고, consumer는 이를 받아 사용함
  • producer와 consumer는 동기화되어야 함 생산되지 않은 정보를 소비하지 않음

 

Buffer

Buffer가 가득차면 Producer는 wait, 아직 모자라면 Consumer는 wait 상태를 유지한다.

  • unbounded buffer : consumer는 item 대기 가능, producer는 계속 생성 가능
  • bounded buffer : consumer는 empty일 때 대기, producer는 full일 때 대기 ⇒ 원형 배열로 구현

 

 

IPC in Message-Passing Systems

Shared-Memory system의 복잡한 내용들을 OS가 처리하게끔 한다! (By cooperating process 수단)

  • 네트워크로 연결된 다른 컴퓨터들의 process간 통신
  • send(message) receive(message)
  • message의 길이는 고정이거나 가변이고, 통신을 원하면 message를 주고 받아야함
    • fixed-sized message : 시스템 수준 구현은 단순하나 task 프로그래밍이 어려움
    • variable-sized message : 시스템 수준 구현은 복잡하나 task 프로그래밍이 쉬움
  • Naming → 통신을 원하는 process들은 서로 통신 상대를 지정해야 

 

Direct  communication (직접 연결 통신)

recipient/sender process의 이름을 명시한다.

  • send(P, message) : process P에게 메세지 전달
  • receive(Q, message) : process Q로부터 메세지 받음
  • P와 Q가 직접적으로 연결됨
    • communication link는 자동으로 생성되고, 하나의 링크만 성립됨

 단점

  • process 정의에서 modularity가 제한됨
  • process id의 변경은 모든 다른 process 정의를 검사해서 변경해야 함
  • id가 직접적으로 명시되는 hard-coding 기법

 

  1. Symmetry in addressing 
    • 통신하려면 둘 다 상대의 이름을 지정
  2. Asymmetry in addressing
    • sender만 recipient를 naming
    • send(P, message)
    • receive(id, message) : any process로부터 받음 (id는 통신이 일어난 process의 이름)

 

Indirect communication (간접 연결 통신) - mailbox

  • mailbox는 process들이 메세지를 저장(send)하고 제거(receive)하는 객체
  • 두 process가 공유 mailbox를 가질 때만 통신 가능
  • 여러 mailbox를 통해 다른 process들과 통신
  • send(A, message), receive(A, message) : mail box A를 통해서 통신

 

둘 이상의 process가 같은 mailbox를 공유하는 경우

  • link가 최대 두 process만 연관되도록 제한
  • 최대 한 번에 한 process만 receive()
  • 어떤 process가 메세지를 receive할 지 선택 (round robin)

 

mailbox 소유

  1. process 소유 
    • 소유자(수신자)와 사용자(송신자)로 구분
    • 각 mailbox마다 고유한 owner가 있음
    • mailbox를 갖는 process가 종료되면 mailbox도 사라짐
    • 사라진 mailbox에 메세지를 보내는 process는 mailbox가 없음을 통보받음
  2. OS 소유
    • 독립적으로 존재하여 특정 process에 속하지 않음
    • process가 mailbox를 생성/삭제하는 기능을 OS가 제공
    • mailbox를 생성하는 process가 owner
    • mailbox에 대한 권한은 system call로 다른 process에 전달 가능

 

 

Synchronization 동기화

  • process 간 통신은 send() / receive() 호출로 발생
  • blocking (synchronous) Send : 수신될 때까지 send 대기 → 수신 후 송신
    • 메세지가 받아들여질 때까지 sender는 블락됨
  • blocking (synchronous) Receive : 메세지가 avail할 때까지 receive 대기 → 송신 후 수신
    • 메세지 available할 때까지 receiver는 블락됨
  • nonblocking (asynchronous) Send, Receive
    • sender가 메세지 보내고 할 일 계속함
    • receiver가 유효하거나 빈 메세지를 되찾아옴 (Retrieve)

⇒ send와 receive의 종류에 따라 4가지 조합 가능

 

 

Buffering 

  • 메세지는 temporary queue에 보관

 

Queue 세 가지 구현 방법

  • Zero capacity : 보관 X
  • Bounded capacity : N개 메세지 보관
  • Unbounded capacity : 무한개 보관

본 포스팅은 공룡책으로 불리는 Abraham Silberschatz, Peter B. Galvin, Greg Gagne의 『Operating System Concept 10th』 기반으로 정리한 글입니다.


 

Operating System Services

OS service

1. user에게 유용한 기능

  • User Interface (사용자와 컴퓨터의 연결 지점)
  • Program execution (프로그램을 메모리에 로드하고 실행, 종료)
  • I/O operations (사용자가 직접 제어X, 입출력 명령으로 I/O 방법 제공)
  • File-system manipulation (I/O, 조작, 권한 관리)
  • Communications (process간 통신) (1. shared memory) (2. message passing)
  • Error detection (오류 탐지 및 수정, 종류에 따른 처리 진행)

 

2. System 자체적으로 효율적인 운영을 위한 기능

  • Systems with multiple processes can gain efficiency
  • Resource allocation (효율적인 CPU 사용을 위해 CPU 속도, 실행돼야 하는 프로세스, core 수 등을 고려하여 CPU 스케줄링 진행)
  • Logging (어떤 프로그램이 어떤 자원을 얼마나 사용하는가)
  • Protection and security (자원 접근 통제, 외부 침입에 대한 인증 요구)

 

 

User and Operating System Interface

  • CLI (Command-Line Interface) or command interpreter
  • GUI (Graphic User Interface)

 

Command Interpreters

  • 사용자-지정 명령 처리
  • 파일 조작
  • 구현 방법
    • 명령 해석기 자체가 명령어를 실행할 수 있는 코드 내장
    • 명령을 실행하는 파일(프로그램)이 별도로 존재하고, 이를 호출해서 실행
    • 이 경우 프로그래머가 system에 새로운 명령어 추가하기 쉬움

 

 

System Calls

OS 서비스에 대한 인터페이스를 제공하는 역할

 

  • System call table : 특정 system call에 할당된 동작들이 있고, 이것들이 모인 곳(Interrupt vector)
  • System call은 kernel과 user program을 이어주는 interface 역할
    • ex) user program이 파일을 열 때 시스템에 접근하기 위해서는 kernel mode로 전환되어야 하고, 여러 동작들을 수행하기 위해서는 system call이 여러 번 호출됨
    • 그 후 실제 I/O 처리가 이뤄지고 후처리 과정 거침

System call sequence

 

Application Programming Interface (API)

  • 단순한 프로그램도 OS의 기능을 많이 사용하며 수많은 system call 호출
  • 개발자가 system call을 직접 처리하는 대신 사용할 수 있는 집합인 API 사용
  • OS가 제공하는 라이브러리를 통해 API를 사용하여 이를 구성하는 함수들이 개발자를 대신하여 실제 system call 수행
  • Most Common API
    • Window, POSIX, Java API

 

API 장점

  • program portability (이식성) : 동일한 API를 사용하면 어떤 시스템에서라도 complie & run
  • 실제 system call은 정교하여 다루기 어려우므로 편리한 API 사용

 

RTE (Run-Time Environment)

  • 특정 프로그래밍 언어로 쓰여진 application을 수행하기 위한 sw 집합
  • system-call interface 제공
    • OS가 제공하는 system call에 대한 연결점
  • API 함수 호출이 일어나면 이를 가로채서 필요한 OS System call을 호출
  • System call에 대한 번호가 있고, 이에 따라 index된 테이블을 유지
  • System call interface는 system call의 상태 정보 return

 

System Call Implementation

 OS에게 parameter 전달 방식

  • register 이용
  • memory block 이용 : 메모리 내 block이나 table에 저장 
    • 그 시작 주소를 register에 parameter로 전달
  • 조합 : parameter의 수에 따라 register or block method 사용

 

 

Types of System Calls 

  • Process Control
  • File management
  • Device management
  • Information maintenance
  • Communications
  • Protection

 

Process Control

  • Program → 정상 end() / 비정상 abort()
  • 정상/비정상적 상황에서 제어는 command interpreter로 무조건 전달
  • process가 다른 program을 load() / excute()
  • 프로세스 생성, 종료, 복제
  • 생성된 프로세스의 실행 종료를 기다림
  • 특정 시간만큼 기다림 (sleep)
  • 특정 event를 기다리거나 일으킴 (signal)
  • 메모리를 할당하거나 해제 (allocate)
  • 공유 데이터 잠금/해제

 

Communication

proces 간 통식 방식

  1. Message Passing Model
    • Mailbox를 통한 message 교환
    • connection 후 통신
    • 상대의 이름(id)을 알아야 통신 가능
    • 충돌이 없고 구현이 더 쉬움
  2. shared-memory 
    • 공유 영역에 읽기/쓰기 함으로써 정보 교환
    • 동시에 같은 위치 기록 X
    • 컴퓨터 내에서 메모리 전송 속도로 수행하므로 속도가 빠르고 통신이 편리
    • process간 보호와 동기화에서의 문제 발생

 

 

 

Linkers and Loader

program은 disk 상에서 binary executable file로 저장되는데, 이런 program을 memory에 load한 후 process로 배치한다.

  • compiler : source program → object file (소스 코드를 바이너리 코드로 변환)
  • linker : object file → executable file (relocatable object file들을 연결하여 single binary executable file로 변환)
  • loader : executable file → program in memory (executable file을 memory에 적재하는 역할)
  • ./main : fork() 시스템 콜을 통해 새로운 process가 생성
  • build : source program이 compiler와 linker를 거쳐 executable file이 되는 과정
  • dll(dynamically linked libraries) : program 중에서 기본적으로 실행하지 않고 사용자가 요청할 때, 실행 중간에 동적으로 link되는데, 여러 process들이 DLL을 공유하여 메모리를 절약
  • object file = 컴파일, 어셈블러를 통해 변환된 파일 (기계어), relocatable object file (ELF 포맷)

 

 

Why Applications Are Operating-System Specific

특정 OS에서 컴파일된 application은 다른 OS에서 실행이 불가능한데, 이는 OS가 고유의 system call을 제공하기 때문이다.

 

 

  • interpreter 언어로 작성한다 (interpreter는 여러 OS에서 사용 가능)
    • 소스 프로그램의 각 line을 읽어서 상응하는 유사 기계어를 실행하고 OS call을 호출한다
    • 기계어로 구성된 것보다 성능이 떨어지고 intepreter가 OS의 일부 기능만 제공한다는 단점이 있다
  • 가상 머신을 포함한 언어로 작성한다 (Java)
    • Java RTE는 java 응용을 java 가상 머신으로 load하기 위한 요소들을 포함한다
    • 이러한 RTE가 많은 IS에 이식되어 어떤 java 응용이던지 RTE 내에서 실행이 가능하다
    • 성능 저하 문제가 있다
  • 표준 언어나 API를 사용한다
    • 어디서든 컴파일이 가능하다
    • 실행될 OS에서 port되어야 하기에 시간이 소요된다

 

 

Operating System Design and Implementation

Mechanisms and Policies (기법과 정책)

  • Mechanism : 기법, 어떻게 할 것인가
  • Poicies : 운영상 정책/수단, 무엇을 할 것인가

→ 기법과 정책을 분리함으로써 융통성이 생긴다. (여러 정책에서 사용될 수 있는 기법)

프로그램 유형에 따라 우선순위를 부여하는 기법과, 자원 할당 여부를 결정할 때마다 정책이 필요하다.

 

ex) CPU protecting의 경우 Timer mechanism을 사용하고 사용자 별로 타이머 시간을 다르게 설정하는 식으로 구현된다.

 

 

 

Operating System Structure

운영체제가 제대로 동작하고 쉽게 수정이 되도록하기 위해서 main 함수에 전체 코드를 넣는 대신 여러 함수로 로직을 분리해야 한다.

 

1) Monolitic Structure (Tightly coupled system)

  • 커널의 모든 기능을 single, static binary 파일에 넣음
  • 단일 주소 공간에서 실행됨
  • 장점
    • 구조가 단순
    • 구현, 확장이 어려움
    • system-call interface에서 overhead가 거의 없고 kernel 내 통신이라 빠름 (한 번에 메모리로)
    • 속도/효율성 때문에 여전히 사용되는 구조
  • ex) original unix os

커널의 너무 많은 기능이 단일 주소로 결합됨

 

  • The Linux OS (UNIX 기반, 변형)

Kernel 부분이 구분됨, module식 설계이지만 통째로 memory에 올라서 실행됨

 

 

2) Layered Approach (Loosely coupled system)

  • System이 개별적인 작은 요소들로 분리됨
  • 모든 요소들이 kernel을 구성
  • Module식 설계의 한 방법

  • OS 계층은 data와 operation으로 구성되는 추상화 객체의 구현
  • 상위 계층 M은 하위 계층의 연산(기능)을 호출함
  • 계층 M은 상위 계층에서 호출되는 data/function으로 구성됨

 

  • 장점 (construction과 debugging의 간편함)
    • Layer 0에서는 hardware가 정확하다고 가정했을 때 다른 부분은 신경 쓰지 않고 디버깅 가능
    • 단계별로 이전/하위 단계가 정확하게 동작한다고 가정
    • error 발견했다면 해당 층에서의 문제
    • 계층별로 이전/하위 계층에서 제공하는 연산이 어떻게 동작하는지는 몰라도 됨
    • 각 계층은 상위 계층에 특정 정보들을 숨김
  • 단점 (Pure layered approach 방식의 OS는 잘 사용되지 않음)
    • 적절한 계층별 기능 정의의 어려움
    • 전체적 성능 저하

→ 계층을 줄여서 모듈화 코드의 장점을 제공하면서 문제점을 피하도록 함

 

 

3) Micorkernels

Smaller kernel(Mach) - Microkernel 접근 방식으로 커널을 모듈화함

  • 중요하지 않은 요소는 커널에서 제거하고 별도의 주소 공간에 존재하는 user level program으로 구현
  • 통신 기능 외에 최소한의 프로세스/메모리 관리 기능 제공
  • Message passing 통한 간접적인 통신 제공
  • 장점
    • OS 확장이 쉬움 - 새로운 service는 user space에 추가, kernel은 수정이 불필요
    • kernel에 대한 보안/신뢰성 제공 - 대부분의 service가 user process로 실행
  • 단점
    • 가중된 시스템 기능 부하로 인한 성능 문제
    • 두 사용자 서비스 통신으로 서비스 간 메세지 복사 (독립된 주소 공간)
    • 메세지 교환을 위한 프로세스 간 전환(switch) 필요
    • 메세지 복사/프로세스 간 전환이 microkernel 기반 OS 성장에 장애가 됨

 

4) Modules → 현대적 OS 구현

Loadable kernel modules (LKMs)

  • kernel은 핵심 요소들을 가지며, boot/run time시 추가 서비스들은 모듈로 연결한다
  • kernel은 핵심 service를 제공하며 다른 service들은 kernel 실행 동안 동적으로 구현한다 (실행 중 변경 가능)

ex) CPU 스케줄링, 메모리 관리 -> kernel에 직접 구현 // 파일 시스템 지원 → LKM으로 구현

 

 

Layered system과 유사

  • 각 kernel 영역이 defined, protected 인터페이스를 가짐
  • 계층적 동작이 아닌 임의의 다른 모듈 호출 가능

 

Microkernel approach 유사

  • 주 모듈이 핵심 기능을 가지며 다른 모듈을 적재하고 통신하는 방법을 안다
  • mesage passing을 사용하지 않으므로 더 효율적이다

 

Linux

  • 리눅스는 device driver, file system 지원을 위해 LKM을 사용한다
  • LKM은 시스템 시작, 또는 실행 중에 kernel에 insert된다
  • 필요한 driver가 없으면 동적으로 적재/제거 가능하다
  • LKM을 허용하면서 monolithic system의 성능에서의 이점을 유지한다

본 포스팅은 공룡책으로 불리는 Abraham Silberschatz, Peter B. Galvin, Greg Gagne의 『Operating System Concept 10th』 기반으로 정리한 글입니다.


 

운영체제

  • Operating system, 컴퓨터 하드웨어를 관리하는 소프트웨어
  • 사용자가 편리하고 효율적으로 프로그램을 실행할 수 있는 환경을 제공

운영체제는 자동차부터 스마트폰, PC, 클라우드 컴퓨팅 환경까지 어느 곳에나 존재한다.

따라서 현대 컴퓨팅 환경에서 OS의 역할을 탐구하기 위해 먼저 컴퓨터 하드웨어의 구성과 구조를 이해하는 것이 중요하다.

필요 기반 지식으로 CPU, 메모리 및 I/O 장치, 저장장치가 포함된다.

 

 

컴퓨터 시스템

  • 컴퓨터 : 정보를 처리하는 기계

 

정보

  • 불확실성을 측정해서 수치적으로 표현한 것 l(X) = -log2P(X)
  • 정보의 최소 단위 bit (binary digit)
    •  

 

What Operating Systems Do

컴퓨터 시스템의 네가지 구성 요소

  • HW
  • OS
  • application program
  • User

 

1) 하드웨어 HW

하드웨어는 중앙 처리 장치(CPU), 메모리 및 입출력(I/O) 장치로 구성되어 시스템의 기본 컴퓨팅 리소스를 제공한다.

 

2) 운영체제 OS

하드웨어 제어와 다양한 응용 프로그램 간의 하드웨어 사용을 제어하고 조정하는 역할을 한다.

 

3) 응용 프로그램 application program

응용 프로그램이란 워드 프로세서, 스프레드시트, 컴파일러, 웹 브라우저 등을 말하며 사용자의 계산 문제를 해결하기 위해 이들 자원이 어떻게 사용될지를 정의한다.

 

4) 사용자 User

 

 

사용자 관점에서의 운영체제

한 사용자가 리소스를 독점하도록 설계되어있다.

사용자가 수행하는 작업을 최대화하며, 대부분이 Resource utilization을 위해 설계된다.

컴퓨터에 대한 사용자의 관점은 인터페이스에 따라 달라진다.

 

 

시스템 관점에서의 운영체제

운영체제는 하드웨어와 가장 밀접하게 연관된 프로그램으로, 자원 할당자(resource allocator)로 볼 수 있다.

자원에 대해 서로 상충되는 많은 요청들이 있으므로 운영체제는 컴퓨터 시스템을 효율적이고 공정하게 운영할 수 있도록 어느 요청에 자원을 할당할 지를 결정해야 한다.

 

또한 제어프로그램으로도 볼 수 있다.

오류 및 부적절한 사용을 방지하기 위해 사용자 프로그램을 관리한다. 또한 I/O 장치의 작동 및 제어를 관리한다.

 

 

다시 한번 운영체제에 대한 정의를 내려보자.

일반적으로 운영체제에 대한 적합한 정의는 없다.

운영체제는 유용한 컴퓨팅 시스템을 만드는 문제를 해결할 수 있는 합리적인 방법을 제공하기 때문에 존재한다.

컴퓨팅 시스템의 기본 목표는 프로그램을 실행하고 사용자의 문제를 더욱 쉽게 해결할 수 있게 하는것인데, 하드웨어만으로는 쉽지 않으므로 응용 프로그램이 개발되는 것이다.

그런데 이러한 프로그램에는 입출력 장치 제어와 같은 특정 공통 작업이 필요하고, 이런 기능은 운영체제라는 하나의 소프트웨어로 통합된다.

 

보다 일반적인 정의는, 운영체제는 컴퓨터에서 항상 실행되는 프로그램이다.

일반적으로 커널이라고 한다.

 

 

컴퓨터 시스템의 구성 (Computer-System Organization)

현대의 범용 컴퓨터 시스템은 하나 이상의 CPUDevice Controller로 구성되며 공유 메모리 사이 액세스를 제공하는 공통 버스를 통해 연결된 여러 장치 컨트롤러로 구성된다.

 

 

 

 

 

일반적인 컴퓨터 시스템 구성

  • 구성 요소 (CPU, Device Controller)
    • 디바이스 컨트롤러 : 일부 로컬 버퍼 스토리지와 특수 목적 레지스터 집합을 유지, 관리
    • CPU와 디바이스 컨트롤러는 메모리 사이클에 대한 경쟁을 하며 병렬로 실행이 가능하다.
  • 시스템 버스
    • 구성요소 <=> 공유 메모리
  • 공유 메모리

 

  • OS는 각 device controller(장치 측면)에 대해서 device driver(OS 측면)라는 장치 제어 프로그램을 가진다.
    • device controller는 local buffer storage와 refister의 집합을 가진다.
  • CPU는 device controller를 통해 device에 I/O 명령을 내린다.
  • device controller는 interrupt를 통해 CPU에게 실행이 끝났음을 알린다.

 

시스템 작동 방식 : 1) 인터럽트 -> 2) 스토리지 구조 -> 3) I/O 구조

 

 

인터럽트 (Interrupts)

인터럽트는 운영체제에서 하드웨어가 상호작용하는 방식의 핵심 부분이다.

CPU가 인터럽트되면 하던 일을 멈추고 즉시 서비스 루틴의 시작 주소로 이동한다. 이후 작업이 완료되면 CPU는 중단되었던 연산을 제거한다.

CPU와 I/O device가 통신하는 방법으로, HW는 언제나 interrupt trigger할 수 있다.

bus를 통해서 CPU에 신호를 전달한다,

 

I/O 작업 수행 방식

  1. 디바이스 드라이버가 디바이스 컨트롤러에 적절한 레지스터를 로드한다.
  2. 디바이스 컨트롤러는 레지스터의 내용을 검사하여 수행 작업을 결정한다.
  3. 디바이스 컨트롤러는 장치에서 로컬 버퍼로 데이터 전송을 시작한다.
  4. 데이터 전송이 완료되면 디바이스 컨트롤러는 디바이스 드라이버에게 작업이 완료되었음을 알린다. 이때 디바이스 컨트롤러에서 디바이스 드라이버에게 작업 완료를 알리는 역할을 하는 것이 인터럽트이다.
  5. 이후 디바이스 드라이버는 읽으려는 데이터의 포인터 또는 상태 정보를 반환한다.

 

인터럽트 작동 방식

  1. 인터럽트별 핸들러 호출
  2. 포인터 테이블을 사용하여 인터럽트 루틴 진행
    • 속도 향상, 중간 과정 필요 X
    • 포인터 테이블: 인터럽트 서비스 루틴의 주소 보유
  3. 인터럽트 벡터(테이블) - 인터럽트 요청 번호 인덱싱
  4. 인터럽트 핸들러 루틴으로 이동
  5. 해당 인덱스 주소에서 서비스 시작
    • 이때 인터럽트 핸들러는 작동 중의 모든 상태 저장  처리 수행  상태 복원 수행
  6. 작업이 모두 완료되면 복귀 명령을 실행하여 CPU를 인터럽트 실행 이전 상태로 복원

인터럽트 아키텍처는 인터럽트 처리 전 상태로 복원할 수 있도록 인터럽트된 상태 정보를 저장한다.

이후, 인터럽트가 서비스된 후 이전 리턴 주소로 돌아가 인터럽트가 발생하지 않은 것처럼 복원하고 이전의 연산을 재개한다.

 

Storage Structure

커널은 실행기(Executor)를 통해 프로그램을 실행시킨다. 실행기는 기억장치(Storage)에서 exe파일(Windows의 경우)을 가져오고, 커널이 이것을 메모리에 할당해 실행시킨다.

 

즉, 모든 program은 반드시 main memory에 올라야만 실행될 수 있다. 하지만 main memory는 DRAM으로 구현하여 휘발성이고 용량이 작기 때문에 별도의 저장장치가 필요하다.

 

 

현대 컴퓨터 시스템은 기본적으로 폰 노이만 구조를 따른다.

 

폰노이만 아키텍처 (von Neumann architecture)

  • 전형적인 시작-실행 사이클
    • fetch & execute
  • 메모리에서 명령어 레지스터, 데이터 레지스터 사용
    • instruction register

 

Storage Hierarchy

  • storage system은 여러개의 hierarchy로 구성
    • 용량과 속도에 따라 계층 구조 구성
    • registers - cache - main memory - ssd (solid-state disk) - HDD (hard disk) - optical disk - magnetic tapes (백업!)

 

 

Computer-System Architecture

  • core : 명령어 실행, 데이터 local 저장하는 요소 (register를 가짐)
  • main CPU with core : 범용 명령어 셋을 실행

 

Computer System Component

  • CPU
  • Processor
  • Core
  • MultiCore
  • MultiProcessor

 

Single-Processor Systems

  • 하나의 CPU(core 1개)를 갖는 시스템

 

Multi-Processor Systems (parallel system, tightly-coupled system)

  • 여러 개의 Single-core CPU를 갖는 시스템
  • processor들은 computer bus, clock, memory, peripheral device 등을 공유한다.
  • throughput 증가 (processor 수와 성능이 정비례하지는 않음)
  • Economy of Scale(규모의 경제 - 여러 개의 단일 시스템에 비해 비용 절약)
  • Increased Reliability(증가된 신뢰성 - 어느 구성 요소의 고장에도 불구하고 동작을 계속할 수 있기에 우아한 퇴보를 넘어 결함 허용이라고 불림)

 

1) Asymmetric MultiProcessing (AMP) - 각 processor에 특정 작업이 할당

  • 서로 다른 칩에 각각 CPU가 있는 구조
  • processor가 실행하는 task가 구분됨
  • core 간 기능 차이가 있음

 

2) Symmetric multiprocessing (SMP) - 각 processor가 모든 작업 수행

  • 메모리 하나에 각각 연결된 CPU가 각각의 register와 cache 가지고 task 수행
  • 서로 다른 칩에 각각 CPU가 있는 구조
  • processor가 실행하는 task 구분이 없으며 core 간 기능 차이가 없음

 

Multi-core systems - 하나의 CPU에 여러 core를 넣음, 진화된 구조

  • 여러 core들이 한 chip에 있는 구조
  • 한 칩에서 CPU core끼리 통신하기에 칩 사이 통신보다 훨씬 빠름
  • power를 적게 쓰며 각 core는 자신의 register set과 local cache를 가짐

이러한 multi-processor system에서 CPU를 추가한다면 분명 성능이 향상될 수 있지만 확장성은 떨어지게 된다.

또한 너무 많은 CPU를 추가하는 경우 system bus에 대한 경쟁을 초래하여 성능 저하로 이어진다.

 

 

Clustered Systems

multiple-processor와 비슷하나 아래와 같은 차이점이 있다.

  • node(individual systems)들로 구성된다.
  • 각 node들은 multicore system으로, 느슨하게 연결된다.
  • storage를 공유하고 LAN이나 faster interconnect를 통해 연결된다.

즉, 멀티프로세서 시스템은 여러 CPU가 하나의 시스템을 이루는 것이지만, 클러스터 시스템은 여러 독립적인 시스템(node)들이 모여 하나의 시스템을 이루는 것이다.

 

  • asymmetric clustering : node들의 기능이 비대칭, 한 machine은 hot-standby mode (모니터링만 하고 대기중). 나머지가 사용됨
  • sysmmetric clustering : node들의 기능이 대칭, 서로 모니터링하고 노는 node없이 모두 사용됨

 

다음과 같은 특징이 있다.

  • high-availabilty service : 한 node가 고장나도 나머지가 정상 작동
  • high-performance computing environments : 한 어플리케이션이 클러스터 내 모든 컴퓨터에서 병렬 실행, 작업 분배  컴퓨팅 파워가 훨씬 좋다.
  • reliability : 안정성이 높다. graceful degradation(연속적인 서비스 제공), fault tilerant(failure에도 불구하고 계속 동작 가능)

 

 

Operating-System Operations

Initial program (Bootstrap program)

  • 컴퓨터 전원 키면서 실행되는 첫 프로그램
  • OS를 메모리에 로딩하는 프로그램

 

컴퓨터를 실행하기 위해 초기 프로그램인 bootstrap program 실행해야 한다.

비휘발성인 firmware로 HW에 저장되어 부팅 시 실행된다.

 

  • 시스템을 초기화시킨다 (CPU register, device controller, memory contents)
  • OS 커널을 찾아 메모리에 로드한다.
  • 커널이 로드되어 실행되면 시스템과 사용자에게 서비스를 제공한다. "systemd"라는 프로그램은 리눅스에서 가장 먼저 실행되며, 다른 daemon을 시작한다.

 

이벤트의 처리 Events

이벤트는 interrupt를 발생시켜 신호를 보낸다.

  • Hardware interrupt : device에서 발생
  • Trap or Exception : software error, system call (CPU의 도움이 필요한 경우 OS 필요)

 

Multiprogramming and Multitasking

프로그램은 단순히 저장 장치에 저장된 파일에 불과하며 이러한 프로그램이 메모리에 올라와 실행되면 그것을 프로세스라 부른다.

 

Multiprogramming

  • 여러 프로그램을 메모리에 로드해두고 한 프로세스가 대기 상태가 되면 다른 프로세스의 작업을 수행하는 시스템
  • 한 개 이상 프로그램이 실행
  • 여러 메모리가 동시에 올라가 있음 → CPU 활용 증가

 

Multitasking (=multiprocessing)

  • multiprogramming의 논리적 확장
  • 하나의 CPU를 가지고 여러 프로세스 돌리기
  • CPU scheduling
    • CPU 효율 제일 좋게 만들도록 고려
    • 어떤 CPU를 다음으로 돌릴까 switch하며 작업
  • 다음과 같은 기능 필요
    • memory management : 여러 process들이 동시에 memory에 접근해서는 안됨
    • CPU scheduling : 어떤 작업을 먼저 처리할 지 결정해야 함
    • process scheduling, disk storage and memory management : 여러 process들을 동시에 실행시키기 위해 서로 영향을 미치는 기능을 제한해야 함

 

Dual-mode and Multimode Operation

악의적으로 사용자가 OS 코드를 멋대로 수정하는 경우 문제를 방지하기 위해 다른 프로그램이 잘못 실행되지 않도록 해야한다.

그래서 나온 것이 Dual mode로, 실행 모드를 구별하여 권한을 달리 하는 것이다.

  • user mode (1)
  • kernel mode (0) (= supervisor mode, system mode, privileged mode)

 

node의 전환은 system call에 의해 이뤄진다.

  • user application이 OS에 service 요청
  • system은 반드시 user mode에서 kernel mode로 전환해야 함

 

Timer

  • 특정 시간이 지난 후 interrupt가 발생하도록 설정
  • 사용자 프로그램이 무한 루프나 system service 호출에 실패하여 제어가 OS로 넘어가지 않는 경우 방지

 

 

Process Management

  • program은 하나지만 procss는 여러 개일 수 있음
  • process는 순차적으로 수행
  • 한 번에 하나의 명령만 수행
  • process가 병행 실행되도록 서브 process들을 생성하는 system call 제공

 

Program counter

process는 program이 어디까지 실행되었는지 북마크하는 program counter를 가지고 있다.

  • Single-thread process : 하나의 program counter 가짐
  • Multi-thread process : 여러 개의 program counter 가짐

 

 

Memory Management

Main memory는 매우 커다란 byte의 배열이며, 각 byte에는 주소가 있다.

CPU는 instruction-fetch cycle 동안 main memory에 올라온 명령어들을 읽는다.

그리고 data-fetch cycle 동안 main memory에 데이터를 읽거나 쓴다.

명령어(Instruction)들은 반드시 CPU에 의해 실행되기 전 memory에 load되어야 한다.

 

Program 실행 과정

  • program이 실행될 때 절대 주소로 mapping되고 memory로 load되어야 한다.
  • program이 실행되면서 memory의 명령어와 데이터에 접근한다.
  • program이 끝나면 memory 공간이 사용 가능함을 공표하고 다음 program이 load되고 실행된다.

 

Cache Management

Cache는 매우 빠르고 작은 저장 장치이며, Caching은 cachce memory를 이용해 컴퓨터 속도를 높이는 기술이다.

cache에 자주 사용되는 data를 미리 담아두고 CPU나 disk가 cache의 data를 참조할 수 있도록 한다.

데이터는 사용되면서 cache에 일시적으로 저장됨

 

  • 특정 데이터 필요 시 cache를 먼저 확인
  • size에 제약이 있어 관리가 필요
  • 파일의 중복성이 증가하지만 속도 역시 증가함
  • 지역성(Locality) 원리를 사용 → 시간지역성은 한 번 접근한 데이터에 다시 접근할 확률이 높다는 것이고, 공간지역성은 특정 데이터와 가까운 메모리 주소에 있는 다른 데이터에도 접근할 가능성이 높다는 것이다.

오늘은 JPA를 이용해 개발을 진행하다보면 자주 접하는 Fetch Type에 대해 알아볼 것이다.

Fetch Type 속성은 언제 어떻게 동작하며, 전에 작성한 N+1 문제와 어떤 관련이 있는지, 그리고 어떻게 해결할 수 있는지에 대해 정리한다.

 


Fetch Type이란?

JPA가 하나의 Entity를 조회할 때, 연관관계에 있는 객체를 어떻게 가져올 것이냐를 나타내는 설정값이다.

크게 Eager와 Lazy 두가지 전략이 있다. Fetch Type Issue 상황은 하나의 Entity를 로드할 때 두가지 전략 중 고민하는 상황을 말한다.

  • 연관관계에 있는 Entity들 모두 가져온다 - Eager
  • 연관관계에 있는 Entity 가져오지 않고, Getter로 접근할 때 가져온다 - Lazy
각 연관관계의 default 속성
- @ManyToOne : EAGER
- @OneToOne : EAGER
- @ManyToMany : LAZY
- @OneToMany : LAZY

JPA 기본 페치 전략
- @ManyToOne, @OneToOne : 즉시 로딩 (optional = false : 내부 조인, optional = true : 외부 조인)
- @OneToMany, @ManyToMany : 지연 로딩 (optional = false : 외부 조인, optional = true : 외부 조인)

 

즉시로딩과 지연로딩

1) 즉시로딩 EAGER

  • 특정 엔티티를 조회할 때 연관된 모든 엔티티를 같이 로딩
  • 항상 외부 조인 (OUTER JOIN)을 사용 (외부 조인보다 내부 조인(INNER JOIN)이 성능 최적화에 더 유리)
  • 실무에서 엔티티 간 관계가 복잡해질 수록 조인으로 인한 성능 저하를 피할 수 없고 JPQL에서 N+1문제를 일으킴
  • 지연로딩을 기본으로 사용하고 상황에 맞게 사용하길 권장

2) 지연로딩 LAZY

  • 연관된 엔티티를 프록시로 조회
  • 프록시를 실제 사용시 초기화하면서 데이터베이스를 조회
  • 지연로딩 적용 상태에서 연관관계 상태의 종(Member와 Order 중 Member)에 접근하려고 하면 proxy [~] - no Session과 같은 에러 메세지 
  • 이미 DB와 연결된 Connection에 커밋을 날리고 트랜잭션이 닫힌 상태, 연결된 Connection이 없음
  • @Transactional 어노테이션을 통해 해결 (해당 메소드를 하나의 트랜잭션으로 처리)  -> 필요할 때마다 다시 데이터베이스와의 연결이 생성되어 정상적으로 실행
  • 조회 대상이 영속성 컨텍스트에 이미 있으면 프록시 객체를 사용할 이유가 없음. 따라서 영속성 컨텍스트에 이미 로딩되어 있으면 프록시 객체가 아닌 실제 객체(엔티티)를 사용
더보기

즉시로딩에서 외부 조인을 사용하는 이유?

  • 다대일 관계인 회원 테이블과 팀 테이블을 조인할 때 회원 테이블의 외래 키에 not null 제약조건을 걸어두면 모든 회원은 팀에 소속되므로 항상 내부 조인을 사용해도 된다.
  • 반대로 팀 테이블에서 회원 테이블로 일대다 관계를 조인할 때 회원이 한 명도 없는 팀을 내부 조인하면 팀까지 조회되지 않는 문제가 발생한다. 데이터베이스 제약조건으로 이런 상황을 막을 수 없다.
  • 따라서 JPA는 일대다 관계를 즉시 로딩할 때 항상 외부 조인을 사용한다.

 

글로벌 페치 전략에 즉시로딩 사용시 단점

  1. 사용하지 않는 엔티티를 로딩한다
  2. N+1 문제 발생
    • JPA는 JPQL을 분석해서 SQL을 생성할 때 글로벌 페치 전략을 참고하지 않고 오직 JPQL 자체만 사용
    • 따라서 즉시 로딩이든 지연 로딩이든 구분하지 않고 JPQL 쿼리 자체에 충실하게 SQL을 만듦
    • 만약 order : member = N : 1인 ManyToOne 관계에서 조회한 order 엔티티가 10개이면 member를 조회하는 SQL도 10번 실행 -> 처음 조회한 데이터 수만큼 다시 SQL을 사용해서 조회하는 것을 N+1 문제
    • N+1이 발생하면 SQL이 상당히 많이 호출되므로 조회 성능에 치명적 -> JPQL Fetch join으로 해결

 

Fetch Type의 동작 시점

 

JPA Entity Manager 에 의해 관리되는 Persistence Context 에 Entity가 Managed 상태로 올라올 때의 동작이다.

queryDSL 과 같은 쿼리 빌더를 이용해 아무리 Join 문을 짜도 Fetch Join 을 하지 않는 이상 메인 도메인의 엔티티만 Persistence Context 에 올라온다.

연관관계에 대한 Fetch 도 메인 도메인만 일어난다.

 

 

 

프로젝트 제작하면서 발생했던 MultipleBagFetchException 에러를 맞닥뜨리게 되면서,

원인부터 파악하기 위해 우선 N+1 문제에 대해 알아보려고 한다.

 

하단의 블로거 분 글을 보고 공부하고 많이 참고하였다!

 

N+1 문제 - Incheol's TECH BLOG

Query를 실행하도록 지원해주는 다양한 플러그인이 있다. 대표적으로 Mybatis, QueryDSL, JOOQ, JDBC Template 등이 있을 것이다. 이를 사용하면 로직에 최적화된 쿼리를 구현할 수 있다.

incheol-jung.gitbook.io


N+1 문제란!

JPA를 사용하면 자주 만나게 되는 것들 중 하나가 N+1 문제다.

N+1 문제란, 연관 관계에서 발생하는 이슈로 연관 관계가 설정된 엔티티를 조회할 경우 조회된 데이터 갯수 (N) 만큼 연관 관계의 조회 쿼리가 추가로 발생하여 데이터를 읽어온다. 이를 N+1 문제라고 한다.

 

N+1 문제를 마주치게 한 코드들을 먼저 살펴보자.

 

Entity 설정

위 사진은 현재 진행하고 있는 졸프 서버 ERD 설계도이다

ERD 설계대로 제작한 Entity 코드는 아래와 같다.

 

  • 관리자 (Admin)은 회원을 여러명 추가할 수 있다.
  • 회원은 한 명(팀)의 관리자에 종속되어 있다.
  • Cctv도 이하 동문!

 

AdminEntity (테스트 위해 수정)

package com.example.ahpuh.admin.entity;

import com.example.ahpuh.cctv.entity.CctvEntity;
import com.example.ahpuh.user.entity.UserEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "admin")
public class AdminEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long adminIdx;

    @Column(nullable = false, length = 100)
    private String email;

    @Column(nullable = false, length = 100)
    private String pwd;

    @Column(nullable = false, length = 100)
    private String poolName;

    @Column(nullable = false, length = 100)
    private String poolNum;

    @Column(nullable = false, length = 100)
    private String poolAddress;

    @Column(nullable = false, columnDefinition = "varchar(10) default 'active'")
    private String status;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "userIdx")
    private Set<UserEntity> userEntities = new HashSet<>();

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "cctvIdx")
    private List<CctvEntity> cctvEntities = new ArrayList<>();

    @Builder
    public AdminEntity(String email, String pwd, String poolName, String poolNum, String poolAddress, String status){
        this.email = email;
        this.pwd = pwd;
        this.poolName = poolName;
        this.poolNum = poolNum;
        this.poolAddress = poolAddress;
        this.status = status;
    }

    public AdminEntity(String s) {
        this.poolName = s;
    }

    public void setUsers(Set<UserEntity> user) {
        this.userEntities = user;
    }
}

 

UserEntity

package com.example.ahpuh.user.entity;

import com.example.ahpuh.admin.entity.AdminEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(name = "user")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long userIdx;

    @ManyToOne
    @JoinColumn(name = "adminIdx")
    private AdminEntity adminIdx;

    @Column(nullable = false, length = 100)
    private String name;

    @Column(nullable = false, length = 100)
    private String phoneNum;

    @Column(nullable = true)
    private String gender;

    @Column(nullable = true)
    private String age;

    @Column(nullable = true)
    private String address;

    @Column(nullable = false, columnDefinition = "varchar(10) default 'ACTIVE'")
    private String lectureStatus;

    @Column(nullable = false, columnDefinition = "varchar(10) default 'active'")
    private String status;

    @Builder
    public UserEntity(String name, String phoneNum, String gender, String age, String address, String lectureStatus, String status){
        this.name = name;
        this.phoneNum = phoneNum;
        this.gender = gender;
        this.age = age;
        this.address = address;
        this.lectureStatus = lectureStatus;
        this.status = status;
    }
}

 

CctvEntity

package com.example.ahpuh.admin.entity;

import com.example.ahpuh.cctv.entity.CctvEntity;
import com.example.ahpuh.user.entity.UserEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

@Entity
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Table(name = "admin")
public class AdminEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long adminIdx;

    @Column(nullable = false, length = 100)
    private String email;

    @Column(nullable = false, length = 100)
    private String pwd;

    @Column(nullable = false, length = 100)
    private String poolName;

    @Column(nullable = false, length = 100)
    private String poolNum;

    @Column(nullable = false, length = 100)
    private String poolAddress;

    @Column(nullable = false, columnDefinition = "varchar(10) default 'active'")
    private String status;

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "userIdx")
    private Set<UserEntity> userEntities = new HashSet<>();

    @OneToMany(fetch = FetchType.LAZY, mappedBy = "cctvIdx")
    private List<CctvEntity> cctvEntities = new ArrayList<>();

    @Builder
    public AdminEntity(String email, String pwd, String poolName, String poolNum, String poolAddress, String status){
        this.email = email;
        this.pwd = pwd;
        this.poolName = poolName;
        this.poolNum = poolNum;
        this.poolAddress = poolAddress;
        this.status = status;
    }

    public void setUsers(Set<UserEntity> user) {
        this.userEntities = user;
    }
}

 

관리자 (Admin)을 조회해보면?

테스트 케이스를 작성하여 조회를 해보자.

테스트 시나리오는 다음과 같다.

 

  • 회원 10명을 추가했다.
  • 관리자 10팀을 생성했다.
  • 관리자는 10명씩 회원을 담당하고 있다.
  • 관리자를 조회해보자.

 

테스트 코드는 다음과 같다.

@Test
	void contextLoads() {
		Set<UserEntity> user = new LinkedHashSet<>();
		for(int i = 0; i < 10; i++){
			user.add(new UserEntity("user" + i));
		}
		userRepository.saveAll(user);

		List<AdminEntity> admins = new ArrayList<>();
		for(int i = 0; i < 10; i++){
			AdminEntity admin = new AdminEntity("admin" + i);
			admin.setUsers(user);
			admins.add(admin);
		}
		adminRepository.saveAll(admins);

		entityManager.clear();

		System.out.println("-------------------------------------------------------------------------------");
		List<AdminEntity> everyAdmins = adminRepository.findAll();
		assertFalse(everyAdmins.isEmpty());
	}

 

결과는?

 

Hibernate SQL Log를 활성화하여 실제로 호출된 결과를 확인했는데, (로그 추가 예정)

 

  • 관리자를 조회하는 쿼리를 호출하였다.
  • 회원을 조회하는 쿼리가 관리자를 조회한 row 만큼 쿼리가 호출하였다.

 

FetchType.EAGER라서 발생하는 것일까?

이와 같은 경우 쿼리가 하나밖에 호출되지 않았지만, 이는 연관관계 데이터를 프록시 객체로 바인딩한다는 것이다.
 
하지만 실제로 우리는 연관관계 엔티티를 프록시만으로는 사용하지 않기에 실제 연관관계의 엔티티를 사용하는 로직을 추가한다면 마찬가지로 N+1 문제가 발생한다.
 
FetchType을 변경하는 것은 단지 N+1 발생 시점을 연관관계 데이터를 사용하는 시점으로 미룰지, 초기 데이터 로드 시점에 가져올지 차이만 있는 것이다.
 
 

N+1은 왜 발생하는 것일까?

N+1은 쿼리를 1개 날렸는데, 이로 인하여 추가 쿼리가 N개 나간다는 의미이다.

JPARepository에 정의한 인터페이스 메서드를 실행하면 JPA는 메서드 이름을 분석해서 JPQL을 생성하여 실행하게 된다. JPQL은 SQL을 추상화한 객체지향 쿼리 언어로서 특정 SQL에 종속되지 않고 엔티티 객체와 필드 이름을 가지고 쿼리를 한다.
 
그렇기에 JPQL은 연관관계 데이터를 무시하고 해당 엔티티 기준으로 쿼리를 호출하기에, findAll()이란 메소드를 수행하였을 때 해당 엔티티를 조회하는 select * from Admin 쿼리만 실행하게 되는것이다. 그렇기 때문에 연관된 엔티티 데이터가 필요한 경우, FetchType으로 지정한 시점에 조회를 별도로 호출하게 된다.

 

해결 방법

1. Fetch join 

최적화된 쿼리를 직접 사용하는 것이다.

이는 JPARepository에서 제공하는 것은 아니고 JPQL로 작성해야 한다.

@Query("select a from Admin a join fetch a.users")
List<AdminEntity> findAllJoinFetch();
 
실제로 INNER JOIN으로 호출되며, 이는 연관관계가 있을 경우 하나의 쿼리문으로 표현할 수 있기에 매우 유리하다.
 
 

그러나 Fetch Join을 사용하는 경우 FetchType을 사용할 수 없다.

Fetch Join을 사용시 데이터 호출 시점에 모든 연관 관계의 데이터를 가져오기에 FetchType을 Lazy로 해놓는 것이 무의미하다.

또한 페이징 쿼리를 사용할 수 없다.

하나의 쿼문으로 가져오다 보니 페이징 단위로 데이터를 가져오는 것이 불가능하다.

 

2. EntityGraph

@EntityGraph의 attributePaths에 쿼리 수행시 바로 가져올 필드명을 지정하면 Lazy가 아닌 Eager 조회로 가져오게 된다.

Fetch join과 동일하게 JPQL을 사용하여 query문을 작성하고 필요한 연관관계를 EntityGraph에 설정하면 된다.

그리고 Fetch join과는 다르게 join문이 OUTER JOIN으로 실행되는 것을 확인할 수 있다.

 

@EntityGraph(attributePaths = "users")
@Query("select a from Admin a")
List<AdminEntity> findAllEntityGraph();

 

 

FetchJoin & EntityGraph 주의사항

Fetch Join과 EntityGraph는 JPQL을 사용하여 JOIN문을 호출한다는 공통점이 있다. 또한, 공통적으로 카테시안 곱(Cartesian Product)이 발생하여 Admin의 수만큼 user의 중복 데이터가 존재할 수 있다.

그러므로 중복된 데이터가 컬렉션에 존재하지 않도록 주의해야 한다.

 

  • 컬렉션 Set을 사용하게 되면 중복을 허용하지 않는 자료구조이기에 중복된 데이터를 제거할 수 있다.
  • JPQL을 사용하기 때문에 distinct를 사용하여 중복된 데이터를 조회하지 않을 수 있다.

 

해결방법

1. FetchMode.SUBSELECT

한번의 쿼리가 아닌 두번의 쿼리로 해결하는 방법이다.

해당 엔티티를 조회하는 쿼리는 그대로, 연관관계 데이터를 조회할 때는 서브 쿼리로 함께 조회하는 방법이다.

 

즉시로딩으로 설정하면 조회시점에, 지연로딩으로 설정하면 지연로딩된 엔티티를 사용하는 시점에 위의 쿼리가 실행된다. 모두 지연로딩으로 설정하고 성능 최적화가 필요한 곳에는 JPQL fetch join을 사용하는 것이 추천되는 전략이다.

 

 

2. BatchSize

하이버네이트가 제공하는 org.hibernate.annotations.BatchSize 어노테이션을 이용하면, 연관된 엔티티를 조회할 때 지정된 size만큼 SQL의 IN절을 사용하여 조회한다.

 

즉시로딩시 @BatchSize가 있을때 User의 row 갯수만큼 추가 SQL을 날리지 않고, 조회한 Admin의 Id들을 모아서 SQL IN 절을 날린다. size는 IN절에 올수있는 최대 인자 개수를 말한다.

만약 User의 개수가 10개이고 size = 5라면, IN절이 2번 실행될것이다.

지연 로딩이라면 지연 로딩된 엔티티 최초 사용시점에 5건을 미리 로딩해두고, 6번째 엔티티 사용 시점에 다음 SQL을 추가로 실행한다.
 
 
그러나 연관 관계 데이터의 최적화 데이터 사이즈를 알기는 쉽지 않다.

추가로, hibernate.default_batch_fetch_size 속성을 사용하면 애플리케이션 전체에 기본으로 @BatchSize를 적용할 수 있다.

 

 

3. QueryBuilder

Query를 실행하도록 지원해주는 다양한 플러그인 중 대표적으로 Mybatis, QueryDSL, JOOQ, JDBC Template 등이 있다.

이를 사용하면 로직에 최적화된 쿼리를 구현할 수 있다.

// QueryDSL로 구현한 예제
return from(admin).leftJoin(admin.users, user)
                   .fetchJoin()

 

MultipleBagFetchException 이 발생했는데, 

entity 설계하면서 한 테이블 내에 @OnetoMany 및 fetch 타입을 2개 썼더니 발생한 에러였다.

보통 이 에러는 해당 어노테이션을 2개 이상 썼을 때 발생하는 에러이다.

 

관련하여 N+1 문제가 발생한다는 ..

 

지금 만들고 있는 프로젝트에서 @OnetoMany가 쓰이는 곳에 fetch = FetchType.EAGERfetch = FetchType.Lazy로 변경해주니 해결되었다.

 

추후에 매핑관계와 fetch 타입, 이어 N+1 문제에 관하여 블로그 글을 추가로 작성해야겠다 !!

참고한 글 : https://eclipse4j.tistory.com/215

오늘은 자바에서 쓰이는 스트림에 관해 정리해보려고 한다.

 

Stream은 자바 8부터 사용할 수 있다.

기존에 자바 컬렉션이나 배열의 원소를 가공할 때 for문, foreach 등으로 원소 하나씩 골라내서 가공했다면,

Stream을 이용해 람다 함수 형식으로 간결하게 요소들의 처리가 가능하다.

 

 

Stream이란!

스트림의 사전적 의미는 '흐르다' 또는 '개울'로 프로그래밍에서의 스트림도 사전적 의미가 크게 다르지 않다.

여기서는 '데이터의 흐름'을 뜻한다.

 

출처 : https://steady-coding.tistory.com/309

stream을 설명하기에 정말 적합한 사진이 있어서 출처란에 기입한 블로그 작성자 분의 이미지를 가져왔다. (짱짱)

물고기와 같은 어류의 이동처럼 Stream도 이에 비유하여 이해할 수 있다.

 

먼저 어부들이 그물망으로 잡고 싶은 물고기들을 잡는다. 이 행위를 filter라고 하며, 중간 연산자라고 한다.

그리고 이 물고기들을 포장하여 판매해야하기에 상자에 담는데, 이 행위를 map이라고 한다. 이 또한 중간 연산자라고 한다.

마지막으로 상자들을 운반하여 다른 곳으로 이동하며 끝난다. 이 행위를 collect라고 하며 이 연산자는 최종 연산자라고 한다.

 

이처럼 스트림은 수많은 데이터 흐름 속 각각의 원하는 값을 가공하여 최종 소비자에게 제공하는 역할을 한다.

 

 

Stream의 특징

스트림 내 요소들에 대해 함수가 적용된 결과의 새로운 요소로 매핑한다.

기능적인 측면에서 스트림은 컬렉션(배열포함)의 저장 요소를 하나씩 참조해 람다식으로 처리할 수 있도록 해주는 반복자이다.

사실 우리는 반복자를 스트림이 아니더라도 계속 사용해왔다. (ex. Iterator 반복자 ,,)

 

 public static void main(String[] args) {
   List<Integer> list = Arrays.asList(1, 2, 3);
   Iterator<Integer> it = list.iterator();
   while (it.hasNext()) {
     int num = it.next();
     System.out.println(num);
   }
 }

상단의 코드는 Iterator 반복자를 사용한 예시로, 정수가 있는 리스트를 하나씩 순회하면서 값을 출력한다.

 

이를 스트림으로 바꿔보면, 

 public static void main(String[] args) {
   List<Integer> list = Arrays.asList(1, 2, 3);
   Stream<Integer> stream = list.stream();
   stream.forEach(System.out::println);
 }

코드가 매우 간결해진 것을 확인해볼 수 있다.

 

이제 본격적으로 스트림의 특징에 대해 알아보겠다.

 

  • 람다식으로 요소 처리 코드를 제공한다.
  • 내부 반복자를 사용하므로 병렬 처리가 쉽다. (Iterator의 경우 외부 반복자)
  • 중간 처리와 최종 처리가 존재한다.
    • 중간 처리 (매핑, 필터링, 정렬) / 최종 처리 (반복, 카운팅, 평균, 총합 등 집계 처리)

 

배열의 원소를 가공하는데 있어 map, filter, sorted 등이 있다.

 

map

요소들을 특정 조건에 해당하는 값으로 변환

요소들을 대/소문자 변형 등의 작업을 하고 싶을 때 사용 가능

 

filter

요소들을 조건에 따라 걸러내는 작업을 해줌

길이의 제한, 특정 문자 포함 등의 작업을 하고 싶을 때 사용 가능

 

sorted

요소들을 정렬해주는 작업

요소들의 가공이 끝났다면 리턴해줄 결과를 collect을 통해 만들어줌

 

 


Test Set

ArrayList<string> list = new ArrayList<>(Arrays.asList("Apple","Banana","Melon","Grape","Strawberry"));

System.out.println(list);

//[Apple, Banana, Melon, Grape, Strawberry]

 

Map

list.stream().map(s->s.toUpperCase());
list.stream().map(String::toUpperCase);

리스트의 요소들을 대문자로 변경한다.

요소들을 대문자로 가공하였다면 collect 를 이용하여 결과를 리턴받을 수 있고, forEach 를 이용하여 바로 출력해볼수 있다.

 

System.out.println(list.stream().map(s->s.toUpperCase()).collect(Collectors.joining(" "))); //APPLE BANANA MELON GRAPE STRAWBERRY

System.out.println(list.stream().map(s->s.toUpperCase()).collect(Collectors.toList())); //[APPLE, BANANA, MELON, GRAPE, STRAWBERRY]
System.out.println(list.stream().map(String::toUpperCase).collect(Collectors.toList())); //[APPLE, BANANA, MELON, GRAPE, STRAWBERRY]

list.stream().map(String::toUpperCase).forEach(s -> System.out.println(s));
//APPLE
//BANANA
//MELON
//GRAPE
//STRAWBERRY

 

filter

list.stream().filter(t->t.length()>5)

filter 는 요소를 특정 기준으로 걸러낼 수 있다.

요소의 크기가 5이상인 값만 뽑아낸다.

 

System.out.println(list.stream().filter(t->t.length()>5).collect(Collectors.joining(" "))); //Banana Strawberry

System.out.println(list.stream().filter(t->t.length()>5).collect(Collectors.toList())); //[Banana, Strawberry]

마찬가지로 filter로 가공한 결과를 얻을 수 있다!

 

Sorted

list.stream().sorted()

리스트의 요소를 정렬한다.

 

System.out.println(list.stream().sorted().collect(Collectors.toList())); //[Apple, Banana, Grape, Melon, Strawberry]

사용 예시

public class Human implements Comparable<Human> {

    private Long idx;
    private String name;
    private Integer money;
    private LocalDate birth;
    private List<String> travelDestinations;
}

기본 사용법

@DisplayName("이름만 가져와서 List 만들기")
void mapTest1() {
    List<String> humanNames = humans.stream()
            .map(h -> h.getName())
            .collect(Collectors.toList());

    for (String humanName : humanNames) {
        System.out.print(humanName + " ");
    }
}

 

이렇게 자바 스트림의 개념, 사용하는 방법들에 대해 알아봤다.

 

'java' 카테고리의 다른 글

[Java] 자바의 interface  (0) 2023.01.25

최근에 erd 설계를 (오랜만에) 하게 되면서 createdAt, updatedAt에 쓰이는 type에 관해 고민이 생겼다.

지금껏 datetime으로 해놨었는데, datetime 외에도 timestamp와 같이 시간을 기록할 수 있는 컬럼에 관해 표현할 수 있는 type은 다양하다.

오늘은 시간 관련 type에 대해 알아보고, 이를 적재적소 어떻게 쓰는지 알아볼 예정이다!

 

TIME

time 타입은 HH:MM:SS시간에 대한 정보를 담는 타입이다.

time이 가질 수 있는 값의 범위는 -838:59:59 ~ 838:59:59이다.

여기서 시간은 날짜 중에 day의 값을 표현할 수 있는 범위까지이기에 838시간이라는 큰 시간까지 포함이 가능하다.

 

TIMESTAMP

timestamp 타입은 날짜, 시간 모두 포함한 타입이다.

범위로는 1970-01-01 00:00:01 ~ 2038-01-19 03:14:07 UTC까지 표현할 수 있다.

 

DATE

date 타입은 날짜는 포함하지만 시간은 포함하지 않을 때 사용하는 타입이다.

YYYY-MM-DD 형식으로 입력이 가능하며, '1000-01-01'부터 '9999-12-31'까지만 입력 가능하다.

 

DATETIME 타입

datetime 타입은 날짜와 시간 모두 포함할 때 사용하는 타입이다.

YYYY-MM-DD HH:MM:SS 형식으로 '1000-01-01 00:00:00'부터 '9999-12-31 23:59:59'까지 입력 가능하다.

 

 

TIMESTAMP vs DATETIME

1. 타입

TIMESTAMP는 숫자형, DATETIME은 문자형이다

 

2. 용량

TIMESTAMP는 4byte, DATETIME은 8byte이다.

 

3. 입력

TIMESTAMP는 데이터값을 입력해주지 않고 저장시 자동으로 현재 날짜가 입력된다. (Default Insert (AUTO))

DATETIME은 데이터값을 입력해주어야만 날짜가 입력된다. (명시적 INSERT)

TIMESTAMP의 경우 Timezone을 적용해서 보여준다.

 

Timezone
타임존은 동일한 로컬 시간을 따르는 지역을 의미하며, 해당 국가에 의해 법적으로 지정된다.

 

DATE와 DATETIME의 경우 범위가 거의 같지만 TIMESTAMP의 경우 다르기에 사용할 때 목적에 따라 명확히 구분해서 사용해야한다!

'CS > DB' 카테고리의 다른 글

[DB] SQL 정규화  (0) 2023.01.10
[MySQL] MySQL 아키텍처 정리  (0) 2023.01.09

타인이 만든 서버에 접속하기 위해 프로젝트 명세서에 있는 정보들을 사용해야한다.

ec2 서버에 접속하려면 window 기준 putty를 사용해야하고, 접속할 수 있는 pem key가 있어야 한다.

주어진 key 정보는 

이런 식인데...

이를 어떻게 ppk 형식의 key로 생성할 수 있을까!

 

우선 메모장을 열어 제공된 pem key를 복붙한다.

해당 파일을 ssh-rsa 의 이름으로 '모든 파일 형식'으로 저장한다. 

 

그러나 저장하고 나서 보면 다시 txt 형식으로 저장되어 있기에 .txt를 지워준다.

 

이제 puttygen을 실행시켜준다.

load 버튼을 누르고 아까 만들었던 ssh-rsa 파일을 선택한다.

 

그럼 이와 같은 화면이 뜨고, Save private key를 눌러주면 위와 같은 경고창이 뜬다.

예를 눌러주면 .ppk 형식으로 원하는 이름으로 저장해준다.

 

그리고 내가 접속하려는 서버 주소를 putty에 위에서 제작했던 ppk key와 함께 입력해주면 서버 접속 완료!

'server' 카테고리의 다른 글

[Server] 로컬에 APM 설치하기  (0) 2022.09.27

 

윈도우에 설치시 발생하는 오류(wslregisterdistribution failed with error: 0x80370102) 해결 | 코드잇

저도 동일한 오류가 발생했었는데, 다음과 같은 방법으로 해결하였습니다. 1. Linux 커널 업데이트 패키지 설치 https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi (error: 0x800701bc wsl 2? ?? ?? ?? ??

www.codeit.kr

처음에 이 에러를 보고 구글링하면서 위 게시물을 발견하고

해당 게시물의 해결방법을 그대로 수행했다.

 

1. Linux 커널 업데이트 패키지 설치

https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi

(error: 0x800701bc wsl 2? ?? ?? ?? ????? ?????. ??? ??? https://aka.ms/wsl2kernel? ??????. 해결)

 

2. Windows 기능 켜기/끄기 에서

  • Linux용 WIndows 하위 시스템
  • Windows 하이퍼바이저 플랫폼
  • 가상 머신 플랫폼

체크여부 확인하고 안되어있을시 클릭하여 체크 후 재부팅

 

3. 개발자 설정에 들어가서 개발자 모드 활성화 시키고 재부팅

(wslregisterdistribution failed with error: 0x80370102 please enable the virtual machine platform windows feature and ensure virtualization is enabled in the bios. 해결)

 

 

 

근데 이 세개를 다 해도 똑같이 에러가 떴다...

 

[WSL] 가상 컴퓨터 플랫폼 Windows 기능을 사용하도록 설정하고 BIOS에서 가상화가 사용하도록 설정

개요 Android 개발환경을 설정하며 의도치 않게 WSL2의 구동에 영향을 미치는 환경설정을 변경하였습니다. C:\Windows\system32>wsl 가상 컴퓨터 플랫폼 Windows 기능을 사용하도록 설정하고 BIOS에서 가상화

rottk.tistory.com

이 글을 참고하자면,

관리자 권한으로 cmd를 실행하고,

bcdedit

와 같은 명령어를 입력한다.

 

그러면 이와 같이 hypervisorlaunchtype이 off 상태이다.

 

bcdedit /set hypervisorlaunchtype auto

hypervisorlaunchtype을 auto로 바꾸기 위해 다음과 같은 명령어를 입력해주고

다시 재부팅한다.

 

그러고 ubuntu 다시 실행해주면..!!!

에러문은 없어지고 정상적으로 이름을 입력하라는 메세지가 뜬다.

성공!

+ Recent posts