-프로세스의 이해와 활용-
두가지 유형의 서버
다중접속 서버의 구현방법들
멀티 프로세스 기반 서버 : 다수의 프로세스를 생성하는 방식으로 서비스 제공
멀티 플렉싱 기반 서버 : 입출력 대상을 묶어서 관리하는 방식으로 서비스 제고
멀티쓰레딩 기반 서버 : 클라이언트의 수만큼 쓰레드를 생성하는 방식으로 서비스 제공
프로세스(Process)의 이해
프로세스 : 메모리 공간을 차지한 상태에서 실행중인 프로그램
프로세스 ID
프로세스 ID : 프로세스는 생성되는 형태에 상관없이 운영체제로 부터 ID를 부여 받는데 이를 프로세스 ID라 한다.
fork 함수호출을 통한 프로세스의 생성
fork 함수는 호출한 프로세스의 복사본을 생성한다. 새로운 다른 프로그램을 바탕으로 생성하는 것이 아니라 이미 실행중인 fork를 실행한 프로세스를 복사해서 생성된다. 이때 완전히 메모리 영역까지 동일하게 복사하기 때문에 fork 함수 이전에 실행했던 변수의 값역시 똑같이 복사한다. 다시 말하면 fork 함수가 호출되는 되서 반환되는 순간 두프로세스는 따로 돌아 가게 되는데 복제된 프로세스와 부모 프로세스의 fork의 반환값이 다르다. 부모 프로세스의 경우 자식 프로세스의 pid를 반환 받고 자식 프로세스의 경우 0을 반환 받는다. 이를 이용해서 프로그래밍을 해야 한다.
-프로세스 좀비(Zombie) 프로세스-
좀비 프로세스
좀비 프로세스 : 프로세스가 생성되고 나서 할일을 다하면 사라져야 하는데 사라지지 않고 시스템의 리소스를 차지 하고 잇는 프로세스
좀비 프로세스의 생성이유
fork에 의해 생성된 자식 프로세스는 1. 인자를 전달하면서 exit를 호출하거나 2. main 함수에서 return문을 실행하면서 값을 반환하는경우 종료된다. 이 반환값은 일단 운영체제로 넘어가게 되고 부모 프로세스로 전달된다. 부모 프로세스로 이 값이 전달 되기 전까지는 자식 프로세스는 종료되지 않는데 이 상태에 있는 자식 프로세스는 좀비 프로세스이다.
좀비 프로세스의 소멸1 : wait 함수의 사용
좀비 프로세스를 안만들려면 부모 프로세스가 운영체제로 부터 자식 프로세스의 반환값을 요청하면 된다. 그 첫번째 방법이 wait함수 이용하는것. pid_t wait(int * statloc); 형태로 성공시 자식의 PID를 반환, 실패시 -1 이 반환되며 성공시 종료된 자식 프로세스가 있다면 전달인자인 statloc에 그 반환값이 저장된다.
이 wait 함수는 호출된 시점에서 종료된 자식 프로세스가 없다면, 임의이 자식 프로세스가 종료될 때까지 blocking 상태에 놓인다.
좀비 프로세스의 소멸2 : waitpid 함수의 사용
위 wait 함수의 blocking 상태가 걱정이 된다면 waitpid 함수를 사용하면 된다. pid_t waitpid(pid_t pid, int * statloc, int options); 의 형태로 인자로 종료를 확인하고자 하는 자식 프로세스의 pid가 pid에(자식 프로세스의 pid대신 -1을 던지면 임의의 자식 프로세스를 의미), options 인자에는 WNOHANG 인자를 전달하면 종료된 자식 프로세스가 존재하지 않아도 블로킹 상태에 있지 않고 0을 반환하면서 함수를 빠져 나온다. 성공시 자식 프로세스의 pid , 실패시 -1반환
-시그널 핸들링-
위의 waitpid를 통한 자식 프로세스의 종료 메시지를 받는 방식은 효율적이지 못하다. 왜? 부모 프로세스가 계속 그 메시지를 기다려야 하므로. 다른 방식을 알아보자
운영체제야! 네가 좀 알려줘
자식 프로세스의 종료의 인식주체는 운영체제이다. 그렇기 때문에 운영체제가 부모 프로세스에게 종료되었음을 알려주면 효율적이다. 이러한 프로그램 구현을 위해 시그널 핸들링(Signal Handling)이라는 것이 존재한다. 시그널이란 특정상황이 발생했음을 알리기 위해 운영체제가 프로세스에게 전달하는 메시지를 의미하고 핸들링또는 시그널 핸들링이란 특정 메시지와 관련하여 미리 정의된 작업을 진행 하는 것을 의미한다.
잠시 JAVA 얘기를 : 열려있는 사고를 지니자!
C나 C++은 프로세스나 쓰레드의 생성방법을 언어차원에서 지원하지 않는다(곧 ANSI 표준애서 이의 지원을 위한 함수를 정의하지 않고 있다). 반면 JAVA는 운영체제 독립적인 플랫폼 위에서 돌아가기 때문에 프로세스나 쓰레드를 생성하는 함수를 지원한다.
시그널과 signal 함수
프로세스가 운영체제에게 특정 시그널이 발생했을때 특정 함수를 호출을 요구할수 있게 해주는 함수가 signal 이란 함수이다. void (*signal(int signo, void (*func)(int)))(int); 형태로 시그널 발생시 호출되도록 등록된 함수의 포인터가 반환된다. signal 함수의 첫번째 인자인 signo에는 SIGALRM(alarm 함수호출을 통해서 등록된 시간이 된 상황) , SIGINT(CTRL+C가 입력된 상황), SIGCHLD(자식 프로세스가 종료된 상황)이 올수 있다.
주의 할것은 시그널이 발생되면 sleep 함수로 blocking되어 있던 프로세스는 깨어나고 다시 block 상태로 돌아가지 않는다. 책의 예제를 살펴보면 이를 확인할 수 있다. 또 하나 alarm함수를 이용해서 특정 시간 이후에 SIGALRM 시그널을 발생시킬 수 있는데, 예를 들어 alarm(2) 라고 한뒤 signal(SIGALRM, FUNC)이라고 했으면 2초뒤 FUNC 함수가 실행되어진다. 그런데 여기서 프로세스 자체가 2초동안 유지 되지 않는다면 프로그램은 FUNC 함수를 실행하지 않은 채로 종료된다.
sigaction 함수를 이용한 시그널 핸들링
signal 함수는 유닉스 계열의 운영체제 별로 동작 방식에 있어서 약간의 차이가 있기 때문에 보통 sigaction 함수를 쓴다. int signaction(int signo, const struct sigaction * act, struct sigaction * oldact); 형태로 signo에는 signal함수와 동일하게 signal 정보를 넣어준다 , act 인자에는 시그널 발생시 호출될 함수의 정보가 담긴 sigaction 구조체를 넣는데 sigaction 구조체의 한 변수에 함수 포인터가 있다.
시그널 핸들링을 통한 좀비 프로세스의 소멸
-멀티태스킹 기반의 다중접속 서버-
프로세스 기반의 다중접속 서버의 구현 모델
다중접속 에코 서버의 구현
fok 함수호출을 통한 파일 디스크립터의 복사
멀티 프로세스 기반의 다중 접속 서버를 코딩할때 처음의 코딩과 accept 함수까지는 동일하다. 그 이후로 fork 함수를 이용해서 프로세스를 생성하고 그 자식 프로세스에서 클라이언트와의 통신을 처리하게 한다. 다만 주의 해야 할점은 fork이후 자식 프로세스에서는 서버 소켓을 close 해야 하고 부모 프로세스에서는 클라이언트 소켓을 close해야 한다. fork를 통해 프로세스가 생성될때 메모리의 모든 내용이 복사 된다고 하였는데 소켓이 복사 되는것은 아니고 소켓을 의미하는 파일 디스크립트가 복사 되는 것이다(소켓은 운영체제의 소유이다). 그래서 각 소켓에 2번의 파일 디스크립트가 있는건데 두 파일 디스크립트가 소멸되야 종료시 소켓이 소멸된다. 그렇게에 미리 각 파일 디스크립트를 close하는 것.
-TCP 의 입출력 루틴 분할-
입출력 루틴 분할의 의미와 이점
입력과 출력 루틴을 분할해서 각각 다른 프로세스에서 각 루틴을 수행하게 하는 것이 프로그래밍 자체도 쉬워지고 속도도 통신의 속도도 빨라진다. 마찬가지로 fork통해 프로세스를 하나 더 생성되면 소켓에 대한 파일 디스크립터 역시 복사 되기 때문에 각 프로세스에서 닫아줘야 한다.
에코 클라이언트의 입출력 루틴 분할
No comments:
Post a Comment