본문 바로가기
전공/유닉스

6장. 프로세스 생성과 실행

by 임 낭 만 2023. 11. 1.

Process의 생성

  • fork 시스템 호출 :
#include <sys/types.h> 
#include <unistd.h> 
pid_t fork(void);
//아무런 인자도 주어지지 않음
//fork를 호출한 프로세스를 parent 프로세스라고 부름
//새로 생성되는 프로세스를 child 프로세스라고 부름
// 이 두 개는 정확히 같음
//parent 프로세스가 자기 자신을 그대로 복사해서 child 프로세스를 만들어내기 때문
//프로그램을 main 함수의 첫 번째 줄부터 시작하지 않음
  • 수행되던 process(parent)의 복사본 process(child) 생성
  • fork() 바로 다음 문장부터 동시에 실행
  • 두 process의 차이점 :
    • pid와 ppid가 다르다
    • fork()의 return 값이 다르다.
      • parent process의 return 값은 child process의 process id
      • child process의 return 값은 0이다
  • fork 실패 시 -1 return
    • 실패 원인 :
      • 시스템 전체 process의 수 제한
      • 한 process가 생성할 수 있는 process 수 제한
int main(void) {
	pid_t pid;

	printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());

	pid = fork();
	if (pid == 0) {
		printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());
		printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());
		return 0;
	}
	wait(0);	//child프로세스가 종료할때까지 대기

	return 0;
}
int main(void) {
	pid_t pid;

	printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());

	pid = fork();
	if (pid == 0) {
		printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());
		sleep(5);	//5초동안 쉼
		printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());
		return 0;
	}
	sleep(1);	//1초동안 쉼 -> wait이 있어야 함
	return 0;
}
//안 좋은 코드
int main(int argc, char **argv) {
	int i, N;
	pid_t pid;

	N = atoi(argv[1]);
	printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());

	for (i = 0; i < N; i++) {
		pid = fork();
		if (pid == 0) {
			printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());
		}
	}
	for (i = 0; i < N; i++) {	//N개의 child 만들었으므로 N번 wait
		if (pid > 0) {
			wait(0);
		}
	}
	return 0;
}
//parent와 child가 작업하는 코드를 분리해야함
//child 함수 마지막에는 exit 필수
void do_child(void) {
	printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());
	exit(0);	//필수
}
int main(int argc, char **argv) {
	int i, N;
	pid_t pid;

	N = atoi(argv[1]);
	printf("pid=%ld ... ppid=%ld\n", getpid(), getppid());

	for (i = 0; i < N; i++) {
		pid = fork();
		if (pid == 0)
			do_child();
	}
	for (i = 0; i < N; i++) {
		wait(0);
	}
	return 0;
}

 

fork : 파일과 자료

  • child process는 parent process의 복제
    • 모든 변수 값이 그대로 복제된다.
    • fork()후에 변경된 값은 복제되지 않는다 (완전히 분리된 별개의 두 프로세스가 됨)
    • file descriptor도 복제된다
      • parent process가 open한 file은 child process에게도 open
    • parent와 child가 file을 공통으로 사용 가능
int main(void) {
	int fd;
	pid_t pid;
	char buf[10];

	fd = open("data", O_RDONLY);
	read(fd, buf, 10);
	printf("befork: %ld\n", lseek(fd, (off_t)0, SEEK_CUR));	//10출력
	switch (pid == fork()) {
	case -1: perror("fork failed\n");
			exit(1);
			break;
	case 0: printf("child before read: %ld\n", lseek(fd, (off_t)0, SEEK_CUR));
			read(fd, buf, 10);
			printf("child after read: %ld\n", lseek(fd, (off_t)0, SEEK_CUR));
			break;
	default: wait((int*)0);
			printf("parent after wait: %ld\n", lseek(fd, (off_t)0, SEEK_CUR));
	}
	return 0;
}
/*
before fork : 10
child before read : 10
child after read : 20
parent after wait : 20
*/

exit 시스템 호출

#include <stdlib.h> 
void exit(int status);

