• Tidak ada hasil yang ditemukan

5장. 프로세스 관리

N/A
N/A
Protected

Academic year: 2023

Membagikan "5장. 프로세스 관리"

Copied!
25
0
0

Teks penuh

(1)

5장. 프로세스 관리

유닉스 프로그래밍 및 실습

(2)

1. 프로세스 ID (1)

 pid : 프로세스 ID

 특정 시점에서 유일한 식별자

 주요 pid

 idle : 0

 init : 1

 리눅스 커널의 init

 /sbin/init

 /etc/init

 /bin/init

 /bin/sh

 순서대로 찾아서 제일 먼저 찾은 init 수행

찾지 못하면 커널 패닉

 프로세스 ID 할당

 최대값 32768 (16비트 하위시스템과의 호환성 유지)

 /proc/sys/kernel/pid_max 수정으로 늘릴 수도 있음

 현재 할당된 가장 큰 값보다 큰 값을 할당 / 마지막에 도달하면 처음부터 다시 시작

(3)

1. 프로세스 ID (2)

 프로세스 계층

 부모-자식

 ppid(parent pid)

 소유 – 사용자(/etc/passwd)/그룹(/etc/group)

 프로세스 그룹 (다른 프로세스와의 관계 표현)

 일반적으로 자식은 부모와 동일한 프로세스 그룹에 속함

 pid_t

 <sys/types.h>에 정의

 아키텍처와 관련 있음 ( 일반적 C int 타입 )

 프로세스 id와 부모 프로세스 id 얻기

 getpid()

 getppid()

(4)

2. 새로운 프로세스 실행하기(1)

 유닉스는 프로그램 이미지를 올리는 행위(exec)와 새로운 프로세스를 생성하는 행위(fork)를 분리함

 새로운 프로세스 생성(fork) 시

 부모 프로세스를 그대로 복제

 exec 호출군

 가장 단순한 형태 execl(const char*path, const char *arg1,

… NULL)

 가변 매개변수

 마지막은 반드시 NULL로 끝나야 함

 execl()은 반환되지 않는다.

(5)

2. 새로운 프로세스 실행하기(2)

 exec 호출군 (계속)

 execl 성공시 일어나는 상황

 새로운 진입점으로 건너뜀

 이전에 수행하던 코드는 더 이상 존재하지 않음

 달라지는 속성

대기 중인 시그널 잃어버림

잡고 있는 시그널 처리

메모리 잠금 해제

스레드 속성 기본값으로 되돌아감

프로세스 통계 재설정

프로세스 메모리와 관련된 설정 해제

 atexit()과 같은 사용자 영역에서 단독으로 존재하는 설정 해제

 유지되는 속성

 pid, ppid, 우선순위, 소유자, 그룹

 열린 파일

그대로 유지됨

관례적으로 exec 전에 파일을 닫음

(6)

2. 새로운 프로세스 실행하기(3)

 exec 호출군 (계속)

 나머지 execl 계열 함수

 execlp(const char *file, const char* arg, …);

 execle(const char *path, const char* arg, …, char *const envp[]);

 execv(const char *path, char*const argv[]);

 execvp(const char *file, char*const argv[]);

 execvpe(const char *file, char*const argv[], char *const envp[]);

 l, v는 인수를 리스트로 제공할지, 배열(vector)로 제공할지를 지정

 p는 실행경로에 위치한 파일 이름만 넘겨줌

 e는 환경변수 사용을 지정

 벡터로 넘겨줄 때도 마지막은 NULL이어야 함

 오류값

 E2BIG

 EACCESS

 …

(7)

2. 새로운 프로세스 실행하기(4)

 fork() 시스템 호출

 사용법

 #include <sys/types.h>

 #include <unistd.h>

 pid_t fork(void);

 성공하면 호출한 프로세스와 거의 유사한 새로운 프로세스가 탄생

 자식 프로세스에서는 fork()의 결과값이 0

 부모 프로세스에서는 fork()의 결과값이 자식의 pid

 부모 자식간 다른 내용

자식 프로세스의 pid

자식 프로세스의 ppid

자원 통계는 0으로 재설정

대기 중인 시그널은 정리

획득한 파일 잠금은 상속되지 않는다

 오류값

 EAGAIN

 ENOMEM

(8)

