Operating System/UNIX

[UNIX] 멀티 프로세스 (Multi Process) 프로그래밍

  • -

개념

프로세스란 실행 중인 프로그램(실행 파일)이자, 현대의 컴퓨팅 시스템에서 작업의 단위이다.

 

프로세스는 실행되는 동안 여러 개의 새로운 프로세스들을 생성할 수 있다.

생성하는 프로세스를 부모 프로세스라 부르고, 새로운 프로세스는 자식 프로세스라고 부른다.

새로운 프로세스들은 각각 다시 자식 프로세스들을 생성할 수 있으며, 이는 프로세스 트리를 형성한다.

UNIX의 운영체제는 고유한 프로세스 식별자(PID)를 사용하여 프로세스를 구분한다.

 

자식 프로세스는 부모 프로세스와 동일한 프로그램을 가지며, 주소 공간(fd 테이블 포함)이 복사된다.

 

 

프로세스 생성

#include <unistd.h>
#include <sys/types.h>	// pid_t

pid_t fork(void);

 

fork() 시스템 콜로 자식 프로세스를 생성한다. pid_tint와 동일하다.

 

fork()가 호출된 이후 두 프로세스가 나뉘며, 각 프로세스는 fork()의 반환 값을 다르게 받는다.

성공하면 부모 프로세스는 자식 프로세스의 PID가 반환되고, 자식 프로세스는 0이 반환된다.

실패하면 -1이 반환되고, 자식 프로세스가 생성되지 않으며, 오류를 나타내는 errno가 설정된다.

 

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>	// pid_t

int main()
{
	pid_t pid = fork();

	if (pid < 0)
	{
		printf("fork error\n");
		return 1;
	}
	
	if (pid == 0)
	{
		printf("I'm Child Process\n");
	}
	else
	{
		printf("I'm Parent Process\n");
	}
	return 0;
}

 

fork()는 위와 같이 사용 가능하며, 실행 결과는 아래와 같다.

 

 

 

자식 프로세스 제어

wait / waitpid

#include <sys/wait.h>
#include <sys/type.h>	// pid_t

pid_t wait(int *_Nullable wstatus);
pid_t waitpid(pid_t pid, int *_Nullable wstatus, int options);

 

부모 프로세스는 자식 프로세스가 종료될 때까지 wait() 시스템 콜로 기다릴 수 있다.

 

프로세스가 종료되면 사용하던 자원은 운영체제가 회수하지만,

프로세스의 종료 상태가 저장되는 프로세스 테이블에는 부모 프로세스가 wait() 할 때까지 남아있게 된다.

종료되었지만 부모 프로세스가 wait() 하지 않은 자식 프로세스를 좀비 프로세스라고 한다.

 

또한, 자식 프로세스가 종료되기 이전에 부모 프로세스가 종료되는 경우, 자식 프로세스를 고아 프로세스라고 한다. 

UNIX는 고아 프로세스의 부모를 init 프로세스로 재설정함으로 인해 문제를 해결한다.

 

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

int main()
{
	pid_t pid = fork();
	int status;

	if (pid < 0)
	{
		printf("fork error\n");
		return 1;
	}
	
	if (pid == 0)
	{
		printf("I'm Child Process\n");
		int meaninglessWork[1000000];
		for (int i=0; i<1000000; ++i)
			meaninglessWork[i] = i;
		printf("Finished work\n");
	}
	else
	{
		printf("I'm Parent Process\n");
		wait(&status);
		if (WIFEXITED(status))
		{
			printf("Wait Status : %d\n", WEXITSTATUS(status));
		}
	}
	return 0;
}

 

wait()은 위와 같이 사용 가능하며, 실행 결과는 아래와 같다.

 

 

int 변수의 주소 값을 인자로 받으며 이는 자식 프로세스의 종료 상태를 나타낸다.

자식의 종료 상태를 알고 싶지 않는 경우, NULL을 전달 할 수 있다.

 

해당 종료 상태는 매크로 함수를 통해 쉽게 판단이 가능하다.

 

매크로 함수 설명
WIFEXITED(wstatus) 참이 반환된 경우, 자식 프로세스는 정상적으로 종료
WEXITSTATUS(wstatus) WIFEXITED에서 참이 반환됐을 경우 사용 가능하며,
어느 코드로 종료되었는지 파악 가능 (main() return value)
WIFSIGNALED(wstatus) 참이 반환된 경우, 자식 프로세스는 시그널에 의해 종료
WTERMSIG(wstatus) WIFSIGNALED에서 참이 반환됐을 경우 사용 가능하며,
어느 시그널로 종료되었는지 파악 가능 (kill signal)

 

