5장. 프로세스 관리
유닉스 프로그래밍 및 실습
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 수정으로 늘릴 수도 있음
현재 할당된 가장 큰 값보다 큰 값을 할당 / 마지막에 도달하면 처음부터 다시 시작
1. 프로세스 ID (2)
프로세스 계층
부모-자식
ppid(parent pid)
소유 – 사용자(/etc/passwd)/그룹(/etc/group)
프로세스 그룹 (다른 프로세스와의 관계 표현)
일반적으로 자식은 부모와 동일한 프로세스 그룹에 속함
pid_t
<sys/types.h>에 정의
아키텍처와 관련 있음 ( 일반적 C int 타입 )
프로세스 id와 부모 프로세스 id 얻기
getpid()
getppid()
2. 새로운 프로세스 실행하기(1)
유닉스는 프로그램 이미지를 올리는 행위(exec)와 새로운 프로세스를 생성하는 행위(fork)를 분리함
새로운 프로세스 생성(fork) 시
부모 프로세스를 그대로 복제
exec 호출군
가장 단순한 형태 execl(const char*path, const char *arg1,
… NULL)
가변 매개변수
마지막은 반드시 NULL로 끝나야 함
execl()은 반환되지 않는다.
2. 새로운 프로세스 실행하기(2)
exec 호출군 (계속)
execl 성공시 일어나는 상황
새로운 진입점으로 건너뜀
이전에 수행하던 코드는 더 이상 존재하지 않음
달라지는 속성
대기 중인 시그널 잃어버림
잡고 있는 시그널 처리
메모리 잠금 해제
스레드 속성 기본값으로 되돌아감
프로세스 통계 재설정
프로세스 메모리와 관련된 설정 해제 atexit()과 같은 사용자 영역에서 단독으로 존재하는 설정 해제
유지되는 속성
pid, ppid, 우선순위, 소유자, 그룹
열린 파일
그대로 유지됨
관례적으로 exec 전에 파일을 닫음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
…
2. 새로운 프로세스 실행하기(4)
fork() 시스템 호출
사용법
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
성공하면 호출한 프로세스와 거의 유사한 새로운 프로세스가 탄생
자식 프로세스에서는 fork()의 결과값이 0
부모 프로세스에서는 fork()의 결과값이 자식의 pid
부모 자식간 다른 내용
자식 프로세스의 pid
자식 프로세스의 ppid
자원 통계는 0으로 재설정
대기 중인 시그널은 정리
획득한 파일 잠금은 상속되지 않는다 오류값
EAGAIN
ENOMEM
2. 새로운 프로세스 실행하기(5)
fork() 시스템 호출 (계속)
쓰기 후 복사
초기에는 단순
페이지 단위의 복사는 시간 낭비
전체 복사하는 대신 COW(copy-on-write) 기법 사용
가상 메모리
fork 후 부모의 원본 페이지를 공유
변경을 시도하는 경우에 page fault가 일어나 사본을 만듬
일반적으로 fork 뒤에 exec가 따르므로, 불필요한 복사를 제거할 수 있음
vfork()
쓰기 후 복사 페이지 기법 도입 전 명시적으로 복사를 저지시킴(BSD 3.0)
fork()와 동일하나 연달아 exec 계열이 수행되거나, _exit()으로 종료하는 점만 차이
과거의 유물이며 더 이상 사용하지 말 것!
예제
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);
더 이상 사용되지 않는 모든 자원 정리
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 예제
3. 프로세스 종료하기 (3)
on_exit()
SunOS 4에서 제공
리눅스 glibc도 지원
atexit()와 유사하나, 등록 대상 함수 서식이 다름
최신 솔라리스에서 더 이상 지원하지 않음
SIGCHLD
프로세스 종료 시 부모에게 보냄
기본 행동은 무시 (signal()이나 sigaction()으로 다른 행동을 하게 할 수 있음)
9장에서 설명
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 예제
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 예제
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에서 정의하지 않으므로 자원활용정보가 정말로 중요한 경우에만 사용
4. 자식 프로세스 종료 기다리기 (4)
새로운 프로세스를 띄운 다음 기다리기
#define _XOPEN_SOURCE /* WEXITSTATUS 등을 설정하기를 원한다면 */
#include <stdlib.h>
int system(const char *command);
동기식 프로세스 호출 또는 ‘시스템 외부로 쉘 띄우기’
종료 코드를 WEXITSTATUS로 얻어야 함
주의할 점
SIGCHLD 차단
SIGINT, SIGQUIT 무시
따라서 자식 프로세스 종료 상태와 시그널을 확인하는 코드 필요
P.217 예제
P.218 예제
system()과 예제의 차이 이해 필요
좀비에 대한 추가 설명
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로만 설정 가능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 예제
6. 세션과 프로세스 그룹 (1)
프로세스 그룹
작업 제어 목적으로 관련된 하나 이상의 프로세스를 모아놓은 집합
프로세스 그룹 ID(pgid) = 그룹 리더의 pid
세션
처음 로그인 할 때 사용자 로그인 쉘 프로세스를 포함한 새로운 세션 생성
로그인 쉘이 세션 리더
하나의 세션에 여러 프로세스 그룹이 존재할 수 있음
그림 5-1 이해
세션 관련 시스템 호출
새로운 세션 생성
#include <unistd.h>
pid_t setsid(void);
setsid() 효과
새로운 세션 내부에 새로운 프로세스 그룹 생성, 호출한 프로세스를 세션과 프로세스 그룹 모두의 리더로 만듬 (데몬 또는 쉘의 특성)
세션 id 얻기
6. 세션과 프로세스 그룹 (2)
프로세스 그룹 관련 시스템 호출
프로세스 ID로 pgid 생성
#define _XOPEN_SOURCE 500
#include <unistd.h>
int setpgid(pid_t pid, pid_t pgid);
성공 조건
프로세스 그룹 ID 얻기
구식 프로세스 그룹 함수
7. 데몬
일반적 특징
백그라운드로 동작하는 프로세스
일반적으로 시스템 구동 시점에서 시작
루트나 다른 특수한 사용자로 동작
시스템 수준의 과업 처리
일반적으로 이름 끝이 d로 끝남 프로그램이 데몬이 되기 위한 단계
fork()로 새로운 프로세스 생성
부모는 exit() 수행
데몬에서 setsid() 호출 – 새로운 프로세스 그룹의 리더가 되며, 제어 터미널과의 관련도 없앤다 chdir()로 작업 디렉토리를 루트 디렉토리로 변경
모든 파일 기술자 닫기
다시 /dev/null로 0, 1, 2 열기 P.233 예제
C 라이브러리
#include <unistd.h>
int daemon(int nochdir, int noclose);
과제(수정)
프로세스와 파일 결합 문제
문제 파일(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 전체
참고 (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);
}
참고 (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 }
참고 (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 }