프로세스와 스레드
프로세스란 실행중인 프로그램을 뜻한다.
같은 프로그램도 별도의 프로세스가 될 수 있다.
포그라운드 프로세스 & 백그라운드 포로세스
많은 종류의 프로세스가 있지만 대표적인 프로세스가 포그라운드 프로세스 & 백그라운드 포로세스가 있다.
백그라운드 프로세스들 중에서는 지금 당장 사용자와 상호작용이 없지만 대기중에 있는 애들이 서비스에 있다.
프로세스 제어 블록(PCB)
동시다발적으로 실행되는 프로세스를 효율적으로 관리하기 위해서 PCB가 있다.
프로세스의 꼬리표라고 생각하면 좋다. 모든 프로세스마다 갖고 있다.
같은 프로그램도 별도의 프로세스가 있으면 각각의 PCB를 갖고 있다.
- PID: Process ID, 프로세스에 할당되어 있는 고유한 번호, 식별자
- 레지스터
- 스케줄링 정보
- 메모리 정보
- 사용한 파일 정보
- 입출력장치 정보
실제로 PCB는 현재 설명보다 더 복잡한 정보들이 포함되어 있다.
깊게 공부하고 싶으면 아래 링크에서 공부하면 좋다.
PID
PID값은 소스코드로도 확인이 가능하다.
#include <stdio.h>
#include <unistd.h>
int main() {
pid_t pid, ppid; // ppid는 부모 프로세스, pid는 자식 프로세스
pid = getpid(); // Get the process ID
ppid = getppid(); // Get the parent process ID
return 0;
}
아래 이미지는 코드를 실행한 결과이다.
문맥 교환(Context Switching)
일반적으로 자원의 개수보다 프로세스 개수가 훨씬 많다.
그래서 번갈아가면서 프로세스들이 자원을 이용한다. 이 때 문맥 교환이 일어난다.
- 문맥: 실행을 재개하기 위해 기억해야 할 정보
- 문맥 교환: 여러 프로세스들이 번갈아가며 실행되는 원리
위의 이미지는 프로세스 A가 실행되고 있다가 프로세스 B에게 전달을 한다.
이때 프로세스 A가 백엎하고 나서 프로세스 B에게 자원을 준다.
아래 이미지를 보면 문맥을 저장하고 복구하고 저장하고 복구하는 것을 볼 수 있다
이 과정을 문맥 교환이라고 한다.
문맥 교환을 할때도 비용이 든다. 빠르게 하면 여러 프로그램이 동시에 실행되는 것 처럼 보이지만 너무 빠르게 하면 비용이 많이들어 성능이 낮아질 수 있다.
프로세스에서 영역 부분
기술 질문에서 자주 등장하는 질문이다.
프로세스에스 크게 분류하면
- 운영체제 영역
- 커널 영역
- 사용자 영역
- 스택 영역
- 힙 영역
- 데이터 영역
- 코드 영역
사용자 영역
- 스택 영역
- 임시로 저장되는 영역(매개변수, 지역변수)
- 힙 영역
- 사용자(개발자)가 직접 할당 가능한 공간
- 개발자가 직접 메모리 영역을 사용하겠다.
- 메모리 영역을 할당 했다면 해제하자, 만약 할당 후 해제를 하지 않는다면 Memory Leak이 발생한다.
- 직접 해제하기, 자동으로 해제 하기(가비지 컬렉션)
- 데이터 영역
- 프로그램이 실행되는 동안 유지할 데이터 (전역 변수)
- BSS 영역: 프로그램 실행 동안 유지할 데이터 중 초기값 없는 데이터
- 코드 영역
- 텍스트 영역이라고 불린다.
- 실행 가능한 코드 기계어로 이루어진 명령어 (Read - only)
- CPU는 기계어로 이루어진 명령어를 해석해서 실행된다. 즉, 코드 영역을 읽고 와서 실행한다.
스택 영역과 힙 영역
코드 영역과 데이터 영역은 크기가 변하지 않아 정적 할당 영역이다.
힙 영역과 스택 영역은 크기가 변하여 동적 할당 영역이다.
주소 중복을 방지하기 위해 스택 영역과 힙 영역은 메모리에 있는 주소 할당이 다른 방식으로 할당이 된다.
- 힙 영역은 낮은 주소에서 높은 주소로 할당
- 스택 영역은 높은 주소에서 낮은 주소로 할당
프로세스 생성과 상태
프로세스 상태는 응답없음, 실행중 이라는 값을 갖고 있다.
대표적인 프로세스 상태
- 생성 상태 (new)
- 이제 막 PCB를 받아 생성이 된 상태
- 준비 상태 (ready)
- 당장 실행이 가능하지만 자신의 차례가 아니라 실행을 안하는 상태
- CPU 자원을 할당을 받는다면 실행 상태가 되는데 그것을 디스패치라고 한다.
- 실행 상태 (running)
- 실행을 하고 있는 상태
- 타이머 인터럽트는 타임 아웃 즉, 자신한테 할당된 CPU 시간이 끝나는 상황
- 입출력 요청, 어떤 이벤트가 있어야만 실행이 가능하다면 대기 상태로 전환
- 대기 상태 (blocked)
- 준비 상태와 다른점이라면 지금 당장 실행할 수 없는 상황이다. (예: 입출력 장치한테 요청한 상황, 입출력 작업이 완료될때까지 기다려야함)
- 입출력 완료가 된다면 다시 준비 상태로 전환
- 종료 상태 (terminated)
- 실행이 끝나서 자원을 반납하는 상태
리눅스 프로세스(태스크) 상태 확인
- R: Running: 실행 상태
- S: Sleeping: 대기 상태
- W: Waiting: 준비 상태
- S: Stopped: 종료 상태
- Z: Zombie: 프로세스 종료 후 자원이 반환되었지만 커널 영역에 프로세스가 남아있는 상태(불안정한 상태)
프로세스의 계층적 구조
많은 운영체제에서 프로세스를 계층적 구조로 관리된다.
- 처음 부팅을 하면 최초의 프로세스가 생긴다.
- 최초의 프로세스가 여러 프로세스를 생성한다. 부모 - 자식 관계 (Tree 형태의 계층 구조)
pstree 명령어를 터미널에 실행한다면 프로세스의 계층적 구조를 확인할 수 있다.
launchd는 MacOS상의 최초 프로세스
계층적 구조로 프로세스가 생성되는 원리
- 여러 원리 중 하나가 fork-exec이다.
fork
- 자신의 프로세스와 동일한 프로세스가 자식 프로세스로 생성이 된다.(PID값은 다르다.)
- 메모리 상에서 A를 포크한 다면 동일한 프로세스가 병렬적으로 실행된다.
exec
- 새로운 코드로 대체 (덮어쓰기)
- pid값은 유지가 된 채 자식 프로세스로 대체 된다.
- 옷을 갈아입는다라는 표현으로 말할 수 있다.
fork-exec
- bash
- 자식 bash (fork, 새로운 메모리 영역 할당)
- ls (exec, 자식 bash 덮어쓰기)
스레드
- 프로세스를 구성하는 실행 흐름의 단위
- 프로그램 개발할 때 사용하는 스레드를 말한다.
- 아래 이미지를 보면 현재 메모리에 다양한 프로세스가 적재되어 있다.
- CPU는 프로세스를 한 번에 하나씩 갖고 와서 실행흐름에 따라 실행이 될 것이다.
- 만약에 웹 브라우저를 이루고 있는 프로세스 실행흐름을 추가하면 웹 브라우저 프로세스는 두 개의 실행 흐름이 있다면 한 번에 여러 코드를 동시에 실행 할 수 있다.
- 한번에 여러개를 실행하는 것이 스레드라고 한다.
어떻게 실행 흐름이 추가된다고 한 번에 여러 코드를 동시에 실행하는 것 인가
스레드는 각기 다른 스레드 ID, 프로그램 카운터, 레지스터, 스택를 갖고 있다.
- 프로세스를 구성하는 요소를 각기 다르게 갖고 있으면 가능하다.
멀티 흐로세스와 멀티 스레드의 차이점
개발자에게 중요한 내용이다.
동일한 작업을 수행하는 별도의 여러개의 프로세스를 실행하는 것과 하나의 프로세스내에 각기 다른 실행흐름을 갖고 있는 여러개의 스레드를 만드는 것은 어떤 차이점이 있을까?
- 위의 이미지는 왼쪽은 멀티 프로세스
- 오른쪽은 멀티 스레드이다.
가장 주된 차이점은 자원 공유 여부(엄청 중요)
- 프로세스간에는 기본적으로 자원을 공유하지 않음
- 스레드간에는 프로세스의 자원을 공유
- 스레드는 공유하는 자원이 있기에 만약 공유하는 자원이 하나라도 문제가 생기면 모든 스레드가 문제가 발생할 수 있다.
- 이러한 예시를 가장 잘보여주는 것이 웹 브라우저로 예시를 들 수 있다.
- 어떤 웹브라우저 A에서는 각각의 탭을 별도 프로세스로 만든다. 탭 만큼 프로세스가 생긴다.
- 어떤 웹브라우저 B에서는 각각의 탭을 스레드로 만든다.탭 만큼 스레드가 생긴다.
- A 같은 경우 하나의 탭이 문제가 생길 때 하나의 탭만 종료하면 된다.
- B 같은 경우는 하나의 탭이 문제가 생길 때 브라우저 자체를 종료한다.
그러면 멀티프로세스가 더 좋은가?
멀티 프로세스는 자원을 공유하지 않기에 멀티 스레드보다 메모리 소요량이 크다.
- 프로세스 간에는 기본적으로 자원을 공유하지 않음
프로세스 간에도 자원을 공유할 수 있다.
프로세스간 통신 (IPC; Inter-Process Communication)
- 공유 메모리를 통한 통신
- 파이프를 통한 통신
- 네트워크 소켓을 통한 통신
(실습) 멀티프로세스와 멀티스레드
C언어로 실제로 코드로 멀티 프로세스와 멀티 스레드에 현상을 관측해보자.
아래 코드는 프로세스의 pid값을 출력한다. pid값은 운영체제가 실행될때 값을 부여 해주는 것, 실행될떄마다 값이 달라짐
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello, os\\n");
printf("my pid is %d", getpid());
return 0;
}
아래 코드는 부모 프로세스와 자식 프로세스의 pid값이 다르지만 executed!가 두 번 실행되는 것을 볼 수 있다
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("parent pid is %d\\n", getpid());
if (fork() == 0) {
printf("child pid is %d\\n", getpid());
}
printf("executed!\\n");
return 0;
}
아래 코드는 자식의 자식 프로세스를 만들어주는 코드이다.
#include <stdio.h>
#include <unistd.h>
void foo() {
printf("execute foo\\n");
}
int main()
{
if (fork() == 0) {
if (fork() == 0) {
printf("child of child pid is %d\\n", getpid());
foo();
}
else {
printf("child pid is %d\\n", getpid());
foo();
}
}
else {
if(fork() == 0) {
printf("child pid is %d\\n", getpid());
foo();
}
else {
printf("parent pid is %d\\n", getpid());
foo();
}
}
return 0;
}
thread를 생성하는 코드도 있다.
아래 코드는 스레드를 생성하는 예제 코드이다.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *foo() {
printf("process id is %d\\n", getpid());
return NULL;
}
int main() {
pthread_t thread1;
pthread_create(&thread1, NULL, foo, NULL);
pthread_join(thread1, NULL);
return 0;
}
아래 코드는 스레드의 아이디를 조회할 수 있다.
**#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *foo() {
long thread_id = (long int)pthread_self();
printf("process id is %d\\n", getpid());
printf("this is thread %ld\\n", thread_id);
return NULL;
}
int main() {
pthread_t thread1;
pthread_create(&thread1, NULL, foo, NULL);
pthread_join(thread1, NULL);
return 0;
}**
스레드를 3개 만들고 각각의 스레드는 foo라는 함수를 실행한다.
foo는 프로세스 id와 스레드 id를 출력한다.
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
void *foo() {
long thread_id = (long int)pthread_self();
printf("process id is %d\\n", getpid());
printf("this is thread %ld\\n", thread_id);
return NULL;
}
int main() {
pthread_t thread1;
pthread_t thread2;
pthread_t thread3;
pthread_create(&thread1, NULL, foo, NULL);
pthread_create(&thread2, NULL, foo, NULL);
pthread_create(&thread3, NULL, foo, NULL);
pthread_join(thread1, NULL);
pthread_join(thread2, NULL);
pthread_join(thread3, NULL);
return 0;
}
스레드는 프로세스 내의 실행단위이기에 같은 프로세스 아이디를 공유한다.
아래는 자바로 여러 스레드를 만들어주는 코드이다.
public class Main {
public static void main(String[] args) {
Thread thread1 = new Thread(new FooRunnable());
Thread thread2 = new Thread(new BarRunnable());
Thread thread3 = new Thread(new BazRunnable());
thread1.start();
thread2.start();
thread3.start();
try {
thread1.join();
thread2.join();
thread3.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class FooRunnable implements Runnable {
@Override
public void run() {
System.out.println("foo executed");
}
}
class BarRunnable implements Runnable {
@Override
public void run() {
System.out.println("bar executed");
}
}
class BazRunnable implements Runnable {
@Override
public void run() {
System.out.println("baz executed");
}
}
'Computer Science > Operating System' 카테고리의 다른 글
[Computer Science] [운영체제] 파일 시스템 (0) | 2025.01.05 |
---|---|
[Computer Science] [운영체제] 가상 메모리 관리 (0) | 2025.01.04 |
[Computer Science] [운영체제] 동기화와 교착상태 (2) | 2025.01.01 |
[Computer Science] [운영체제] CPU 스케줄링 (0) | 2024.12.31 |