jun-wiki

View My GitHub Profile

Posts (Latest 10 updated) :
Read all
Contents:
  1. 유저 메모리 (User Memory)
  2. 시스템 콜 (System Calls)
    1. 시스템 콜 상세
      1. void halt (void);
      2. void exit (int status);
      3. pid_t fork (const char *thread_name)
      4. int exec (const char *cmd_line);
      5. int wait (pid_t pid);
      6. bool create (const char *file, unsigned initial_size);
      7. bool remove (const char *file);
      8. int open (const char *file);
      9. int filesize (int fd);
      10. int read (int fd, void *buffer, unsigned size);
      11. int write (int fd, const void *buffer, unsigned size);
      12. void seek (int fd, unsigned position);
      13. unsigned tell (int fd);
      14. void close (int fd);
    2. 추가 정보

GitBOOK이다

프로젝트 2 기본적으로 테스트 돌리려면 다 읽어봐야 하기에

정리 한다


유저 메모리 (User Memory)

시스템 콜 구현하려면 사용자 가상 주소 공간의 데이터 읽고 쓰기가 필요하다

인자 가져오기만 할땐 그리 필요하지 않지만…

시스템 콜의 인자로 전달된 포인터가 가리키는 데이터를 읽거나 쓸 때는

반드시 필요하다 (프록시 기능)

잘못된 포인터, 커널 메모리 가리키는 포인터, 부분만 겹치는 블록을 넘길 수 있기엔

이러한 경우 해당 사용자 프로세스 종료해 처리해야 한다


시스템 콜 (System Calls)

userprog/syscallc에 시스템 콜 핸들러 구현해야한다

기본 스켈레톤 구현은 그냥 프로세스 종료하지 시스템 콜 처리 안한다

시스템 콜 번호 가져와 인자를 읽어 동작 수행하도록 바꿔야 한다

시스템 콜 상세

운영체제가 사용자 프로그램으로 부터 제외 되찾는 방식을 배웠고

CPU 외부의 요인으로 발생하기에 외부 인터럽트라고 한다

운영체제는 소프트웨어 예외(프로그램 코드에서 발생하는 사건)도 처리해야 한다

페이지 폴트, 0으로 나누기 등이 그 오류다

예외는 또한 시스템 콜이기도 하다

syscall 명령은 시스템 콜을 호출하는 가장 일반적인 수단이다

Pintos에서도 이를 쓴다

유의할 점은 syscall을 실행하기 직전 일반적인 규약대로

레지스터에 시스템 콜 번화와 인자들을 설정해야 한다는건데 두 가지 예외가 있다

  • %rax에는 시스템 콜 번호를 넣는다

  • 네 번째 인자%rcx가 아니라 %r10 에 담는다

따라서 syscall_handler()가 제어를 넘겨받을 때,

시스템 콜 번호는 rax 에 있고

인자들은 %rdi, %rsi, %rdx, %r10, %r8, %r9 순서로 전달된다

호출자의 레지스터들은 핸들러에 전달되는 struct intr_frame을 통해 접근 가능하다

반환값은 RAX 레지스에 넣는게 관례다

struct intr_frame을 통해 rax의 멤버를 수정해 값을 돌려줄 수 있다




구현해야 할 시스템 콜들이다

프로토타입은 include/lib/user/syscall.h를 포함한 사용자 프로그램에 보이는 인터페이스다

각 시스템 콜의 번호는 include/lib/syscall-nr.h에 정의되어 있다


void halt (void);

power_off()를 호출하여 Pintos를 종료 (선언: src/include/threads/init.h)

교착 상태 등에 대한 정보를 잃을 수 있으므로 드물게만 사용되어야 한다


void exit (int status);

현재 사용자 프로그램을 종료하고, status를 커널에 반환한다

부모가 이 프로세스를 wait하면 이 값이 반환된다 (아래 참조)

0은 성공, 0이 아니면 오류 의미한다 (관례적으로)


pid_t fork (const char *thread_name)

현재 프로세스를 복제하여 이름이 THREAD_NAME인 새 프로세스를 만든다

레지스터 중 callee-saved인 %RBX, %RSP, %RBP, %R12%R15만 복제하면 되고, 나머지 레지스터 값은 복제할 필요가 없다고 한다

뭔 소리냐 하면 피호출자가 책임지고 관리하는 집합이 callee-saved다
이 집합을 그대로 복사하고 부모/자식 규칙에 따라 RAX만 잘 설정하면 된다

성공 시 자식의 pid를 반환하고, 자식 프로세스 쪽의 반환값은 0

