[크래프톤 정글 3기] 12/12(화) TIL 🤒

ClassBinu·2023년 12월 12일
0

크래프톤 정글 3기 TIL

목록 보기
59/120

코로나 일주일 째. 유난히 기침이 심함.
점심까지 자고 밥 먹고 12:30 착석


알고리즘

KMP(Knuth-Morris-Pratt)

문자열 매칭 알고리즘
접두사와 접미사를 활용

(코드 구현 실패)


Pintos

seek, tell, close


seek()

"다음에 읽거나 쓸 바이트를 파일 디스크립터(fd)에서 파일의 시작부터 바이트 단위로 표시된 위치로 변경합니다. 따라서 위치 0은 파일의 시작점을 나타냅니다. 현재 파일 끝을 넘어서 이동하는 seek 작업은 오류가 아닙니다. 나중에 읽기 작업은 0 바이트를 얻어 파일의 끝을 나타냅니다. 나중에 쓰기 작업은 파일을 확장하며, 쓰지 않은 간격을 모두 0으로 채웁니다. (그러나 Pintos에서 파일은 프로젝트 4가 완료될 때까지 고정된 길이를 가지므로 파일 끝을 넘어서 쓰기 작업은 오류를 반환할 것입니다.) 이러한 의미론은 파일 시스템에 구현되며 시스템 호출 구현에 별도의 특별한 노력이 필요하지 않습니다."

근데 팀원분들이 다 열심히 달려서 거의 다 완성함.
코드 복기를 우선 열심히 해보고 개선할 게 있으면 개선해 보자!

void seek (int fd, unsigned position) {
	struct file *f = get_file_from_fd_table(fd);
	if (f == NULL) {
		return;
	}
	file_seek(f, position);
}

file_seek()도 들여다 보았다.

void
file_seek (struct file *file, off_t new_pos) {
	ASSERT (file != NULL);
	ASSERT (new_pos >= 0);
	file->pos = new_pos;
}

file구조체의 pos값을 새로운 값으로 바꾸는 것!
복잡한 함수는 아니였다!

이건 해당 위치로 포인터를 이동시키는 것!
position이 파일의 시작점을 기준으로 offset을 의미한다.

먼저 fd로 테이블에서 파일 구조체를 가지고 온다.
그 다음 file_seek 시스템 콜로 해당 위치로 커서를 이동한다.


tell()

unsigned tell (int fd) {
	struct file *f = get_file_from_fd_table(fd);
	if (f == NULL) {
		return -1;
	}
	return file_tell(f);
}

off_t
file_tell (struct file *file) {
	ASSERT (file != NULL);
	return file->pos;
}

이건 해당 파일의 커서 위치를 반환하는 함수다!


close()

void close (int fd) {
	struct thread *t = thread_current();
	struct file **fdt = t->fd_table;
	if (fd < 0 || fd >= 128) {
		return;
	}
	if (fdt[fd] == NULL) {
		return;
	}
	file_close(fdt[fd]);
	fdt[fd] = NULL;
}

void
file_close (struct file *file) {
	if (file != NULL) {
		file_allow_write (file);
		inode_close (file->inode);
		free (file);
	}
}

void
inode_close (struct inode *inode) {
	/* Ignore null pointer. */
	if (inode == NULL)
		return;

	/* Release resources if this was the last opener. */
	if (--inode->open_cnt == 0) {
		/* Remove from inode list and release lock. */
		list_remove (&inode->elem);

		/* Deallocate blocks if removed. */
		if (inode->removed) {
			free_map_release (inode->sector, 1);
			free_map_release (inode->data.start,
					bytes_to_sectors (inode->data.length)); 
		}

		free (inode); 
	}
}

열려 있는 파일을 닫는 함수.
상주된 메모리를 해제하는 것 같음.


power_off 함수 살펴보기

void
power_off (void) {
#ifdef FILESYS
	filesys_done ();
#endif

	print_stats ();

	printf ("Powering off...\n");
	outw (0x604, 0x2000);               /* Poweroff command for qemu */
	for (;;);
}

그 전에 C에서 많이 보이는 #if가 뭔지 알아 봄!
우선 #이 붙으면 전처리기 지시문이다.
#ifdef는 만약 이 심볼이 정의되어 있으면을 뜻한다.
#ifdef FILESYS는 만약 filesys심볼이 정의되어 있다면
filesys_done()을 호출하라는 뜻이다.

for(;;);는 무한 루프를 생성한다.
이 코드의 목적은 시스템 종료되는 동안 다른 작업이 수행되지 않도록 하는 것.

outw()는 시스템 전원을 끄라는 명령을 하드웨어(핀토스에서는 가상머신)에게 전달
물리적인 전기신호를 전달한다.


인터럽트와 트랩 차이는?

인터럽트는 하드웨어 인터럽트와 소프트웨어 인터럽트로 나뉜다.
뭔가 이벤트가 발생했음을 의미한다.
인터럽트가 발생하면 현재 실행 중인 프로세스가 중단되고,
인터럽트 핸들러를 통해 해당 작업이 수행된다.
(해당 인터럽트 번호가 핸들로 함수에 전달됨.)
인터럽트는 비동기적 실행 흐름을 지닌다.

