인텔 맥에서 부트캠프 이용 시, 가상화 안되는 문제 해결 – 시스템 무결성 보호 비활성화

지금 뭐 여러모로 돈이 나갈 일이 많아서 오랫동안 써오던 맥북 프로 15인치 2015 버전에다가 부트캠프를 쓰고 있습니다. 그리고 거기에 윈도우를 설치해서 쓰고 있죠. 아직 전 윈도우 개발이 주인 개발이 여럿 있어서요…ㅠㅠ

그런 와중에, 언제부턴가 가상화가 사용할 수 없는 현상이 발생하더군요. 테스트 머신인 가상머신이 있는데…. 그걸 써야 되는데…왜…..ㅠㅠㅠㅠㅠㅠㅠㅠ

안돼....ㅠㅠㅠ
안돼…ㅠㅠ

그러던 와중에, 맥을 쓸 일이 있어서 맥으로 부팅을 해서 맥을 업데이트 하고, 여러모로 작업 후 다시 윈도우로 돌아와도 안되더군요….ㅠㅠ

‘보통 맥을 쓰다가 다시 돌아오면 된다고는 하던데…뭐지….ㅠㅠ’

하다가 발견한 녀석이 있었으니,

바로 시스템 무결성 보호라는 녀석이었습니다. 설명에 대한 것은 링크로 달아뒀습니다.

OS X El Capitan과 iOS 9에 적용된 오래된 기술이더군요. 간단히 말하면, 유닉스 기반의 운영체제이기 때문에 rm -rf / 같은 명령어면 그냥 시스템 싹 다 날려먹고 그럴 수 있으니, 그걸 보호하기 위해 root 권한을 무시하는 루트리스(rootless)라는 녀석을 쓰는 거더군요.

이러면 root 권한이 있더라도 쉽게 변경할 수 없도록 되었고, 그 안에는 애플 소프트웨어 업데이트와도 연계되어 있어서 업데이트가 꾸준히 되어서 항상 시스템 관련된 부분을 보호하는 형태가 되는 것입니다.

써놓은 거 보면 그냥 SELinux라고 보면 이해가 확 될 겁니다만… 그게 뭐야 씹덕아 라고 하면…ㅠㅠ

SELinux 정도는 알아야 합니다! (퍽!)

뭐, 그 덕분에… 솔직히 그냥 아무 잘못없이 시스템 망가지는 건 있을 수 없게 되긴 했습니다. 일반 이용자 입장에서는요…

대신 아마 시스템 유틸리티나 그쪽 관련된 개발 하던 사람들은 그냥 포기하고 나가지 않았을까 싶은데… 생각보다 코드 인젝션 처리해서 유틸리티 만들던 사람들이 많아서요. 그게 불가능한 상황이니 그냥 개발 포기하는 그런 형태가 되었을 꺼 같군요.

근데 이녀석이, CPU의 가상화 기능과도 연관이 있더군요. 하긴, 프로세서 관련된 것들은 보호받는 쪽이 제조사나 os 제공자의 입장에서는 확실히 문제가 없는 거니깐요…

뭐, 좀 잡설이 길었는데, 이거 해제하는 방법은 간단합니다.

초기 부팅할 때, 전원 누른 다음에 Command + R을 누르면 복구 모드에 들어갑니다. 그때에 터미널을 열어서 다음과 같은 명령어를 입력하면 됩니다.

csrutil disable

다시 설정하려면 아래와 같이 입력하면 됩니다.

csrutil enable

어렵지 않죠? 그렇게 해서 다시 가상화가 사용되게 되었습니다. ㅠㅠ

평화로운 개발 환경이 다시 왔습니다. ㅠㅠ

snoopy – 리눅스 커맨드는 history 말고도 별도로 저장해두자. ㅠㅠ

리눅스 커맨드 환경에서 계속 쓰다보면 생각지도 못하게 꼬인 짓들을 할 때가 많다. 그럴 때, 어떤 걸 했는지 헷갈려서 이런 저런 생각 막 하다가 결국 history 쳐보는 게 제일 빠르다는 걸 알긴 한데…..

history도 저장하는 커맨드 수가 한정되어 있다. (근데 이건 당연히 그래야..)

근데 history로도 어떻게 할 수 없는 그런 것들은…. 그냥 커맨드 로거 프로그램 같은 것들로 어떻게 해야 한다. 안그러면 진짜…ㅠㅠ

내 기억력이 그렇게 좋지 않다는 걸 요즘들어서 깨닫고 있는지라..

