본문 바로가기

카테고리 없음

Python Virtual machine(PVM)-0

python 모듈 test.py가 주어지면 이 모듈은 $python test.py와 같은 python 인터프리터 프로그램에 인수로 전달하여 명령줄에서 실행할 수 있다. 이것은 파이썬 실행 파일을 호출하는 방법 중 하나일 뿐이다. 대화형 인터프리터를 시작하고 코드로 문자열을 실행할 수 있지만 이러한 다른 실행 방법은 우리에게 관심이 없다. 모듈이 명령줄에서 실행 파일에 인수로 전달될 때 그림 2.1은 제공된 모듈의 실제 실행과 관련된 다양한 활동의 흐름을 가장 잘 보여준다.

 

 

모듈 이름을 인수로 사용하여 파이썬 실행 파일이 호출되면 실행파일이 실행되는 플랫폼에 따라 표준 프로세스 초기화가 시작된다. C 런타임은 라이브러리가 로드되면 모든 초기화 마법을 수행한다. 환경 변수가 확인되고 설정되면 파이썬 실행파일의 메인 방법은 c 프로그램처럼 실행된다. 파이썬 실행파일의 매인 프로그램은 ./Programs/python.c 파일에 위치되며, 이것은 모듈에 전달되는 커맨드 라인 인수 프로그램의 복사본과 같은 일부 초기화를 다룬다. 메인 함수는 커맨드라인 인수 파싱과 프로그램 flags 설정, 환경 변수 읽음, running hooks, hash randomization 수행 등을 하는 인터프리터 초기화 프로세스를 다루는 ./Modules/main.c 에 위치한 Py_Main 함수를 호출한다. 초기화 프로세스의 일부로 pylifecycle.c로부터 Py_Initialize가 호출된다; 이것은 2개의 매주 중요한 데이터 구조인 인터프리터의 초기화와 쓰레드 상태 데이터 구조를 다룬다.

 

인터프리터와 쓰레드 상태에 대한 데이터 구조를 보면 그 데이터 구조의 함수에 일부 context를 제공한다. 인터프리터와 스레드 상태는 그저 프로그램의 실행에 요구돠는 정보를 유지하는 field의 포인터를 가지는 구조이다. 인터프리터 상태 typedef(c 용어의 타입 정의라고 가정한다) 

 

 

오랜동안 충분히 파이썬을 사용한 누구나 이 구조에서 언급하는 fields(sysdict, builtins, codec)*의 일부를 인식할 수 있다.

 

1. *next fileds는 다중 파이썬 인터프리터는 같은 프로세스 내에서 존재할 수 있기에,  또 다른 인터프리터 인스턴스의 참조이다.

2. *tstate_head fileds는 실행의 메인 스레드를 가리킨다. 파이썬 프로그램은 멀티스레드인 경우라면, 인터프리터는 프로그램에 의해 만들어진 모든 스레드로 공유될 수 있다. ( 스레드 상태의 구조는 곧 다뤄진다)

3. modules, modules_by_index, sysdict, builtins 그리고 importlib은 자명하다. 이것들은 모두 PyObjects의 인스턴스로 정의되었다. PyObject 인스턴스는 virtual machine 세계의 모든 파이썬 오브젝트에 대한 root 타입이다.

4. codec* 위치와 인코딩 로딩을 도와주는 정보 유지와 관련된 fileds이다. 이것은 디코딩 바이트에 대해 매우 중요하다.

 

프로그램의 실행은 반드시 스레드 내에서 일어난다. 스레드 상태 구조는 파이썬 일부 코드 오브젝트를 실행하는 스레드에 필요한 모든 정보를 포함한다.

 

 

모든 초기화가 완료되면, Py_Main 함수는 run_file 함수를 호출하고 main.c 모듈에 위치한다.

다음과 같은 일련의 함수를 호출:

PyRun_AnyFileExFlags → PyRun_SimpleFileExFlags → PyRun_FileExFlags -> PyParser_ASTFromFileObject

위 일련의 과정들은 PyParser_ASTFromFileObject 함수에 구성된다.