자식은 파일 디스크립터와 가상 메모리 공간 포함해 리소스 복제해야한다

부모는 자식의 복제 성공여부 알기 전까지는 fork에서 반환 금지

만약 실패했을 경우 TID_ERROR를 반환해야 한다


int exec (const char *cmd_line);

현재 프로세스를 cmd_line로 주어진 실행 파일로 바꾸며 인자들도 함께 전달한다

성공하면 반환 없다

못하면 -1 종료상태로 끝난다

파일 디스크립터는 exec 이후에도 열린 상태로 유지된다


int wait (pid_t pid);

자식 프로세스 pid를 기다리고 종료 상태 돌려준다

pid가 살아있으면 종료 할 떄까지 기다린다

그리고 exit에 전달된 상태 반환

pidexit()호출 안했는데 커널에서 종료할 경우 -1 반환

wait호출 전에 종료되어도 커널은 부모가 자식의 종료 상태를 가져가거나 커널이 자식을 종료시켰다는 사실을 언제든 알 수 있게 해야 한다

다음은 wait의 실패 조건들이다 -1 반환해야 한다

  • pid가 호출 프로세스의 직계자식이 아닐 경우
    바로 아래 직계가 아닐 경우 의미한다

  • wait을 중복 호출 했을 경우

모든 wait패턴 고려해야 한다
어느때든 리소스도 잘 해제되어야 하고 말이다

초기 프로세스가 종료될 떄까지는 pintos도 종료되지 않아야 한다


bool create (const char *file, unsigned initial_size);

이름 file의 새 파일 만들고 초기 크기를 initial_size 바이트로 설정한다

성공 시 true, 실패 시 false 반환

여는 것과 별개다


bool remove (const char *file);

이름이 file인 파일을 삭제하고 성공 시 true, 실패 시 false

파일이 열려 있든 닫혀 있든 삭제 가능

열려있는건 삭제해도 닫히지는 않는다

(닫히고 나면 그제서야 공간 해제)


int open (const char *file);

이름이 file인 파일 연다

파일 디스크립터 반환하며 못열면 -1 반환


int filesize (int fd);

fd로 열린 파일의 바이트 단위 크기 반환


int read (int fd, void *buffer, unsigned size);

파일 디스크립터 fd에서 size 바이트 읽어 buffer에 저장

읽은 바이트 반환하고 못 읽었으면 -1 반환

fd0input_getc()로 키보드에서 읽는다


int write (int fd, const void *buffer, unsigned size);

fdbuffer로부터 size바이트에 쓴다

실제로 쓴 바이트 수 반환

가능한 만큼 쓰고 쓴 바이트 수 반환 시작도 못하면 0

fd 1은 콘솔에 쓴다

콘솔에 쓰는 코드는 수백바이트 이하면 그냥 putbuf() 한 번 호출해서

전체 buffer 출력


void seek (int fd, unsigned position);

fd에서 다음에 읽거나 쓸 바이트의 위치를
파일 시작부터 postion 바이트로 바꾼다

현재 파일 끝 넘어도 오류가 아니란다

이후 읽을 때는 0바이트 얻어 파일 끝 나타낸다

이후 쓰기는 파일 확장하며 쓰지 않은 틈 0으로 채운다
(프로젝트 4전에는 파일 길이 고정이라 넘으면 오류처리)

파일 시스템에서 하는 거라 시스템 콜 구현시엔 딱히 할 거 없단다


unsigned tell (int fd);

열린 파일 fd에서 다음에 읽거나 쓸 바이트의 파일 시작 기준 위치 반환


void close (int fd);

파일 디스크립터 fd를 닫는다

프로세스가 종료될떄에는 각 열린 fd에 대해 이 함수 호출한 것처럼 닫힌다




추가 정보

시스템 콜 여러개 호출해도 안전하도록 동기화 해야 한다

특히, filesys 디렉터리에 제공된 파일 시스템 코드
여러 스레드가 동시에 호출하는 것은 위험하다

그렇기에 시스템 콜 구현에서 파일 시스템 코드를 임계 구역으로 다루어야 한다

아직은 filesys 디렉터리 코드 수정하는 걸 권장하지 않는다

구현 이후 사용자 프로그램은 절대 OS를 오동작으로 몰아가면 안된다
OS를 멈추는 유일한 방법은 halt 시스템 콜 호출이어야 한다

시스템 콜에 잘못된 인자 전달된 경우, 다음 중 하나로 대응해야 한다

오류값 반환, 정의되지 않은 값 반환, 프로세스 종료