그래서 snoopy를 써서 요즘은 커맨드 로깅을 좀 하고 있다. 쓰는 법은 github에도 나와있고 해서 뭐 여러모로 편하게 설치해서 쓸 수 있고, 이런 걸로 성능저하 신경쓸 정도도 아니고…

이런 툴들 하나하나 알아둬서 나쁠 건 없으니 알아두자.

빅서의 농간 – 버추얼박스 데리고 버그 보여주지마! ㅠㅠ (You must specify a machine to start, using the command line. 같은 개소리)

버추얼박스 가지고 늘 일상적으로 윈도우 환경에서 여러 작업이랑 테스트(지금 운영체제 개발 삽질을 윈도우에서 하는중..)를 하는데…. 뭣모르고 눌렀는데 저런 메세지가 나와서 황당했다. ㅇㅅㅇ;;;

진심으로 이 소리 하면서 바로 당장 구글링을 하니…

재설치?

ㅈㄹ….

하지마셈.

저런 개소리나 계속 나오는데….. 정작 원인은 다른 데 있었다. 원인 찾아내는 데 있어서는 오랜 시간 걸리지 않았다.

위에 그림에서 처럼 보면 버추얼박스를 독에 미리 올려놓은 녀석(빨간색)과 실행시마다 실행중인 아이콘이 독에 올라오는 녀석(녹색) 아이콘이 나타나는데, 이게 종료가 되어도 저 녹색 아이콘은 계속 남아있는다. 이때, 저 녹색 아이콘으로 실행하면 저 오류가 무조건 뜬다. 반면에 빨간 아이콘으로는 정상 실행된다.

이거 알고나서 얼마나 어이가 없었는지 진짜….

밑에 녹색 아이콘은 전에 실행했던 가상머신 관련 쑛컷이었던 거다. 그러니 저걸로 직접 실행하려 하니 저 아이콘으로 이용했던 실행처리 문제가 있으니 vm을 직접 실행하려면 커멘드로 다음과 같이 처리하라는 그런 건데…

…..ㅅㅂ 무지하게 헷갈리게 보인다….

저거 헷갈릴 거 같으면 독 설정에서 Dock에서 최근 사용한 응용 프로그램 보기에 대해서 선택을 안하면 된다.

….순간 뭣모르고 클릭해서 발견한 오류지만…. 저런 어정쩡한 버그나 보여줄 거 같으면 그냥 넣질 마라….

사람 농간하는 것도 아니고 원…ㅠㅠ

버추얼박스가 Big Sur랑 손잡고 나한테 kernel driver not installed (rc=-1908) 오류를 던져줘서 삽질하게 만들었다.

지금 일땜에 쓰고 있는 맥북이 있는데… Big Sur로 업뎃해줌. 2015년 15인치 맥북프로인데 이건 별 문제 없겠지 싶었고.. 문제 생겨도 영어 되니깐. (응?)

근데 워낙 빅서 저녀석이 문제가 많다는 걸 들어서 전에 작업할 때 쓰던 데이터들 싹 다 백업뜨고 나서 그냥 usb 만들어서 클린 인스톨을 진행했다. 그리고는 하나하나 지금 다시 설치하면서 진행중인데…. 버추얼 박스 깔고, 전에 쓰던 윈도우 가상머신을 불러와서 윈도우에서만 하는 코드 작업을 진행하려 하는데…

하는데….

뭐야 이 오륜…?!!!?!?!

찾아보니깐 맥에서 모르는 프로그램에 대해서 실행 권한 필요한데, 안주면 이게 커널쪽에서 필요한 녀석을 처리하질 못해서 생기는 오류라고 한다. 즉, 시스템 바닥까지 가서 처리해야 하는데(가상머신들 가상화니 당연하겠지…) 그걸 못해서 그런거다.

뭐, 해결 방법이야 간단하다. 이걸 실행하는 곳에서 보안 및 개인 정보 보호에서 허용하면 되는 것. 그냥 제어판 열고 하려는데…

훗, 이제 허용만 하면…

?!?!!!?! (이때 마침 바탕화면도 시간 지나서 랜덤으로 바뀐..)

있어야 할 게 없다?

내 눈이 이상한 게 아니었다…?!