//return : 함수를 호출한 위치로 되돌아감
//exit : 무조건 그냥 종료.
//status : child가 parent한테 어떻게 종료했는지 전달하는 숫자 (0~255)
//exit하는 위치별로 status 숫자를 다르게 써줘야함
  • 사용법: 
    • exit: process 정지→ open된 file 닫기→ clean-up-action
    • status의 값은 프로세스 종료 후, $ echo $?
    • 명령에 의해 알아낼 수 있다.
  • clean-up-action 지정 :
#include <stdlib.h> 
int atexit(void (*func) (void));
  • 지정된 순서의 역순으로 실행
void ABC(void) {
	printf("ABC ...\n");
}
void DEF(void) {
	printf("DEF ...\n");
}
void GHI(void) {
	printf("GHI ...\n");
}
int main(void) {
	atexit(ABC);
	atexit(DEF);
	atexit(GHI);
	exit(15);
}
//ghi def abc 순서대로 실행됨

exec을 이용하여 새 프로그램을 수행

(프로그램이 다른 프로그램으로 변신하는 시스템 call)

  • 사용법 :
#include <unistd.h> 
int execl(const char *path, const char *arg0, ..., const char *argn, (char *) 0); 
int execlp(const char *file, const char *arg0, ..., const char *argn, (char *) 0); 
int execv(const char *path, char *const argv[]); 
int execvp(const char *file, char *const argv[]);
//네 가지 다 새로운 프로그램을 실행시키는 것은 동일함
//인자를 사용하는 방법과 파일 이름을 사용하는 방법에 차이가 있음
//l과 lp는 인자값들을 전부 다 적어줌 (첫번째 인자는 실행시키는 프로그램의 이름, 마지막은 널)
//v와 vp는 포인터 배열을 인자값으로 넣어줌, 포인터 배열의 이름만 줌
//l과 v는 (p가 없으면) 파일의 정확한 경로를 적음 (파일의 이름만 적을 수 없음)
  • 공통점 :
    • 호출 프로세스의 주기억장치 공간에 새로운 프로그램을 적재
      → 호출 프로세스는 no longer exists.
      → 새 process는 처음부터 실행
      → 새 process는 호출 프로세스의 id로 실행
    • 실패 시 -1 return; 성공 시 return이 없다
    • fork와의 차이점 : 기존 프로세스와 병렬 수행이 아니다.
  • 차이점 :
    • path : 파일의 경로 이름 포함; vs. file : 파일 이름
    • 인수 :
      • arg0 : 프로그램 이름(경로 이름 빼고); and arg1,...,argn : 프로그램에 입력으로 사용될 인수들, 마지막엔 null pointer
      • argv[] : 배열로 받기
    • file 이름을 쓰는 경우는? 환경 변수에 의해 설정된 path안의 file; ($echo $PATH)
int main(void) {
	execl("./test1", "test1", "3", "5", "7", (char*)0);
	printf("execution(execl) fails...\n");
	exit(1);
}
int main(void) {
	char* const av[] = { "test1", "3", "5", "7", (char*)0 };

	execv("./test1", av);
	printf("execution(execv) fails...\n");
	exit(1);
}

 

int main(void) {
	char* const av[] = { "test1", "3", "5", "7", (char*)0 };

	execv("./test1", av);
	exit(1);
}
int main(void) {
	char* const av[] = { "test1", "3", "5", "7", (char*)0 };

	execv("test1", av);
	exit(1);
}
int main(void) {
	char* const av[] = { "test1", "3", "5", "7", (char*)0 };

	execvp("./test1", av);
	exit(1);
}
int main(void) {
	char* const av[] = { "test1", "3", "5", "7", (char*)0 };

	execvp("test1", av);
	exit(1);
}

'전공 > 유닉스' 카테고리의 다른 글

5장. 프로세스 정보  (0) 2023.11.01
4장. 시스템 정보  (0) 2023.11.01
3장. 파일과 디렉토리  (1) 2023.11.01
2장. 파일 입출력 (2)  (1) 2023.10.23
2장. 파일 입출력 (1)  (1) 2023.10.05

댓글