3.1 프로세스 개념
프로세스
프로세스의 현재 활동의 상태는 프로그램 카운터 값과 프로세서 레지스터의 내용으로 나타낸다.
텍스트 섹셔 - 실행 코드
데이터 섹션 - 전역 변수
힙 섹션 - 프로그램 실행 중에 동적으로 할당되는 메모리
스택 섹션 - 함수를 호출할 때 임시 데이터 저장장소
함수가 호출될 때마다, 함수 매개변수, 지역 변수 및 복귀 주소를 포함하는 활성화 레코드가 스택에 푸쉬
함수가 제어에서 돌아오면 활성화 레코드가 팝된다.
스택 및 힙 섹션이 서로의 방향으로 커지더라도 운영체제는 서로 겹치지 않도록 해야한다.
프로세스의 상태
•
new: 프로세스가 생성
•
running: 명령어가 실행 중
•
waiting: 프로세스가 어떤 이벤트를 기다림
•
ready: 프로세스가 처리기에 할당되기를 기다림
•
종료: 프로세스 종료
PCB
프로세스 제어 블록으로써 process control block
•
프로세스 상태: new, ready, running, waiting, halted
•
프로그램 카운터
•
CPU 레지스터들
•
CPU-스케줄링 정보: 우선순위, 스케줄 큐에 대한 포인터 등등
•
메모리 관리 정보: 기준 레지스터와 한계 레지스터의 값, 페이지 테이블 또는 세그먼트 테이블
•
accounting : CPU사용 시간과 경과된 실시간, 시간 제한, 프로세스 번호 등
•
입출력 상태 정보: 이 프로세스에 할당된 입출력 장치들과 열린 파일의 목록
쓰레드
한 프로세스가 다수의 스레드를 가질 수 있다.
다중 처리기 상에서 여러 스레드가 병렬로 실행될 수 있다.
3.2 프로세스 스케줄링
각 CPU코어는 한 버넹 하나의 프로세스를 실행할 수 있다.
단일 CPU코어가 있는 시스템의 경우 한 번에 2개 이상의 프로세스가 실행될 수 없지만, 다중 코어 시스템은 한 번에 여러 프로세스를 실행할 수 있다.
프로세스는 일반적으로 I/O 바운드 프로세스와 CPU 바운드 프로세스로 나뉜다.
I/O 바운드 프로세스는 계산에 소비하는 것보다 I/O에 더 많은 시간을 소비하는 프로세스.
CPU바운드 프로세스는 계산에 더 많은 시간을 사용하여 I/O 요청을 자주 생성하지 않는다.
스케줄링 큐
PCB 포인터의 linked list 형태이다.
문맥 교환
인터럽트 처리가 끝난 후에 컨텍스트를 복구할 수 있도록 현재 프로세스의 문맥을 저장해야한다.
문맥은 프로세스의 PCB에 표현됨. 이 작업이 문맥 교환이라고 한다.
문맥 교환이 진행될 동안 시스템이 아무런 유용한 일을 못하기 때문에, 문맥 교환 시간은 순수한 오버헤드.
교환 속도는 메모리의 속도, 반드시 복사되어야하는 레지스터의 수, 특수 명령어의 존재에 좌우
물론 레지스터 집합들보다 활성 프로세스들이 더 많다면, 시스템은 전처럼 레지스터 자료를 메모리로 또는 메모리에서 복사해야한다. 복잡한 고급 메모리 관리 기법을 사용하면 문백 교환 시 더 많은 자료들을 교환해야함.
3.3 프로세스에 대한 연산
프로세스 생성
유일한 프로세스 식별자, pid를 가진다.
언제나 pid가 1인 systemd프로세스가 모든 사용자 프로세스의 부모 역할을 하고 시스템이 부트될 때 생성되는 첫번째 프로세스이다.
일반적으로 프로세스가 자식을 생성할 때, 자원이 필요한데, 이 자원을 운영체제로부터 직접 얻거나, 부모 프로세스가 가진 자원의 부분 집합만을 사용하도록 제한될 수 있다.
부모 프로세스는 자원을 분할하여 자식 프로세스들에게 나누어주거나 메모리나 파일과 같은 몇몇 자원들을 자식 프로세스들이 같이 사용하게 할 수도 있다.
프로세스 실행 방법에는 2가지가 있다.
•
부모가 자식과 병행하게 실행을 계속
•
부모는 일부 또는 모든 자식이 실행을 종료할 대까지 기다린다.
주소 공간 측면에서 볼 때, 두 가지 경우가 있다.
•
자식 프로세스는 부모 프로세스의 복사본
•
자식 프로세스가 자신에게 적재될 새로운 프로그램을 가지고 있다.
프로세스 종료
다음과 같은 이유로 자식의 실행을 종료할 수 있다.
•
자식이 자신에게 할당된 자원을 초과하여 사용할 때. 이 때는 부모가 자식들의 상태를 검사할 수 있는 방편이 주어져야한다.
•
자식에게 할당된 태스크가 더 이상 필요 없을 때
•
부모가 exit을 하는데, 운영체제는 부모가 exit한 후에 자식이 실행을 계속하는 것을 허용하지 않는 경우
◦
연쇄식 조 ㅇ료
프로세스가 종료하면 사용하던 자원은 운영체제가 되찾아간다.
프로세스의 종료 상태가 저장되는 프로세스 테이블의 해당 항목은 부모 프로세스가 wait()를 호출할 때까지 남아있다. 종료되었지만 부모 프로세스가 아직 wait() 호출을 하지 않은 프로세스를 좀비 프로세스라고 한다. 부모가 wait()를 호출하면 좀비 프로세스의 식별자와 프로세스 테이블의 해당 항목이 운영체제에 반환된다.
부모 프로세스가 wait()를 호출하지 않고 종료한다면, 자식 프로세스는 고아 상태가 된다.
고아 프로세스의 새로운 부모 프로세스로 init프로세스를 지정함으로써 이 문제를 해결.
init프로세스는 주기적으로 wait를 호출한다.
프로세스 간 통신
프로세스 간의 통신을 하는 이유
•
정보 공유 : 여러 프로그램이 동일한 정보를 사용하는 경우
•
계산 가속화: 특정 태스크를 서브로 나눠서 할당하는 경우
•
모듈성: 시스템 기능을 별도의 프로세스들 또는 스레들로 나누어 모듈식 형태로 시스템을 구성
IPC: interprocess communication
기본적으로 공유 메모리와 메시지 전달 방법이 있다.
공유 메모리 모델은 공유되는 메모리 영역에서 데이터를 읽고 쓰고 함으로써 정보를 교환할 수 있다.
메시지 전달 방법은 프로세스들 사이에 교환되는 메시지를 통해 이루어진다.
공유 메모리 모델
•
커널 간섭 등의 부가적인 시간 소비 작업이 없기에, 더 빠르다.
•
공유 메모리 영역을 구축할 때만 시스템 콜이 필요하다.
메시지 전달
•
충돌을 회피할 필요가 없기에 적은 양의 데이터를 교환하는데 유용
•
분산 시스템에서 공유 메모리보다 구현하기 쉽다.
공유 메모리
공유 메모리 세그먼트를 생성하는 프로세스에 공유 메모리 영역이 위치한다. 이 공유 메모리 세그먼트를 사용하려고 하는 프로세스들은 이 세그먼트를 자신의 주소 공간에 추가해야한다.
한 프로세스가 다른 프로세스의 메모리에 접근하는 것을 금지하는데, 공유 메모리는 이 제약조건을 제거한다. 하지만 데이터의 형식과 위치는 이들 프로세스에 의해 결정되며, 프로세스들은 동시에 동일한 위치에 쓰지 않도록 책임져야한다.
메시지 전달 시스템
운영체제가 제공하는 메시지 기법을 사용하면 우리가 데이터를 책임질 필요가 없다.
네이밍
통신을 원하는 프로세스들은 서로를 가리킬 방법을 알아야한다.
직접 통신할 때는
•
각 프로세스의 쌍들 사이에 연결이 생기고, 서로의 신원(id)를 알아야한다.
•
연결은 정확히 두 프로세스 사이에서만 연관됨
•
통신하는 프로세스들의 쌍 사이에는 정확히 하나의 연결이 존재해야함.
완전한 대칭성을 보인다. 송신자와 수신자 프로세스가 모두 통신하려면 상대방의 이름을 제시.
비대칭 기법에서는 송신자만 수신자의 이름을 대고, 수신자는 제시할 필요가 없다.
하지만 이 방식은 모듈성을 제한한다. 프로세스 이름을 바꾸면 참조를 다 찾아서 새로운 이름으로 변경해야하기 때문이다.
간접 통신에서 메시지는 메일 박스 또는 포트로 송수신된다.
간접 통신할 때는
•
한 쌍의 프로세스들 사이의 연결은 이들 프로세스가 공유 메일박스를 가질 때만 구축됨
•
연결은 두 개 이상의 프로세스들과 연관될 수 있다.
•
통신하고 있는 각 프로세스 사이에는 다수의 연결이 존재할 수 있고, 각 연결은 하나의 메일박스에 대응된다.
만약 P1, P2, P3가 메일박스 A를 공유하고, P1은 송신, P2,P3가 수신한다고 할 때, P1이 보낸 메시지를 누가 수신하는가?
•
하나의 링크는 최대 두 개의 프로세스와 연관되도록 허용
•
한번에 하나의 프로세스만 receive()를 수행할 수 있도록
•
어느 프로세스가 메시지를 수신할 것인지 시스템이 임의로 선택하도록.
메일박스가 프로세스에 의해 소유된다면,
소유자(메일 박스로부터 수신만 가능), 사용자(메일박스로 송신만 가능)을 구분할 수 있고, 이러면 수신자에 대한 혼란이 없다. 메일박스를 소유하고 있는 프로세스가 종료할 때, 메일박스가 사라지기에, 송신하는 모든 프로세스가 더는 메일 박스가 존재하지 않는다는 사실을 통보받아야한다.
반면 운영체제가 소유한 메일박스는 자체적으로 존재. 독립적으로 존재, 프로세스에 예속되지 앟는다.
동기화
•
봉쇄형 보내기: 송신하는 프로세스는 메시지가 수신 프로세스 또는 메일 박스에 의해 봉쇄
•
비봉쇄형 보내기: 송신하는 프로세스가 메시지를 보내고 작업을 재시작
•
봉쇄형 받기: 메시지가 이용 가능할 때까지 프로세스가 봉쇄
•
비봉쇄형 받기: 송신하는 프로세스가 유효한 메시지 또는 널을 받는다.
버퍼링
•
무용량: 큐의 최대 길이가 0이다. 대기하는 메시지들을 가질 수 없고, 송신자는 수신자가 메시지를 수신할 때까지 가뎔야함.
•
유한 용량: 유한한 길이n을 가진다. 만원이 아니라면 큐에 놓이며, 송신자는 대기하지 않고 실행을 계속.링크가 다차있다면, 송신자는 봉쇄된다.
•
무한용량: 잠재적으로 무한한 길이를 가진다.
파이프
•
파이프가 단방향 통신, 또는 양방향 통신을 허용하는가
•
양방향 통신을 허용한다면 반이중인가 전이중인가
◦
반이중은 한 순간에 한 방향만 허용, 전이중은 동시에 가능
•
통신하는 프로세스 간에 부모-자식 같읕 관계가 존재해야만하는가?
•
파이프는 네트워크를 통해서 가능한가?
일반 파이프
생산자-소비자 형태로 두 프로세스 간의 통신을 허용.
생산자는 한 종단에 쓰고, 소비자는 다른 종단에서 읽는다.
한쪽으로만 데이터를 전송할 수 있고, 단 방향 통신만을 가능하게 한다.
양방향 통신이 필요하다면 파이프를 두 개를 서야한다.
pipe(int fd[])
JavaScript
복사
fd[]파일 디스크립터를 통해 접근되는 파일을 생성
fd[0]은 파이프 읽기 종단, fd[1]은 파이프 쓰기 종단
파이프를 파일의 특수한 유형으로 취급하기에, 일반적인 read()와 write() 시스템 콜을 사용하여 접근될 수 있다.
일반 파이프는 파이프를 생성한 프로세스 이외에는 접근할 수 없다.
통상 부모가 파이프를 생성하고 fork()로 생성한 자식 프로세스와 통신하기 위해 사용.
이 때, 자식 프로세는 부모 프로세스로부터 열린 파일을 상속받기에, 자식은 파이프를 상속받는다.
부모가 파이프의 쓰기 종단에 쓰면 자식은 읽기 종단에서 읽을 수 있다.
처음에 자신들이 사용하지 않는 파이프의 종단을 닫아야한다. writer가 파이프의 종단을 닫았을 대, 파이프로부터 읽는 프로세스가 EOF를 탐지하는 것을 보장하기 때문에 매우 중요한 절차이다.
#define BUFFER_SIZE 25;
#define READ_END 0
#define WRITE_END 1
int main(){
char wriete_msg[BUFFER_SIZE] = "Greetings";
char read_msg[BUFFER_SIZE];
int fd[2];
pid_t pid;
if(pipe(fd) == -1){
fprintf(stderr, "Pipe failed");
return 1;
}
pid = fork();
if(pid < 0){
fprintf(stderr, "Fork Failed");
return 1;
}
if(pid > 0){
close(fd[READ_END]); // 닫지 않으면 read할 수 없는 상태일 때 read에 대한 오류가 리포트가 되지 않는다.
//왜냐하면 reference counter가 0이 아니기 때문이다.
write(fd[WRITE_END], write_msg, strlen(write_msg)+1);
close(fd[WRITE_END]);
}else{
close(fd[WRITE_END]);// 닫지 않으면 read할수 없다. 레퍼런스 카운터가 1이기에 계속 write할 때까지 기다린다.
//따라서 EOF가 발생하지 않고 read할 수가 없다.
read(fd[READ_END],read_msg, BUFFER_SIZE);
printf("read %s", read_msg);
close(fd[READ_END]);
}
return 0;
}
C
복사
pipe는
•
read를 할 때, write가 열려 있으면 쓰기가 일어날 것으로 간주한다. 그래서 write가 다 끝나더라도 EOF를 리포트하지 않는다.
•
pipe가 만약에 다 찼다면, write하려는 프로세스에서 read가 열려 있으면 write는 단순히 hang된다. 열려있는 read에서 읽어주기를 기다리므로, 에러를 보고 받지 못한다.
따라서 파이프에서 지속적인 write와 read를 해야할 때 필수적이다.
한번만 읽을거면 상관이 없지만, EOF를 잡을 때까지 읽어야하거나 여러번 써야하는 경우 필수다.
파이프는 단방향이고 통신하는 두 프로세스는 부모-자식 관계여야함.