제2부 프로세스 관리 (Process Management) 실행중인 프로그램(program in execution) CPU time, memory, files, I/O devices 등 자원 요구 시스템의 작업단위 (the unit of work) 종류 1. 사용자 프로세스(user process) : user code 실행 2. 시스템 프로세스(system process) : system code 실행 프로세스 관리 Chapter 3. 프로세스 (Process) Chapter 4. 스레드 (Threads) Chapter 5. CPU 스케줄링 (CPU Scheduling) Chapter 6. 프로세스 동기화 (Process Synchronization) Chapter 7. 교착상태 (Deadlock) 운영체제
8th Chapter 3. 프로세스 관리 (Process Management) 프로세스 개념 (Process Concept) 프로세스 스케줄링 (Process Scheduling) 프로세스에 대한 연산 (Operation on Processes) fork + exec 프로세스 협조 (Cooperating Processes) 생산자-소비자 문제 프로세스간 통신 (Interprocess Communication) IPC 시스템의 사례 (Examples of IPC Systems) 클라이언트 서버 환경에서의 통신 (Communication in Client-Server Systems) 요약 (Summary) 운영체제
9th Chapter 3. 프로세스 관리 (Process Management) 프로세스 개념 (Process Concept) 프로세스 스케줄링 (Process Scheduling) 프로세스에 대한 연산 (Operation on Processes) fork + exec 프로세스간 통신 (Interprocess Communication) 생산자-소비자 문제 IPC 시스템의 사례 (Examples of IPC Systems) 클라이언트 서버 환경에서의 통신 (Communication in Client-Server Systems) 요약 (Summary) 운영체제
Chapter 3. 프로세스 개념(Process Concept) Questions of the day 프로세스는 □ □ 중인 프로그램? Linux 프로세스를 표현하는 PCB(Process Control Block) 구조체 이름은? Unix/Linux 프로세스를 생성하는 시스템 호출은? 프로세스 스케줄링이 사용하는 큐는? 프로세스의 문맥(context)은 어디에 저장? 컴퓨터 시스템 안에서 발견할 수 있는 생산자-소비자 관계에 있는 프로세스들의 예는? 유한 버퍼 생산자-소비자 문제 (bounded buffer producer-consumer problem) 해결책은? 운영체제
프로세스 개념(Process Concept) 예전 1 program 실행 오늘날 multiple program의 동시 실행 프로세스 (The Process) 프로그램 코드 + 현재 활동(Current activity) PC(Program Counter) 레지스터 값 스택(stack) : 서브루틴, 매개변수, 복귀주소, 임시변수 등 데이터 부분(data section) : 전역변수 프로세스 상태(Process State) 생성(new) 수행(running) : CPU가 실행 대기(waiting) : I/O완료나 signal 기다림 준비(ready) : Processor를 받을 준비가 됨 종료(terminated) 운영체제
프로세스 상태도 (Diagram of Process State) Linux $ ps –el | more $ ps aux | more 운영체제
프로세스 상태 코드 Linux $ ps aux | more D uninterruptible sleep (usually IO) R running or runnable (on run queue) S interruptible sleep (waiting for an event to complete) T stopped, either by a job control signal or because it is being traced. W paging (not valid since the 2.6.xx kernel) X dead (should never be seen) Z defunct ("zombie") process, terminated but not reaped by its parent. For BSD formats and when the stat keyword is used, additional characters may be displayed: < high-priority (not nice to other users) N low-priority (nice to other users) L has pages locked into memory (for real-time and custom IO) s is a session leader l is multi-threaded (using CLONE_THREAD, like NPTL pthreads do) + is in the foreground process group Unix $ ps –el | more O Process is running on a processor. S Sleeping: process is waiting for an event to complete. R Runnable: process is on run queue. T Process is stopped, either by a job control signal or because it is being traced. W Waiting: process is waiting for CPU usage to drop to the CPU-caps(한도) enforced limits. Z Zombie state: process terminated and parent not waiting. 운영체제
프로세스 상태도 (Diagram of Process State) Linux $ ps –el | more $ ps aux | more 운영체제
프로세스 상태도 (Diagram of Process State) Unix (117.16.244.171에서 117.16.244.157 접속) $ telnet 117.16.244.157 $ ps –el | more $ ps aux | more 운영체제
(참고) zombie.c /* gcc zombie.c –o zombie */ #include <stdio.h> #include <stdlib.h> main () { int pid; pid = fork (); /* Duplicate */ if (pid != 0) /* Branch based on return value from fork () */ while (1) /* Never terminate, and never execute a wait () */ sleep (1000); } else exit (42); /* Exit with a silly number */ $ ./zombie & $ id $ ps –l 또는 $ ps x $ kill -9 PPID 운영체제
Process in Memory
BSS(Block Started by Symbol) (참고) Linux 가상 메모리 구조 하나의 태스크마다 4GB 주소 공간을 할당 각 태스크에게 3GB 할당 나머지 1GB는 모든 태스크들의 공통 영역으로서 커널 공간으로 할당 (root 권한으로) # pmap 1 # cat /proc/1/maps 또는 $ ps $ pmap PID $ cat /proc/PID/maps BSS(Block Started by Symbol) 운영체제
(참고) Linux 메모리 관리 자료구조 task_struct 구조체내의 struct mm_struct *mm /usr/src/kernels/linux-3.19.3/include/linux/????.h (????행 CentOS Linux 3.19.3) /usr/src/kernels/linux-3.19.3/include/linux/mm_types.h (222행, 131행) vm_area_struct 안의 struct mm_struct * vm_mm은 메모리 디스크립터 포인터 운영체제
프로세스 개념(Process Concept) 프로세스 제어 블럭(Process Control Block) /proc 참고 각 프로세스는 PCB로 표현됨 Solaris 11 (117.16.244.157) Unix: /usr/include/sys/????.h ???행 CentOS (117.16.244.171) Linux: /usr/src/kernels/linux-3.19.3/include/linux /?????.h (????행) task_struct 구조체는 어디에? PCB 프로세스 상태: new, ready, running, waiting, halted 프로그램 카운터: next instruction의 주소 CPU레지스터들: accumulator, index register, stack pointers, 범용 registers, condition-code CPU스케줄 정보: priority, pointers to scheduling queues 메모리 관리 정보: base and limit registers, page tables, segment tables 계정 정보: time used, time limits, account numbers, job#, process# 입출력 상태 정보: I/O devices list allocated to the process, list of open files 스레드(Threads) a process = a single thread of execution (one task) 많은 현대 OS들이 multiple threads of control (multitasks at a time) 지원 stu stu415 운영체제
Process Control Block (PCB) 운영체제
Linux의 프로세스 표현 C 언어 구조체 task_struct로 표현 pid t pid; /* process identifier */ long state; /* state of the process */ unsigned int time slice /* scheduling information */ struct task struct *parent; /* this process’s parent */ struct list head children; /* this process’s children */ struct files struct *files; /* list of open files */ struct mm struct *mm; /* address space of this process */
(참고) CentOS task_struct 예 (1274~1704) struct task_struct { volatile long state; /* -1 unrunnable, 0 runnable, >0 stopped */ void *stack; atomic_t usage; unsigned int flags; /* per process flags, defined below */ unsigned int ptrace; #ifdef CONFIG_SMP struct llist_node wake_entry; int on_cpu; struct task_struct *last_wakee; unsigned long wakee_flips; unsigned long wakee_flip_decay_ts; int wake_cpu; #endif int on_rq; int prio, static_prio, normal_prio; unsigned int rt_priority; const struct sched_class *sched_class; struct sched_entity se; struct sched_rt_entity rt; #ifdef CONFIG_CGROUP_SCHED struct task_group *sched_task_group; struct sched_dl_entity dl; ... ... source code http://www.kernel.org 운영체제
(참고) Linux task_struct 구조체 구조체를 통한 정보 관리 프로세스가 생성되면 task_struct 구조체를 통해 프로세스의 모든 정보를 저장하고 관리 모든 태스크들에게 하나씩 할당 /usr/src/kernels/linux-3.19.3/include/linux/?????.h (????행) 태스크 ID, 상태 정보, 가족 관계, 명령, 데이터, 시그널, 우선순위, CPU 사용량 및 파일 디스크립터 등 생성된 태스크의 모든 정보를 가짐 alloc_task_struct 매크로를 통해 커널 영역의 메모리에서 8KB를 할당 받아 프로세스 디스크립터와 커널 스택의 자료를 저장 current : 현재 실행되고 있는 태스크를 가리키는 변수 /usr/src/kernels/linux-3.19.3/arch/x86/include/asm/current.h (17행) 커널 스택과 프로세스 디스크립터 운영체제
프로세스 스케줄링(Process Scheduling) 스케줄링 큐(Scheduling Queues) 작업 큐 (job queue) : memory 할당 기다리는 큐(disk에서) 준비 큐 (ready queue) : CPU에 할당 기다리는 큐 장치 큐 (device queue) : 입출력 기다리는 큐 큐잉 도표 (queueing diagram) : 그림 3.7 스케줄러(Schedulers) 장기 스케줄러(long-term scheduler, job scheduler) pool memory(degree of multiprogramming) Unix 같은 시분할 시스템에는 없음 단기 스케줄러(short-term scheduler, CPU scheduler) CPU 할당 : must be very fast 중기 스케줄러(medium-term scheduler) swapping degree of multiprogramming을 줄임 memory backing store 운영체제
(참고) Linux task_struct 구조체 태스크 관계와 관련된 변수들 p_opptr과 p_pptr : 부모 태스크 p_oppr : Original Parent p_cptr : 자식, p_ysptr : 아우, p_osptr : 형 커널에 존재하는 모든 태스크들은 원형 이중 연결 리스트로 연결 SET_LINKS : 리스트에 추가하는 매크로 REMOVE_LINK : 리스트서 삭제하는 매크로 struct task_struct *p_opptr, *p_pptr, *p_cptr, *p_ysptr, *p_osptr; struct task_struct *next_task, *prev_task; task_struct 구조체 운영체제
큐잉 도표에 중기 스케줄링(Medium Term Scheduling) 추가 운영체제
스레드 (Threads) 과거에는, 하나의 프로세스는 하나의 제어 흐름(threads of control)을 가졌음 현대 운영체제는 프로세스 마다 여러 PC(program counter)를 가질 수 있게 진화됨 여러 부분을 동시에 수행 가능 여러 제어 흐름을 가짐 Threads 스레드 정보를 담을 장소가 필요 여러 PC 정보를 포함하도록 PCB가 확장됨 4장 스레드 참조
준비큐와 다양한 입출력 장치 큐 운영체제
프로세스 스케줄링을 표현하는 큐잉 도표 운영체제
프로세스 스케줄링(Process Scheduling) 문맥 교환 (Context Switch) CPU가 한 process에서 다른 process로 switch될 때 save the state of the old process : CPU와 메모리 상태 (PCB정보) load the saved state for new process : CPU와 메모리 상태 (PCB정보) pure overhead : performance bottleneck threads로 해결 context-switch time : 1~1000microsecond address space 보존 방법 : memory 관리기법에 좌우 운영체제
한 프로세스에서 다른 프로세스로의 CPU Switch 운영체제
모바일 시스템의 멀티태스킹 과거에는 하나의 프로세스만 수행되고 다른 프로세스는 멈춤(suspended) iOS 안드로이드 배터리 수명과 메모리 용량 때문에 제한된 멀티태스킹(multitasking) 단일 foreground 태스크 : 사용자 인터페이스로 제어되고 디스플레이 제한된 수의 다중 background 태스크 : 메모리에서 수행되나 디스플레이 되지 않음 단일 태스크, 고정 태스크(download), 이벤트 감지 태스크, 긴 playback 태스크 등 안드로이드 무제한 멀티태스킹 background 태스크를 대행하는 service 응용으로 태스크 수행 사용자 인터페이스 없는 독립된 작은 메모리 공간(memory footprint)인 service는 background 태스크가 멈춰도 동작
Multiprocess Architecture – Chrome Browser 아직도 많은 웹 브라우저들이 단일 프로세스로 동작 만일 한 웹 사이트에서 문제 생기면 웹 브라우저 전체가 느려지거나 멈춤 Google Chrome Browser는 3개 영역 멀티프로세싱 Browser : 사용자 인터페이스, 디스크 I/O, 네트워크 I/O 담당 Renderer : HTML, Javascript, 이미지 등 웹 페이지 표시, 새 웹사이트가 열릴 때마다 새 프로세스 생성 Plug-in : Flash, QuickTime 등 사용 중인 플러그인 종류마다 생성
(1장에서) 프로세스 관리 (Process Management) Process(active entity )는 실행 중인 Program(passive entity): 작업의 단위 Process 는 작업 수행 위해 자원을 필요로 함: CPU, memory, I/O, files, data Process 는 종료되면 재사용 가능한 자원을 반환 Single-threaded process는 실행할 다음 명령(instruction)의 위치를 가리키는 program counter(PC)를 한 개 가짐 Multi-threaded process 는 thread 마다 하나의 program counter(PC) 가짐 일반적으로 시스템에는 하나 또는 다수의 CPU 상에서 다수의 process들(user processes, OS processes)이 동시에 실행됨 CPU 다중화(multiplexing)로 process/thread 동시성(concurrency) 유지 OS의 프로세스 관리 활동 사용자 프로세스와 시스템 프로세스의 생성과 삭제 프로세스의 일시 중지와 재수행 프로세스 동기화를 위한 기법 제공 프로세스 통신을 위한 기법 제공 교착상태 처리를 위한 기법 제공 운영체제
(2장에서) 시스템 호출 6팩 프로세스와 작업 제어(Process and Job Control) 작업제어를 위한 시스템 호출들 프로세스 제어(load, execute) ld, exec 프로세스 생성(create process or submit job) fork, vfork, __clone 프로세스 속성 획득과 설정(get process attribute and set process attribute) getpid, setpid, getpgrp, setpgrp 프로세스 종료(terminate process) exit, abort 시간대기(wait time) sleep, pause 사건대기(wait event) wait, pause 사건신호(signal event) kill 기억장치 할당 및 해제 malloc, free 디버깅을 위한 시스템 호출들 od, dump, dtrace, ptrace 시간 프로필(time profile): 특정 부분 수행 시간의 양 추적 time $ time ls 프로세스 실행 비교(그림 2.7, 그림 2.8) single-tasking system MS-DOS: 새 프로세스 생성 없음 TSR (Terminate and Stay Resident) 프로그램 제한적 동시수행: hooks an interrupt 후 TSR system call과 함께 exit: 자신의 subroutine을 interrupt routine으로 설정하여 interrupt를 가로챔 TSR system call: MS-DOS가 덮어쓰지 못하게 함 multitasking system UNIX: fork로 새 프로세스 생성 fork + exec system call .vs. system programming 운영체제
Linux 프로세스 트리 예 $ ps –ef | more
Solaris 프로세스 트리 예 $ ps –ef | more Q: Linux의 1번 프로세스는? 운영체제
프로세스에 대한 오퍼레이션 (Operations on Processes) 프로세스 생성(Process Creation) 프로세스 생성 시스템 호출 : fork, CreateProcess, etc. 부모 프로세스 계속 실행 모든 자식이 끝날 때 까지 기다림 : wait system call로 자식 프로세스 : 사용 자원을 부모 프로세스의 자원(memory, files) 공유 자식은 부모의 것을 복제 : fork 자식은 자신의 새 프로그램을 가짐 : fork + exec 군 운영체제
프로세스에 대한 오퍼레이션 (Operations on Processes) Unix의 예 : fork + exec 프로그램 (forkexecl.c & forkexecv.c 참조) 1) fork : 자식 process 생성, 모든 process는 PID(Process identifier)를 가짐 2) fork + exec : 호출하는 프로세스의 기억장소에 새 프로그램 load execl : 문자형 인수 포인터들 execl(“/bin/ls”, “ls”, “-l”, NULL); execv : 인수배열의 포인터 char *av[3]; av[0] = “ls”; av[1] = “-l”; av[2] = (char *)0; execv(“/bin/ls”, av); 운영체제
프로세스에 대한 오퍼레이션 (Operations on Processes) 프로세스 종료(Process Termination) exit 시스템 종료 계산 결과는 부모에게 return 메모리(물리적/가상적), 오픈한 화일, I/O버퍼를 OS에게 돌려줌 abort 시스템 호출 부모만 호출(그렇지 않으면 서로 죽이는 일 생김) 실행 종료 이유 자식이 할당된 자원을 초과 사용할 때 자식의 task가 더 이상 필요 없을 때 부모가 종료될 때 DEC VMS 또는 Unix 계단식(cascading) 종료 부모 종료 OS가 모든 자식 종료 exit 상태정보를 부모에게 전달 (예) wait ($status) mystatus.c 참조 운영체제
프로세스 종료 예 wait (&status) /* int status */ status :자식이 exit으로 종료될 때의 상태정보 정상종료 경우 하위 8 비트는 0 상위 8 비트는 exit status (자식이 전달한 값) 비정상종료(signal로 종료) 경우 하위 8 비트는 signal 번호 상위 8 비트는 0 (하위 8 비트 추출) status & 0x00FF; (상위 8 비트 추출) status >> 8 e x i t s i g n a l e x i t & 1 e x i t >> 8 e x i t
(Lab.3) 프로세스 생성과 종료 One, Two 출력하는 onetwo.c 코딩, 컴파일, 실행 시스템 호출 fork() + execlp() forkexeclp.c 코딩, 컴파일, 실행 시스템 호출 fork() + execvp() forkexecvp.c 코딩, 컴파일, 제출 N개 fork 예제 nfork.c 코딩, 컴파일, 실행 Shell을 느끼는 background.c 코드 분석, 컴파일, 실행 정상종료 상태정보를 전달하는 mystatus.c 코드 분석, 컴파일, 실행 비정상종료 상태정보를 전달하는 childkill.c 코드 분석, 컴파일, 실행 Win32 API를 이용한 새 프로세스 생성 (교재 p126) 코딩, 컴파일, 실행 3번만 제출 (제출 방법) http://cyber.incheon.ac.kr (Lab.3)에 제출 운영체제
1. fork 시스템 호출: onetwo.c printf("One\n"); /* gcc onetwo.c –o onetwo */ #include <stdio.h> main() { int pid; printf("One\n"); pid = fork(); printf("Two\n"); } printf("One\n"); pid = fork(); printf("Two\n"); PC 부모 BEFORE fork AFTER getpid() 추가 printf("One\n"); pid=fork(); printf("Two\n"); printf("One\n"); pid = fork(); printf("Two\n"); PC PC 부모 운영체제
2. fork와 exec 시스템 호출의 조합 pid = fork(); PC A BEFORE FORK AFTER FORK wait((int*)0); execl("/bin/ls" …); PC PC A B AFTER FORK AFTER EXEC wait((int*)0); /* first line of ls */ PC PC A B (now runs ls) 운영체제
2.& 3. fork와 exec 시스템 호출의 조합 execlp (쉘 환경변수 PATH를 따름) /* gcc forkexecl.c –o forkexecl */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> void main(int argc, char *argv[]) { int pid; /* fork another process */ pid = fork(); if(pid < 0) { /* error occurred */ fprintf(stderr, "Fork Failed"); exit(-1); } else if (pid == 0) { /* child process */ execl("/bin/ls", "ls", "-l", NULL); } else { /* parent process */ wait(NULL); printf("Child Complete\n"); exit(0); } execlp (쉘 환경변수 PATH를 따름) execlp("ls", " ls ", " -l ", (char *)0); execv char *av[3]; av[0]="ls"; av[1]="-l"; av[2]=(char *)0; execv("/bin/ls", av); execvp (쉘 환경변수 PATH를 따름) execvp(av[0], av); 운영체제
4. n개의 fork() 예제 nfork.c 자식 process 개수는? ①만 없애보세요! ②만 없애보세요! /* gcc nfork.c –o nfork */ #include <stdio.h> #include <stdlib.h> int main() { int pid1, pid2; printf("[Parent] : Hello, world ! pid=%d\n", getpid()); if ((pid1=fork()) == 0) { printf("[Child 1] : Hello, world ! pid=%d\n", getpid()); exit(0); /* ① */ } if ((pid2=fork()) == 0) { printf("[Child 2] : Hello, world ! pid=%d\n", getpid()); exit(0); /* ② */ 자식 process 개수는? ①만 없애보세요! ②만 없애보세요! ①&② 모두 없애보세요! 운영체제
5. Shell을 느끼는 background.c 후면처리 (background processing) 코드 /* gcc background.c –g background */ #include <stdio.h> main (int argc, char *argv[]) { if (fork () == 0) /* Child */ execvp (argv[1], &argv[1]); /* Execute other program */ fprintf (stderr, "Could not execute %s\n", argv[1]); } 실행 $ gcc background.c –o background $ ./background ls -l 운영체제
6. 정상종료 상태정보 전달 mystatus.c /* gcc mystatus.c –o mystatus */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> main () { int pid, status, childPid; printf ("I'm the parent process and my PID is %d\n", getpid ()); pid = fork (); /* Duplicate */ if (pid != 0) /* Branch based on return value from fork () */ printf ("I'm the parent process with PID %d and PPID %d\n", getpid (), getppid ()); childPid = wait (&status); /* Wait for a child to terminate. */ printf ("A child with PID %d, terminated with exit code high: %d, low: %d\n", childPid, status >> 8, status & 0xFF); /* Linux */ } else printf ("I'm the child process with PID %d and PPID %d\n", getpid (), getppid ()); /* execlp ("ls", "ls", "-li", (char *)0); */ /* execlp ("myexit", "myexit", (char *)0); */ exit (42); /* Exit with a silly number */ printf ("PID %d terminates\n", getpid () ); /* gcc myexit.c –o myexit */ #include <stdio.h> #include <stdlib.h> main () { printf ("I'm going to exit with return code 33\n"); exit (33); } 운영체제
7. 비정상종료 상태정보 전달 killchild.c /* killchild.c */ #include <stdio.h> #include <stdlib.h> #include <signal.h> /* 명령줄 인수로 받은 명령을 실행시킨다 */ int main(int argc, char *argv[]) { int child, pid, status; pid = fork( ); if (pid == 0) { // 자식 프로세스 execvp(argv[1], &argv[1]); fprintf(stderr, "%s:실행 불가\n",argv[1]); } else { // 부모 프로세스 printf("SIGINT=%d, SIGKILL=%d, SIGTERM=%d\n", SIGINT, SIGKILL, SIGTERM); kill(pid, SIGINT); /* SIGKILL과 SIGTERM도 확인 */ child = wait(&status); printf("[parent: %d] 자식 프로세스 %d 종료 \n", getpid(), pid); printf("[child : %d] 종료 코드 상위: %d 하위: %d \n", child, status >> 8, status & 0x00FF); } $ kill –l $ gcc -g –o killchild killchild.c $ killchild sleep 100 GDB 실행하여 확인해 보세요!
(참고) $ gdb ./killchild argc argv 1 2 3 0xbffff6a4 0xbffff7e4 ./childkill\0 sleep\0 100\0 0xbffff7e4 0xbffff801 0xbffff807 1 2 3 argc 0x0 $ gcc -g -o childkill childkill.c $ gdb ./childkill (gdb) b 8 Breakpoint 1 at 0x804850d: file childkill.c, line 8. (gdb) b 14 Breakpoint 2 at 0x8048583: file childkill.c, line 14. (gdb) r sleep 100 Starting program: /home/mysung/osprog/ childkill.c sleep 100 Breakpoint 1, main (argc=3, argv=0xbffff6a4) at execute33.c:8 8 pid = fork( ); Missing separate debuginfos, use: debuginfo-install glibc-2.12-1.107.el6_4.5.i686 (gdb) p &argc $1 = (int *) 0xbffff600 (gdb) p &argv $2 = (char ***) 0xbffff604 (gdb) p argv[0] $3 = 0xbffff7e4 "/home/mysung/cprog/ childkill.c " (gdb) p argv[1] $4 = 0xbffff801 "sleep" (gdb) p argv[2] $5 = 0xbffff807 "100" (gdb) p argv[3] $6 = 0x0 (gdb) p pid $7 = 8065012 (gdb) n Detaching after fork from child process 1647. 9 if (pid == 0) { // 자식 프로세스 (gdb) c Continuing. SIGINT=2, SIGKILL=9, SIGTERM=15 Breakpoint 2, main (argc=3, argv=0xbffff6a4) at execute33.c:14 14 kill(pid, SIGKILL); /* SIGKILL과 SIGTERM도 확인 */ (gdb) p pid $8 = 1647 (gdb) p SIGKILL No symbol "SIGKILL" in current context. (gdb) n 15 child = wait(&status); (gdb) p child $9 = 134514203 (gdb) p status $10 = 134513744 16 printf("[parent: %d] 자식 프로세스 %d 종료 \n", getpid(), pid); (gdb) p getpid() $11 = 1641 $12 = 1647 [parent: 1641] 자식 프로세스 1647 종료 17 printf("[child : %d] 종료 코드 상위: %d 하위: %d \n", child, status>>8, status & 0xFF); $13 = 1647 $14 = 9 (gdb) p status >> 8 $15 = 0 (gdb) p status & 0xFF $16 = 9 (gdb) quit
8. Win32 API를 이용한 새 프로세스 생성 새 프로젝트 Win 32 콘솔 응용 프로그램 빈 프로젝트 생성 #include <Windows.h> #include <stdio.h> int main(void) { STARTUPINFO si; PROCESS_INFORMATION pi; ZeroMemory( &si, sizeof(si) ); si.cb = sizeof(si); ZeroMemory( &pi, sizeof(pi) ); if(!CreateProcess(NULL, // 명령어라인사용 "C://Windows/System32//mspaint.exe", // 명령어라인 NULL, // 프로세스를상속하지말것 NULL, // 스레드핸들을상속하지말것 FALSE, // 핸들상속다제이블 0, // 생성플래그없음 NULL, // 부모환경블록사용 NULL, // 부모프로세스가존재하는디렉토리사용 &si, &pi)) fprintf(stderr, "Create Process Failed"); return -1; } // 부모프로세스가자식프로세스가끝나기를기다림 WaitForSingleObject(pi.hProcess, INFINITE); printf("Child Complete"); // 핸들닫기 CloseHandle(pi.hProcess); CloseHandle(pi.hThread); 새 프로젝트 Win 32 콘솔 응용 프로그램 빈 프로젝트 생성 컴파일 오류 나면 Visual Studio의 프로젝트 구성 속성 일반 문자집합 [멀티바이트 문자집합 사용]으로 변경 운영체제
(HW2) mysh.c 단순한 나만의 쉘 프로젝트 표준 입력으로부터 명령을 한 줄 읽어 프로그램 이름과 매개변수로 나누고 fork()를 이용하여 입력된 명령을 실행시키는 매우 단순한 형태의 쉘 mysh.c 프로그램을 작성하세요 나만의 쉘 프롬프트; 예, COMMAND-> 명령줄에 입력된 모든 매개변수들을 명령으로 전달하여 실행시킵니다 전면작업(foreground job) 예, $ ls -l 은 명령이 끝날 때까지 기다리도록 구현합니다 후면작업(backgroud job) 예, $ ls -l & 도 잘 수행되게 합니다 문법 오류를 조사하는 파싱은 하지 않아도 좋습니다 Control-D 또는 exit 을 입력하면 쉘에서 빠져 나오도록 구현합니다 힌트1: (Lab.3) background.c execvp() 참조 힌트2: myshhint.c 참조 (제출 방법) 이러닝 http://cyber.incheon.ac.kr (HW2) 제출 소스파일과 실행파일은 zip으로 압축하여 업로드 제출한 zip 파일이 잘 열리는지 반드시 확인하세요!ZZ 운영체제
(HW2) myshhint.c (1) /* gcc myshhint.c –o myshhint */ #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define MAX_LINE 80 /* 80 chars per line, per command, should be enough. */ void setup(char[] , char *[], int *); int main(void) { char inputBuffer[MAX_LINE]; /* buffer to hold the command entered */ int argc, i, background; /* equals 1 if a command is followed by '&' */ char *args[MAX_LINE/+1];/* command line (of 80) has max of 40 arguments */ while (1){ /* Program terminates normally inside setup */ if (background != 0) background = 0; if (background == 0) printf("COMMAND->"); fflush(stdout); setup(inputBuffer,args,&background); /* get next command */ /* the steps are: * (1) fork a child process using fork() * (2) the child process will invoke execvp() * (3) if background == 0, the parent will wait, * otherwise returns to the setup() function. */ } 운영체제
(HW2) myshhint.c (2) /** * * setup() reads in the next command line, separating it into distinct tokens * * using whitespace as delimiters. setup() sets the args parameter as a * * null-terminated string. **/ void setup(char inputBuffer[], char *args[],int *background) { int length, /* # of characters in the command line */ i, /* loop index for accessing inputBuffer array */ start, /* index where beginning of next command parameter is */ ct; /* index of where to place the next parameter into args[] */ ct = 0; /* read what the user enters on the command line */ length = read(STDIN_FILENO, inputBuffer, MAX_LINE); start = -1; if (length == 0) exit(0); /* ^d was entered, end of user command stream */ if (length < 0){ perror("error reading the command"); exit(-1); /* terminate with error code of -1 */ } /* examine every character in the inputBuffer */ for (i=0;i<length;i++) { switch (inputBuffer[i]) { case ' ': case '\t' : /* argument separators */ if(start != -1){ args[ct] = &inputBuffer[start]; /* set up pointer */ ct++; inputBuffer[i] = '\0'; /* add a null char; make a C string */ break; case '\n': /* should be the final char examined */ if (start != -1){ args[ct] = &inputBuffer[start]; inputBuffer[i] = '\0'; args[ct] = NULL; /* no more arguments to this command */ default : /* some other character */ if (start == -1) start = i; if (inputBuffer[i] == '&'){ *background = 1; args[ct] = NULL; /* just in case the input line was > 80 */ 운영체제
프로세스 협조(Cooperating Processes) 프로세스 협조하는 이유 정보 공유 (information sharing) 계산 속도 증가 (computation speedup) : parallel computing으로 모듈화(modularity) 편이성(convenience) : parallel computing으로 프로세스 협조 예 : 생산자-소비자(producer-consumer)문제 compiler : assembly code 생산 assembler : assembly code 소비, object code 생산 loader : object code 소비 생산자와 소비자가 동시에 수행되려면 buffer가 필요(동시 수행을 위해) // wating의 단점 해결을 위해 생산자와 소비자의 동기화 필요(생산되지 않은 자료 소비하지 않게) 생산자-소비자 문제 종류 1. 무한 버퍼(unbounded-buffer) 생산자-소비자 문제 // 소비자 대기 생산자는 항상 생산, 소비자는 소비할 자료를 기다릴 수도 2. 유한 버퍼(bounded-buffer) 생산자-소비자 문제 // 생산자 대기 버퍼가 꽉 차 있으면 생산자가 대기, 버퍼가 비어 있으면 소비자가 대기 운영체제
프로세스 협조(Cooperating Processes) 유한 버퍼 생산자-소비자 문제(Bounded-Buffer Producer-Consumer Problem) 스레드(LWP)로 하면 효과적 Version 1: 공유 메모리를 이용한 해결책 (3장) Version 2: 메시지 시스템(IPC)을 이용한 해결책 (3장) Version 3: 세마포어를 이용한 해결책 (6장) Version 4: 세마포어와 스레드를 이용한 해결책 (6장) Version 5: Java synchronization을 이용한 해결책 (6장) 운영체제
Shared Memory count=3 count=0 in=0 in=0 out=0 out=0 count=0 count=2 * Count가 없을 경우, 비어있는 경우와 꽉 찬 상황이 같아지므로(In=0,out=0) count 변수가 필요하다. count=3 in=0 out=0 count=0 in=0 out=0 count=0 in=0 out=0 count=2 in=2 out=0 2 count=1 in=0 out=2 count=2 in=0 out=1 1 count=1 in=1 out=0 운영체제
Bounded-Buffer : Shared-Memory Solution Shared data #define BUFFER_SIZE 3 typedef struct { . . . } item; item buffer[BUFFER_SIZE]; int in = 0; int out = 0; Solution is correct, but can only use BUFFER_SIZE-1 elements
Bounded-Buffer : Producer item next produced; while (true) { /* produce an item in next produced */ while (((in + 1) % BUFFER SIZE) == out) // BUFFER SIZE로 나눔으로써 count 3에서 0으로 들어가게끔 만든다 ; /* do nothing */ buffer[in] = next produced; in = (in + 1) % BUFFER SIZE; }
Bounded Buffer : Consumer item next consumed; while (true) { while (in == out) ; /* do nothing */ next consumed = buffer[out]; out = (out + 1) % BUFFER SIZE; /* consume the item in next consumed */ }
(참고) 공유 메모리를 이용하는 생산자-소비자 문제 /ossrc/chap3/sharedmem/BoundedBuffer.java import java.util.*; public class BoundedBuffer { public BoundedBuffer() /* buffer is initially empty */ count = 0; in = 0; out = 0; buffer = new Object[BUFFER_SIZE]; } /* producer calls this method */ public void enter(Object item) { while (count == BUFFER_SIZE) ; /* do nothing */ // wating (꽉 찼을 때) /* add an item to the buffer */ ++count; buffer[in] = item; // index 증가 in = (in + 1) % BUFFER_SIZE; if (count == BUFFER_SIZE) System.out.println("Producer Entered " + item + " Buffer FULL"); else System.out.println("Producer Entered " + item + " Buffer Size = " + count); /* consumer calls this method */ public Object remove() { Object item; while (count == 0) ; /* do nothing */ // wating (비어있을 때) /* remove an item from the buffer */ --count; item = buffer[out]; out = (out + 1) % BUFFER_SIZE; if (count == 0) System.out.println("Consumer Consumed " + item + " Buffer EMPTY”); else System.out.println("Consumer Consumed " + item + " Buffer Size = " + count); return item; } public static final int NAP_TIME = 5; private static final int BUFFER_SIZE = 3; private volatile int count; private int in; /* points to the next free position in the buffer */ private int out; /* points to the next full position in the buffer */ private Object[] buffer; 운영체제
(참고) 공유 메모리 시스템의 생산자 프로세스와 소비자 프로세스 MessageQueue buffer; while (true) { Date message = new Date(); buffer.enter(message); } 소비자 프로세스 MessageQueue buffer; while (true) { Date message =(Date) buffer.remove(); } 운영체제
(참고) 공유 메모리 시스템의 Producer.java /ossrc/chap3/sharedmem/Producer.java import java.util.*; public class Producer extends Thread { public Producer(BoundedBuffer b) { buffer = b; } public void run() Date message; while (true) int sleeptime = (int) (BoundedBuffer.NAP_TIME * Math.random() ); System.out.println("Producer sleeping for " + sleeptime + " seconds"); try { sleep(sleeptime*1000); } catch(InterruptedException e) {} /* produce an item & enter it into the buffer */ message = new Date(); System.out.println("Producer produced " + message); buffer.enter(message); private BoundedBuffer buffer; 운영체제
(참고) 공유 메모리 시스템의 Consumer.java /ossrc/chap3/sharedmem/Consumer.java import java.util.*; public class Consumer extends Thread { public Consumer(BoundedBuffer b) buffer = b; } public void run() Date message; while (true) int sleeptime = (int) (BoundedBuffer.NAP_TIME * Math.random() ); System.out.println("Consumer sleeping for " + sleeptime + " seconds"); try { sleep(sleeptime*1000); } // 실행속도가 너무 빨라서 일부러 sleep함수 삽입 catch(InterruptedException e) {} /* consume an item from the buffer */ System.out.println("Consumer wants to consume."); message = (Date)buffer.remove(); if (message != null) System.out.println("Consumer consumed " + message); private BoundedBuffer buffer; 운영체제
(참고) 공유 메모리 시스템의 Server.java /ossrc/chap3/sharedmem/Server.java $ javac *.java $ java Server public class Server { public static void main(String args[]) { BoundedBuffer server = new BoundedBuffer(); /* now create the producer and consumer threads */ Producer producerThread = new Producer(server); Consumer consumerThread = new Consumer(server); producerThread.start(); consumerThread.start(); } 운영체제
프로세스간 통신(Inter-Process Communication) // message 전달 통신 방식 한 시스템에서 둘 다 사용해도 됨 공유 메모리 방식(shared-memory) 응용 프로그램 작성자가 응용 레벨에서 통신기능 제공 (예)유한버퍼 생산자-소비자 문제 version 1 메시지 전달 방식(message-passing) IPC(interprocess-communication)기능 이용 : OS가 통신기능 제공 (예)유한 버퍼 생산자-소비자 문제 version 2 IPC 기본 구조(Basic Structure) // IPC :process간 통신 IPC기능의 2연산 send (message) receive(message) 프로세스 P와 Q가 통신함 통신선이 전재 링크 공유 메모리 bus network send/receive 연산 // 시스템에서 제공해주는 함수 사용 운영체제
통신 모델
프로세스간 통신(Inter-Process Communication) 메시지 시스템을 구현하는 기법들 직접(direct) 또는 간접 통신 대칭(symmetric) 또는 비대칭(symmetric) 통신 자동(automatic) 또는 명시적(explicit) 버퍼링 복사(copy)에 의한 전송 또는 참고(reference)에 의한 전송 고정길이(fixed-sized) 또는 가변길이(variable-sized) 메시지 운영체제
프로세스간 통신(Inter-Process Communication) 명칭 부착(Naming) 1) 직접통신(Direct Communication) 대칭적 통신 : 두 프로세스(sender/receiver)가 상대의 이름을 호출 Send(P, message) : 프로세스 P에게 메시지 보냄 Receive(Q, message) : 프로세스 Q로부터 메시지 받음 // 주고 받을 사람 서로 지정해줌 비대칭적 통신 : sende만 receiver 호출 Receive(id, message) : 임의의 프로세스로부터 메시지 받음 Id = 통신이 일어난 순간 메시지를 보낸 프로세스의 이름으로 설정됨 // 보내면 무조건 받음(단, 보낸이는 알 수 있음) 직접통신의 단점 일일이 코딩해줘야함 ( 받는 사람과 보내는 사람) 프로세스 이름 바뀌면 전부 고쳐야(limited modularity) 2) 간접통신(Indirect Communication) mailbox(ports) 통해 통신 send(A, message) : mailbox A에 메시지 보냄 receive(A, message) : mailbox로부터 메시지 받음 mailbox의 구현 프로세스가 mailbox소유 OS가 mailbox소유 운영체제
프로세스간 통신(Inter-Process Communication) 동기화(Synchronization) // 기다림 // Data 받을 사람 생길 때 까지 계속 기다림 Blocking send: 수신 프로세스가 메시지를 받을 때까지 멈춤 // Data 보내는데 받을 사람 없으면 일단 재끼고 다른 일 처리 Nonblocking send: 메시지 보내고 다른 연산 계속 Blocking receive: 메시지가 있을 때까지 멈춤 Nonblocking receive: 올바른 메시지이거나 널 메시지이거나 상관하지 않고 받음 버퍼링(Buffering) 링크의 메시지 보유 용량 Zero capacity : rendez-vous(no buffering)…동기적 통신 Bounded capacity : 유한 길이 큐 이용…자동 버퍼링 Unbounded capacity : 무한 길이 큐 이용…자동 버퍼링 운영체제
프로세스간 통신(Inter-Process Communication) 자동 버퍼링 경우 보통 비동기적 통신(asynchronous communication)이 일어남 보낸 메시지 도착 여부 모름 꼭 알아야 할 경우 : 명시적 통신 P : send(Q, message); Q : receive(P, message); send(P, “acknowledgment”); receive(Q, message); 특별한 경우 비동기적 통신: 메시지 보낸 프로세스는 절대로 지연되지 않음 보낸 메시지 미처 받기 전에 새 메시지 보내면 이전 메시지 유실될 수 있음 메시지 유실 방지 위해 복잡한 명시적 동기화 필요 동기적 통신: 메시지 보낸 프로세스는 받았다는 회신 받을 때까지 기다림 Thoth OS : reply(P, message) 가 메시지 보낸 프로세스와 받는 프로세스의 수행 재개 Sun RPC(Remote Procedure Call) 동기적 통신(synchronous communication) sender : subroutine call reply올 때까지 블록 됨 receiver : 계산 결과를 reply (http://marvel.incheon.ac.kr의 Information 참조) * RPC 란? 상대방이 Data를 받았다는 확인을 반드시 알아야 할 경우 사용하는 기법 운영체제
프로세스간 통신(Inter-Process Communication) 예외 조건(Exception Conditions) centralized 또는 distributed system에서 고장 발생시 오류의 회복(예외 조건) 필요 프로세스 종료(Process Terminates) P는 종료된 Q를 기다림 P는 블록 됨 P 종료 Q 종료 사실을 P에 알림 P가 종료된 Q에 메시지 보냄 Q의 reply 기다려야 할 경우 블록 됨 메시지 유실(Lost Messages) OS가 탐지 및 처리 책임 sender가 탐지 및 처리 책임 OS가 탐지, sender가 처리 훼손된 메시지(Scrambled Messages) 통신 채널의 잡음(noise) 때문 보통 OS가 재전송 오류검사코드(check sums, parity, CRC)으로 조사 운영체제
(참고) 메시지 시스템(IPC) 을 이용하는 생산자-소비자 문제 /ossrc/chap3/ipc/MessageQueue.java import java.util.*; public class MessageQueue { public MessageQueue() { queue = new Vector<Object>(); } /* This implements a nonblocking send */ public void send(Object item) { // send(생산자)는 기다림 x queue.addElement(item); /* This implements a nonblocking receive */ public Object receive() { Object item; if (queue.size() == 0) return null; else { item = queue.firstElement(); queue.removeElementAt(0); return item; private Vector<Object> queue; 운영체제
(참고) 메시지 시스템(IPC)의 생산자 프로세스와 소비자 프로세스 MessageQueue mailBox; while (true) { Date message = new Date(); mailBox.send(message); } 소비자 프로세스 MessageQueue mailBox; while (true) { Date message =(Date) mailBox.receive(); if (message !=null) // consume the message } 운영체제
(참고) 메시지 시스템(IPC)의 Producer.java /ossrc/chap3/ipc/Producer.java import java.util.*; class Producer extends Thread { public Producer(MessageQueue m) mbox = m; } public void run() Date message; while (true) int sleeptime = (int) (Server.NAP_TIME * Math.random() ); System.out.println("Producer sleeping for " + sleeptime + " seconds"); try { sleep(sleeptime*1000); } catch(InterruptedException e) {} message = new Date(); System.out.println("Producer produced " + message); /* produce an item & enter it into the buffer */ mbox.send(message); private MessageQueue mbox; 운영체제
(참고) 메시지 시스템(IPC)의 Consumer.java /ossrc/chap3/ipc/Consumer.java import java.util.*; class Consumer extends Thread { public Consumer(MessageQueue m) mbox = m; } public void run() Date message; while (true) int sleeptime = (int) (Server.NAP_TIME * Math.random() ); System.out.println("Consumer sleeping for " + sleeptime + " seconds"); try { sleep(sleeptime*1000); } catch(InterruptedException e) {} /* consume an item from the buffer */ System.out.println("Consumer wants to consume."); message = (Date)mbox.receive(); if (message != null) System.out.println("Consumer consumed " + message); private MessageQueue mbox; 운영체제
(참고) 메시지 시스템(IPC)의 Server.java /ossrc/chap3/ipc/Server.java $ javac *.java $ java Server import java.util.*; public class Server { public Server() /* first create the message buffer */ MessageQueue mailBox = new MessageQueue(); /* now create the producer and consumer threads */ Producer producerThread = new Producer(mailBox); Consumer consumerThread = new Consumer(mailBox); producerThread.start(); consumerThread.start(); } public static void main(String args[]) Server server = new Server(); public static final int NAP_TIME = 5; 운영체제
IPC 실례: POSIX Shared Memory POSIX Shared Memory APIs 프로세스는 일차적으로 shmget (SHared Memory GET)을 이용하여 공유 메모리 세스먼트 생성 segment id = shmget(IPC PRIVATE, size, S_IRUSR | S_IWUSR); 프로세스는 공유 메모리에 접근하기 위해 shmat (SHared Memory ATtach)를 이용하여 자신의 주소 공간(address space)에 부착 shared memory = (char *) shmat(segment_id, NULL, 0); 프로세스는 반환된 포인터가 가리키는 공유 메모리에 read & write sprintf(shared memory, "Writing to shared memory"); 작업이 끝나면 프로세스는 shmdt (SHared Memory DeTach)를 이용하여 자신의 주소공간에서 공유 메모리를 분리 shmdt(shared memory); 마지막으로 프로세스는 shmctl(SHared Memory ConTroL operation)에 IPC_RMID 플래그를 지정하여 공유메모리 세그먼트를 시스템에서 제거 shmctl(segment_id, IPC_RMID, NULL); 운영체제
IPC 실례: Mach 분산시스템을 위한 OS 시스템 호출, task 간 정보전달을 메시지로 port(= mailbox) task 생성 (kernel mailbox, notify mailbox) 생성 system calls msg_send msg_receive msg_rpc (remote procedure call) port_allocate : 새 mailbox 생성, buffer size = 8, FIFO order port_status : 주어진 mailbox의 메시지 수 반환 메시지 형태 고정길이 header 메시지 길이 두 mailbox 이름(그 중 하나는 sender의 mailbox; reply 위한 return address 포함) 가변길이 data portion: 정형화된(typed) 데이터 항목들의 리스트 운영체제
POSIX 공유 메모리를 사용하는 생산자 프로세스 shmproducer.c /* gcc producer.c –o shmproducer –lrt */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/shm.h> #include <sys/stat.h> #include <sys/mman.h> int main() { const int SIZE = 4096; const char *name = "OS"; const char *message0= "Studying "; const char *message1= "Operating Systems "; const char *message2= "Is Fun!\n"; int shm_fd; void *ptr; /* create the shared memory segment */ shm_fd = shm_open(name, O_CREAT | O_RDWR, 0666); /* configure the size of the shared memory segment */ ftruncate(shm_fd,SIZE); /* now map the shared memory segment in the address space of the process */ ptr = mmap(0,SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0); if (ptr == MAP_FAILED) { printf("Map failed\n"); return -1; } /* Now write to the shared memory region. Note we must increment the value of ptr after each write. */ sprintf(ptr,"%s",message0); ptr += strlen(message0); sprintf(ptr,"%s",message1); ptr += strlen(message1); sprintf(ptr,"%s",message2); ptr += strlen(message2); return 0; -lrt : real time library 운영체제
POSIX 공유 메모리를 사용하는 소비자 프로세스 shmconsumer.c /* gcc shmconsumer.c –o shmconsumer –lrt */ #include <stdio.h> #include <stdlib.h> #include <fcntl.h> #include <sys/shm.h> #include <sys/stat.h> #include <sys/mman.h> int main() { const char *name = "OS"; const int SIZE = 4096; int shm_fd; void *ptr; int i; /* open the shared memory segment */ shm_fd = shm_open(name, O_RDONLY, 0666); if (shm_fd == -1) { printf("shared memory failed\n"); exit(-1); } /* now map the shared memory segment in the address space of the process */ ptr = mmap(0,SIZE, PROT_READ, MAP_SHARED, shm_fd, 0); if (ptr == MAP_FAILED) { printf("Map failed\n"); /* now read from the shared memory region */ printf("%s",ptr); /* remove the shared memory segment */ if (shm_unlink(name) == -1) { printf("Error removing %s\n",name); return 0; gcc shmconsumer.c –o shmconsumer -lrt ( -lrt : real time library ) 운영체제
IPC 실례: Mach send 연산 수신 mailbox가 full이 아니면 메시지 복사 기다리지 않고 즉시 복귀 메시지를 임시로 cache : 메시지 하나만 OS가 보관 (메시지가 실제로 목표 mailbox에 들어갔을 때 reply ; only one pending message) line printer driver 등 서버 태스크 경우 receive 연산 어떤 mailbox 또는 mailbox set로부터 메시지를 읽을지를 명시 지정된 mailbox로부터 또는 mailbox set 중 한 mailbox로부터 메시지 수신 읽을 메시지 없으면 최대 n 밀리초 대기하거나 대기하지 않음 메시지 시스템의 단점 double copy(sender mailbox, mailbox receiver) Mach는 virtural memory 기법으로 두 번 복사 않음(송신 스레드와 수신 스레드를 같은 주소 공간으로 mapping 시킴) 운영체제
IPC 실례: Windows XP XP subsystem server와 메시지 전달 방식으로 통신 : 지역 프로시주어 호출 기능 (LPC; Local Procedure Call facility) LPC: RPC(Remote Procedure Call) 기법과 유사 연결 포트(connection port)와 통신 포트(communication port) 사용 통신 작업 클라이언트가 연결 포트 객체에 대한 handle을 open 클라이언트가 연결 요청 서버는 두 개의 사적인(private) 통신 포트를 생성하고 클라이언트에게 두 포트 중 하나의 handle을 돌려줌 클라이언트와 서버는 해당 통신 포트의 handle을 이용하여 메시지를 보내거나, 응답호출(callback)을 하거나, 응답(reply)을 기다림 운영체제
IPC 실례: Windows XP 세 가지 메시지 전달 기법 포트의 메시지 큐(~256 bytes)를 중간 저장소로 이용, 즉시 응답하기 어려울 때 알려주는 callback 기법 사용 가능 전송할 메시지가 크면 공유 메모리인 섹션객체(section object)를 이용, 섹션 객체의 포인터와 크기 정보 전송하여 데이터 복사 피함, 즉시 응답하기 어려울 때 알려주는 callback 기법 사용 가능 quick LPC : 클라이언트가 연결 요청 후 quick LPC 지시, 서버는 클라이언트 전용 서버 스레드(dedicated server thread) 생성 연결 요청, 메시지 담을 64KB 섹션 객체, 동기화를 수행하는 한 쌍의 사건객체(event pare object) 처리 장점 : 메시지 복사, 포트 객체 사용, 호출한 클라이언트 파악 위한 overhead 제거 단점 : 다른 두 방법보다 많은 자원 사용 메시지 전달 overhead 감소 위해 여러 메시지들을 한 메시지로 batch 하기도 함 운영체제
Windows XP 지역 프로시저 호출 운영체제
클라이언트-서버 환경에서의 통신 ① 소켓 ② 파이프 함께 실습, ③ RPC ④ RMI 온라인 학습 소켓 (socket) 파이프(pipe) (참고) 원격 프로시저 호출 (RPC) (참고) 원격 메소드 호출 (RMI)
클라이언트-서버 통신 ① 소켓 (Socket) 소켓은 종단점(endpoint) 프로세스간 상호 양방향 통신 방식 종단점 = IP 주소와 포트 번호 결합 소켓 117.16.244.171:80 호스트 117.16.244.171 포트 80 통신은 한 쌍의 소켓을 포함 네트워크를 통한 통신 가능 소켓을 통한 프로세스 통신은 클라이언트-서버 모델(client-server model) 활용 한 기계에 존재하는 파일을 다른 호스트에서 프린트 한 기계에서 다른 호스트로 파일을 전송
Socket 운영체제
Time-of-Day: Server.java (Linux) $ Java Server 실행은 방화벽 꺼야 함 # /sbin/service iptables stop 또는 # /etc/init.d/iptables stop # java DateServer & # java DateClient import java.net.*; import java.io.*; public class DateServer { public static void main(String[] args) { try { ServerSocket sock = new ServerSocket(55555); // port 번호 /* now listen for connections */ while (true) { Socket client = sock.accept(); // we have a connection PrintWriter pout = new PrintWriter(client.getOutputStream(), true); // write the Date to the socket pout.println(new java.util.Date().toString()); // close the socket and resume listening for more connections client.close(); } catch (IOException ioe) { System.err.println(ioe);
Time-of-Day: Client.java import java.net.*; import java.io.*; public class DateClient { public static void main(String[] args) { try { /* this could be changed to an IP name or address other than the localhost */ Socket sock = new Socket(“117.16.244.171",55555); InputStream in = sock.getInputStream(); BufferedReader bin = new BufferedReader(new InputStreamReader(in)); String line; while( (line = bin.readLine()) != null) System.out.println(line); sock.close(); } catch (IOException ioe) { System.err.println(ioe); (Unix는 putty나 Xshell로 접속 Translation 또는 인코딩 UTF-8) $ telnet 117.16.244.157 login: stu Passwd: stu*** … DateClient.java 코딩 … $ javac DateClient.c $ java java DateClient
소켓 통신: helloserver.c /* gcc helloserver.c –o helloserver [ -lsocket –lnsl ] */ #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define PORTNUM 50000 main(int argc, char *argv[]) { int serverFd, clientFd, clilen, childpid; struct sockaddr_in cli_addr, serv_addr; /* Open a TCP socket (an Internet stream socket). */ if((serverFd = socket(AF_INET, SOCK_STREAM, 0)) < 0){ printf("server: can't open stream socket"); return -1; } /* Bind our local address so that the client can send to us. */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); serv_addr.sin_port = htons(PORTNUM); if(bind(serverFd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) printf("server: can't bind local address"); printf("Server is binded\n"); listen(serverFd, 5); for( ; ; ){ /* * Wait for a connection from a client process. * This is an example of a concurrent server. */ clilen = sizeof(cli_addr); clientFd = accept(serverFd, (struct sockaddr *) &cli_addr, &clilen); printf("Server called\n"); if((childpid = fork()) < 0){ printf("server: fork error"); exit(0); } else if(childpid == 0){ /* child process */ /* printf("serverFd = %d, clientFd = %d\n", serverFd, clientFd); */ /* process the request */ write(clientFd,"Hello!",7); close(clientFd); /* close original socket */ return -1; close(clientFd); /* parent process */
소켓 통신: helloclient.c /* gcc helloserver.c –o helloserver [ -lsocket –lnsl ] */ #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <netdb.h> #define PORTNUM 50000 main(int argc, char *argv[]) { int clientFd; char str[10]; char* hostaddress; struct sockaddr_in serv_addr; struct hostent* hostStruct; struct in_addr* hostNode; if(argv[1] == NULL){ printf("Usage: helloclient hostname(or server IP)\n"); printf(" (Ex) $ helloclient multi.incheon.ac.kr(or 117.16.244.171)\n"); exit(1); } hostStruct = gethostbyname(argv[1]); if(hostStruct == NULL) return(0); hostNode = (struct in_addr*) hostStruct->h_addr; hostaddress = inet_ntoa(*hostNode); printf("host name is %s, host address is %s\n", argv[1], hostaddress); /* Fill in the structure "serv_addr" with the address of the * server that we want to connect with. */ bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = hostNode->s_addr; serv_addr.sin_port = htons(PORTNUM); /* Open a TCP socket (an Internet stream soskcet). */ if((clientFd = socket(AF_INET, SOCK_STREAM, 0)) < 0) printf("client: can't open stream socket"); /* Connect to the server */ if(connect(clientFd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) printf("client: can't connect to server"); /* printf("clientFd = %d\n", clientFd); */ read(clientFd, str, 10); printf("%s\n", str); close(clientFd); exit(0); } (Unix는 putty나 Xshell로 접속 Translation 또는 인코딩 UTF-8) $ telnet 117.16.244.157 login: stu Passwd: stu*** … helloclient.c 코딩 … $ gcc helloclient.c –o helloclient –lsocket –lnl $ ./helloclient 117.16.244.171
클라이언트-서버 통신 ② 파이프(pipe) 두 프로세스가 통신하는 전달자 파이프 구현시 고려 사항 단방향(unidirectional) 통신 또는 양방향(bidirectional) 통신인가? 양방향 통신인 경우, 반이중 방식(half-duplex) 또는 전이중 방식(full-duplex)인가? 통신하는 두 프로세스 사이에 부모-자식과 같은 특정 관계가 존재해야만 하는가? 네트워크 상에서의 통신인가 아니면 같은 호스트 안에서 통신하는가? 익명 보통 파이프(Ordinary Pipes) ➜ pipe() 지명 파이프(Named Pipes) ➜ mknod()
익명 보통 파이프 (Ordinary Pipes) 생산자-소비자 형태의 통신이다 생산자는 한 파이프 종단 (the write-end of the pipe; fd[1]) 에 쓰고, 소비자는 다른 파이프 종단(the read-end of the pipe; fd[0]) 에서 읽는다 보통 파이프는 단 방향이다 부모-자식 관계에서만 통신한다 같은 호스트에서만 통신한다
익명 보통 파이프 예제 1 talk.c $ gcc talk.c –o talk $ ./talk #include <stdio.h> #include <stdlib.h> #include <string.h> #define READ 0 /* The index of the read end of the pipe */ #define WRITE 1 /* The index of the write end of the pipe */ char* phrase = "Stuff this in your pipe and smoke it"; main () { int fd [2], bytesRead; char message [100]; /* Parent process' message buffer */ pipe (fd); /*Create an unnamed pipe */ printf("fd[READ]=%d, fd[WRITE]=%d\n", fd[READ], fd[WRITE]); if (fork () == 0) /* Child, writer */ close(fd[READ]); /* Close unused end */ write (fd[WRITE],phrase, strlen (phrase) + 1); /* include NULL*/ close (fd[WRITE]); /* Close used end*/ } else /* Parent, reader*/ close (fd[WRITE]); /* Close unusedend */ bytesRead = read (fd[READ], message, 100); printf ("Read %d bytes: %s\n", bytesRead, message); /* Send */ close (fd[READ]); /* Close usedend */ $ gcc talk.c –o talk $ ./talk 운영체제
익명 보통 파이프 예제 2 connect.c $ gcc connect.c –o connect $ ./connect who wc #include <stdio.h> #include <stdlib.h> #include <unistd.h> #define READ 0 #define WRITE 1 main (argc, argv) int argc; char* argv []; { int fd [2]; pipe (fd); /* Create an unamed pipe */ printf ("fd[READ]=%d, fd[WRITE]=%d\n", fd[READ], fd[WRITE]); if (fork () != 0) /* Parent, writer */ close (fd[READ]); /* Close unused end */ dup2 (fd[WRITE], 1); /* Duplicate used end to stdout */ close (fd[WRITE]); /* Close original used end */ execlp (argv[1], argv[1], NULL); /* Execute writer program */ perror ("connect"); /* Should never execute */ } else /* Child, reader */ close (fd[WRITE]); /* Close unused end */ dup2 (fd[READ], 0); /* Duplicate used end to stdin */ close (fd[READ]); /* Close original used end */ execlp (argv[2], argv[2], argv[3], NULL); /* Execute reader program */ perror ("connect"); /* Should never execute */ $ gcc connect.c –o connect $ ./connect who wc $ ./connect ls wc $ ./connect ls wc -l 운영체제
지명 파이프 (Named Pipes) 지명 파이프는 이름 없는 보통 파이프 보다 강력하다 양방향 통신이다 통신하는 프로세스 사이에 부모-자식 관계가 필요 없다 다른 호스트의 프로세스와도 통신한다 여러 프로세스가 통신을 위하여 지명 파이프를 사용할 수 있다 UNIX 뿐 아니라 Windows 시스템에서도 사용 가능하다
지명 파이프 예제 $ gcc reader.c –o reader $ gcc writer.c –o writer /* gcc reader.c –o reader */ #include <stdio.h> #include <sys/types.h> #include <sys/stat.h> /* For SIFIFO */ #include <fcntl.h> main () { int fd; char str[100]; unlink("aPipe"); /* Remove named pipe if it already exists */ mknod ("aPipe", S_IFIFO, 0); /* Create named pipe */ chmod ("aPipe", 0660); /* Change its permissions */ fd = open ("aPipe", O_RDONLY); /* Open it for reading */ while (readLine (fd, str)) /* Display received messages */ printf ("I am the process %d: %s\n", getpid(), str); close (fd); /* Close pipe */ } readLine (fd, str) char* str; /* Read a single NULL-terminated line into str from fd */ /* Return 0 when the end-of-input is reached and 1 otherwise */ int n; do /* Read characters until NULL or end-of-input */ n = read (fd, str, 1); /* Read one character */ while (n > 0 && *str++ != (int)NULL); return (n > 0); /* Return false if end-of-input */ /* gcc writer.c –o writer */ #include <stdio.h> #include <string.h> #include <fcntl.h> main () { int fd, messageLen, i; char message [100]; /* Prepare message */ sprintf (message, "Hello from PID %d", getpid ()); messageLen = strlen (message) + 1; do /* Keep trying to open the file until successful */ fd = open ("aPipe", O_WRONLY); /* Open named pipe for writing */ if (fd == -1) sleep (1); /* Try again in 1 second */ } while (fd == -1); for (i = 1; i <= 3; i++) /* Send three messages */ write (fd, message, messageLen); /* Write message down pipe */ sleep (1); /* Pause a while */ close (fd); /* Close pipe descriptor */ $ gcc reader.c –o reader $ gcc writer.c –o writer $ ./reader & ./reader & ./writer & ./writer &
(참고) 클라이언트-서버 통신 ③ 원격 프로시저 호출 (RPC) 한 호스트의 클라이언트 프로시주어가 원격에 있는 서버 호스트의 프로시저를 호출할 수 있게 하는 가장 일반적인 클라이언트/서버 지원 기법 구조화가 잘된 고수준 (high-level) 메시지 전송 (cf.) 소켓 통신은 저수준(low-level) 패킷 스트림 전송 Sun의 NFS (Network File System) 구현에 유용하게 이용됨 rpcbind (전신은 portmapper)가 port number 111로 서비스 (참고) /etc/services 의 sunrpc 111/tcp 111/udp Sun RPC가 가장 많이 이용됨 역사 1981 RPC 기반 Xerox Courier 1985 Sun RPC package: ONC(Open Network Computing) RPC, XDR(eXternal Data Representation) TCP 또는 UDP 상에서 동작하는 socket API들로 구현 공개 소스 RPCSRC 1990 초 TLI (Transport Layer Interface): XTI(X/Open Transport Interface)의 전신 공개 소스 TI (Transport Independent)-RPC 1980 중반: RPC 기반 Apollo NCA(Network Computing Architecture) RPC, NIDL(Network Interface Definition Language) 1989 DCE(Distributed Computing Environment) RPC
(참고) Execution of RPC (Remote Procedure Call) 네트워크 상의 프로세스들 사이에서 프로시저 호출(procedure calls)을 추상화 클라이언트 측 스터브(stub) 원격 서버와 포트를 찾고 파라미터를 정돈(marshal)하여 메시지 전달 서버 측 스터브(stub) 원격 서버의 프로시저에 대한 대행자(proxy)로서 메시지 받아 정돈된 파라미터를 풀어(unmarshal) 프로시저를 수행함 운영체제
(참고) Sun RPC 예제 1: DATE_PROG 구성 rpcgen: remote procedure interface(date.x)를 컴파일하여 server stub와 client stub를 생성 XDR(eXternal Data Representation): 다른 시스템 간에 이식 가능한 형태로 데이터를 코드화하는 표준 방법 run-time library: 모든 세세한 것들을 다룸 –lnsl (Network Service Library) 소스 코드 RPC 명세 파일 date.x 서버 프로그램 date_proc.c 클라이언트 프로그램 rdate.c
(참고) Sun RPC 예제 1: DATE_PROG (Linux) Super user 만 RPC 서버 실행 가능 원격 RPC 서비스는 방화벽 꺼야 함 # /sbin/service iptables stop 또는 # /etc/init.d/iptables stop 처리 방법 % rpcgen date.x % gcc -o date_svc date_proc.c date_svc.c –lnsl % gcc -o rdate rdate.c date_clnt.c –lnsl 실행 방법 서버 호스트에서 $ date_svc & ( Linux에서는 super user 만 서버 실행 가능) 클라이언트 호스트에서 $ ./rdate 호스트이름(도메인이름 또는 IP 주소) (실행 파일이 rdate인 경우 /usr/bin/rdate가 이미 존재할 수 있으므로 rdate 명령 앞에 현재 디렉토리 ./ 붙여서 ./rdate로 실행시킴) time on host = 984459987 00:00:00 GMT(Greenwitch Mean Time) January 1, 1970부터의 초의 수 time on host = Tue Mar 13 14:06:27 2001 실습 1 서버(117.16.244.157) 호스트: $ ./date_svc & 클라이언트(117.16.244.171) 호스트: $ ./rdate 117.16.244.157 (반드시 내 ./rdate 실행하세요) (cf.) $ which rdate /usr/bin/rdate (내 rdate가 아니고 다른 rdate 실행) 실습 2 (반대의 경우) 서버(117.16.244.171) 호스트: # ./date_svc & ( Linux에서는 super user 만 서버 실행 가능) 클라이언트(117.16.244.157) 호스트: $ ./rdate 117.16.244.171
(참고) Sun RPC 예제 1: DATE_PROG
(참고) Sun RPC 예제 1: DATE_PROG RPC 명세 파일 : date.x program DATE_PROG { version DATE_VERS { long BIN_DATE(void) = 1 ; /* procedure no. = 1 */ string STR_DATE(long) = 2 ; /* procedure no. = 2 */ } = 1 ; /* version no. = 1 */ } = 0x31234567 ; /* program no. = 0x31234567 */ 프로그램 번호 0x00000000 ~ 0x1fffffff Sun에 의해 정의된 0x10000000 ~ 0x3fffffff 사용자에 의해 정의된 0x40000000 ~ 0x5fffffff 일시적인 (transient) 0x60000000 ~ 0xffffffff 예약된 (reserved)
(참고) Sun RPC 예제 1: DATE_PROG 서버 프로그램 : date_proc.c (Unix version) /* date_proc.c */ #include <rpc/rpc.h> #include "date.h" long * bin_date_1() { static long timeval ; long time() ; timeval = time((long *) 0) ; return(&timeval) ; } char ** str_date_1(bintime) long *bintime ; static char *ptr ; char *ctime() ; ptr = ctime(bintime) ; return(&ptr) ;
(참고) Sun RPC 예제 1: DATE_PROG 클라이언트 프로그램 : rdate.c (Unix version) #include <stdio.h> #include <rpc/rpc.h> #include "date.h" /* generated by rpcgen */ main(argc, argv) int argc; char *argv[]; { CLIENT *cl; /* RPC handle */ char *server; long *lresult; /* return value from bin_date_1() */ char **sresult; /* return value from str_date_1() */ if (argc != 2) fprintf(stderr, "usage: %s hostname\n", argv[0]); server = argv[1]; if ((cl = clnt_create(server,DATE_PROG,DATE_VERS,"udp")) /* client handle 생성 */ == NULL) clnt_pcreateerror(server); if ((lresult = bin_date_1(NULL, cl)) == NULL) clnt_perror(cl, server); printf("time on host %s = %ld\n", server, *lresult); if ((sresult = str_date_1(lresult, cl)) == NULL) printf("time on host %s = %s", server, *sresult); clnt_destroy(cl); }
(참고) Sun RPC 예제 1: DATE_PROG 서버 프로그램 : date_proc.c (Linux version) /* date_proc.c */ #include <stdio.h> #include <rpc/rpc.h> #include "date.h" #include <time.h> long *bin_date_1_svc(void *arg1, struct svc_req *arg2) { static long timeval; /* must be static */ timeval = time((long *) 0); return(&timeval); } char **str_date_1_svc(long *bintime, struct svc_req *arg2) static char *ptr; /* must be static */ ptr = ctime(bintime); /* convert to local time */ return(&ptr);
(참고) Sun RPC 예제 2: SQUARE_PROG RPC 명세 파일 : square.x struct square_in { /* input argument */ long arg1; }; struct square_out { /* output result */ long res1; program SQUARE_PROG { version SQUAREPROG { square_out SQUARE_PROC (square_in) = 1; /* procedure no. =1 */ } = 1; /* version number */ } = 0x31230000 /* program number */ 소스 코드 : square.x server.c client.c 처리 방법 % rpcgen square.x % gcc -o server server.c square_svc.c square_xdr.c -lnsl % gcc -o client client.c square_clnt.c square_xdr.c –lnsl (RPC 명세 파일에 구조체를 포함하기 때문에 square_xdr.c 파일이 생성되므로 컴파일시 square.xdr.c 파일을 반드시 포함해야 함)
(참고) Sun RPC 예제 2: SQUARE_PROG
(참고) Sun RPC 예제 2: SQUARE_PROG
(참고) Sun RPC 예제 2: SQUARE_PROG 서버 프로그램 : server.c (Unix version) #include “square.h” square_out * squareproc_1 (square_in *inp, struct svc_req *rqstp) { static square_out out ; out.res1 = inp -> arg1 * inp -> arg1 ; return (&out); }
(참고) Sun RPC 예제 2: SQUARE_PROG 클라이언트 프로그램 : client.c (Unix version) #include “square.h” int main(int argc, char **argv) { CLIENT *cl ; square_in in ; square_out *outp ; if ( argc != 3 ) clnt_perror(cl, “usage : client <hostname> <interger_value>”); cl = clnt_create(argv[1], SQUARE_PROG, SQUARE_VERS, “tcp”); in.arg1 = (long)atol(argv[2]); if ( ( outp = squareproc_1(&in, cl) ) == NULL) clnt_perror( cl, argv[1] ) ; printf(“result : %ld\n”, outp -> res1); exit(0) }
(참고) Sun RPC 예제 2: SQUARE_PROG 서버 프로그램 : server.c (Linux version) #include "square.h" square_out * squareproc_1_svc(square_in *inp, struct svc_req *rqstp) { static square_out out; out.res1 = inp->arg1 * inp->arg1; return(&out); }
(참고) 클라이언트-서버 통신 ④ 원격 메소드 호출 (RMI) RPC의 Java 버전 저수준(low-level) 소켓을 이용하지 않고 원격 객체의 메소드를 호출할 수 있는 방법을 제공하는 객체 지향 언어인 Java 기반의 분산 컴퓨팅 환경(클라이언트/서버 지원)을 위한 미들웨어(RPC와 유사) 스레드(thread)가 원격 객체(Remote Object)의 메소드(method) 호출 다른 Java Virtual Machine 상에 있는 객체는 “원격” 객체 RPC 보다 좋은점 객체 지향적이다 프로그램 작성 및 사용이 쉽다 안전하고 보안성이 있다 기존 시스템과 통합해서 사용할 수 있다 작성 절차 원격 인터페이스를 정의한다 원격 인터페이스를 구현(implement)하는 원격 객체(서버)를 작성한다 원격 객체를 이용하는 프로그램(클라이언트)을 작성한다 stub와 skeleton 클래스를 생성한다 rmiregistry를 실행시킨다 서버와 클라이언트를 실행시킨다
3장 정리 PCB & task_struct 프로세스 생성과 종료 fork fork + exec 유한 버퍼 생산자-소비자(bounded-buffer producer-consumer) 문제 Version 1: 공유 메모리를 이용한 해결책 Version 2: 메시지 전달(IPC)을 이용한 해결책 클라이언트-서버 통신 socket pipe RPC RMI 운영체제