이걸 찾아보니깐 처음에 설치하고 실행했을 때에 떴는데, 그때에 바로 안하면 빅서에서는 두번의 기회따윈 없단다. 30분 뒤에 다시 튀어나온다는 포럼글도 있었는데… 웃기는 소리. 한시간을 기다려도 안나타난다. 뭔 근거로 쓴 포럼글이었던 건지 따지고 싶었다…. (액션이 있어야 뭐낙 반응하는 녀석일텐데 그게 시간지나 나온다는 건 그것도 이상한 거 아냐…?)

이거 해결법은 그냥 버추얼박스 깨끗하게 지우고 다시 설치하는 것이다. ㅠㅠ

그래서 버추얼박스 인스톨러 받아서 그 안에 있는 언인스톨러를 터미널로 실행해서 깔끔하게 지워주고(이거 중요!!!! 버추얼박스는 절대로 그냥 휴지통에 던지면 안된다. 맥용 클리너들 가지고 처리되겠지 생각하기도 뭐하니 언인스톨할 때에는 저 스크립트 꼭 터미널에서 돌려라.) 나서 다시 설치해주고 나니 저렇게 세부사항이라고 뜬다. 저 안에 보면 버추얼박스 허용하도록 하는 체크박스가 있는데, 체크해준다. 그렇게 적용하고 나오면 필요에 따라서는 시스템도 다시 시작한다.

이게 이렇게 바뀌어있는 것이 Big Sur의 특징인듯 한데…..

저거 적용하고 나니 시스템 다시 시작한다고 메시지 떠서 OK 눌렀더니 iTerm2 끄려고 하는 상황이다. 내 경우에 진짜로 시스템 재시작을 진행했었기 때문에 시스템 재시작에 대한 내용을 적은 것….

그렇게 재시작을 하고, 다시 작업하려고 가상머신 불러와서 보니…

나한테 왜그래 대체….ㅠㅠ

근데 알고보니깐 버추얼박스 익스텐션 다시 설치해야 하는데 안해서 그랬던 거다. ㅇㅅㅇ;;;

설치 다 하고 나서 실행 후에 버추얼박스 드라이버 툴도 업뎃…

….이게 빅서가 나한테 던져준 첫번째 오류이긴 한데…. 의외로 골때렸다. ㅇㅅㅇ

운영체제 개발 – GRUB 부팅과정

그럼 실제로 어떻게 되는지를 보여주기 전에, GRUB이 운영체제 커널을 어떻게 부팅하는 지 좀 정리해서 적어보려고 한다. 일단 아주 간단한 과정에 대해서 정리를 해보면 다음과 같다.

  1. 바이오스가 부팅 장치를 찾고 MBR(Master Boot Record)를 읽어온다.
  2. MBR에 GRUB 스테이지 1이 있으며, GRUB 스테이지 1은 스테이지 1.5나 2를 불러온다.
  3. 스테이지 1.5는 MBR 직후 30킬로바이트 영역에 저장되며 이는 스테이지 2를 불러온다.
  4. GRUB 스테이지 2는 부트 메뉴나 명령 프롬프트를 실행하고 보여준다.
  5. 기본 값으로 지정된 커널이나 사용자가 선택한 커널을 메모리에 적재시켜서 커널 엔트리를 실행한다.

이 정보가 GRUB에 대한 위키피디아 내용을 정리한 것이다. MBR에 대한 설명을 간단하게 적으며, MBR은 저장매체의 첫 번째 섹터를 바이오스가 메모리로 읽어들인다. 그 후에 읽어들인 부트 코드로 제어권이 이양되어 코드가 실행되도록 해주는 녀석이다. 자세한 건 운영체제 이론쪽을 보면 될 것이고, 저는 일단 이렇게 간단하게 요약해서 설명합니다.

하드 디스크에는 논리적으로 여러 개의 하드 디스크(파티션이라고 한다)로 나눠서 쓸 수 있다. 최초 컴퓨터가 부팅된 후 MBR에서 부팅 가능한 논리 하드 디스크를 조회하고 부팅 가능한 논리 하드 디스크가 있을 경우에는 해당 논리 디스크의 부트 섹터를 찾아서 메모리로 읽어오고 그 부트섹터의 코드를 실행하게 된다.

이게 윈도우 운영체제인 경우에는 부트섹터는 Ntldr이라는 커널 로더를 메모리에 적재시키며 제어권을 Ntldr에 이양한다. Ntldr은 커널의 코어와 파일시스템 관련 시스템 파일을 로드해서 운영체제를 가동시키며 커널 코어와 파일 시스템은 시스템 자원을 활용하기 위해 여러 커널 모듈을 동적 또는 정적으로 로드시켜 운영체제를 실행하면서 유저 어플리케이션의 요청에 대응하게된다. (이 내용은 윈도우 시스템 관련 내용에 나와있습니다.)