2. 새로운 프로세스 실행하기(5)

 fork() 시스템 호출 (계속)

 쓰기 후 복사

 초기에는 단순

 페이지 단위의 복사는 시간 낭비

 전체 복사하는 대신 COW(copy-on-write) 기법 사용

 가상 메모리

 fork 후 부모의 원본 페이지를 공유

 변경을 시도하는 경우에 page fault가 일어나 사본을 만듬

 일반적으로 fork 뒤에 exec가 따르므로, 불필요한 복사를 제거할 수 있음

 vfork()

 쓰기 후 복사 페이지 기법 도입 전 명시적으로 복사를 저지시킴(BSD 3.0)

 fork()와 동일하나 연달아 exec 계열이 수행되거나, _exit()으로 종료하는 점만 차이

 과거의 유물이며 더 이상 사용하지 말 것!

(9)

예제

(10)

3. 프로세스 종료하기 (1)

 표준 함수

#include <stdlib.h>

void exit(int status);

 exit()는 몇 가지 종료단계를 거쳐 커널에게 프로세스 종료를 지시

 C 라이브러리 수행 내용

 atexit()나 on_exit()로 등록된 함수들을 등록된 역순으로 호출

모든 표준입출력 스트림 비우기

 tmpfile()로 만든 임시 파일 지우기

 status 변수는 프로세스의 종료 상태를 지정하기 위해 사용

 status & 0377을 반환

 시스템 호출

#include <unistd.h>

void _exit(int status);

 더 이상 사용되지 않는 모든 자원 정리

(11)

3. 프로세스 종료하기 (2)

 다른 종료 방식

 ‘끝까지 진행하는’ 방법

컴파일러가 알아서 _exit() 호출

 exit()나 main의 return 값으로 종료상태를 명시적으로 반환하는 것이 바람직

전통적으로 반환값이 0이면 성공

시그널로 인한 프로세스 종료

자폭(abort)

유효하지 않는 명령어, 세그먼트 폴트, 메모리 소진 등

 atexit()

 POSIX 1003.1-2001에서 정의

프로세스 종료과정에서 수행할 함수를 등록

#include <stdlib.h>

int atexit( void (*function)(void));

인수로 넘어온 함수를 등록

등록할 함수는 인수도 없고, 반환값도 없는 함수이어야 함 (void my_function(void);)

등록 역순으로 수행되며, 등록된 함수 내에서 exit()을 부르면 무한 재귀호출이 발생하므로 주의

함수는 LIFO방식으로 수행

POSIX 표준에서는 ATEXIT_MAX까지 등록 가능하며 적어도 32는 되어야 함

 P.205 예제

(12)

3. 프로세스 종료하기 (3)

 on_exit()

 SunOS 4에서 제공

 리눅스 glibc도 지원

 atexit()와 유사하나, 등록 대상 함수 서식이 다름

 최신 솔라리스에서 더 이상 지원하지 않음

 SIGCHLD

 프로세스 종료 시 부모에게 보냄

 기본 행동은 무시 (signal()이나 sigaction()으로 다른 행동을 하게 할 수 있음)

 9장에서 설명

(13)

4. 자식 프로세스 종료 기다리기 (1)

 일반적으로 부모는 자식 프로세스가 반환한 값과 같은 많은 정보를 원함

 좀비 프로세스

자식 프로세스가 부모 프로세스가 기다리지 않는 상태에서 종료되는 경우 들어가는 특수한 상태

프로세스를 지탱하는 최소한의 뼈대만 유지

부모가 확인하면 공식적으로 프로세스 종료

 POSIX 인터페이스

#include <sys/types.h>

#include <sys/wait.h>

pid_t wait(int *status);

종료된 자식의 pid 반환하거나 오류 시 -1 반환

자식이 종료되지 않으면 블록

 SIGCHLD를 받은 경우 바로 반환

 status 확인 매크로

 p.207

 p.208 예제

 p.209 예제

(14)

4. 자식 프로세스 종료 기다리기 (2)

 특정 프로세스 기다리기

#include <sys/types.h>

#include <sys/wait.h>

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

 wait()보다 훨씬 강력

 기다리는 프로세스를 정확히 지정

 Options

 WNOHANG

 WUNTRACED

 WCONTINUED

 오류

 ECHILD

 EINTR

 EINVAL

 p.211 예제

(15)

4. 자식 프로세스 종료 기다리기 (3)

 훨씬 더 복잡하게 기다리기

#include <sys/wait.h>

int waitid(idtype_t idtype, id_t id,

siginfo_t *infop, int options);

자식 프로세스를 기다리고, 상태 종료 정보를 얻기 위해 사용

 p.213 idtype, options, 오류 값

 BSD 세상에서 기다리기: wait3(), wait4()

