Safari를 열고 싶으면 하단의 독바에 있는 사파리를 클릭하면 되고,

메모장을 열고 싶으면 메모장을 클릭하면 된다. 

하지만 터미널에서 작업을 하고 있는 개발자라면 마우스를 따로 사용하기가 귀찮을수 있는데,

리눅스 우분투와는 달리 맥 터미널에서는 응용 프로그램을 실행하려면 다음과 같이 복잡한 과정을 거쳐야 한다.


$ open /Applications/Safari.app/


리눅스 우분투에서는 safari라고 하는 응용 프로그램이 있으면 단순히 쉘 커맨드에 'safari'라고 입력해주면 된다.



리눅스 우분투에서 손쉽게 하던 응용프로그램 실행을 맥에서도 하고 싶다면? 어떻게 해야 할까?


리눅스 우분투에서는 어떻게 저렇게 간단하게 프로그램 실행이 가능했는지부터 생각해보아야 할 것이다.

기본적으로 우분투에서의 터미널은 bash 쉘을 사용하는데, bash쉘이 기본적으로 사용하는 명령어들이

/usr/bin/ 폴더에 존재하게 된다. 즉, /usr/bin 폴더 아래에 있는 모든 실행 파일들 (혹은 쉘 스크립트)은

어느 경로에서든지 바로 사용이 가능한 것이다.


이 원리를 알았으니 어떻게 하면 될까? 

맥에서는 응용 프로그램이 ~~~.app 과 같은 확장자를 가지고 있고, /Applications/ 폴더 아래에 있는데, 이들을 함부로 

위에 있는 경로로 옮기면 될까? 이후 앱 실행에 있어서 문제가 발생할 여지도 있고, 

spotlight 서비스에서 응용 프로그램을 찾는 등의 과정을 굳이 방해하고 싶지 않을 것이다.


그렇다면 해결책은 무엇인가?

shell script를 만들어 위에 있는 명령어를 손쉽게 해결해주도록 만들 것이다.

shell script를 다음과 같이 작성해보자. 파일 이름은 'safari'라고 작성하자.

#!/bin/bash

open /Applications/Safari.app/

만든 쉘 스크립트 파일을 '실행'하려면 실행권한을 주어야 한다. 

아래와 같은 명령어를 통해 실행권한을 주도록 한다.

$ sudo chmod +x safari

그 후에 만들어진 쉘 스크립트 파일을 /usr/bin폴더로 옮기도록 하자.

$ sudo mv safari /usr/bin/


이로써 작업은 끝났다! 앞으로 여러분은 어디서든지 'safari'라고 명령어를 치기만 하면 바로 safari앱을 실행할 수 있다.

(프로젝트 개요)


현재 pintos는 시스템 콜 핸들러가 제대로 구현되어 있지 않다. 시스템 콜이 발생해도 system call!! 이라는 출력문 하나만 달랑 뿌리고, 그대로 스레드가 종료된다.(thread_exit()) 따라서, 이번 프로젝트 2는 핀토스의 시스템 콜 핸들러 함수를 구현하고, 간단한 시스템 콜(halt, exit, create, remove)를 구현해본다.



| 시스템 콜?

사용자 모드 수준에서의 프로그램이 커널 기능을 사용할 수 있도록 해주는 인터페이스.

시스템 콜의 기능들은 커널 모드에서 실행되고, 처리 후 사용자 모드로 복귀됨.



(프로젝트 해결 방법 개요)


우선, 2가지 함수를 구현해야 함. 


1. 특정 포인터(주소값)가 유저 영역에 존재하는지 확인하는 함수.

void check_address(void * address);


2. 유저 스택에 들어 있는 인자들의 주소를 저장하는 함수.

void get_argument(void *esp, void *arg, int count);


1. 유저 영역은 0x08048000 에서 0xc0000000 사이에 (경계값 제외) 존재하는 주소값이면 유저 영역인 것이다. check_address를 구현할 때 이 바깥 범위의 값이면 exit (-1)을 호출해 프로그램을 종료시켜야 한다. (유저 영역이 아닌 커널 영역을 건드리는 프로그램은 프로그램을 종료시키는 것이 적절한 운영체제의 반응이다.)