Windows NT 계열의 운영체제 부트 과정을 그림으로 정리…. 이러면 쉬운데…;ㅅ;

부트로더의 존재를 통해서 커널에 어떻게 보여주는지를 정리하기 위해 윈도우를 기준으로 설명을 먼저 해봤습니다.

GRUB을 통한 리눅스를 어떻게 이용한 방식으로 이야기를 하면, GRUB은 부팅 후 Ntldr 까지의 역할을 GRUB이 실행한다고 보면 된다. 운영체제가 설치된 환경이 하드 이스크던, 시디던, USB던 상관 없다. 한편 우리가 제작할 운영체제의 커널은 GRUB 덕분에 Ntoskrnl.exe 같은 커널 프로세스에만 집중하면 된다. 즉, 커널을 메모리에 적재하는 과정을 구현하지 않아도 된단 것이다!!!! 이건 이전 글에도 작성했지만 운영체제 개발에 있어서 큰 부분을 덜어주는 것이다.

중요한 거니 두번 적습니다. \ㅇㅅㅇ/

그럼 GRUB은 어떻게 되는지를 또 그려서 보여줍니다.

바이오스는 부트 섹터에 기록된 boot.img 512바이트를 메모리에 적재한 뒤 제어권을 넘긴다. (이 과정이 스테이지 1) boot.img 파일은 스테이지 2에 해당하는 core.img를 메모리에 적재 해서 해당 코드를 실행하는데 여기서 menu.lst 파일이나 grub.cfg 설정 파일을 참고해서 커널 리스트를 가져온다. 그리고 커널을 선택해서 실행하면 GRUB은 커널을 메모리에 적재시킨 뒤 커널 엔트리 포인트에 제어권을 넘기고 자신의 일부를 종료해서 일을 끝낸다.

GRUB 같은 경우에는 버전이 1.0과 2.0이 있는데, 1.0대 버전에는 위의 설명과 같이 menu.lst 파일을 사용한다. 근데 2.0에서는 그냥 grub.cfg에서 다 처리한다. 근데 일단 그런 버전 차이에 상관없이 적고 싶어서 하다보니 위에처럼 정리가 되더라….

어려워…ㅠㅠ

이렇게 해서 GRUB을 통한 부팅 과정에 대한 설명을 정리했다. 커널 부팅에 대해서 오래된 자료들에 메모리의 어디 주소 영역을 찍어서 부팅하고 뭐하고 하는 그런 거 없어서 편하게 설명할 수 있었지만 이것도 제대로 다 보면 진짜 산더미만한 내용을 보게 될 것이다. 근데 규링이 만들 운영체제에서는 범용으로 부팅되는 과정에 대한 이해만 하고 제대로 넘기면 되니….ㅇㅂㅇ

이제 자세한 걸 만들어가면서 보여주려고 하는데…. 만들어봐야 할 것들이 있어서 그러니 좀 걸릴 것이다. ;ㅅ;

운영체제 개발 – GRUB

GRUB은 GNU 프로젝트의 부트로더이다. 대부분의 운영체제 커널을 불러올 수 있으며 우리가 제작한 커널 또한 호환되게 개발만 하면 호출할 수 있다. 뭐 이름과 유래에 대해서 내용이 있긴 한데 간단하게 그냥 GRUB도 우리가 운영체제를 부팅할 때 하드 디스크, 플로피, USB 등에 구애받지 않고 부팅 가능하도록 도와준다는 것이 제일 중요한 것이다.

GRUB 화면

이걸 제일 많이 볼 때가 있다면… 윈도우랑 리눅스 멀티부팅 해서 쓰는 분들이 제일 많이 볼 듯 하다. 아니면 여러 운영체제를 선택적으로 설치하거나 부팅해서 쓰기 위해서 자작으로 제작할 때에도 이용하기도 한다.

GRUB은 상용적으로 사용되는 운영ㅇ체제를 로드할 수 있을 뿐 아니라 개인이 자체 제작한 운영체제 커널도 규격에 맞추면 로드할 수 있는 능력을 가지고 있다. 그 능력에 대해서 살펴보면

  • 파일 시스템 직접 접근 기능
  • 다양한 실행 파일 형식 지원 기능
  • 비 멀티부팅 운영체제에 대한 지원
  • 메뉴 인터페이스 지원(부팅 화면)
    • 그래픽 메뉴 및 배경 그림도 사용 가능
    • 콘솔 인터페이스 지원
  • 다양한 파일 시스템 지원