#include <sys/types.h>

#include <sys/time.h>

#include <sys/resource.h>

#include <sys/wait.h>

pid_t wait3(int *status, int options, struct rusage *rusage);

 rusage를 통해 자식 프로세스 자원 활용과 관련된 정보 제공

 p.216 구조체 확인

 POSIX에서 정의하지 않으므로 자원활용정보가 정말로 중요한 경우에만 사용

(16)

4. 자식 프로세스 종료 기다리기 (4)

 새로운 프로세스를 띄운 다음 기다리기

#define _XOPEN_SOURCE /* WEXITSTATUS 등을 설정하기를 원한다면 */

#include <stdlib.h>

int system(const char *command);

 동기식 프로세스 호출 또는 ‘시스템 외부로 쉘 띄우기’

 종료 코드를 WEXITSTATUS로 얻어야 함

 주의할 점

 SIGCHLD 차단

 SIGINT, SIGQUIT 무시

 따라서 자식 프로세스 종료 상태와 시그널을 확인하는 코드 필요

 P.217 예제

 P.218 예제

 system()과 예제의 차이 이해 필요

 좀비에 대한 추가 설명

(17)

5. 사용자와 그룹 (1)

 프로세스와 사용자/그룹

 실제, 유효, 저장된 사용자 ID와 그룹 ID

 4 종류의 사용자 ID

Real

프로세스를 실행한 원래 사용자 uid

Effective

프로세스가 현재 영향을 미치는 사용자 ID(접근 권한은 이 값을 기준으로 함)

 exec 호출 도중 setuid가 실행되면 유효 사용자 ID를 바꿀 수 있음

 /usr/bin/passwd (프로그램 파일을 소유한 사용자 ID로 설정됨)

Saved

 exec 호출 도중 커널은 저장된 사용자 ID를 유효사용자 ID로 설정

Filesystem

 실제, 유효, 저장된 사용자 ID와 그룹 ID 변경하기

#include <sys/types.h>

#include <unistd.h>

int setuid(uid_t uid);

int setgid(gid_t gid);

현재 프로세스의 유효 사용자 ID 설정 (프로세스의 현재 유효 사용자 ID가 0인 경우 실제 사용자 ID와 저장된 사용자 ID도 설정)

루트는 세가지 모두 설정 / 일반 사용자는 실제 사용자 ID 또는 저장된 사용자 ID로만 설정 가능

(18)

5. 사용자와 그룹 (2)

 유효 사용자 ID와 유효 그룹 ID 변경하기

#include <sys/types.h>

#include <unistd.h>

int seteuid(uid_t uid);

int setegid(gid_t gid);

 루트가 아닌 경우 setuid()와 동일하게 동작하며, 더 바람직

 프로세스가 루트로 돌아가는 경향이 있다면 setuid() 사용

 BSD 방식으로 사용자 ID와 그룹 ID 변경하기

 P.223 예제

 HP-UX 방식으로 사용자 ID와 그룹 ID 변경하기

 P.224 예제

 바람직한 사용자/그룹 ID 조작

 저장된 사용자 ID 지원

 실제 사용자와 그룹 ID 얻기

 P.225 예제

(19)

6. 세션과 프로세스 그룹 (1)

 프로세스 그룹

 작업 제어 목적으로 관련된 하나 이상의 프로세스를 모아놓은 집합

 프로세스 그룹 ID(pgid) = 그룹 리더의 pid

 세션

 처음 로그인 할 때 사용자 로그인 쉘 프로세스를 포함한 새로운 세션 생성

 로그인 쉘이 세션 리더

 하나의 세션에 여러 프로세스 그룹이 존재할 수 있음

 그림 5-1 이해

 세션 관련 시스템 호출

 새로운 세션 생성

#include <unistd.h>

pid_t setsid(void);

 setsid() 효과

새로운 세션 내부에 새로운 프로세스 그룹 생성, 호출한 프로세스를 세션과 프로세스 그룹 모두의 리더로 만듬 (데몬 또는 쉘의 특성)

 세션 id 얻기

(20)

6. 세션과 프로세스 그룹 (2)

 프로세스 그룹 관련 시스템 호출

 프로세스 ID로 pgid 생성

#define _XOPEN_SOURCE 500

#include <unistd.h>

int setpgid(pid_t pid, pid_t pgid);

 성공 조건

 프로세스 그룹 ID 얻기

 구식 프로세스 그룹 함수

(21)

7. 데몬

 일반적 특징

백그라운드로 동작하는 프로세스