PyRun_SimpleFileExFlags 함수 호출은 파일 내용이 실행될 __main__ 네임스페이스를 만든다. 또한 파일의 pyc 버전이 존재한다면 확인한다. pyc 파일은 실행 중인 파일의 컴파일된 버전을 포함하는 파일이다. 파일이 pyc 버전을 가지는 경우라면, 바이너리로 읽고 실행하려고 시도할 것이다. 이 경우 pyc 파일이 없으므로 PyRunFileFlags 등이 호출될 것이다.

PyParser_ASTFromFileObject 함수는 모듈 내용을 읽고 파스 트리를 빌드하 PyParser_ParseFileObject를 호출한다. 만들어진 파스 트리는 파스트리로부터 AST를 만드는 PyParser_ASTFromNodeObject에게 전달된다.

 

생성된 ASTrun_mod 함수에 전달된다. 이 함수는 AST로 부터 코드 오브젝트를 만드는 PyAST_CompileObject 함수를 호출한다. PyAST_CompileObject 호출 동안 생성된 바이트코드는 단순한 peephole 최적화를 통해 전달된다. 단순한 peephole 최적화는 코드 오브젝트가 만들어지기 전에 생성된 바이트코드의 low hanging 최적화를 수행한다. run_mod 함수는 코드 오브젝트의 ceval.c 파일로 부터 PyEval_EvalCode를 호출한다. 코드 오브젝트는 각 함수의 대부분에 하나의 형식이나 다른 형태로 인수로서 전달된다. _PyEval_EvalFramEX는 코드 오브젝트의 실행을 다루는 실제 인터프리터 루프이다. 그러나 인수로서 코드 오브젝트를 호출하는게 아니라 코드 오브젝트를 참조하는 fields를 가지는 frame 오브젝트가 인수 중 하나이다. 이 frame 오브젝트는 오브젝트 코드의 실행에 대한 문맥을 제공한다. 매우 단순화된 버전은 인터프리터 루프가 명령어의 배열로부터 명령 카운터가 가리키는 다음 명령어를 읽는다. 명령어를 실행 - 배열에서 실행될 명령어가 더 이상 없거나 루프에서 발생하는 멈추는 예외가 있을 때까지 프로세스 안의 value 스택에서 오브젝트를 더하거나 제거한다.  

 

파이썬은 실제 코드 오브젝트를 탐사하는데 사용하는 함수의 집합을 제공한다. 예를 들어 단순한 프로그램은 코드 오브젝트에서 컴파일될 수 있고 리스트 2.3에서 보여지는 것처럼 파이썬 가상 머신에 의해 실행되는 opcode를 얻기 위해 분해될 수 있다.

 

 

./Include/opcodes.h 헤더 파일은 파이썬 가상 머신에 대한 모든 명령어 혹은 opcode의 완전한 리스스를 포함한다. opcode은 개념적으로 꽤 간단하다. Listing 2.3에서 LOAD_FAST는 그 인수의 값을 evaluation(value) 스택에 로드한다(이 경우 x를 로드). 파이선 가상 머신은 스택 기반 가상 머신으로 이 의미는 opcode에 의한 평가 값은 스택으로부터 얻어지고 평가의 결과는 다른 opcode에서 나중에 사용할 수 있도 스택에 다시 놓여진다. BINARAY_MULTIPLY opcode는 value 스택으로부터 2개의 아이템을 팝하고 두 개의 값의 이항 곱 연산을 수행한다. 그리고 다시 value 스택에 이항 곱 연산의 결과를 배치한다. RETURN VALUE 연산은 스택으로부터 값을 팝하고 객체에 대한 리턴 값을 이 값으로 설정하고 인터프리터 루프를 중단한다.

 

모든 명령어가 실행된 다음에, Py_Main 함수는 그 실행을 지속하지만 이번에는 프로세스 정리를 시작한다. 인터프리터 시작 동안 초기화를 실행하기 위해 Py_Initialize가 호출된 것처럼, Py_FinalizeEx가 일부 작업 정리를 위해 호출된다. 이 작업 정리 프로세스는 스레드가 종료될 때까지 대기하고 종료 hook를 호출하고, 여전히 사용중인 인터프리터 메모리 할당을 해제하는 작업이 포함한다.

 

 

 

 

1. Where are the values such as that loaded by the LOAD_FAST instruction gotten from ?

2. Where do arguments that are used as part of instructions come from ?

3. How are nested function and method calls managed ?

4. How does the interpreter loop handle exceptions ?