시스템 프로그래밍 10-1(Exceptional Control Flow)

TonyHan·2021년 5월 26일
1

3. Reaping Child Processes

child process가 terminate되면 컴퓨터 시스템에서 사용하는 resource들이 있다. 프로세스에 해당하는 Exit status나 여러가지 OS table이라는 것이 있다. 이것을 정상적으로 OS에서 제거해 주어야 한다. 하지만 여전히 리소스를 사용하고 있는 것을 보고 zombie라고 한다.

  • zombie : OS에서 정상적으로 제거되지 못하고 리소스를 사용하고 있는 child process

이런것을 제거하는 것을 보고 Reaping이라고 한다.

  • reaping : child process를 제거하는 과정

  • Idea
    • When process terminates, it still consumes system resources
      • Examples: Exit status, various OS tables
    • Called a “zombie”
      • Living corpse, half alive and half dead
  • Reaping
    • Performed by parent on terminated child (using wait or waitpid)
    • Parent is given exit status information
    • Kernel then deletes zombie child process


리눅스에서는 메모리를 두가지로 나눈다. kernel, user process가 사용하는 address space로 나뉜다. 프로세스 실행시 커널에는 PA, PB에 해당하는 자료구조가 따로 있다.(PB는 PA의 자식 프로세스이다.)

만약 PB가 fork가 끝나서 종료되면 커널에서 사용하는 PB의 메모리를 커널이 제거해주어야 한다. 근데 이걸 지워주지 않은 상태로 PB를 종료하면 PB에 대한 정보가 메모리에 살아있게 된다. 이것을 보고 zombie 상태라고 부른다.

이것을 해결하기 위해 PB가 제거시 PA에 알려주어서 커널에 PB 메모리 해제를 요청하게 된다. 이것을 보고 Reaping이라고 부른다.

만약 부모 process가 요청을 안하면 memory 누수가 발생한다.

알려주는 시스템 콜은 wait, waitpid이다.

  • What if parent doesn’t reap?
    • If any parent terminates without reaping a child, then the orphaned child will be reaped by init process (pid == 1)
    • So, only need explicit reaping in long-running processes
      • e.g., shells and servers

만약에 child가 reaping 하지않고 parent가 제거되면 고아가 되었다고 한다.(orphan) child process는 나중에 PA의 조상인 init process가 orphan process를 reaping해준다.

Zombie Example

  • ps shows child process as “defunct” (i.e., a zombie)
  • Killing parent allows child to be reaped by init
void fork7() {
	if (fork() == 0) {
		/* Child */
		printf("Terminating Child, PID = %d\n", getpid());
		exit(0);
	} else {
		printf("Running Parent, PID = %d\n", getpid());
		while (1)
			; /* Infinite loop */
	}
} forks.c


잘보면 6640은 defunct로 현재 zombie process인것을 확인할 수 있다.

그래서 부모 프로세스를 제거해주면 init process가 orphan process를 제거해준다.

Non-terminating Child Example

int fork8()
{
	if (fork() == 0) {
		/* Child */
		printf("Running Child, PID = %d\n",getpid());
		while (1)
			; /* Infinite loop */
	} else {
		printf("Terminating Parent, PID = %d\n",
			getpid());
		exit(0);
	}
}

wait: Synchronizing with Children

wait라는 시스템 콜을 이용해서 reaping할 수 있다.

  • Parent reaps a child by calling the wait function

parent가 wait를 이용해서 child를 reap할 수 있다.

wait 함수는 현재 부모가 child가 끝날때까지 자기는 suspend되어 있는 상태이다. 즉 내가(parent가) cpu사용중 cpu제어권에 대해 나는 그냥 suspend(wait) 상태이다. child가 terminate될때까지 부모는 계속 기다리고 있는 것이다.

그리고 child가 정상적으로 terminate되면 child process pid가 반환된다. 인자로 넘어오는 child_status는 null이 아니면 integer의 왜 child가 terminate되었는지에 대한 표시하기 위한 값이 들어가 있다.

  • int wait(int * child_status)
    • Suspends current process until one of its children terminates
    • Return value is the pid of the child process that terminated
    • If child_status != NULL, then the integer it points to will be set to a value that indicates reason the child terminated and the exit status:
    • Checked using macros defined in wait.h
      • WIFEXITED, WEXITSTATUS, WIFSIGNALED,
        WTERMSIG, WIFSTOPPED, WSTOPSIG,
        WIFCONTINUED
      • See textbook for details

