프로세스 ID
: 각 프로세스에 대한 식별 번호
#include <unistd.h>
int getpid(void);
// 현재 프로세스의 ID 반환
int getppid(void);
// 현재 프로세스의 부모 프로세스 ID 반환
실습: pid.c
#include <stdio.h>
#include <unistd.h>
int main(void)
{
printf("pid = %d\n", getpid()); /* pid 가져오기 */
printf("ppid = %d\n", getppid()); /* ppid 가져오기 */
return 0;
}
자식 프로세스의 생성 - fork() 시스템 호출 함수
프로세스의 생성: fork()
: 자신을 복제한 자식 프로세스를 새로 생성
- 새 프로세스를 생성하는 유일한 방법
- 부모 프로세스는 자식 프로세스 ID 반환
- 생성된 자식 프로세스는 0 반환
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

프로세스 생성 전후

실습: fork1.c, fork2.c, fork3.c
fork1.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
printf("[%d] process: start\n", getpid());
pid_t pid = fork();/* 프로세스 생성 */
printf("[%d] process: return value %d\n", getpid(), pid);
printf("[%d] process: end\n", getpid());
return 0;
}
fork2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
printf("[%d] process: start\n", getpid());
pid_t pid = fork();
/* child */
if (pid == 0){
printf("Hello, child process: %d\n", getpid());
exit(0);
}
/* parent */
else if (pid > 0) {
printf("Hello, parent process: %d\n", getpid());
}
printf("[%d] process: end\n", getpid());
return 0;
}
fork3.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
printf("[%d] process: start\n", getpid());
pid_t pid1 = fork();
if (pid1 == 0) {
printf("Hello, child1 process: %d\n", getpid());
exit(0);
}
pid_t pid2 = fork();
if (pid2 == 0) {
printf("Hello, child2 process: %d\n", getpid());
exit(0);
}
printf("Hello, parent process: %d\n", getpid());
printf("[%d] process: end\n", getpid());
return 0;
}
자식 프로세스와 동기화: wait()
: 자식 프로세스 중 하나가 종료할 때까지 대기
- 반환값: 종료된 자식 프로세스의 id
- status를 통해 자식 프로세스의 종료 코드를 전달받음 (POSIX 표준에서 0~255의 1바이트 데이터:
status >> 8 == WEXITSTATUS(status))
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int* status);
pid_t waitpid(pid_t pid, int* status, int options);
// options : 보통 0 사용으로 자식이 종료될 때까지 부모 대기

실습: wait.c, waitpid.c
wait.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
printf("[%d] process: start\n", getpid());
pid_t pid = fork();
if (pid == 0) {
printf("Hello, child process: %d\n", getpid());
exit(123); // bbbB
}
int status;
int child = wait(&status); /* 자식이 종료되기를 대기 */
printf("[%d] child process end: %d\n", getpid(), child);
/* 종료 코드값 얻기: bbBb -> 0bbB -> B
=> (char)(status >> 8) == WEXITSTATUS(status) */
printf("\tend code: %d\n", WEXITSTATUS(status));
return 0;
}
waitpid.c
// P1이 종료할 때까지 대기
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(void)
{
printf("[%d] process: 시작\n", getpid());
pid_t pid1 = fork();
if (pid1 == 0) {
printf("Hello, child1 process: %d\n", getpid());
sleep(3);
printf("bye, child1 process: %d\n", getpid());
exit(1);
}
pid_t pid2 = fork();
if (pid2 == 0) {
printf("Hello, child2 process: %d\n", getpid());
sleep(2);
printf("bye, child2 process: %d\n", getpid());
exit(2);
}
int status;
int child = waitpid(pid1, &status, 0); /* pid1이 종료하기를 대기 */
printf("[%d] child1 process 종료: %d\n", getpid(), child);
printf("\t종료 코드: %d\n", WEXITSTATUS(status)); // status >> 8
return 0;
}
새 프로세스의 실행 - exec() 시스템 호출 함수
새 프로그램 실행: exec()
: 현재 프로세스의 내용을 새 프로그램으로 대체
- 새 프로그램의 main()이 실행됨
- pid는 바뀌지 않음

