IT_Study/Operating System

Operating System (13) : Memory Leakage, Process Control Block, Process State, Virtual Memory

__Vivacé__ 2023. 4. 5. 10:24

메모리 누수 (Memory Leakage)

 - 정의 : 컴퓨터 프로그램이 필요하지 않은 메모리를 계속 점유하고 있는 현상
 - 원인 : 일반적으로 프로그램이 메모리 할당 후 해제하지 않거나, 필요가 없어지더라도 계속해서 메모리를 차지할 경우 등
 - 결과 : 시스템의 성능이 느려지거나, 시스템 충돌 등의 문제가 발생해서 동작하지 않을 수 있음
 - 해결책
     1) 코드 작성 잘 하기
         - 할당했으면 해제 ( close(), free(), del ~ )
         - 열었으면 닫기
         - 항상 세트로 코드를 작성하는 습관을 가진다
     2) RAII패턴 사용 - C++
         - Resource Acquisition IsIntialization 디자인 패턴
     3) 스마트 포인터 C++
     4) 코드 리뷰
     5) 테스트와 디버깅

 

프로세스 (Process)

운영 체제는 각 프로세스가 사용하는 가상 주소 공간(virtual address space)을 제공하며, 이를 통해 프로세스는 자신만의 독립적인 메모리 공간을 사용할 수 있다.

최초의 프로세스(PID = 1)를 제외한 모든 프로세스는 부모 프로세스에서 복제되어 생성

(Shell에서 App을 수행하면, Shell이 부모 프로세스가 된다.)

 - 자식은 부모의 ID 값을 알고 있다. (Parent Process ID = PPID)

 - 부모는 자식의 ID 값을 모른다.

 

프로세스 확인하기

// e : 커널프로세스를 제외한 모든 프로세스 출력
// f : Full Format으로 출력 (PID 등 모든 정보)
$ ps -ef

$ top

$ htop

 

트리 구조로 확인하기

$ pstree

 

pstree로 확인했을 때 parent와, ps -ef로 확인했을 때 parent가 다른 이유

- pstree : 종료된 부모 프로세스를 제외한 자식 프로세스만 보여줌
- ps -ef : 부모 프로세스와 함께 자식 프로세스도 모두 보여짐

 


프로세스가 사용 중인 Memory Map 확인하기

$ cat /proc/[PID]/maps

 

일단 다음 코드를 실행해서, 각 메모리 주소를 확인한다.

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4
  5 int t;
  6 int g = 32;
  7
  8 int main(){
  9     int q = 31;
 10     int *p = (int *)malloc(4);
 11
 12     while(1)
 13     {
 14         printf("DATA(%d) = %llX\n", g, &g);
 15         printf("BSS = %llX\n", &t);
 16         printf("HEAP = %llX\n", p);
 17         printf("STACK = %llX\n", &q);
 18         printf("=====================\n");
 19         sleep(1);
 20         g++;
 21     }
 22
 23     return 0;
 24 };

 

위 코드를 실행하면, 다음과 같은 화면이 나타남

위 프로세스의 PID를 알아내서, memory map을 확인하면 메모리 주소가 잘 할당되어 있음을 알 수 있다.


 

Process Scheduling

 - 여러 개의 프로세스가 시스템 리소스를 경쟁할 때 컴퓨터 운영 체제가 다음에 실행할 프로세스를 결정하는 방법

 - OS 마다 작동방식이 다르지만, 기본적으로 *Round Robin으로 동작

*Round Robin : 우선순위를 두지 않고, 순서대로 시간단위(Time Quantum/Slice) CPU를 할당하는 방식

 

PCB (Process Control Block)

 - 커널이 프로세스를 제어하기 위한 정보를 저장하는 블럭, 프로세스 Descriptor 라고도 한다

 - 프로세스 상태 (State), PID 등 프로세스에 대한 다양한 정보들을 보관

PCB의 역할

시간이 다 되어서 다음 프로세스를 수행해야 할 때, register를 어딘가에 백업해야 한다 --> PCB 내부에 저장

이런 식으로 진행하면, Program Counter Register가 정상적으로 복구되어 끊겼던 곳부터 이어서 수행이 가능하다

Process 끼리의 전환을 Context Switching 이라 한다.
Process 전환시 "Context Switching Cost가 발생한다" 라고 표현한다.

htop

프로세스 상황을 모니터링할 수 있는 리눅스 명령어

 

htop 설정

[F2] 눌러서 해당 창 진입

[Columns] - [PID] 에서 [PPID] 선택 후 [F5]를 눌러서 추가하면, Activate Columns에 PPID가 표시

이 상태로 [F10]을 눌러 설정을 저장

PPID가 추가된 것을 볼 수 있음

간단한 단축키

  F5 : Tree 로 보기
  Shift + H : User Thread 보기
  Shift + K : Kernel Thread 보기

 


Process State

Linux Process State는 5개가 존재

R : Running / Runnable
S : Interruptible Sleep
D : Uninterruptible Sleep
T : Stopped
Z : Zombie

https://cloudchef.medium.com/linux-process-states-and-signals-a967d18fab64

 