waitpid()wait() 보다 더욱 많은 기능을 제공한다.

wait()먼저 종료되는 자식 프로세스를 감지하는 반면, waitpid()pid를 지정하여 감지가 가능하다.

 

더 많은 종료 상태 확인 매크로와 waitpid()에 대한 정보는 메뉴얼 확인

 

waitpid()wait()과 동일하게 사용하고 싶은 경우, 아래와 같이 인자를 전달한다.

 waitpid(-1, &wstatus, 0);

 

kill

#include <signal.h>
#include <sys/types.h>	// pid_t

int kill(pid_t pid, int sig);

 

프로세스간 통신 방법 중에 시그널(signal)이 존재한다.

kill() 시스템 콜로 특정 프로세스나 프로세스 그룹에게 시그널을 보낼 수 있다.

 

fork()의 반환 값으로 자식 프로세스의 pid를 알고 있으므로, 종료 시그널을 보내 자식 프로세스를 종료시킬 수 있다.

 

sig에는 원하는 시그널 값을 넣는다. 시그널의 종류는 아래 링크 참고

 

Signal (IPC) - Wikipedia

From Wikipedia, the free encyclopedia Form of inter-process communication in computer systems This article is about form of inter-process communication. For event-driven processing concept, see Signal programming. Signals are standardized messages sent to

en.wikipedia.org

 

#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <signal.h>

int main()
{
	pid_t pid = fork();
	int status;

	if (pid < 0)
	{
		printf("fork error\n");
		return 1;
	}
	
	if (pid == 0)
	{
		printf("I'm Child Process\n");
		while (1)
		{
			// infinity work
		}
		printf("Finished work\n");
	}
	else
	{
		printf("I'm Parent Process\n");
		usleep(100000);
		kill(pid, SIGINT);
		wait(&status);
		
		if (WIFSIGNALED(status))
		{
			printf("Child Process Killed : %d\n", WTERMSIG(status));
		}
	}
	return 0;
}

 

위는 kill()무한 루프에 빠진 자식 프로세스를 종료시키는 코드다.

(자식 프로세스가 무한 루프에 도달하기 이전 kill()하는 것을 막기 위해 usleep()을 주어 잠시 block 시켰다.)

 

아래는 실행 결과.

 

 

 

프로세스 간 통신 (IPC)

부모 프로세스와 자식 프로세스는 독립적인 메모리 공간이므로 데이터를 공유할 때 특정 방법을 이용해야한다.
IPC (Inter-Process Communication)는 프로세스가 데이터를 공유하기 위해 운영 체제에서 제공하는 메커니즘이다.

파일, 시그널, 소켓, 메시지 큐, 파이프, 공유 메모리 등이 있다.

 

파일

 

[UNIX] 입출력 및 리다이렉션 (I/O Redirection)

유닉스 계열 운영체제에서 파일은 연속된 n개의 바이트다. 네트워크, 디스크, 터미널 같은 모든 I/O 디바이스들은 파일로 모델링되며, 모든 입력과 출력은 해당 파일을 읽거나 쓰는 형식으로 수

sikpang.tistory.com

 

파이프

 

[UNIX] 파이프 (Pipe) 제대로 사용하기

개념 파이프는 두 프로세스가 통신할 수 있게 하는 전달자로, UNIX 기반 운영체제에서 제공하는 프로세스 간 통신 (Inter-Process Communication, IPC) 기법 중 하나이다. 멀티 프로세스에 대한 자세한 설명

sikpang.tistory.com

 

세마포어

 

[UNIX] 멀티 프로세스 및 스레드 공유 데이터 동기화

개념 협력적 멀티 프로세스 및 멀티 스레드는 공유 데이터를 동시에 접근하면 그 결과가 접근 순서에 의존되어 데이터의 일관성을 망칠 수 있다. 따라서 이들의 질서있는 실행을 보장하여, 데이

sikpang.tistory.com

 

소켓

 

[UNIX] TCP/IP 소켓 (Socket) 프로그래밍

개념 물리적으로 연결된 네트워크상에서의 데이터 송수신에 사용할 수 있는 소프트웨어적 장치다. (네트워크 망의 연결에 사용되는 도구, 더 나아가서 네트워크를 통한 두 컴퓨터의 연결) 커널

sikpang.tistory.com

 

 

참고 자료

https://man7.org/linux/man-pages/man2/fork.2.html
https://man7.org/linux/man-pages/man2/waitpid.2.html
https://man7.org/linux/man-pages/man2/kill.2.html

 

 

개인 공부용 포스팅인 점을 참고하시고, 잘못된 부분이 있다면 댓글로 남겨주시면 감사드리겠습니다.
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.