[sw정글-북] 📚CSAPP 8장 Exceptional Control Flow
카테고리: swjungle CSAPP
📚CSAPP 8장 Exceptional Control Flow
🚀노션에서 업데이트 된 내용으로 보기
📚CSAPP 8장 <== 클릭
📚CSAPP 8장 <== 클릭
📚CSAPP 8장 <== 클릭
📕start
- 8.1 Exceptions
- 예외적인 제외흐름
- 컴퓨터에게 명령한다!!
- Instruction을 쭉~ 따라 실행시키는데
- PC카운터가 (Ak, Ak+1, Ak+2….)
- 주우욱… 프로그램 카운터를 따라서, 실행됨
- 하지만,
- 컴퓨터가 명령어를 차례차례 수행하던 와중 어떤 문제가 생기거나, 특정 방향으로 제어하고 싶을 때
- 제어흐름을 바꾸어야함
- (= 예외적인 제외흐름) = ECF(Exceptional Control Flow)
- 컴퓨터가 명령어를 차례차례 수행하던 와중 어떤 문제가 생기거나, 특정 방향으로 제어하고 싶을 때
-
예외상황 처리하고
- 프로그램을 종료할수도, 원래의 프로그램으로 돌아올 수도 있음
- 예외테이블
- 시스템을 부팅시 생성
- 한 시스템 내에서, 가능한 예외상황들에 대해 예외 번호를 할당
- 운영체제 커널에서 생성한것도 있음
- OS서브루틴을 실행시킴(=간접프로시저 콜을 실행)
- 핸들러 역할을 함
- 예외상황과 프로시저콜의 차이점
- 프로시저콜을 사용해서, 스택에 리턴주소를 푸시
- 이게 예외상황 그 자체랑은 다름
- 예외상황이 생기면, 프로시저콜이 리턴주소를 스택에 넣고 핸들러를 실행시킴으로써 알아서 잘 처리하고 다시 돌아오는 것이다
- 운영체제는
- 커널모드, 사용자모드 두가지가 있음
- 커널모드는 운영체제의 핵심
- 시스템콜을 통해 커널모드(컴터의 low level의 중요한부분 건드리는거)
- 우리가 일반적으로 사용하는 프로그램들은, 사용자모드에서 돌아감
- 예외처리 핸들러는 커널모드에서 돌아감
- 8.1.2 예외의 종류
- interrupt, trap, fault, abort 4가지가 있다
interrupt
- 비동기적 예외사항
- interrupt만 유일하게, instruction으로 인한 예외상황이 아님
- 나머지 세개는 instruction으로 생긴 예외사항
- instruction과 상관없이, (인터넷이 끊겼다던지, 누가 갑자기 이상한 버튼 눌렸다던지) 일어나는 사건들에 대한 예외
trap
and system call- 일부러 만든 예외상황
- systemcall
- 커널루틴을 호출하는 예외핸들러로 가게 함
- 커널에 접근할 수 있는 특권을 가진 instruction을 실행할 수 있도록 해줌
fault
- fault(오류)와 error는 다르다
- 핸들러가 처리할 수 있으면
fault
- 오류를 수정할 수 없으면 프로그램 종료
abort
- 복구할 수 없는 치명적인 에러
-
8.1.3 리눅스 x86-64 시스템콜
- 예외적인 제외흐름
- 8.2 Processes
- 실행 프로그램
- 마치 운영프로그램을 독점하듯, 메모리를 독점하듯 보임
- 8.2.1 논리적인 제어흐름
- PC값들의 배열을 논리적인 제어흐름이라고 한다
- 위그림을 보면, 여러 프로세스들이 있다. 이게 교대로 돌아가는 것임
- 8.2.2 concurrent flow
- 프로세서 A와 프로세서B가 실행시간이 겹침
- 멀티태스킹
- 다른 프로세스랑 교대로 실행됨(하나의 코어 내에서)
- 병렬흐름
- 멀티프로세싱같은거, 여러개의 코어에서 여러개의 작업 실행됨
- 8.2.3 Private address space
- 한 프로그램이 메모리독차지?? ⇒ nono, 사실 아님
-
프로그램들은 자신만의 주소공간을 제공함(다쓰는거 아님, 난 여기까지만 쓰는거)
- (위 그림)stack의 top부터 데이터를 넣어줌
- 커널에서 사용할 수 있기 때문에, 맨 윗공간은 남겨둠
- 8.2.4 user and kernel modes
- 사용자들이 못쓰도록, 제한적으로 제공
- 모드비트(mode bit)
- 모드비트를 설정하면…사용자모드에서 커널모드로 동작
- supervisor모드라고도 불림
- user program은 syscall을 통해 커널에게 간접접근
- 8.2.5 context switch
-
context switch를 통해 multitasking 사용
- 그림보면, 유저영역이랑 커널 영역 와리가리 치면서 돌아다님
- 터미널에 ㅇㅇㅇ읽어! 명령 하면 유저모드에서 커널모드로 전환됨
- 다 읽고 나면, 다시 유저모드로 컴백
- 스케줄링
- 실행시키다가, context_switch가 필요하면 스케줄링 해줌
- 이전 컨텍스트 저장
- 일시저장된 컨텍스트 복원
- 새롭게 복원된 프로세스로 전달한다
- 요약:
- 걍 sleep시켜두고, 다른거하다가 다시 돌아옴
- 실행시키다가, context_switch가 필요하면 스케줄링 해줌
- 예시
- read실행시키면,
- 메모리 접근해야되니까 잠깐 커널모드
-
잠깐 커널모드 해둔 상황에서, 놀수는 없지 ⇒ 하던거 잠깐 좀더 하고 있음
-
- 8.3 System Call Error Handling
-
포크 함수
if ((pid = fork()) < 0) { fprintf(stderr, "fork error: %s\n", strerror(errno)); exit(0); }
strerror
함수는 errno의 특정값과 연계된 에러 string을 리턴.-
이 코드를 다음과 같이 단순화 가능
void unix_error(char *msg) /* Unix-style error */ { fprintf(stderr, "%s: %s\n", msg, strerror(errno)); exit(0); }
-
위처럼 함수로 쓰면???? 아래처럼 두줄로 소환
if ((pid = fork()) < 0) unix_error("fork error");
-
이거를 Stevens가 개발한
error handling wrappers
이용 단순화 가능pid_t Fork(void) { pid_t pid; if ((pid = fork()) < 0) unix_error("Fork error"); return pid; }
-
그렇다면! 다음과 같이 한줄 가능
pid = Fork();
- 대문자는
wrapper
을, 소문자는기본이름
을 나타냄 - 8.3장의 결론
- 에러핸들링 중요하니까 좀 써라
- 코드길다고? ⇒ 노노, stevens아저씨가 만든
wrapper
쓰면 사용시 코드 1줄로 짧아지니까, 에러핸들러좀 써줘라
-
- 8.4 Process Control
- Unix는 C로부터 프로세스를 제어하기 위한 많은 시스템콜을 제공, 그중 중요한 함수들을 설명하고 어떻게 사용되는지 예제를 제공
- 8.4.1 Obtaining Process IDs
- PID(process ID)
- getpid는 호출하는 함수의 PID를 리턴함
#include <sys/types.h> #include <unistd.h> pid_t getpid(void); pid_t getppid(void);
- 위의 함수들은 정수값 리턴(types.h에 정의되어있음)
- 8.4.2 Creating and Terminating Processes
- 프로그래머 관점 프로세스는 다음과 같은 세가지
- Running
- 프로세스는 CPU에서 실행중이겠지???
Stopped
(별로 안중요)SIGSTOP
,SIGTSTP
,SIGTTOU
등등 다양한 중지 시그널을 받게 됨- 위의 시그널 받으면 정지상태임
SIGCOUNT
시그널 받으면 다시 달리기 시작함
- Terminated
- 프로그램종료 3가지 경우
- (1)종료하라고 하거나 (2)메인루틴 끝냈을때 (3)exit함수 호출될 때
- 프로그램종료 3가지 경우
- Running
Fork
함수- 뭐하는 함수인가
- 자식프로세스를 만들어냄
- 자식프로세스는 엄마함수랑 거의 동일함
- 엄마가 할 수 있는거 다 할수 있지만, 엄마함수랑 다른 PID를 가짐
- 엄청 헷갈린다
- 헷갈리는 이유: 한번호출되지만, 두번 리턴하기 때문
- 첫번째 리턴
- 부모에서 fork하면 자식의 PID를 리턴
- 두번째 리턴
- 자식에서의 fork는 0을 리턴한다
- 사용법
-
아래 코드 돌리면
1 int main() 2 { 3 pid_t pid; 4 int x = 1; 5 6 pid = Fork(); 7 if (pid == 0) { /* Child */ 8 printf("child : x=%d\n", ++x); 9 exit(0); 10 } 11 12 /* Parent */ 13 printf("parent: x=%d\n", --x); 14 exit(0); 15 }
-
다음과 같은 결과
//linux> ./fork parent: x=0 child : x=2
-
아래와 같이 작동
-
하나만 부르면 쉽지…….
-
여러개 한번 불러보도록 하겠음
1 int main() 2 { 3 Fork(); 4 Fork(); 5 printf("hello\n"); 6 exit(0); 7 }
-
fork 2번하면… 4번 실행됨(😇…)
-
-
- 뭐하는 함수인가
- 프로그래머 관점 프로세스는 다음과 같은 세가지
- 8.4.3 Reaping Child Processes(프로세서 청소)
- 좀비
- 프로그램이 죽지않고 계속 실행되어서 주욱~메모리잡아먹고 앉아있음(좀비 프로세서)
- 따라서, 잘 종료 해 주는것도 중요
waitpid
로 프로그램 죽여벌임안중요해보이는 내용들
(노란 영역)- WNOHANG, WUNTRACED, WCONTINUED 들로 waitpid 사용법 변경 가능
- 그리고 WIFEXITED, WEXITSTATUS, WTERMSIG 등등으로, 종료상태 체크 가능
- 정상적으로 종료되었는지
- 시그널을 받지못해서 종료된건지
- 종료시킨 시그널은 어쩐지 등등
- waitpid는 사용법이 어렵다
- 종료시킬때도, 자식 process들이 있다면 어떻게 종료될지 알 수 없다
- 최종 결과는 같지만, 컴퓨터시스템 특성에 따라 예측할수 없는 순서로 청소(종료)됨
- 좀비
- 8.4.4 Putting Processes to Sleep
sleep
으로 재우면, 요청시간동안 프로그램 멈춰있음pause
으로 재우면, 해당 함수 다시 부를때까지 멈춰있음
- 8.4.5 Loading and Running Programs
execve
함수- 현재 프로그램 내에서 새로운 프로그램을 로드하고 실행함
- 파일 이름을 찾을 수 없을때만, 다시 돌아감(리턴됨)
- 따라서,
- 한번 호출되고 절대로 리턴하지 않음
- (참조: 아까 fork는 한번호출하고 두번 리턴함)
execve
함수 작동- 작동시 필요한 세가지
- executable object file의 파일명
-
인자리스트 argv
-
환경변수리스트 envp
-
작동모습(아래사진)
- 스택 맨위를 보면
- libc_start_main이 있음
- (7.9장 내용)해당함수가 시스템 초기화 시켜줌
- “자~~ 다들 오늘 하루도 잘해봅시다 이제 프로그램 출발합니다~” 하고 초기화 해버리는 역할
- 그다음
- 인자리스트, 환경변수 읽어들이면서 프로그램 실행
- 스택 맨위를 보면
- 작동시 필요한 세가지
- 8.4.6 Using fork and execve to Run Programs
sh
- 쉘의 역할
- 사용자를 대신해, 다른 프로그램을 실행시켜주는 역할
- 쉘의 후배들
- csh, zsh, bash 등 나중에 만들어졌음
- 쉘의 실행순서
- 읽기: 사용자가 알려준 명령줄 읽음
- 계산: 명령줄 분석해서 프로그램 실행시켜줌
- 쉘의 역할
- 쉘프로그램 실행순서
-
코드(클릭)
1 #include "csapp.h" 2 #define MAXARGS 128 3 4 /* Function prototypes */ 5 void eval(char *cmdline); 6 int parseline(char *buf, char **argv); 7 int builtin_command(char **argv); 8 9 int main() 10 { 11 char cmdline[MAXLINE]; /* Command line */ 12 13 while (1) { 14 /* Read */ 15 printf("> "); 16 Fgets(cmdline, MAXLINE, stdin); 17 if (feof(stdin)) 18 exit(0); 19 20 /* Evaluate */ 21 eval(cmdline); 22 } 23 }
- 코드 해석
- parseline함수 호출
- (사용자가 입력한)스페이스로 구분된 명령(들) 분석
- execve로 전달될 argv벡터를 만듬
- 첫번째 인자
pip
install 어쩌고 저쩌고unzip
A파일 B파일 -o 어쩌고저쩌고 압축해제- 위처럼, 첫인자는 즉시 해석가능한 내부 쉘 명령어
- 혹은, 실행가능한 목적파일
&
인자(앰퍼서드)- 백그라운드에서 계속 열어둬라
- 첫번째 인자
- 파싱 끝나면
biltin_command
함수 호출해서, 내장명령어인지 여부 체크- 내장명령어가 아니라면?
- 쉘은 자식 프로세스를 만들고, 자식 프로그램을 실행
- waitpid함수로 작업 종료될때까지 대기
- parseline함수 호출
- 코드의 오류
- 위의 코드는 쉘이 백그라운드를 전혀 청소하지 않음(zombie 제거 안함)
- 8.5장의 시그널을 사용하여 좀비프로세스 해결해야함
-
- 8.5 Signals
- 하위수준의 하드웨어, 상위수준의 소프트웨어 두개의 시그널이 있음
- 상위수준의 소프트웨어를 제어하기 위한게 signal
- 가끔 하위수준 하드웨어를 관리하기도 함
- 시그널을 제어할 때는 항상 작은 메시지가 같이 뜸
- 어떤 이벤트가 발생했답니다!~ 라고 알려주는 메시지
- 잘못된 메모리 참조, 0으로 나눌때 등 하위 수준에 관여함, kill함수로 종료할 때
- 두가지 절차
- signal 보내기
- 하위수준 컨트롤 하기 위해
- 의도적으로(상위수준에서) 요구할 때
- signal 받기
- signal handler가 처리해줌
- 사용자 수준 함수를 내가 설정 할 수 있음
- signal을 무시하는 방법
- 프로그램을 종료하는 방법
- signal대로 프로그램이 받아서 획득
- pending
- pending이 이루어지는 두가지 상황(ex, 수민이 찾아오는 상황)
- 블럭처리 해두었기 때문에pending
- 진섭: “수민이 말은 앞으로 무시할게~”
- 다른 작업중이기때문에 pending
- 진섭: “수민아 중선이랑 이야기중이니 바쁘다, 조금 이따 와라~”
- 블럭처리 해두었기 때문에pending
- 진섭에게 여러가지 기능이 있을텐데(대화기능, 공부기능, 식사기능, 이동 기능)
- 같은종류(진섭-대화기능 사용중에, 수민이가 대화하려고 오면)가 오면, 한타입당 1개만 pending할 수 있음
- 프로세스들을 그룹핑 해두면, 한번에 다 받을 수 있음
- pending이 이루어지는 두가지 상황(ex, 수민이 찾아오는 상황)
- core dump
- 특정 시점 상태에서의 메모리를 기록한다
- 아 ㅈ 댔다… 에러나서 종료해야돼(비상🚨), 그래서 빨리 기록해야함
- 심각한 오류가 있을 때, 강제로 프로그램이 꺼진다는 뜻으로 와전되어있긴 함
- signal 보내기
- 8.5.1 signal Terminology
- 8.5.2 시그널 보내기
- 시그널을 보내는 방법은 여러가지가 있다
그룹
- 한방에 모아서 보내면 효율적임
- 다음 그림은 서로다른 pid를 group-pid(pgid)=20으로 묶어서 한방에 kill
키보드로 보내기
- control + Z
- control + C
bin/kill/
알람함수
- 시그널을 보내는 방법은 여러가지가 있다
- 8.5.3 시그널 수신
핸들러 installing
- 핸들러 넘겨준다는 말
핸들러를 catching
- 핸들러를 호출한다는 말
핸들러 handling
- 핸들러를 실행시킨다는 말
- 8.5.4 시그널 블록, 블록 해제
- 묵시적 블록방법
- 내가 안해줘도, 법칙에 의해 블록
- 한가지 타입 시그널 실행중이면, 내가 따로 설정 안해줘도 블록처리됨
- 명시적 블록방법
- 앞으로 ㅇㅇ말은 안들을꺼야!
- 내가 명시적으로 블록처리해줌
- 각 시그널마다 블록된거 1, 블록되지 않은거 0으로 됨
- 묵시적 블록방법
- 8.5.5 시그널 핸들러 작성하기
- 시그널이라는건 제어권을 가져오는 것
- 동시성(concurrent)의 문제가 발생할 수 있음
- 따라서, 시그널 핸들러는 아주 보수적으로 처리해야함
- 문제점 3개
- 같은전역변수로 뒤섞일 수 있음
- 어떻게, 그리고 언제 시그널이 올 수 있는지 직관적이지 않음
- 다른 시스템은 다른 시그널 처리방식을 가짐
- 안전한 시그널 작성
- 가장 작고, 가장 단순하게 시그널 핸들러를작성해야함
- 핸들러 작성 진~~짜 어려움
- malloc, exit, printf등등 안전하지 않음(또 웃긴게, _exit은 안전하다고 함)
- 출력관련 유일한 안전한 방법은 write
- 그냥 뭘 해도 다 안전하지 않다고함(그많큼 어렵다는 거지)
- 캐시로부터 보호
volatile
- volatile int c;
- 야, int c저친구는, 캐시에다가 넣지 지마!! 캐시 금지!!
- 캐시는 결국, 경향성을 따져서 자기가 캐싱해버리는거니까 위험함, 그래서 캐싱 금지시키는 것
- 중단을 시킬 수 없도록 하는 방법
atomic
처리를 해줄 수 있음- 원자는 쪼갤 수 없으니까… 쪼개지 말고, 중단시키지도 말라는 뜻으로 작명된 듯(추측)
- 정확한 시그널 처리
- 좀비 프로세스 만들어지는 사례…
- 시그널의 특성때문에, 값이 없는 자식이 만들어지는 상황 등
- 시그널 이벤트를 일일히 카운트 할 수 없다!!!
- 지멋대로 펜딩될때도 있고, 실행되기 때문에 그걸 카운팅하면 안됨
- 좀비 프로세스 만들어지는 사례…
- 호환성
- 운영체제마다 signal의 의미가 다름
- 어떤 시스템콜은 느림
- pending에 들어갈수도, 안들어 갈 수도 있고, 중단될 수도 있음
- 따라서 느린 시스템콜들에 대해서는 재시작 코드를 삽입해주어야 한다
- 8.5.6 Synchronizing Flows to Avoid Nasty Concurrency Bugs
- 프로그램은 몇개인데, 시그널은 엄청 많으니까 시그널이 기하급수적으로 늘어남
- 따라서 버그가 발생하기 쉬움
- 경주
- 어떨때는 삭제가 더 빠르고, 어떨때는 자식들을 쉬게 하는 녀석들이 더 빠르기도 하고, 어떨때는 프로그램 제어(생성, 호출) 등이 빠름
- 어떤 순서로 처리될지 알 수 없음
- 따라서 때에 따라서는 좀비프로세서가 만들어 지기도 함
- 8.5.7 Explicitly Waiting for Signals
pause
하려고 할 때 문제가 생길 수 있음- pause를 사용하려고 하면 경쟁조건이 발생할 수 있음
sleep
을 하려고 해도 문제가 생길 수 있음- 잠을 자야하는 길이를 결정할 수 있는 결정규칙이 없음
sigsuspend
- 이걸로 멈춰두면 좋다
- 8.6 Nonlocal Jumps
setjump
longjump
두가지가 있음- 엄청 고급기술임
- 원하는 위치로 이동
- 8.7 Tools for Manipulating Processes
- pass
- 8.8 Summary
- pass
😵배우면서 깨달은 내용을 정리해 보았습니다. 틀린 것 같은 개념을 아래 댓글에 달아주시면 감사합니다😵
🌜 Thank you for reading it. Please leave your comments below😄
댓글 남기기