Run Queue

우선순위 값에 따라 스케쥴러가 먼저 수행할 것을 결정을 지을 수 있는 후보들이 존재하는 큐

Context Switching으로 선택된 하나의 프로세스만 수행이 된다

 - 선택된 하나의 프로세스 Running State에 있는 것

 - Queue에 있는 나머지 프로세스 Runnable (Ready) State에 있는 것

 


Interruptible Sleep vs Uninterruptible Sleep 

  Interruptible Sleep Uninterruptible Sleep
정의 프로세스가 자원이나 이벤트를 기다리고 있는 동안 일시 중지되어있는 상태 특정한 System Call 호출 시 발생하는 상태
특징 Incoming signal을 받을 수 있는 상태 SIGKILL과 SIGSTOP을 제외한 모든 signal을 block하고 있는 상태
예시 프로세스가 파일을 읽기 위해 대기하는 경우
보드에서 입력을 기다리는 경우 등
파일시스템에 관여하는 작업을 수행 시
(디스크에서 데이터를 읽거나 쓰는 동안 등),
또는 커널의 동작이 완료될 때까지 기다리는 작업

 

vi를 킨 화면에서, 바로 htop으로 State 확인해보면, S가 찍혀있음을 알 수 있다.

--> 키보드 입력을 기다리는 상태라, S state로 존재

 


Zombie State

- 하나의 프로세스는 자식 프로세스를 생성 가능 (fork System Call 사용)
- 본인은 본인의 PCB를 제거하지 못하기 때문에, 자식이 죽으면 부모가 자식의 Process Descriptor 를 제거해줌 (PCB 제거)

    --> 부모 프로세스가 바쁘다면, 자식 프로세스는 좀비 프로세스가 된다.

 

확인해보기

해당 코드 작성 후, 실행하면 무한 루프가 걸릴 것이다.

  1 #include <stdio.h>
  2 #include <sys/types.h>
  3 #include <unistd.h>
  4
  5 int main(){
  6     pid_t child_pid = fork();
  7
  8     if (child_pid > 0){
  9         // parent
 10
 11         while(1);
 12     }
 13     else if (child_pid == 0){
 14         // sone
 15
 16         // die
 17
 18     }
 19
 20     return 0;
 21 }

이 상태에서, htop 명령어 입력 후 Shift + K를 누르고 밑으로 내려보면, Z 상태를 확인 가능하다.


Virtual Memory

구글 드라이브가 제공하는 용량 - 1인 15GB
10만명이 계정 가입하면 1,500TB를 준비해야 할까? --> X, 모든 사용자들이 메모리를 꽉 채워서 쓸 일이 없음

그럼 어떻게? --> Virtual Memory 이용

장점
 1. 모든 프로세스는 메모리 Address 를 0x0000 0000 부터 사용할 수도 있다
 2. 다른 프로세스가 쓰는 메모리가 있더라도 신경쓰지 않고 , 메모리를 마음껏 사용 가능

https://ko.wikipedia.org/wiki/%EA%B0%80%EC%83%81_%EB%A9%94%EB%AA%A8%EB%A6%AC

가상메모리를 사용하려면 주소 장치가 필요 --> MMU

 

MMU (Memory Management Unit)

 - 가상 메모리와 물리 메모리 간의 주소 변환을 관리하고 메모리 접근을 제어하는 역할을 담당

 - 주요 기능

     1.주소 변환: 페이지 테이블을 사용해 가상 주소의 페이지 번호를 물리 주소의 프레임 번호로 매핑
     2.메모리 보호: 각 프로세스가 할당된 메모리 공간 내에서만 작동하도록 제한
     3.페이지 폴트 처리: 페이지 폴트를 감지하고 운영 체제에 알림

*페이지 폴트(Page fault) : 가상 메모리에서 물리 메모리에 없는 데이터나 코드에 접근했을 때 발생하는 현상

 

Shared memory

 - IPC(Inter-Process Communication) 방법 중 가장 빠른 방법

 - 동작 순서

     1. Attach : shared memory를 얻어 낸 후 get 프로세스에 붙임
     2. Control : shared memory에 값을 읽고 씀

     3. Detach : 모두 사용 후 shared memory를 프로세스에서 떼어 반환

 - shared memory가 context switching cost를 줄여준다

 


Signal

Thread / Process 에게 정보를 전달하는 신호 - 단순 정보를 보낼 때 사용

 

Linux에서는 최대 256 개 신호 까지 전달될 수 있도록 함

$ kill -l

해당 명령어를 입력하면, SIGNAL 종류를 볼 수 있다.

리눅스에서, SIGNAL이 발생하면 SW Interrupt가 발생하여 하던 일을 멈추고 지정된 Handler 함수가 즉시 호출
지정된 Handler가 없다면, Kernel 내부에서 Default 동작을 수행

ISR (Interrupt Service Routine)

인터럽트에 대응하여 호출되는 코드 (주로 함수)
인터럽트 핸들러 라고 부르며 , ISR / Handler 모두 자주 쓰이는 용어