2. get_argument 의 함수는 esp 인자에서 인자들을 4바이트씩 긁어온 후, arg라는 변수에 저장하는 행동을 한다. 참고로, arg에 저장하는 값들은 인자의 주소값이다. (이 부분을 정확히 이해하라.) arg에 주소값을 저장하지 않고 인자값 자체를 저장하면 안된다. 왜냐하면, 시스템 콜의 인자들이 전부 4바이트로 표현할 수 있다는 보장이 없기 떄문이다. (가령 문자열이 전달되어 온다면 어떻게 할것인가..? 해결방법은 문자열 자체의 주소값을 저장하는 것이다.)


따라서, arg에는 반드시 인자의 주.소.값이 저장되어야 함을 무한 강조!!

f->esp 의 첫 4바이트는 시스템 콜 넘버이므로, 그 다음 4바이트씩 count 횟수만큼 긁어와서 arg에 저장하면 된다.



============

시스템 콜 핸들러 함수

static void syscall_handler (struct intr_frame *f UNUSED) { /* 유저 스택에 저장되어 있는 시스템 콜 넘버를 이용해 시스템 콜 핸들러 구현 */ /* 스택 포인터가 유저 영역인지 확인 */ /* 저장된 인자 값이 포인터일 경우 유저 영역의 주소인지 확인*/

}

f->esp (유저 스택)에는 4바이트씩 인자들이 저장되어 있다. f->esp의 첫 번째 4바이트에는 시스템 콜의 넘버가 저장되어 있고, 그 뒤로 4바이트씩 시스템 콜의 인자가 저장되어 있다.


예를 들어, create 시스템 콜은 인자로 파일 이름과 파일의 첫 크기를 인자로 받는다. 그러면, create 시스템 콜이 발생할 때 유저 스택 포인터는

다음과 같은 구조를 띈다.


f->esp

(4바이트) 4

(4바이트) 0x100

(4바이트) 0x104

....

0x100은 파일 이름 문자열의 주소값이고, 0x104는 파일의 첫 크기의 주소값이다. 물론, 파일의 첫 크기값은 정수일테지만, 정수값 자체를 유저 스택에 저장하지 않는다. 유저 스택에는 '주소값'이 저장되어 있음을 무조건 기억하라. 따라서, 나중에 시스템 콜 핸들러에서 create 시스템 콜을 처리할 때 다음과 같이 처리해야 한다.


    case SYS_CREATE:/* 4 */
      f->eax = create((char *)*(int *)arg[0], (unsigned)*(int *)arg[1]);
    break;

현재 arg에는 인자들의 '주소값'이 저장되어 있다. 즉, arg[0], arg[1],...등은 전부 '주소값'이다. 따라서, *arg[0]의 의미는, arg의 첫 번째 값의 내용물을 찾아오는 것이다. 따라서, 첫 번째 인자의 주소값의 내용물을 찾아와서 그들을 시스템 콜 함수의 인자로 넘겨줘야 하는 것이다. 


pintos 는 현재, 프로그램이름과 인자를 구분하는 규칙이 존재하지 않는다.

ls -l // 핀토스는 'ls -l' 을 하나의 프로그램 명으로 인식하고 있다. 


첫 번째 과제의 목표는 프로그램의 이름과 인자를 구분하여 스택에 저장, 인자를 응용 프로그램에 전달하는 기능을 구현하는 것이다.


이를 위해 소규모 목표를 잡아 보면,

1. 응용프로그램의 실행 흐름을 추적하여 파싱 시점을 알아내야 한다.

2. 파싱을 어떻게 할 것인가?

3. 파싱한 인자들을 전달하는 인자 전달 메커니즘을 이해하고, 인자를 전달하는 인터페이스를 구현한다.




| 1. 응용프로그램의 실행 흐름을 추적하여 파싱 시점을 알아내야 한다.


< pintos 의 응용 프로그램 실행 메커니즘 >

운영체제의 main 함수에서 run_action 함수 호출

-> run_task 함수 호출

-> process_execute 함수 호출

-> thread_create 함수와 start_process 함수 호출

...


이러한 과정을 지나치며 응용 프로그램이 시작된다. 


run_task에서 start_process 함수까지 진행이 되는 동안 함수들의 인자는 char ** argv 가 전달되는데, 이는 커맨드 라인 전체를 포함하고 있다. (예를 들어, 'ls -a'를 pintos 시작 시 받은 경우, argv 를 'ls -a' 전체를 받는 것이다.)

따라서, 이를 파싱해야 할 시점을 찾을 수 있다. 바로, start_process 에서 load 함수를 호출하는 그 때, 문자열을 파싱하여 넘겨주어야 한다.





| 2. 파싱을 어떻게 할 것인가?