일반적으로 시스템 구동 시점에서 시작

루트나 다른 특수한 사용자로 동작

시스템 수준의 과업 처리

일반적으로 이름 끝이 d로 끝남

 프로그램이 데몬이 되기 위한 단계

 fork()로 새로운 프로세스 생성

부모는 exit() 수행

데몬에서 setsid() 호출 – 새로운 프로세스 그룹의 리더가 되며, 제어 터미널과의 관련도 없앤다

 chdir()로 작업 디렉토리를 루트 디렉토리로 변경

모든 파일 기술자 닫기

다시 /dev/null로 0, 1, 2 열기

 P.233 예제

 C 라이브러리

#include <unistd.h>

int daemon(int nochdir, int noclose);

(22)

과제(수정)

 프로세스와 파일 결합 문제

문제 파일(problems.txt)은 다음과 같이 구성됨 (3자리 수가 10개씩 = 40bytes)

123 456 879 124 23 0 457 945 123 342

부모 프로세스는 문제 파일과 결과 파일(results.txt)을 연 다음 fork()를 수행하여 자식 프로세스를 4개 만들기

자식들과 부모 프로세스는 문제 파일에서 40bytes씩 읽어들여 각 수들의 합을 결과 파일에 저장

… 3452

자식 프로세스는 더 이상 읽어들일 문제가 없으면 파일들을 닫고 종료

부모 프로세스는 결과 파일을 다시 읽어들여 총 합을 계산하여 화면에 출력

 제출 기한 : 2012년 10월 21일 자정

 Vi 사용 팁

소스 파일 들여쓰기 재정렬 =G

= 들여쓰기

G 전체

(23)

참고 (mk_datafile.c)

#include <stdio.h>

#include <stdlib.h>

#include <time.h>

#include <string.h>

#define LINE_SIZE 40

int main(void) {

FILE *stream;

char buffer[LINE_SIZE];

int i, j;

srand(time(NULL));

stream = fopen("datafile", "w");

for (i = 0; i < 1000; i++) {

memset(buffer, 0, LINE_SIZE);

for (j = 0; j < 10; j++) {

sprintf(&buffer[j*4], "%3d ", rand() % 1000);

}

buffer[LINE_SIZE - 1] = '\n';

fputs(buffer, stream);

}

fclose(stream);

}

(24)

참고 (multi_calc.c) (1)

1 #include <sys/types.h>

2 #include <sys/stat.h>

3 #include <fcntl.h>

4 #include <unistd.h>

5 #include <stdio.h>

6 #include <stdlib.h>

7

8 #define BUFFER_SIZE 40 9

10 int main(void) 11 {

12 pid_t pid;

13 int i;

14 int fd1;

15 int fd2;

16 char buffer[BUFFER_SIZE];

17 int nr;

18

19 fd1 = open("datafile", O_RDONLY);

20 if (fd1 < 0) {

21 perror("open");

22 exit(1);

23 } 24

25 fd2 = open("result", O_RDWR | O_CREAT | O_TRUNC, S_IRWXU);

26 if (fd2 < 0) {

27 perror("open");

28 exit(1);

29 } 30

31 if ((pid=fork()) < 0) { 32 perror("fork");

33 exit(2);

34 }

(25)

참고 (multi_calc.c) (2)

35 else if (pid) { /* parent */

36 while ((nr = read(fd1, buffer, BUFFER_SIZE)) > 0) { 37 fprintf(stderr, "P");

38 write(fd2, buffer, nr);

39 }

40 close(fd1);

41 wait();

42

43 lseek(fd2, 0L, SEEK_SET);

44 while ((nr = read(fd2, buffer, BUFFER_SIZE)) > 0) { 45 write(1, buffer, nr);

46 }

47 close(fd2);

48 exit(0);

49 }

50 else { /* child */

51 while ((nr = read(fd1, buffer, BUFFER_SIZE)) > 0) { 52 fprintf(stderr, "C");

53 write(fd2, buffer, nr);

54 }

55 close(fd1);

56 close(fd2);

57 exit(0);

58 } 59 }

Referensi

Dokumen terkait

대졸 신입사원 선발시 귀사가 가장 중요하게 고려하는 것을 다음 보기에서 5가지만 골라 중요한 순서대로 기입하십시오?. 직무능력 검사혹은 그에 준 하는

다음은 귀사가 실시하고 있는 종업원참여에 대한 귀하의 의견을 묻는 것입니다.. 다음은 귀사가 실시하고 있는 종업원참여에 대한 귀하의 의견을 묻는