프로그램이 어떻게 프로세스가 되는가?
- 프로그램
- 스태틱한 객체로, instruction들의 나열
- instruction의 시퀀스로, 정의된 일들을 실행하기 위함
- 컴파일을 통해 object file을 생성. 라이브러리를 사용하고 있다면 링크 과정까지 포함 ⇒ 실행 가능한 파일 *.c ⇒ *.out
- 프로세스
- 하드디스크에 있던 프로그램의 정보들이 메모리에 로드가 되어 실행이 되는 것
- OS가 pid, state 등과 같은 Information이 메모리에 로드 / 메모리 공간 할당
- 바로 실행되는 것이 아닌 ready queue에 대기하고 있다가, CPU로 실행 된다.
- 프로세스 내부에는 실행 흐름을 담당하는 객체가 같이 실행되는데, 이를
thread
라고 한다.
Threads and thread of execution
Program counter
- 다음에 실행할 instruction의 주소 정보가 들어가 있다.
- thread의 실행 흐름
- instruction의 stream
- Program counter에 할당된 다음 실행될 instruction의 주소값을 통해 표현된다.
Thread of Execution example
1번 프로세스가 실행할 루프 안의 statements가 245, 246, 247
표현 방식: statement(pid)
→ Result: 245(1), 246(1), 247(1), 245(1), 246(1), 247(1)
1번 프로세스는 루프 안의 statements 245, 246, 247 / 2번 프로세스는 10, 11, 12, 13, … 을 실행한다.
→ Result: 245(1), 246(1), 247(1), 245(1), 246(1) [context-switch instruction] 10(2), 11(2), 12(2), 13(2) [context-switch instruction] 247(1), 245(1), 246(1), …
Multiple threads
- Thread
- 스레드는 프로세스의 실행 흐름을 표현하는 하나의 가상 데이터 타입.
- stack, pc, register set, state를 독립적으로 가지고 있다.
- 여기서 pc는 CPU가 갖고있는 독립적인 pc와는 다르다.
- CPU의 pc는 multi programming을 위한 프로세서의 instruction address를 나타낸다.
- Multiple processes vs multiple threads
- 하나의 프로세스 내부에서 여러개의 스레드를 관리한다면,
context switch overhead
를 줄일 수 있다. 코드, 데이터를 공유할 수 있다. - 스레드가 가지고 있는 정보가 프로세스가 가지고 있는 정보보다 상대적으로 적기 때문에, switch를 할 때마다 정보를 저장 / 읽기 에 소요되는 리소스가 적다
- 다중 스레드를 사용한다면 스레드간에 동기화 하는 부분을 고려해야 한다 ( 단점 ).
- 여러개의 스레드는 하나의 프로세스가 할당받은 address space를 사용할 수 있기 때문이다.
Layout of a program image
- A program image
- 하나의 프로세스가 사용하는 메모리 공간도 여러가지의 section 으로 구분된다.
Sample layout of a program image
- Activation record
- 메모리의 block은 process stack의 맨 위에 할당이 되는데, 함수가 호출이 됐을 때 execution context를 갖고 있기 위함
- 각각의 함수가 실행될 때 새로운 activation record가 생성된다.
- return address, parameter, status information, automatic variables 등을 포함한다.
Library function calls
- SYNOPSIS box
- 요약본 ( 더 자세하게 알기 위해서는 man command를 사용하자 )
- 함수를 사용하기 위해선 어떠한 header file을 import 해야하고, function의 prototype을 알려 줌
- Error
- 전통적인 UNIX 함수의 경우 -1을 리턴해준다. ( 혹은 NULL )
errno
라는 변수에 어떠한 에러가 일어났는지 에러 코드를 적어준다.- 새로운 라이브러리 함수의 경우 errno를 따로 사용하지 않고, 그 대신에 에러 코드 자체를 return
Error handling functions
- void perror(const char *s)
- #include <stdio.h>
- errno에 대해 해당하는 에러 메세지를 화면에 출력해준다.
- char* strerror(int errnum)
- #include <string.h>
- 보고싶은 에러 코드에 대해 에러 메세지를 return
Good model of a function
- 함수를 실행하다가 에러가 났을 경우 exit 하지 말고, error 값을 return 하라
- 버퍼의 사이즈가 불필요하게 가정해서 사용하지 마라 ( 잘 설계 해라.. )
- 상수값을 임의의 값으로 정해서 사용하지 말고, 시스템에서 defined된 상수값이 있을 경우 그것을 써라
- 함수가 파라미터를 받아서 이용한다면, 그 파라미터를 웬만하면 수정하지 말아라.
- 가능하다면 static 변수, 동적 메모리 할당은 피해라
- malloc family에 대해서는 잘 분석하여 사용해라
Argument arrays
- string array 이다.
- 리눅스 터미널 상에서 argument를 파싱하여 토큰으로 쪼갠다.
Thread-safe strtok()
string tokenizer 함수이다.
여러개의 스레드가 동시에 호출할 때 문제가 될 뿐더러,
하나의 스레드가 동시에 두가지 이상의 strtok() 작업을 할 때에도 문제를 일으킬 수 있다.
example)
- strtok(”a b c d”, “ “); ⇒ return a
- strtok(NULL, “ “) ⇒ return b
strtok 자체에서
정적 변수
로서 char *str 를 가지고 있기 때문에, 계속 진행하고 싶으면 null을 넣어주면 된다.return 할 것이 없다면 null을 return 한다.
⇒
정적 변수를 사용하고 있기 때문에 thread safe 하지 않다.
- strtok()의 문제
- 내부적으로 static variable을 가지고 있다.
chatper02/wordaveragebad.c
1번 실행 시 여러 줄이 static variable에 저장 된다.
그 이후에 한줄이 나오자마자 2번을 실행하여 static variable에 저장된다.
다시 1번을 실행했을 때 제대로 tokenizing이 되지 않는다.
strtok_r
char* strtok_r(char*s, const char* sep, char **lasts)
- lasts는 전역변수로 쓰이는 부분을 유저가 포인터로서 제공한다.
Use of static variables
strtok의 예시를 봤으니, 이를 방지하기 위해 최대한 정적 변수는 사용하지 않도록 하자
- static 변수가 사용된 파일 내부에서만 access 가능
Process environment
환경변수 정보는
env
cmd 를 사용해 확인 가능하다.- extern 변수 char **environ ⇒ Array<“key=value”>
- char* getenv(const char* name)
- 키 값을 argument로 하여 그에 대한 값을 받을 수 있는 함수
Process termination
- Normal
- main함수에서 return 되는 경우
- main함수에서 끝까지 간 경우 내부적으로 return이 됨
- 프로그램 중간에 exit, _Exit, _exit 함수를 호출한 경우
- exit()
- 마무리 작업에 해당하는 int atexit(void (*func)(void))
- 등록된 함수를 exit 시에 실행할 수 있다.
- Abnormal
- abort 함수
- 종료를 야기하는 signal을 받았을 때
- core dump가 되는 경우