본문 바로가기

STUDY/linux

Process and Threads in LINUX

linux 환경에서 C로 multi-process, multi-thread 를 다루는 것을 정리해보자

process와 thread가 무엇인지는 예전에 쓴 포스팅을 참고하기를 바란다.

 

 

process concept

process가 무엇인가? process는 프로그램 실행 흐름의 가장 기본적인 단위이다. 다들 이렇게 설명하는데 사실 뭔 소린지 이해가 안갈 수 있다. 그럴때는 프로그램 실행을 위한 작업의 대상, 즉 os가 sc

josushell.tistory.com

 

 

multi-thread programming

앞에서 배운 multi-process programming은 마냥 좋기만 할까? 딱히 그렇지도 않다. process process는 기본적으로 Heavy-weight이다. 즉 프로세스 1개를 만드는 것은 오버헤드가 크다. 왜냐하면 process는 가진..

josushell.tistory.com

 

 

terminal에서 현재 실행중인 process를 확인하기 위해서는 ps 를 치면 된다.

 

 

이때 보이는 PID는 process ID, 즉 process 식별자이다.

이러한 PID를 반환하는 함수는 getpid() 이다.


Process ID를 얻는 함수

# include <sys/types.h>

# include <unistd.h>

pid_t getpid(void);

함수 명세

return process ID (pid_t 타입: int)
description 현재 process의 ID를 구한다.

 

파라미터

void nothing

 

# include <sys/types.h>

# include <unistd.h>

pid_t getppid(void);

함수 명세

return parent process ID (pid_t 타입: int)
description 부모 process의 ID를 구한다.

 

파라미터

void nothing

 

fork()의 결과는 자식 프로세스가 생성되는 것이다. 이때 부모 process의 PID는 getppid() 를 통해서 얻게 된다.


Process를 생성하는 함수

# include <sys/types.h>

# include <unistd.h>

pid_t fork(void);

함수 명세

return child process: 0

parent process: child PID

실패 시 -1
description 자식 process 새로를 생성한다.

 

파라미터

void nothing

 

fork() 의 결과는 자식 프로세스가 새로 생성되는 것이다.

즉 같은 코드에 대해서 이를 처리하는 프로세스가 fork() 를 하는 순간 두개가 된다.

이때 자식 process에서는 fork()의 결과가 0이 되고, 부모 process에서는 fork()의 결과가 자식 process의 PID이다.

 

Example

fork() 를 통해 생긴 두개의 process의 ID를 구하는 프로그램을 작성해보자

 

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

int main()
{
	pid_t	pid;

	pid = fork();

	if (pid == 0)
		printf("Child Process - PID: %d, PPID: %d\n",getpid(), getppid());
	else
		printf("Parent Process - PID: %d, child PID: %d\n", getpid(), pid);

}

 

gcc 로 컴파일 한 결과는 다음과 같다.

 

 

이때 중요한 점은 두 프로세스가 실행되는 순서는 os의 scheduling algorithm에 따른 것이므로 고정된 것이 아니라는 것이다. 즉 실행 순서는 언제든지 바뀔 수 있다.

process scheduling 에 대한 포스팅은 다음을 참고하면 좋다.

 

 

process scheduling

앞에서 배운 process에서 cpu는 처리량을 늘리기 위해서 context switch를 한다고 했다. 즉 cpu는 running과 waiting의 2가지 상태가 번갈아가며 수행되는 것이다. 처리하려는 작업에 따라 cpu burst가 길 수도..

josushell.tistory.com

 


Process를 종료하는 함수

 

process는 정상 종료와 비정상 종료 두가지의 상태로 종료될 수 있다.

정상 종료 시에는 exit(), _exit() 함수가 실행된다.

비정상 종료시에는 signal에 의해 abort()가 실행된다.

 

main 함수에서 return을 하게 되면 exit()함수가 먼저 실행되고, _exit() 함수가 마지막으로 시스템에 의해 실행되어 메모리와 PID 등의 자원을 반납하고 종료하게 된다.

 

# include <stdlib.h>

void exit(int status);

void _exit(int status);

int atexit(void (*func) (void));

함수를 exit handler에 쌓는다. 이때 exit handler는 stack으로 관리된다.

따라서 종료하기 전에 exit handler의 함수를 실행하고 _exit()을 통해 종료하게 된다.


Process를 기다리는 함수

# include <sys/types.h>

# include <sys/wait.h>

pid_t wait(int *statloc);

 

함수 명세

return 성공 시 PID, WNOHANG option시 상황에 따라 0,실패 시 -1
description child process가 종료되는 것을 기다린다.

 

파라미터

int *statloc 종료 상태 값

 

# include <sys/types.h>

# include <sys/wait.h>

pid_t waitpid(pid_t pid, int *statloc, int options);

함수 명세