이 정도가 된다. 우리가 순간적으로 그냥 보는 기능 안에 이렇게 많은 기능들이 들어있는 것이다. ㅇㅂㅇ

고마운 녀석입니다. ㅇㅅㅇ

직접 자작한 운영체제를 GRUB이랑 연동하였다면, GRUB은 직접 제작한 커널 파일이 유효한 커널인지 확인한 후 커널을 메모리로 적재한다. 또한 하드웨어 제반사항을 초기화한 후 커널 엔트리를 호출해서 제어권을 커널 코드로 이양할 것이다. 이 과정에서 GRUB은 커널에 하드웨어 초기화 정보를 넘겨준다. 이 과정을 아래와 같이 보면 이해가 쉬울 것이다. 조직도와 화살표 방향에 주목해서 글을 읽어보면 된다.

자바 가상 머신과 유사한 느낌으로 그려봤다. 근데 이렇게 보면 관계도 이해가 좀 더 쉽게 될 것이다.

그리고 당연한 이야기지만 GRUB을 통해서 커널을 메모리에 적재시켜서 실행시키고 싶다면 GRUB에서 요구하는 규격대로 만들어 줘야 한다. 이 내용을 작성하기 전에 GRUB 부팅에 대한 자세한 내용을 다음 글에서 작성하도록 하겠다.

운영체제 개발 – 부트로더 선택(?!)

운영체제는 일반 응용 프로그램과 시작점이 다른 것이 있다면, 메모리에 올리는 녀석이 있냐 없냐의 차이로 보면 쉽게 이해된다. 리눅스의 응용 프로그램이던 윈도우의 응용 프로그램이던, 실행 명령을 입력받는 운영체제가 존재하고, 실행을 하면 운영체제가 해당 프로그램의 실행 정보를 메모리에 올린다. 가상 머신을 쓰는 환경들은 더더욱 쉽다. 자바, 안드로이드를 예로 보면, 자바 프로그램은 바이트코드화 되어서 자바 가상 머신(JVM)에 올라간다.

자바 떡칠인 모 반도에 맞는 설명… (좋은 소리 아니다.)

근데 운영체제는? 운영체제를 실행할 무언가는? 운영체제를 메모리에 올릴 무언가는? 그런 거 없다. 하나하나 직접 올려야 한다. 그걸 위해서 개발해야 하는 것이 바로 부트로더이다.

부트로더는 이런 일들을 주로 한다.

  • 커널(및 모든 커널 부트스트랩 필요)을 메모리로 가져오기
  • 커널에 올바르게 작동하는 데 필요한 정보 제공
  • 커널이 좋게 동작하는 환경으로 전환
  • 커널로 제어 전송

뭐, 이런 기능을 제공하기 위해서는 운영체제 개발 책들에서는 운영체제 로드를 위한 부트로더 개발 작업을 어셈블러로 작성하고 그걸 설명해주는데도 여러모로 시간을 잡아먹는다.

그러면 좋은 것이 컴퓨터 리소스를 쓰기 위해서 16비트 모드, 32비트 모드를 직접 작업해서 넘어오는 걸 해보게 된다. 20비트 세그먼트 가지고 1MB 메모리 주소에 접근하고 그걸 가지고 시작하는 CPU를 제어할 수 있는 하드웨어 종속적인 코드 짜고, 이걸 32비트, 64비트 모드로 점점 넘어오는 작업을 해야 하는 그런 일들을 하고 해서 이제 화면 접근 작업을 통해서 “Hello World!” 찍어내는데만 엄청나게 시간 소요를 하게 되어주는….

이렇게 생각하고 포기해서 1장이나 2장에서 멈춘 당신, 뜨끔합니까?

저러다가 요즘 윈도우 10에서는 저 영역을 건드리지 못하게 막아내는 거 땜에 오류 팍팍 떠서 걍 멘붕하고 포기하는 당신…

부트로더는 개발해서 나쁠 건 없다. 근데 수많은 운영체제 제작 코드들이 자신만의 코드를 이용해서 개발하고 한다면… 운용하기 힘들어진다. 게다가 요즘처럼 다른 플랫폼까지 이용해야 하는 경우라면…? 더더욱 개발 힘들어진다. 아키텍쳐마다 비트 모드에 접근하고 사용하는 방식 자체가 다르다.

