pid_t fork();
성공 시 프로세스 ID, 실패시 -1 반환
fork는 호출한 프로세스의 복사본을 생성한다
이미 실행 중인 프로세스를 fork를 통해 복사한다.
이후 fork 함수 반환 이후의 문장을 실행
fork의 특징
프로세스를 생성했으면 소멸시키는것도 중요하다
제대로 소멸 안시키면 남아서 시스템의 자원을 사용한다 => 시스템에 부담
자식이 종료되는 경우
인자를 전달하면서 exit를 호출
main에서 return을 실행하며 값을 반환
반환값 모두 OS에 전달됨, 근데 이 값이 부모로 전달될때까지 자식을 안죽임
이때 남아있는 프로세스 => 좀비프로세스라 부름
좀비는 언제 소멸??
부모에게 exit의 인자나 return 문의 반환값이 전달 될때
그럼 부모가 값을 안받는다면.. 자식은 좀비네??
부모는 반환값을 받을 준비를 해야한다!
부모가 종료되면 좀비 자식도 죽는다
근데 일반 자식(좀비아님)은 안죽는다
pid_t wait(int* statloc);
성공 시 종료된 자식 프로세스의 ID, 실패시 -1 반환
종료된 자식이 있으면 자식이 종료되면서 전달한 값(exit, return 값)이 매개변수로 전달된 주소의 변수에 저장됨
변수에는 자식이 종료되면서 전달한 값 이외의 정보가 포함되어 있으니, 값의 분리를 거쳐야함
만약 wait이 호출되었는데 종료한 프로세스가 없다면, 자식이 종료될때까지 부모가 block 상태가 된다
블로킹을 안하는 함수는 업나..
wait과 달리 블로킹을 유발하지 않는다.
pid_t waitpid(pid_t pid,int* statloc, int options);
wait, waitpid 호출 전 종료된 자식 프로세스 값이 있다면 함수의 인자에 종료 값이 들어간다
signal(int signo,void (*func)(int)));
signo: 특정 상황의 정보
둘째 파라미터: signal handler
signal handler
unsigned int alarm(unsigned int seconds);
시그널이 발생하면 sleep 함수로 블로킹된 프로세스가 깨어난다
int sigaction(int signo,const struct sigaction* act, struct sigaction* oldact);
sigaction을 사용하기 위해서는 sigaction이라는 이름의 구조체 변수를 선언 및 초기화 해야함
struct sigaction{
void (*sa_handler)(int); // 시그널 핸들러 함수의 포인터
sigset_t sa_mask; // 시그널 핸들러가 실행중일때 추가적으로 블럭할 시그널
int sa_flags; // 시그널 행동에 영향을 주는 flag
}
SIGCHLD에 대한 시그널 처리를 하고 해당 핸들러 함수에서 waitpid를 이용해 자식 프로세스의 종료값을 확인
동시에 둘 이상의 클라이언트에게 서비스를 제공하는 형태 구현!
클라이언트의 서비스 요청이 있을때 마다 서버는 자식 프로세스를 생성해서 서비스를 제공한다
ex) 5개의 클라이언트라면 5개의 자식을 생성해 서비스를 제공한다
fork를 하고 부모에선 accept로 만든 서버 소켓을 자식에선 서버 소켓을 close해야한다
왜??
부모 프로세스가 지니던 서버, 클라이언트 소켓의 파일 디스크립터가 fork로 자식에게 복사되었다.
BUT!! 같은 파일 디스크립터가 아니다.
그래서 하나의 소켓에 두개의 파일 디스크립터가 존재하게 된다.
사용하지 않는 소켓을 미리 닫는다.(파일디스크립터를 닫는다)
지금까지 구현한 데이터 에코 방식은
서버로 데이터 전송 -> 데이터가 에코될때까지 기다림 -> 에코로 돌아온 데이터 수신 -> 다른 데이터를 전송
즉, 한번 데이터를 전송하면 에코되는걸 기다려야함!
이걸 두개의 프로세스로 쪼개 입출력을 나눈다면??
데이터 수신 여부에 관계없이 데이터를 전송 가능!
인터렉티브 방식의 데이터 송수신을 진행하는 경우에는 이러한 형태의 구현이 어울리는 상황이 있고, 어울리지 않는 상황이 있다.