return 성공 시 PID, WNOHANG option시 상황에 따라 0,실패 시 -1
description pid를 가진 child process가 종료되는 것을 기다린다.

 

파라미터

int *statloc 종료 상태 값

 

 

이 함수들은 process간의 실행 순서, 즉 동기를 맞춰주는 역할을 한다.

예를 들어, 5개의 process가 실행중이라고 할때 wait() 함수는 불특정한 하나의 프로세스가 종료될 때까지 다음 코드를 실행하지 않는다.

이렇게 코드 진행을 의도적으로 막는 것을 block이라고 한다.

반면에 waitpid() 함수는 pid를 가진 특정한 프로세스가 종료될 때까지 다음 코드를 실행하지 않는다는 차이점이 있다.

 

Example

위의 PID 확인 예제를 수정해서 Parent process가 무조건 child process의 실행 후에 실행되도록 하자.

 

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

int main()
{
	pid_t	pid;
	int	status;

	pid = fork();

	if (pid == 0)
	{
		printf("Child Process - PID: %d, PPID: %d\n",getpid(), getppid());
		sleep(2);
	}
	else
	{
		waitpid(pid, &status, 0);
		printf("----- Child Process Terminated: %d -----\n", status);
		printf("Parent Process - PID: %d, child PID: %d\n", getpid(), pid);
	}

}

 

 

실행 결과는 위와 같다.

즉 parent process는 pid에 해당하는 프로세스가 종료될 때 까지 기다리게 된다.

이때 status 파라미터는 process가 종료하면서 return 하게 되는 상태값에 대한 정보이며 필요하지 않는 경우 NULL을 쓰면 된다.

 


Thread Control with POSIX library

process를 복사하는 것은 사실 cost한 작업이다. 모든 자료구조와 자원을 새롭게 생성해야 하기 때문이다.

따라서 하나의 프로세스 안에서 실행 흐름을 여러개 만드는 thread의 개념이 나왔다. 이 중 POSIX에서 정한 thread의 표준이 바로 Pthread이다. link 시 -lpthread option이 반드시 필요하다.

 

# include <pthread.h>

int pthread_create(pthread_t *tid, pthread_attr_t *attr, void *(start_routine) (void *), void *arg);

함수 명세

return 성공 시 0, 실패 시 error 값에 따른 nonzero error number
description thread를 새로 생성한다.

 

파라미터

pthread_t *tid thread의 id
pthread_attr_t *attr thread의 option, NULL 일 경우 default 속성이 적용됨
void *(start_routine) (void *) thread가 실행할 함수 포인터 (이 함수를 실행하는 thread가 생성됨)
void *arg 세 번째 파라미터의 함수 포인터로 전달될 파라미터 (1개만 가능)

 

 

# include <pthread.h>

void pthread_exit(void *retval);

함수 명세

return nothing
description thread를 종료한다.

 

파라미터

void *retval thread가 종료하고 return 하게 되는 값 (InOut)

이 함수의 파라미터로 넣어준 포인터는 pthread_join의 파라미터에서 회수하여 값을 확인할 수 있다.

 

 

# include <pthread.h>

int pthread_join(pthread_t tid, void **thread_return);

함수 명세

return 성공 시 0, 실패 시 error 에 따른 nonzero error number
description thread가 종료되는 것을 기다린다.

 

파라미터

pthread_t tid 종료 상태 값
void **thread_return thread의 start routine이 반납하는 return 값

 

Example

사실 thread, process 는 함수만 봐서는 뭔 소리인지 잘 모를 수 있다.

이럴 때는 바로 예시를 본다.

thread를 두개를 생성해서 각각의 thread가 print를 실행하도록 하자.

 

#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

static void print(char *msg)
{
	printf("Thread Message: %s\n", msg);
	pthread_exit(NULL);
}

int main(int argc, char *argv[])
{
	pthread_t	tid1, tid2;
	
	if (pthread_create(&tid1, NULL, (void *)print, (void *)"thread1") != 0)
		exit(1);
	if (pthread_create(&tid2, NULL, (void *)print, (void *)"thread2") != 0)
		exit(1);

	printf("---- Thread Created: tid1 = %d, tid2 = %d ----\n", tid1, tid2);

	if (pthread_join(tid1, NULL) != 0)
		exit(1);
	if (pthread_join(tid2, NULL) != 0)
		exit(1);
}

 

컴파일 할때는 다음과 같이 link option에 -lpthread option을 줘야한다.

 

결과는 다음과 같다.

 

'STUDY > linux' 카테고리의 다른 글

Files and Directories in LINUX  (0) 2022.03.31
SIGNAL in LINUX  (0) 2022.03.29
File I/O functions in C Library  (0) 2022.03.23
File I/O system call in LINUX  (0) 2022.01.04
shell commands  (0) 2022.01.04