해봐서 좋은 경험이 된다. 근데 커널 자체에 집중해서 개발하는 것을 위해서 요즘은 그냥 일련의 커널 로딩 과정을 간편하게 해주기 위해 부트로더를 이용한다. 흔히 대표적으로 이용하는 것이 바로 GRUB인데, 이녀석을 이용하여 개발하는 걸 보여주려고 한다.

부트로더 직접 개발하면 진짜 피곤하다.

부트로더 직접 개발하는 거 어떤지 알고 싶으면 한승훈 저자님의 “64bit 멀티코어 OS 원리와 구조” 이 책 보고 직접 스터디 해봐도 된다. 근데 아마 도서관에 1권만 스터디한다면서 이미 다 빌려가고 없거나 할 수도 있다. ㅇㅅㅇ;;

운영체제 이론 – 스택

스택은 FILO(First in last out이라고도 함) 형태의 자료구조로, 마지막에 입력된 자료가 먼저 나오는 형태이다. 반대로 FIFO(First in first out이라고도 함) 형태의 자료구조는 큐가 있는데, 이건 먼저 입력된 자료가 먼저 출력되는 형태를 가진다.

스택의 구조

이런 자료구조에서 보던 녀석을 왜 운영체제 이론 설명하는데 굳이 끼우냐면… 여러모로 볼 녀석이다. 진짜로….

이런 반응 나올 정도로 지겹게 볼 것이다. 그러니 읽는 분이 자료구조 개판으로 공부했다면 운영체제 공부는 엄청나게 괴로운 공부가 될 것이다. 진심어린 충고다.

콜스택은 특정 함수가 호출될 때에는 지역변수나 함수 파라미터가 특정 공간에 저장되는데 이 공간을 말한다. x86 아키테겨에서는 스택에 변수나 파라미터가 저장될 때 주소 공간이 줄어드는 방향으로 데이터가 저장된다. 이걸 설명하기 위해 다음과 같은 함수가 있다고 보자.

example code

이런 함수를 호출할 때의 콜 스택에 대해서 손으로 직접 그려보았다.

콜스택 상황

함수의 수행이 끝날 경우에는 이 함수를 호출한 실행 코드의 위치로 돌아갈 필요가 있다. 이때 필요한 값은 해당 함수의 복귀 주소와 EBP값이 필요하다. 이런 값들은 함수를 호출할 때마다 자동으로 생성되는데, 이러한 값들의 모음을 스택 프레임(Stack Frame)이라고 한다. 콜스택의 실제 상황을 직접 그린 이유는 이걸 가지고 스택 프레임에 들어갈 포인터 녀석들을 설명하려 한다.

  • ESP(Extended Stack Pointer) 레지스터
    스택의 밑바닥을 가리키는 포인터다. 최초 함수가 호출될 때 EBP와 ESP의 값은 같은 값이며, 로컬변수가 선언되면 ESP는 낮은 값으로 증가한다. (x86 아키텍쳐를 기준으로 설명한다.) ESP는 다음 데이터를 push할 위치가 아니라 pop했을 때 뽑아낼 데이터의 위치를 가리킨다. (이거 헷갈린다.)
  • EBP(Extended Base Pointer) 레지스터
    스택 프레임의 시작 주소를 가리킨다. 새로운 함수가 호출되면 파라미터와 스택 프레임 값이 스택에 채워지는데 EBP 레지스터값은 바로 그 다음 주소를 가리킨다. 즉, 호출된 함수가 로컬 변수를 선언하기 직전의 시작점이 되며 EBP값은 함수 실행 동안 변하지 않으므로(다른 함수를 호출하지 않는 이상 안변함) 파라미터나 로컬 변수를 참조할 수 있는 기준점이 되어준다. EBP 레지스터는 현재 실행중인 함수가 종료되어 리턴되면 이 함수를 호출한 함수의 EBP값으로 변경된다. (스택 프레임에 저장된 EBP값)

프로세스를 적고 정리하면서 거의 항상 꼭 보는 스택과 실제 실행과 관련된 내용에 대해서 정리를 해봤다. 어려운 내용은 아니지만 머릿속엔 있는 게 좋다.

운영체제 이론 – 스레드

프로세스가 프로그램의 주체라면 스레드는 프로세스의 실제 실행 단위이다. 프로세스는 여러개의 스레드를 담고 있으며 커널은 프로세스가 담고 있는 스레드를 관리해서 프로세스의 동작을 조정한다.