#include <unistd.h>
int execl(char* path, char* arg0, char* arg1, ... , char* argn, NULL)
int execv(char* path, char* argv[])
int execlp(char* file, char* arg0, char* arg1, ... , char* argn, NULL)
int execvp(char* file, char* argv[])
- path가 나타내는 새 프로그램의 코드/데이터/힘/스택 등으로 대체 후 처음부터 실행
~l: 인자를 개별적으로 전달~v: 인자를 배열로 전달~p: 실행파일의 경로명을 PATH 상에서 찾음
- 반환값: 성공시 반환되지 못함. 실패시 -1 반환
실습: exec1_1.c, exec1_2.c, exec2.c, exec3.c
exec1_1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("executing ls\n");
/* "/bin/ls"을 인자 목록 "ls", "-l"를 가지고 실행 */
execl("/bin/ls", "ls", "-F", "/", NULL) // == "ls-F /", 인수의 개수가 가변적이므로 마지막에 NULL을 넣어야 정상적으로 처리됨
perror("execl failed to run ls");
printf("end");
return 1;
}
exec1_2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main(void)
{
printf("executing ls\n");
char* av[] = { "ls", "-l", NULL };
/* "/bin/ls"을 인자 목록 av를 가지고 실행 */
execv("/bin/ls", av)
perror("execl failed to run ls");
printf("end");
return 1;
}
exex2.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int fatal(char* s);
int main()
{
printf("executing ls\n");
pid_t pid;
/* 자식 프로세스 생성 */
switch (pid = fork()) {
/* 오류 발생하면 */
case -1:
fatal("fork failed");
break;
/* 자식이면 */
case 0:
execl("/bin/ls", "ls", "-F", NULL);
fatal("exec failed");
break;
/* 부모이면 */
default:
wait(NULL);
printf("ls completed\n");
}
return 0;
}
int fatal(char* s)
{
perror(s);
exit(1);
}
exec3.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char* argv[]) // ./exec3 ls -F /
{
pid_t pid = fork();
// 자식 프로세스이면..
if (pid == 0) {
/* 명령행 인자 1번 이후의 인자로 실행 */
execvp(argv[1], &argv[1]);
fprintf(stderr, "%s: 실행 불가\n", argv[1]);
}
// 부모 프로세스라면..
else {
int status;
int child = wait(&status);/* 자식이 끝나기를 대기 */
printf("[%d] child process end: %d\n", getpid(), pid);
printf("\texit code: %d\n", WEXITSTATUS(status));
}
return 0;
}
명령어 실행 호출: system()
: 내부적으로 자식 프로세스를 생성하고 /bin/sh을 이용해 지정된 명령어 실행
#include <stdlib.h>
int system(const char* cmdstring);
/bin/sh -c cmdstring를 호출하여cmdstring으로 지정된 명령어를 실행 : 내부적으로 fork(), exec(), waitpid() 시스템 호출 이용- 명령어 실행이 끝난 후 명령어의 종료코드 반환
- -1 with errno: fork()나 waitpid() 실패시
- 127: exec() 실패 시
실습: syscall.c, mysystem.c
syscall.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/wait.h> // WEXITSTATUS()
int main(void)
{
int status;
/* date를 실행 */
if ((status = system("date"))) < 0)
perror("system() error");
printf("\t종료 코드: %d\n", WEXITSTATUS(status));
/* hello를 실행 */
if ((status = system("hello")) < 0)
perror("system() error");
printf("\t종료 코드: %d\n", WEXITSTATUS(status));
/* who; exit 44를 실행 */
if ((status = system("who; exit 44")) < 0)
perror("system() error");
printf("\t종료 코드: %d\n", WEXITSTATUS(status));
return 0;
}
mysystem.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <errno.h> // EINTR
int mysystem(const char* cmdstring); //./mysystem pwd
int main(int argc, char* argv[])
{
if (argc == 2)
mysystem(argv[1]);
return 0;
}
int mysystem(const char* cmdstring)
{
if (cmdstring == NULL)
return 1;
pid_t pid = fork();
if (pid == -1)
return -1;
if (pid == 0) {
/* /bin/sh -c cmdstring 형태로 실행 */
execl("/bin/sh", "sh", "-c", cmdstring, NULL);
_exit(127); /* 명령어 실행 오류 */
}
do {
int status;
/* pid번 자식이 끝날 때까지 대기 */
if (waidpid(pid, &status, 0) == -1) {
if (errno != EINTR) // Error INTeRrupt
return -1;
}
else {
return status;
}
} while (1);
}
프로그램의 종료 처리
프로그램의 종료 : 정상 종료(normal termination)
: main에서 return 시 C 시작 루틴은 그 반환값을 가지고 exit() 호출
- 프로그램 내에서 exit 호출
- 프로그램 내에서 _exit 호출
프로그램의 종료: 비정상 종료(abnormal termination)
- 실행 중 시그널을 받아 종료
- abort() 호출: 프로세스에 SIGABRT 시그널을 보내 종료
정상 종료 처리 과정
1. 모든 열린 스트림을 닫고, 출력 버퍼의 내용을 쓰는 등의 뒷정리 수행 : exit()와 달리, _exit()는 이 과정 생략
2. 프로세스를 정상적으로 종료
3. 종료 코드를 부모 프로세스에게 전달
#include <stdlib.h>
void exit(int status);
// 뒷정리를 한 후 프로세스를 정상적으로 종료
#include <unistd.h>
void _exit(int status)
// 뒷정리를 하지 않고 프로세스를 즉시 종료
프로그램의 시작과 종료 과정

exit 핸들러
: 프로세스 정상 종료시 사용자가 지정된 별도의 뒷정리 작업 수행 위해 등록
: 프로세스 당 32개까지 등록 가능 - exit()는 exit 핸들러를 등록 역순으로 호출됨
- 매개변수 func: void f(void); 유형의 함수에 대한 포인터
#include <stdlib.h>
int atexit(void (*func)(void));
// 반환값: 0 if OK, nonzero on error
실습 : atexit.c
atexit.c
#include <stdio.h>
#include <stdlib.h>
static void exit_handler1(void);
static void exit_handler2(void);
int main(void)
{
if (atexit(exit_handler1) != 0)
perror("exit_handler1을 등록할 수 없음!");
if (atexit(exit_handler2) != 0)
perror("exit_handler2를 등록할 수 없음!");
printf("main() 끝\n");
exit(0);
}
static void exit_handler1(void)
{
printf("1st exit handler\n");
}
static void exit_handler2(void)
{
printf("2nd exit handler\n");
}
(+) _exit()를 쓰는 경우, 라이브러리 함수가 아니므로 오류가 발생한다. 따라서 코드 상단에 #include <unistd.h>를 추가해야 한다.
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
static void exit_handler1(void);
static void exit_handler2(void);
int main(void)
{
if (atexit(exit_handler1) != 0)
perror("exit_handler1을 등록할 수 없음!");
if (atexit(exit_handler2) != 0)
perror("exit_handler2를 등록할 수 없음!");
printf("main() 끝\n");
_exit(0);
}
static void exit_handler1(void)
{
printf("1st exit handler\n");
}
static void exit_handler2(void)
{
printf("2nd exit handler\n");
}