저자 : Randal E Bryant, David R O'Hallaron
저서 :
옮김 : 김형신
펴낸 곳: 퍼스트 북(First Book), PEARSON
초판 :
2쇄 : 2016년 8월 31일
예제 사용 시 그냥 따라하면 정의되지 않음 참조 에러를 만나게 된다.
발췌독을 하다보니 무슨 헤더인지 몰랐는데 csapp.h를 포함해서 사용한다. (718p 설명)
해당 헤더는 csapp.c 에 정의된 함수들을 선언한다.
즉, 테스트 프로그램 컴파일&링킹시 csapp.o 와 헤더파일이 필요하다
해당 파일은 http://csapp.cs.cmu.edu/3e/code.html 참조.
code-all.tar 내용을 보면 ~/src/Makefile도 있고 이미 object 파일도 존재한다.
저자 서문
이 책(CS:APP로 알려진)은 컴퓨터 시스템의 "내부에서" 어떤 동작을 하고 있는지를 학습해서 보다 좋은 프로그램을 작성하고자 하는 컴퓨터 과학자, 컴퓨터 엔지니어, 그리고 다른 모든 사람들을 위한 책이다.
Part 1. 프로그램의 구조와 실행
Part 2. 시스템에서 프로그램의 실행
CHAPTER 7. 링커
CHAPTER 8. 예외적인 제어흐름
8.4 프로세스의 제어 710
8.4.1 프로세스 ID 가져오기
- 각각의 프로세스는 고유의 양수(0이 아닌) 프로세스 ID(PID)를 가진다.
- getpid함수는 호출하는 함수의 PID를 리턴
- getppid함수는 자신의 부모의 PID를 리턴(호출하는 프로세스를 만든 프로세스)
- getpid와 getppid 루틴은 pid_t 타입의 정수 값 리턴(리눅스 시스템의 types.h에 정수로 정의)
8.4.2 프로세스의 생성과 종료
- 프로그래머의 관점에서 프로세스는 다음의 세 가지 상태 중의 하나로 생각할 수 있다.
1) 실행중 Running : 프로세스는 CPU에서 실행하고 있거나 실행을 기다리고 있으며, 궁극적으로 커널에 의해서 스케줄될 것이다.
2) 정지 Stopped : 프로세스의 실행은 정지한 상태이고 스케줄되지 않는다. SIGSTOP, SIGTSTP, SIGTTIN, SIGTTOU 시그널을 받게 되면 그 결과로 정지하고, SIGCONT 시그널을 받을 때까지 정지 상태로 남아 있으며, 이 시그널을 받은 시점에서 다시 실행을 시작
3) 종료 Terminated : 프로세스는 영구적으로 정지. 1)프로세스 종료 시그널 수신 2) 메인 루틴에서 리턴 3) exit함수 호출
- 부모 프로세스는 fork함수를 불러서 자식 프로세스를 생성
- 새롭게 생성된 자식 프로세스는 완벽하게는 아니지만 부모와 거의 동일
- 자식은 코드, 데이터 세그먼트, 힙, 공유된 라이브러리, 사용자 스택을 포함하는 부모의 사용자수준 가상 주소공간과 동일한(그러나 분리된) 복사본을 갖는다.
- 자식은 또한 부모가 오픈한 파일 식별자 모도와 동일한 사본을 갖는다. 이것은 부모가 fork를 호출했을 때 부모가 오픈한 파일 모두를 읽고 쓸 수 있다는 것을 의미한다.
- fork함수는 한 번 호출되지만 두 번 리턴한다. 한 번은 호출한 프로세스에서(부모), 다른 한 번은 새롭게 생성된 자식 프로세스에서
- 부모에서 fork는 자식의 PID를 리턴
- 자식에서 fork는 0을 리턴, 즉 부모/자식 프로세스 구분 가능
- 예제의 미묘한 측면들
1) 한 번 호출 두번 리턴 : 부모/자식 각 한번씩, 단 여러 개의 fork를 갖는 프로그램은 헷갈릴 수 있다.
2) 동시 실행 : 부/자는 동시에 돌아가는 별도의 프로세스. 즉, 논리적 제어흐름 내의 인스트럭션들은 커널에 의해서 임의의 순서로 중첩될 수 있다. 예를 들면 printf를 찍는 순서를 생각해보라. 일반적으로, 프로그래머로서 우리는 서로 다른 프로세스내에서 인스트럭션들 간의 중첩 실행에 관해 절대로 가정할 수 없다.
3) 중복된, 그러나 별도의 주소공간 : 각 프로세스는 동일한 사용자 스택, 지역변수 값들, 힙, 전역변수 값, 동일한 코드를 가진다. 주소공간이 동일하다. 그렇지만 별도의 프로세스이므로 자신만의 사적인 주소공간을 가진다. 즉, 포크 이후 수정사항들은 모두 개인적이며 다른 프로세스의 메모리에는 반영되지 않는다.
4) 공유된 파일 : 예제 프로그램을 실행할 때, 부모와 자식 모두가 자신의 출력을 화면을 통해서 한다는 것을 알 수 있다. 그 이유는 자식이 부모가 오픈한 모든 파일들을 상속받았기 때문이다. 부모가 fork를 호출할 때 stdout 파일은 열려 있으며, 화면으로 재지정되어 있다. 자식은 이 파일을 상속하였으며, 그래서 자신의 출력도 화면으로 가게된다.
#4번이 이해가 안간다. ㄷㄷ
8.4.3 자식 프로세스의 청소
- 프로세스가 어떤 이유로 종료할 때, 커널은 시스템에서 즉시 제거하지 않는다.
- 프로세스는 부모가 청소할 때가지 종료된 상태로 남아 있다.
- 부모가 종료된 자식을 청소할 때 커널은 자식의 exit 상태를 부모에게 전달하며, 그 후 종료된 프로세스를 없애며, 이 시점에서 프로세스가 사라지게 된다.
- 좀비(Zombie) : 종료되었지만 아직 청소되지 않은 프로세스
- 부모 프로세스가 종료할 때, 커널은 init 프로세스로 하여금 모든 고아가 된 자식들의 입양된 부모가 되도록 한다.
- 프로세스는 waitpid함수를 호출해서 자신의 자식들이 종료되거나 정지되기를 기다린다.
#include <sys/types.h>
#include <sys/wait.h>
pid_t watipid(pid_t pid, int *statusp, int options);
Returns : PID of child if OK, 0(if WNOHANG), or -1 on error
- 기본적으로(options = 0일 때) watipid는 대기하는 집합 wait set 내의 하나의 자식 프로세스가 종료할 때까지 호출한 프로세스의 실행을 정지시킨다.
- 어떤 경우이든 waitpid함수는 자신을 리턴하게 만든 종료된 자식의 PID를 리턴한다.
대기 집합의 멤버 결정하기
- 대기 집합의 멤버들은 pid 인자에 의해서 결정된다.
기본 동작의 수정
- options를 상수들의 조합을 사용해서 설정
1) WNOHANG
: 대기 집합 내의 자식 프로세스 중 아직 아무것도 종료되지 않았다면 즉시 리턴
: 기본동작은 자식이 종료할 때까지 호출한 프로세스를 정지하는 것
: 자식이 종료하기를 기다리는 동안 다른 유용한 작업을 계속하기를 원하는 경우들에 유용
2) WUNTRACED
: 대기 집합의 프로세스가 종료되거나 정지될 때까지 호출한 프로세스의 실행을 정지
: 리턴을 발생시킨 정지 혹은 종료 상태인 자식의 PID를 리턴
: 기본 동작은 종료된 자식에 대해서만 리턴
: 종료되고 정지된 자식들 모두에 대해 체크하고 싶을 때 유용
3) WCONTINUED
: 대기 집합에 속한 동작하는 프로세스가 종료될 때까지, 또는 대기 집합에 속한 정지한 프로세스가 SIGCONT 시그널을 받고 다시 실행을 시작할 때까지 호출하는 프로세스의 실행을 유예
- 옵션들을 OR해서 함께 연결할 수 있다.
ex) WNOHANG | WUNTRACED : 대기 집합 모든 자식 프로세스들이 정지하였거나 종료하였다면 리턴 값 0으로 즉시 리턴되거나 자식들 중 한 개의 PID와 동일한 값으로 리턴
청소된 자식의 exit 상태 체크하기
- statusp 인자가 NULLdl dkslaus, waitpid는 리턴을 하도록 만든 자식 프로세스에 대한 상태 정보를 status로 인코딩한다. 이 값은 statusp에 의해 지시되는 값이다.
- wait.h include 파일은 status 인자를 해석하기 위한 여러 개의 매크로를 정의하고 있다.
에러 조건
- 호출하는 프로세스가 자식이 없다면, watipid는 -1을 리턴, errno를 ECHILD로 설정
- waitpid함수가 어떤 시그널에 의해 중단되었다면, -1을 리턴, errno를 EINTR로 설정
wait함수
- wait함수는 waitpid의 보다 단순화된 버전
- wait(&status)를 호출하는 것은 waitpid(-1, &status, 0)을 호출하는 것과 동일
8.5.2 시그널 보내기
시그널을 /bin/kill 프로그램을 사용해서 보내기
- /bin/kill 프로그램은 다른 프로세스로 임의의 시그널을 보낸다.
ex)
/bin/kill -9 15213 (시그널 9번(SIGKILL)을 프로세스 15213에 보냄)
/bin/kill -9 -15213 (음수 PID는 시그널이 프로세스 그룹 PID 내의 모든 프로세스에 보냄)
- 일부 Unix 쉘은 자신만의 내장 kill 명령어를 가지고 있다.(주의)
키보드에서 시그널 보내기
CHAPTER 9. 가상메모리
Part 3. 프로그램들 간의 상호작용과 통신
CHAPTER 10. 시스템 수준 입출력
CHAPTER 11. 네트워크 프로그래밍
CHAPTER 12. 동시성 프로그래밍
12.1 프로세스를 사용한 동시성 프로그래밍
12.2 I/O 다중화를 이용한 동시성 프로그래밍
12.3 쓰레드를 이용한 동시성 프로그래밍 949
- 쓰레드는 프로세스의 컨텍스트 내에서 돌아가는 논리흐름이다.
- 쓰레드는 커널에 의해서 자동으로 스케줄된다.
- 각 쓰레드는 고유의 정수 쓰레드ID(TID), 스택, 스택 포인터, 프로그램 카운터, 범용 레지스터, 조건 코드를 포함하는 자신만의 컨텍스트를 가진다.
- 한 개의 프로세스에서 돌고 있는 모든 쓰레드는 이 프로세스의 전체 가상주소를 공유한다.
- 쓰레드는 커널에 정수ID로 알려진다.
- 다수의 쓰레드는 한 개의 프로세스의 컨텍스트에서 돌아가며, 그러므로 이 프로세스 가상 주소공간의 전체 내용을 공유한다.
- 공유에는 코드, 데이터, 힙, 공유 라이브러리, 오픈한 파일들이 포함된다.
12.3.1 쓰레드 실행 모델
- 다중 프로세스를 위한 실행 모델과 어떤 면에서는 비슷하다.
- 각 프로세스는 메인 쓰레드라고 부르는 한 개의 쓰레드로 생명을 시작한다.
- 어떤 시점에서 메인 쓰레드는 피어 쓰레드를 생성하고 두 쓰레드가 동시에 돌아간다.
- 제어는 문맥전환을 통해서 피어 쓰레드로 전달
- 피어 쓰레드는 제어를 메인 쓰레드로 돌려주기 전에 잠시 동안 실행하는 식으로 진행
- 쓰레드 문맥전환이 프로세스 문맥전환보다 빠르다.
- 쓰레드는 경직된 부모-자식 계층구조에서 구성되지 않았다.
- 하나의 프로세스에 연계된 쓰레드들은 피어들의 풀을 구성
- 피어의 풀에 관한 이 개념의 주요 영향은 쓰레드가 자신의 피어 모두를 죽일 수 있거나 자신의 피어들이 종료하는 것을 기다릴 수 있다는 것이다.
- 나아가서, 각 피어는 동일한 고유 데이터를 읽고 쓸 수 있다.
#12.3.1 부분은 이해가 잘 되지 않는다.
12.3.2 Posix 쓰레드
- Posix 쓰레드(Pthreads)는 C 프로그램에서 쓰레드를 조작하는 표준 인터페이스
- 1995년 채택되어 모든 리눅스 시스템에서 사용 가능
메인 쓰레드
피어 쓰레드 생성
피어 쓰레드
hello, world 출력
피어 쓰레드 종료
피어 쓰레드 종료 검출
exit 호출
프로세스 종료
- 쓰레드에 대한 코드와 지역 데이터는 쓰레드 루틴에 캡슐화되어 있다.
- 다수의 인자를 쓰레드 루틴으로 전달하려면 인자들을 구조체에 넣고 포인터를 구조체로 전달
(반대도 마찬가지, 포인터를 구조체로 리턴)
- 메인 쓰레드는 exit()를 호출하고, 이것은 현재 프로세스에서 돌고 있는 모든 쓰레드를 종료
#일부 번역이 이상하지 않은가? 아니면 내가 이해하지 못한 것
12.3.3 쓰레드 생성
int pthread_create(pthread_t *tid, pthread_attr_t *attr, func *f, void *arg);
Returns : 0 if OK, nonzero on error
- pthread_create함수는 새 쓰레드를 만들고 쓰레드 루틴 arg를 새 쓰레드의 컨텍스트 내에서 입력 인자 attr를 가지고 실행된다. 인자는 새롭게 만들어진 쓰레드의 기본 성질을 바꾸기 위해 사용될 수 있다.
- pthread_create이 리턴할 때, 인자 tid는 새롭게 만들어진 쓰레드의 ID를 갖는다.
12.3.4 쓰레드 종료하기
- 쓰레드 종료 방법
1) 쓰레드는 자신의 최상위 쓰레드 루틴이 리턴할 때 묵시적으로 종료
2) 쓰레드는 pthread_exit함수를 호출해서 명시적으로 종료한다.
if 메인 쓰레드가 pthread_exit를 호출하면 다른 모든 쓰레드가 종료하기를 기다린다.
그 후엔 메인 쓰레드와 전체 프로세스를 thread_return 리턴 값으로 종료
#프로세스와 메인 쓰레드가 거의 동급으로 봐야하는 거 아닌가?
3) 일부 피어 쓰레드는 리눅스 함수를 호출하며, 이것은 프로세스와 이 프로세스에 관련된 쓰레드 모두를 종료
# 피어 쓰레드가 프로세스를 종료시킬 수 있다
4) 다른 피어 쓰레드는 pthread_cancel함수를 현재 쓰레드의 ID로 호출해서 현재 쓰레드를 종료
12.3.5 종료한 쓰레드의 삭제 Reaping
- 쓰레드는 pthread_join함수를 호출해서 다른 쓰레드가 종료하기를 기다린다.
- pthread_join함수는 쓰레드 tid가 종료할 때까지 멈춰 있으며, 쓰레드 루틴이 리턴한 기본 (void *)포인터를 thread_return이 가리키는 위치로 할당하고, 그 후에 종료된 쓰레드가 가지고 있던 모든 메모리 자원을 삭제한다.
#join함수로 종료하지 않으면 메모리 누수?
12.3.6 쓰레드 분리하기
- 언제나 쓰레드는 연결 가능joinable 하거나 분리되어detached 있다.
- 연결 가능한 쓰레드는 다른 쓰레드에 의해 청소되고 종료될 수 있다.
- 자신의 메모리 자원들은 다른 쓰레드에 의해 청소될 때까지는 반환되지 않는다.
- 반대로 분리된 쓰레드는 다른 쓰레드에 의해서 청소되거나 종료될 수 없다.
: 쓰레드가 종료할 때 시스템에 의해 자동으로 반환
'책을 읽.쓰.' 카테고리의 다른 글
<재무제표 모르면 주식투자 절대로 하지마라> (8) | 2020.02.11 |
---|---|
<Operating System Concepts> (0) | 2020.02.07 |
<아는 만큼 보이는 데이터베이스 설계와 구축>, UPDATE.20200210 (0) | 2020.02.06 |
<부자들의 생각법> (6) | 2020.01.20 |
<이직의 패러독스> (3) | 2020.01.17 |