wait: Synchronizing with Children

아래의 코드가 정상적인 코드이다.

Hello Child가 정상적으로 exit하면 wait에 signal을 보낸다.

void fork9() {
	int child_status;
	if (fork() == 0) {
		printf("HC: hello from child\n");
		exit(0);
	} else {
		printf("HP: hello from parent\n");
		wait(&child_status);
		printf("CT: child has terminated\n");
	}
	printf("Bye\n");
}

Another wait Example

  • If multiple children completed, will take in arbitrary order
  • Can use macros WIFEXITED and WEXITSTATUS to get information about exit status
void fork10() {
	pid_t pid[N];
	int i, child_status;
	for (i = 0; i < N; i++)
		if ((pid[i] = fork()) == 0) {
		exit(100+i); /* Child */
	}
	for (i = 0; i < N; i++) { /* Parent */
		pid_t wpid = wait(&child_status);
		if (WIFEXITED(child_status))
			printf("Child %d terminated with exit status %d\n",
			wpid, WEXITSTATUS(child_status));
		else
			printf("Child %d terminate abnormally\n", wpid);
	}
} 

10개의 child process를 띄운다음에 각각의 프로세스를 exit한다. 그다음에 부모 프로세스에서 자식 프로세스를 받아서 각각 reaping해준다.

waitpid: Waiting for a Specific Process

얘는 프로세스를 지정할 수 있다.

  • pid_t waitpid(pid_t pid, int &status, int options)
    • Suspends current process until specific process terminates
    • Various options (see textbook)
void fork11() {
	pid_t pid[N];
	int i;
	int child_status;
	for (i = 0; i < N; i++)
		if ((pid[i] = fork()) == 0)
			exit(100+i); /* Child */
	for (i = N-1; i >= 0; i--) {
		pid_t wpid = waitpid(pid[i], &child_status, 0);
		if (WIFEXITED(child_status))
			printf("Child %d terminated with exit status %d\n", 
            		wpid, WEXITSTATUS(child_status));
		else
			printf("Child %d terminate abnormally\n", wpid);
	}
}

process id는 pid 배열에 일단 저장해 놓는다.

waitpid(pid[i] ... ) 형태인데 child에 해당하는 pid를 보고 wait하고 기다린다.

execve: Loading and Running Programs

어떤 시행하는 binary program을 loading해서 running하는 프로그램이다.

  • int execve(char filename, char argv[], char * envp[])

쉘에서 > ls -al 과 같은 것을 실행하면 > 프로세스가 ls -al프로세스를 fork해서 실행한다.

  • Loads and runs in the current process:
    • Executable file filename
      • Can be object file or script file beginning with #!interpreter
        (e.g., #!/bin/bash)
    • …with argument list argv
      • By convention argv[0]==filename
    • …and environment variable list envp
      • “name=value” strings (e.g., USER=droh)
      • getenv, putenv, printenv
  • Overwrites code, data, and stack
    • Retains PID, open files and signal context
  • Called once and never returns
    • …except if there is an error

execve를 실행하면 code, data, stack을 모두 overwrite하게 된다. 하지만 PID, file, signal은 그냥 유지한다.

Structure of the stack when a new program starts

execve Example

  • Executes “/bin/ls –lt /usr/include” in child process using current environment:

Summary

  • Exceptions
    • Events that require nonstandard control flow
    • Generated externally (interrupts) or internally (traps and faults)
  • Processes
    • At any given time, system has multiple active processes
    • Only one can execute at a time on a single core, though
    • Each process appears to have total control of processor + private memory space
  • Spawning processes
    • Call fork
    • One call, two returns
  • Process completion
    • Call exit
    • One call, no return
  • Reaping and waiting for processes
    • Call wait or waitpid
  • Loading and running programs
    • Call execve (or variant)
    • One call, (normally) no return
profile
신촌거지출신개발자(시리즈 부분에 목차가 나옵니다.)

0개의 댓글