가상주소 공간을 공유하고 있는 스레드의 모습을 그려봄…

위에 그린 그림은 스레드가 프로세스의 자원을 공유하는 것을 표현했다. 코드 영역을 동시에 접근하는 것은 문제가 되지 않지만 쓰기 가능한 데이터 영역에 동시에 접근할 경우에는 데이터 무결성 장애를 일으킬 수 있다. 스택은 스레드 고유의 자원이며, 일반적인 경우에는 다른스레드의 간섭을 받지 않는다. 따라서 동기화 걱정 없이 마음대로 이용할 수 있다.

스레드 경합은 자료구조나 데이터에 복수의 스레드가 접근하면 문제가 발생할 수 있는 것을 나타낸다. 이런 상태를 경쟁 상태, 경합 상태라고 하는데, 의외로 쉽게 일어난다. 아래 예시 코드처럼 짜면 거의 반반의 확률로 발생한다.

스레드 경합 관련 예시 코드. 잠안와서 작성한…(?!)

단위 연산이 아닌 작업이 들어있기 때문에 실제로 출력되는 것은 1,1 혹은 1,2가 된다. 이런 문제를 해결하기 위해 동기화를 적용해야 한다. 그리고 그 동기화를 적용하기 위해서 동기화 객체를 사용하여 구현을 하는데 그것들은 다음과 같다.

  • 크리티컬 섹션
  • 뮤텍스
  • 세마포어
  • 스핀 락

이것들에 대한 건 운영체제 책에서 더 자세히 볼 수 있으니 자세한 복습은 운영체제 내용을 참조한다.

TCB(Thread Control Block)은 프로세스에 PCB가 존재하듯, 스레드에도 스레드 정보를 관리하기 위한 블록이 존재한다. TCB는 커널에서 스레드를 관리하기 위해 필요로 하는 정보를 담고 있는 구조체이고, 그 구조는 아래 그림과 같다.

PCB와 TCB의 관계를 보여줌..

그리고 다음과 같은 정보들을 가지고 있다.

  • 스레드 식별자: 고유 아이디는 스레드마다 새롭게 만듬.
  • 스택 포인터: 스레드의 스택을 가리키는 포인터
  • 프로그램 카운터: 스레드가 현재 실행 중인 명령어 주소
  • 스레드 상태: 실행, 준비, 대기, 시작, 완료
  • 레지스터 값들
  • 스레드를 담고 있는 프로세스의 PCB의 포인터

스레드에 대해서는 이정도만 정리해도 충분할 것이다. 간단한 거라서 밤 늦게 정리를 해보고 자려고 한다. ㅇㅅㅇ

그럼 전 일단 자러….

운영체제 이론 – 프로세스

본인이 뭐 어느 정도의 주니어 혹은 시니어 프로그래머 이상급 된다면 사실 이런 내용은 이미 다 알고있는 것일껍니다. 그런 게 아니라서 뭐 간단한 정리를 보고 싶으시다면 이 글이 도움이 되었으면 좋겠군요.

쓸데없이 서론이 길어졌다. ㅠㅠ 오랜만의 블로그라….

그럼 시작해보겠습니다. 주요 용어에 대해 볼드체를 처리했고, 그에 대한 내용을 풀어가는 형태로 글을 써봤습니다.

프로세스는 컴퓨터에서 실행되는 프로그램을 의미합니다. 일반적으로 프로그램은 하드디스크에 저장되어 있는데 프로그램을 실행하면 운영체제의 로더에 의해 메모리로 적재됩니다. 이렇게 메모리에 적재되어 실행되는 프로그램을 프로세스라고 합니다.

싱글테스킹은 운영체제에서 하나의 프로세스가 독점적으로 시스템 자원을 사용하는 것이고, 멀티태스킹은 운영체제에서 하나의 프로세스가 독점적으로 시스템 자원을 사용하는 것을 막기 위해 프로세스에 자원 사용 시간을 적절히 배분하여 이용합니다.

프로세스가 메모리에 로드되었을 때의 대략적인 녀석은 다음과 같습니다.

이미 운영체제 시간이나 프로그래밍 시간에 다 배운 녀석이죠…ㅇㅅㅇ;;

프로세스 컨텍스트(Process Context)는 프로세스 문맥이라도고 하며, 운영체제가 관리하는 프로세스 정보라고 보면 된다. 주로 다음과 같은 녀석들이다.

  • CPU 상태: CPU 레지스터, 현재 프로세스가 수행되고 있는 위치 등 (ex: instruction pointer)
  • PCB(Process Control Block): 커널이 관리하는 프로세스 정보 구조체
  • 가상주소공간 데이터: 코드 데이터, 스택, 힙

