리눅스 프로그래밍 7주차 5.5~5.8

고강희·2022년 10월 12일
1

Example: execl(2)

execl은 path name과, argument list를 받아 exec을 수행하는 것을 의미

# include <unistd.h>
int main() {
	printf(“executing ls\n”);
	execl (/bin/ls”, “ls”,-l”, (char*) 0); // char(*) 0 = NULL pointer
	perror (“execl failed to run ls”);
	return 1;
}

이때 path가 모두 들어가야함 (filename을 받는것이 아닌, 경로를 받음)
이렇게 해서 /bin/ls가 실행이 되면, 커널은 환경변수를 셋팅하고, argument를 정리하는 일을 한 후에 ls program의 main(argc,"ls","-1")을 호출하게 됨

5.4 Using exec and fork together

# include <unistd.h>

int fatal(char *s) {
	perror(s);
	exit(1);
}

int main() {
	pid_t pid;
	switch (pid = fork()) {
		case -1: //error
			fatal (“fork failed”); 
			return 1;
		case 0:
			execl (/bin/ls”, “ls”,-l”, (char *)0); /* child calls exec */
			fatal (“exec failed”); //exec을 하는 순간 프로세스가 아예 바뀜
			return 1;
		default:
			wait((int*)0); //자식을 기다림
			printf(“ls completed\n”);
			return 0;
	}
}

5.5 Inherited data and file descriptors

fork(2): Files and data

  • 부모에서 열린 모든 파일들은 자식에서 역시 열림.
  • 즉 자식역시 부모와 똑같은 process file table entry를 가지게 되는데, 이렇게 자식과 부모가 같은 파일 데이터를 공유하는 것은 굉장히 중요함 (이 file descriptor table을 이용해 process간의 소통이 가능)
  • 부모의 pointer들은 자식이 standard out에 write을 할때마다 업데이트됨

Inherited properties from parent process

• Real user ID, real group ID, effective user ID, effective group ID
• Supplementary group IDs, Process group ID
• Session ID
• Controlling terminal
• The set-user-ID and set-group-ID flags
• Current working directory, Root directory
• File mode creation mask, Signal mask and disposition
• The close-on-exec flag for any open file descriptors
• Environment
• Attached shared memory segments, Memory mappings, Resource limits

The Difference between the Parent and Child

  • The return value from fork
  • The process ID
  • The parent process ID
  • The process time (tms_utime, tms_stime, tms_cutime, and tms_cstime) : 프로세스의 나이
    • The process time values of child are set to 0
  • File locks set by the parent : 부모와의 약속 ex) critical section)
  • Pending alarms
  • The set of pending signals : 부모가 처리하지 못한 신호들

exec and Open Files

  • exec을 해서 새로운 프로그램이 시작되도 process table은 항상 유지됨
  • read-writer pointer(file offset) 들은 exec call에 의해 바뀌지 않음
  • 하지만 close-on-exec flag(fd flag)를 on으로 설정하면, exec이 call 됐을 때 file들은 모두 closed되고, flag는 다시 off로 설정이됨.

The Process - 2

5.6 Terminating Processes with the exit System Call

Process Termination

  • process를 종료시키는 8가지 방법이 있음
  1. 정상정료 ( 5가지 - 일단 3가지만 ):
  • return from main (main에서 리턴)
  • Calling exit (exit 시스템 콜)
  • Calling _exit, or _Exit
  1. 비정상 종료 ( 3가지 - 일단 2가지만 ) :
  • Calling abort()
  • 외부에서 signal을 줬을 때

System Call: exit(2)

#include <stdlib.h>

void exit(int status);
void _Exit(int status);
#include <unistd.h>
void _exit(int status);
  • exit, _Exit, _exit은 종료 상태값(status)을 받는데, parent가 종료상태를 알기위해서 받음(parent가 wait으로 봄).
  • 이때 status는 4byte(int형)이지만 실제로는 하위 8bit만을 봄
    • 만약 exit(255)를 하면, 이것은 exit(-1)을 한것과 동일하게 해석함
  • 프로세스는 관습적으로 정상종료일 때 0을 return하고, 비정상 종료일 때 0이아닌 다른 값을 받음
  • exit을 하게되면, kernel은 어떤 클린업 동작을 진행하고 종료함. 반면 _exit과 _Exit은 클린업 동작 없이 즉시 종료함.
    • 이때 클린업 동작이란, 쌓여있는 모든일을 마무리하는 것을 의미 ex) buffer에 있는 값을 모두 write하는 것
  • exit(0)은 main함수에서 return(0)와 사실상 동일함.

Function: atexit(3)

#include <stdlib.h>

int atexit(void (*func)(void));
  • exit handler는 프로그램이 종료될 때 실행되는 함수
  • ISO C에서 하나의 프로세스는 32개의 exit handler를 등록할 수 있음 (플랫폼마다 갯수가 다름)
  • 이때 atexit은 이 exit handler들을 등록해줌 (return 0 on success, nonzero on error)
  • 이 exit handler들은 등록의 역순으로 실행 됨