start_process 함수에서 load 함수를 호출하기 전에 문자열을 파싱해야 한다. 문자열 파싱 함수로는 strtok_r 을 쓰도록 하자.


char *strtok_r (char *s, const char *delimiters, char **save_ptr) /* string.h */ 


example)

char s[] = "String to tokenize.";
char *token, *save_ptr;
for (token = strtok_r (s, " ", &save_ptr); token != NULL;

     token = strtok_r (NULL, " ", &save_ptr))

     printf ("'%s'\n", token); 






| 3. 파싱한 인자들을 전달하는 인자 전달 메커니즘을 이해하고, 인자를 전달하는 인터페이스를 구현한다.


유저 스택에 파싱된 토큰들을 저장하는 함수를 구현해야 한다.


void argument_stack(char **parse ,int count ,void **esp);

parse: 프로그램 이름과 인자가 저장되어 있는 메모리 공간

count: 인자의 개수

esp: 스택 포인터를 가리키는 주소


이 함수는 parse에서 커맨드 라인을 읽어와 파싱하고, esp에 알맞게 저장하는 함수이다. 


이 함수가 start_process 함수에서 호출될 때는 다음과 같은 꼴로 사용해야 한다.


argument_stack(&parse, count, &if_.esp);


즉, 첫 번째와 세 번째 인자는 주소값으로 인자를 전달한다는 가정 하에 다음 설명을 진행하겠다.


pintos에서는 스택이 아래로 자라는 경향이 있다.

제일 처음에는 *esp 값이 0xc0000000(유저스택의 최고점, 3GB)일 것이다.


*esp -= 4; 


이렇게 하면, esp 값은 0xbffffffc가 될 것이다. 이 때,


**esp = 10;  // 정확하게는, *(int *)(* esp) = 10; (캐스팅을 제대로 해야 한다.)


이렇게 되면, 0xbffffffc 라는 유저 스택 위치에서, 10이라는 값이 저장되는 것이다. 

이러한 논리를 이용해서 프로젝트를 진행할 수 있을 것이다. 




| 결과 확인


첫 번째 프로젝트를 제대로 완료했다면, 다음 명령어를 입력해보자.


$ pintos -v -- run 'echo x y z'


참고로, 결과를 확인하기 전에는 반드시 핀토스 파일 시스템 안에 'echo'라는 응용 프로그램이 들어가 있어야 한다.


'핀토스' 운영체제의 몇몇 기능들을 구현해나가며 개선하는 것이 목표입니다.


프로젝트 개요는 다음과 같습니다.


1. Command Line Parsing

2. System Call

3. Hierarchical Process Structure

4. File Description

5. Alarm System Call

6. Priority Scheduling and Synchronization

7. Priority Inversion Problem

8. Virtual Memory

9. Memory Mapped File

10. Swapping

11. Buffer Cache

12. Extensible File

13. Subdirectory





윈도우의 Eclipse와 Mac에서의 Eclipse는 큰 차이가 나는 것은 아닙니다. 하지만, 메뉴 바의 구성이 살짝 다르기 때문에, 흔히들 윈도우 Eclipse를 기준으로 설명하는 경우, Mac Eclipse랑은 메뉴 구성이 달라서 당황한 경험이 많을 것입니다.


보통, 윈도우에서는 Help > Software Updates > Find and Install 메뉴 등을 클릭하여 진행하는 반면,


맥에서는 다음과 같은 과정을 통해서 플러그인을 설치할 수 있습니다.



1. 메뉴바에서 'Help' 클릭 (단축키: 커맨드 + 쉬프트 + /)



2. 'Help' 탭에서 'Install new software' 클릭!




3. 새롭게 뜬 창에서 'Add..' 버튼을 클릭!




4. 또 새롭게 뜨는 창에서, Name 필드와 Location 필드를 원하는 플러그인에 맞게 작성 후 'OK' 버튼을 클릭한다. (제가 설치할 플러그인은 파이썬 어플리케이션 개발을 위한 PyDev 플러그인 입니다.)




5. 원하는 플러그인을 체크, 그리고 'Next'를 누른다.




6. 이후에 설치 세부사항등에 대한 이야기가 나오는데, 계속해서 'Next' 버튼을 누르고, 마지막에 라이센스를 확인하는 창이 뜨게 된다.

이 때, 반드시 라이센스에 동의한다는 표시에 체크를 하고 'finish' 버튼을 누른다.



7. 끝입니다.



간단하죠? 맥에서도 이클립스 플러그인 설치 어렵지 않게 쉽게 할 수 있습니다!! 


+ Recent posts