위와 같은 내용이 프로세스 컨텍스트를 의미한다. 멀티테스킹 운영체제에서 실행되는 프로세스는 위와 같은 정보를 토대로 커널이 프로세스를 실행하고 있다는 것을 의미하며 컨텍스트 스위칭은 지금 실행하고 있는 프로세스의 실행을 멈추고 다른 프로세스의 컨텍스트를 가져와 실행함을 의미한다.

PCB(Process Control Block)는 운영체제 커널의 자료구조로써 프로세스를 표현하기 위해 사용된다. 커널은 이 자료구조를 사용해서 프로세스를 관리한다. PCB는 프로세스가 생성될 때 같이 생성되며 프로세스 고유의 정보를 포함한다. Win32 프로세스에서의 PCB 구조는 교과서 같은 곳에 많이 나와있는데, 외우고 있는 걸 노트에 그려봤다.

(이런 걸 외우고 있으니 머리가 이상해지는 겁니다, 규링!) (응?)

PCB는 체인으로 다른 PCB랑 연결되어 있다. (우리는 이런 걸 흔히 링크드 리스트라고 한다.) PCB에 포함된 정보가 글씨가 좀 지저분한데… 정리하면 다음과 같이 볼 수 있다.

  • OS가 관리상 사용하는 정보: vm로세스 상태, 프로세스 ID, 스케쥴링 정보, 우선순위 등
  • CPU 수행 관련 하드웨어 값: 프로그램 카운터, 레지스터 등
  • 메모리 정보: 코드, 데이터, 스택의 위치 정보
  • 파일 정보: 열어둔 파일 정보(핸들)

프로세스 상태는 컨텍스트 스위칭에 의해서 프로세스는 실행 상태에 놓일 수도 있고, 정지 상태에 놓일 수도 있는데, 이를 운영체제 교과서에서 이렇게 표현한다.

프로세스 상태 다이어그램

프로세스 상태에 대해서는 아래와 같이 정리해보자.

  • 실행
    프로세스가 CPU를 점유하고 있는 상태.
  • 대기
    프로세스가 CPU를 점유하기 위해 기다리고 있는 상태. 메모리에는 이미 올라와 있을 뿐 아니라 CPU 동작을 위한 모든 조건들을 만족한 상태이다.
  • 블록(wait, sleep)
    당작은 작업이 수행될 수 없는 상태. sleep 함수나 동기화를 위해 대기해야 할 경우에 프로세스는 블록 상태가 된다.
  • 정지
    스케쥴러나 인터럽트 때문에 비활성화 된 상태. 외부에서 다시 재개시켜야 활성화 상태로 변경된다.

컨텍스트 스위칭(Context Switching)은 CPU가 한 프로세스에서 PCB 정보로 스위칭 되는 과정을 의미한다. 그 과정도 일단 손으로 그려보았다.

프로세스 컨텍스트의 스위칭 과정. 스위칭 과정은 엄청 짧으나 v 체크 되어있는 만큼의 작업을 하고 있는 것이다.

위에 그림에서 보면 두 프로세스가 실행과 중지를 반복하여 컨텍스트 스위칭 되는 과정을 보여준다. 프로세스 1이 실행중에 있다가 컨텍스트 스위칭이 발생할 경우, 운영체제는 프로세스 1의 PCB를 저장한 뒤 프로세스 2의 PCB를 복원시킨다. 그러고 나서 프로세스 2의 실행이 진행된다. 그러다가 다시 프로세스 1을 실행할 때에는 프로세스 2 PCB를 저장하고 프로세스 1의 PCB를 불러온다. 이런 과정을 반복하면서 두 프로세스는 실행과 중지를 반복한다. 컨텍스트 스위칭은 시스템 콜이나 외부 인터럽트에 의해서 주로 발생한다.

프로세스에 관해서 간단하게 정리해보면 이 정도가 될 듯 하다. 너무 간단하게 적긴 했다만… 자세한 걸 보고 싶으면 운영체제 책을 가지고 제대로 공부하면 된다.

다음 글에서는 스레드에 대해서 지금처럼 간단하게 작성하려 한다.

p.s. 너무 오랜만에 글을 썼는데… 앞으로 자주 쓰려고 합니다. ;ㅅ;