Exit Handlers

Example:Exit handlers

void func1() { printf(“print func1\n”); }
void func2() { printf(“print func2\n”); }
void func3() { printf(“print func3\n”); }
void func4() { printf(“print func4\n”); }
int main() {
	pid_t pid;
	atexit(func1);
	atexit(func2);
	atexit(func3);
	atexit(func4);
	if ((pid = fork()) < 0) {
		perror(“fork failed”);
		exit(1);
	}
	if (pid == 0) {
		printf(“child process is called\n”);
		printf(“child process calls exit\n”);
		exit(0);
	}
	wait(NULL);
	printf(“parent process calls exit\n”);
	exit(0);
}
$ ./exit_handler_test
child process is called
child process calls exit
print func4
print func3
print func2
print func1
parent process calls exit
print func4
print func3
print func2
print func1
$

5.7 Synchronizing Process

Synchronizing with children

  • 커널은 프로세스의 모든 파일 디스크립터를 close하고, 사용했던 메모리를 반납함
  • 반면 직접적인 프로세스에 대한 정리는 하지 않고, 주변정리를 진행함 ex) pid,termination stauts... 등 process table entry의 정보)
  • 이 정보는 종료된 프로세스의 부모 프로세스가 자식의 정보를 확인할 때까지 없애지 않음
  • 이 정보를 확인하는 과정은 wati()을 통해 진행함

System Call: wait(2)

#include <sys/wait.h>

pid_t wait(int *statloc);
  • wait()이 실행되면 부모는 자식이 죽을때까지 process 수행이 block됨.

    • return 자식의 pid on success, -1 on error
  • 이때 가장 먼저 끝나는 child가 return이 되고, block됐던 부모 process는 다시 진행함

  • wait이 -1을 리턴했다는 것은, 자식이 존재하지 않는다는 것을 의미.

  • 이때 statloc argument는 종료 상태 값을 가리키는 포인터

    • exit이 종료 상태값을 받고, 이 상태값을 wait을 통해 봄
    • statloc을 NULL로하고 wait을 call한다는 것은 부모 process가 자식이 어떻게 죽었는지 관심이 없다는 것을 의미
  • 이때 이 statloc의 주소를 다시 종료 상태값으로 바꿔주는 매크로가 두가지 있음 (wait(&status))

    • WIFEXITED(status) : exit status가 0인지 0이 아닌지만 확인
    • WEXITSTATUS(status) : 실제 exit status 값으로 바꿔줌 (statloc 값을 오른쪽으로 8bit 이동)

System Call: waitpid(2)

#include <sys/wait.h>

pid_t waitpid(pid_t pid, int *statloc, int options);
  • waitpid는 여러자식중 특정한 자식만 기다리고 싶을때 하는 system call 아까 wait은 여러 자식중 가장 먼저 끝나는 자식이 리턴될 때까지 기다린다고 했음. 하지만 그게 아니라 특정 자식만 기다리고 싶을 때 waitpid 사용
  • return child pid on success, -1 on error
  • Argument:
    pid == 1 : 어떤 자식이든 상관없이 기다리겠다.
    pid > 0 : pid에 해당하는 자식을 wait하겠다.
    pid == 0 : 동일한 PGID를 가지면 어떤 자식이든 상관없이 기다리겠다.
    pid< 0 : |pid|를 PGID로 가지는 자식을 기다리겠다.
  • options == WNOHANG : 종료된애가 없으면 0을 리턴하겠다. 즉, 기다리지 않고 바로 리턴하겠다.
    WNOHANG을 쓰는이유? 부모 프로세스를 block 시키지않고 계속 진행하면서 자식의 종료상태만 계속 확인하고 싶을때 사용

5.8 Zombie and premature exits

Zombie Process (Defunct Process)

  • zombie: 만약 부모가 wait을 계속 호출하지 않으면 자식이 죽어도 자식이 죽었는지, 살았는지 모름. 즉 process는 존재하지 않는데, process table에만 존재하는 상태

  • system 내에는 process maximum 수가 존재함. 따라서 zombie process가 너무 많으면 system상에서 문제가 생길 수 있다.

Orphan Process

  • 자식 프로세스가 살아있지만 부모 process가 종료된 상태. -> 자식이 죽어도 평생 wait호출이 안되기 때문에 자식은 평생 zombie 상태가 된다.
  • 해결법: init process가 부모를 대체해서 자식의 죽음을 거둬줌.

  • init process는 주기적으로 wait을 호출해서 child process의 죽음을 거둬줌
  • 사실 exit이 호출 되고 wait이 호출된것을 받을 때까진 몇 ns라도 시간 term이 생기기 때문에 모든 child process는 종료될때 한번씩은 zombie상태로 존재함
profile
그냥 AI 관련 유익해보이는거 이것저것 적어놓음

0개의 댓글