트랩은 예외 상황이 발생했을 때 이다.
(잘못된 메모리 참조, 0으로 나누기 등)
트랩이 발생되면 실행 중인 프로세스가 중단되고,
트랩 핸들어를 통해 예외 처리 작업이 수행된다.
트랩은 동기적 실행 흐름을 지닌다.
(예외 상황을 안전하게 처리해야 하니까)

인터럽트는 정상적인 실행 과정에서 발생한다면,
트랩은 뭔가 기대하지 않은 작업으로 인해 발생한다고 볼 수 있다.


단순 랩핑 함수 왜 쓰는 걸까?

int wait (tid_t tid) {
	return process_wait (tid);
}

이런 건 그냥 래핑만 하는 건데.. 왜 쓰는 거지? 그냥 바로 process_wait 호출하면 안 되나?
1. 인터페이스 일관성 유지: 이름을 더 쉽게 알아볼 수 있게
2. 추상화 수준 조절: 세부사항을 숨길 수 있음
3. 유연성과 확장성 제공: 아.. 그렇네.
4. 코드 재사용성 증가: 이것도 그렇네
5. 함수 분리
6. 호환성 유지

GPT 물어보고 바로 납득함.
nest.js에서 그냥 리턴만 하는 건데 왜 service, controlloer나누는 거야? 란 똑같은 생각이었음.


퀴즈 복귀

커널 코드(시스템 콜) 예시

명확하게 정리하기.
시스템 콜 호출은 사용자 모드에서 이루어진다.
하지만 시스템 콜에 의해 수행되는 작업은 커널 모드에서 실행된다.
즉, 시스템 콜은 사용자 모드와 커널 모드의 상호작용이다.

대표적인 시스템 콜은 다음과 같다.

파일 관련

  • open()
  • read()
  • write()
  • close()

프로세스 관련

  • fork()
  • exec()
  • wait()
  • exit()

메모리 관련

  • brk(), sbrk()
  • mmap()

통신 및 네트워킹 관련

  • socket()
  • bind()
  • listen()
  • accept()

기타

  • getpid()
  • sleep()

이 이미지가 잘 나타내 줌!

https://www.youtube.com/watch?v=bjGIf_AKtAc

커널 모드가 왜 필요할까?

커널 모드에서는 시스템의 모든 하드웨어 자원에 접근할 수 있는 권한이 주어진다.
운영체제가 시스템 자원을 효과적으로 관리하는데 커널 모드가 필요하다.

커널 모드가 없으면 유저 애플리케이션이 시스템 자원에 직접 접근할 수 있다.
이때 우발적인 시스템 손상이나 악의적인 침해가 발생할 수 있다.

커널 모드가 없는 경우 런타임 시 문제가 발생하면 시스템 전체에 문제가 생긴다.
이를 커널 모드와 유저 모드로 나누어서 유저 모드에서 문제가 생기면
문제의 유효 범위가 유저 애플리케이션에 한정되게 된다.

컨텍스트 스위칭

컨텍스트 스위칭 시 저장 및 복원되는 정보

1. CPU 레지스터 상태

  • 일반 목적 레지스터 : 연산 사용 레지스터
  • 프로그램 카운터 : 다음 실행 명령어 주소
  • 스택 포인터 : 프로세스 스택 상단
  • 상태 레지스터 : 현재 프로세스 상태

2. 프로세스 제어 블록(PCB)

  • PID
  • 프로세스 상태
  • 프로그램 카운터
  • CPU 레지스터들이 값
  • CPU 스케줄링 정
  • 메모리 관리 정보: 페이지 테이블, 세그먼트 테이블, 메모리 한계 등
  • 계정 정보: CPU 사용 시간, 실행한 명령어 수 등
  • 입출력 상태 정보: 프로세스에 할당된 입출력 장치, 열린 파일 목록 등

요약하면 문맥 교환에서 크게 두 가지 정보를 기억함.
CPU 레지스터 상태 & PCB 정보


fsync()

주요 역할

파일 시스템의 메모리(버퍼 캐시)에 있는 데이터를 실제 디스크에 기록함.
데이터의 일관성과 내구성을 보장.
시스템의 예상치 못한 종료에도 데이터의 안정성을 확보하는 데 사용

작동 원리

  1. 버퍼 캐시 데이터 쓰기
    효율을 위해 파일에 대한 쓰기 작업은 즉시 디스크에 쓰는 것이 아니라 버퍼 캐피에 먼저 저장

  2. fsync()가 호출되면 운영체제는 해당 파일에 대한 모든 버퍼 캐시 데이터를 디스크에 씀.
    이때 파일 메타 데이터도 같이 업데이트 됨

  3. 완전하게 기록되면 반환됨.(그 전까지는 반환 안 됨)

성능 고려 사항

  1. 디스크에 데이터를 쓰는 작업이기 때문에 입출력 지연 발생 할 수 있음.
    (디스크는 메모리보다 훨~~~씬 느림)

  2. 빈번한 fsync() 호출은 성능 저하

  3. 데이터 중요성과 애플리케이션 동작을 고려해서 적절하게 호출

오늘은 syscall.c와 process.c를 타고타고 들어가면서 코드 여행(?)을 해봤음.
이번 과제가 사실 거의 래핑 함수를 구현하는건데 이렇게 어렵다니..
운영체제 커널 만든 사람들 정말 대단하다..

0개의 댓글