1. 파일시스템
● 파일시스템이란 ' 운영체제가 저장장치에 데이터를 저장하고 관리하는 방식 '
● 보조기억장치인 디스크를 논리적으로 나눠서 각 파티션마다 파일시스템을 만들 수 있다. (예시 C드라이브 D드라이브)
● 파일시스템의 구성 : boot block, super block, FCB list, data blocks
1. boot block은 패스
2. super block은 파일시스템에 대한 메타 정보 저장
3. FCB list (inode list) 는 FCB들을 배열로 저장한 것.
4. Data Blocks는 실제 파일 내용이 저장되는 공간임
Data blocks에서 파일을 할당하는 3가지 방법
1. Contiguous allocation : 연속 할당 방식
2. Chained allocation (Linked allocation) : 블록에 다음 블록을 가리키는 포인터를 둬서 연결하는 방식
3. indexed allocation : 한 파일에 대한 블록들의 위치를 저장하는 인덱스 블록을 사용하여 저장
Free Space 관리하는 4가지 방법
1. Counting : 빈 블록의 번호와 연속된 길이는 저장. Contiguous allocation 방식과 비슷함
2. Linked list (free list) : 빈 블록마다 다음 빈 블록을 가리키는 포인터를 둬서 연결하는 방식. Chained allocation과 비슷함.
3. Grouping : 인덱스 블록을 사용해서 빈 블록의 위치를 저장하는 방식. indexed allocation과 비슷.
4. bit vector (bit map) : 각 블록에 대한 할당/비할당 여부를 1비트로 표시하는 배열을 사용하는 방식. 단점은 크기가 너무 커짐.
2. 파일시스템 예시
UNIX의 파일 관리
파일의 종류 : 일반파일, 디렉토리, 스페셜파일로 구성
파일시스템의 구성 :
1. 부트블록
2. 슈퍼블록
3. inode list (inode table)
4. data blocks
UNIX에서 Data Blocks을 할당하는 방식 :
indexed allocation 방식을 약간 변형하여 사용한다.
이유 : 일반적인 indexed 방식은 인덱스 블록 -> 실제 블록 이렇게 두번 이상 접근해야함.
변형 : 각 파일의 FCB에 첫 10개의 블록을 따로 저장한 후, 이후 것들에 대해서는 인덱스 블록에 저장하고, FCB에 인덱스 블록을 저장한다.
인덱스 블록을 중첩적으로 둬서 큰 파일도 저장할 수 있지만, 그정도로 커질빠에는 나눠서 저장하는 게 좋다.
> 이유는 한 파일이 너무 크면 속도가 느리기에, 나눠서 저장하는게 읽거나 쓰는 것이 빠르기 때문이다.
리눅스의 Virtual File System
파일시스템은 여러개를 사용할 수가 있다.
이때 사용자 프로그램에서 여러 파일시스템에 대한 조작법을 다 알고 있어야 할까?
> 아니다! VFS를 통해서 일관적인 인터페이스를 제공한다. 사용자 프로그램은 어떤 파일시스템이든 동일한 systemcall을 사용할 수 있게 해주는게 VFS이다.
리눅스의 Inode (FCB)
리눅스의 FCB는 Inode라고 부르며, UNIX와 다른 점은 다이렉트로 접근할 수 있는 블록 개수가 10개에서 12개까지 늘어났다는 점
최대 저장 크기도 뭐 8KB (2블록크기) 정도 늘어났을거야.
3. I/O관리와 디스크 스케쥴링
▶ 먼저 I/O 디바이스라는건 입출력 장치이다.
이 I/O 장치는 기계파트와 전자파트(컨트롤러)로 이루어져 있다.
컨트롤러는 제어 레지스터(명령함), 상태 저장 레지스터, 내부 버퍼 등으로 구성된다.
컨트롤러는 폴링이나 인터럽트에 의해 제어된다.
I/O시스템의 구성요소는
1. 커널에 있는 I/O management = kernel I/O subsystem
2. Device deriver interface
3. Device drivers
디바이스 드라이버 인터페이스는?
OS가 연결가능한 모든 I/O 장치에 대해서 알 필요가 없도록하는 표준 인터페이스!
디바이스 드라이버 인터페이스에게 요청하면 이 녀석이 해당 디바이스 드라이버와 연결
그래서 실제 디바이스에게 명령함
디바이스 드라이버는?
I/O 장치를 제어하는 기능들의 모음이다!
모듈 형식이라 필요할 때만 커널에 올라간다.
Kernel I/O management의 6가지 기능
1. 장치예약 - Device reservation : 장치를 한 가지 프로세스만 사용할 수 있도록
2. 디바이스 스케쥴링 - Device scheduling : 한 장치에 대해 여러 프로세스가 요청할 때 큐를 통해 순서관리
3. 오류처리 - Error Handling : I/O 처리 과정에서 오류 발생하면 적절히 조치를 취함
4. 버퍼링 : 장치간 데이터를 전송하는 동안 메모리에 저장
5. 캐싱 : 자주 쓰는 데이터를 캐시에 복사본으로 저장 (원본 x)
6. 스풀링 - Spooling : 출력을 디스크에 대기열로 저장
중
3-1. Interrupt Handling
▶커널로 들어가는 3가지 방법
1. Interrupt : 하드웨어 신호
2. Trap : CPU가 거는 인터럽트 (소프트웨어 신호)
3. System call
1. 인터럽트
인터럽트란 장치가 OS에게 비동기적인 이벤트를 알리는 방법
중첩되어 발생하거나 처리가 가능하다.
예를들어 타입 인터럽트, 키보드 입력, 마우스 입력 등이 있다.
타입 인터럽트가 자주 발생하는 이유 > 더 자주 커널을 실행하기 위해서이다!
기본적으로 CPU는 명령어를 1개 수행한 후에 인터럽트가 걸렸는지 확인한다.
▶인터럽트를 처리하는 방법
1. 유저모드라면 모드체인지하여 커널모드로 진입
2. 커널스택에 현재 문맥을 저장한다. (레지스터 값 등)
3. 인터럽트 핸들러가 누가 인터럽트를 걸었는지 확인
4. IDT(interrupt descriptor table)에서 해당 인터럽트의 ISR을 실행
5. 이때 최소한의 일(급한일)만 해준다.
6. 처리 중 인터럽트가 또 발생하면 위 과정을 반복
7. 모든 ISR처리가 끝나면 특별코드블록을 실행하여, 남은 부분을 처리하고 ready로 상태를 변경하는 등의 일을 한다.
8. 스케쥴러를 실행한다. (이때 문맥전환이 일어날 수도, 아니면 기존 프로세스가 계속 실행될수도)
2. 트랩
Trap은 CPU가 발생시키는 동기적인 이벤트로 이벤트 발생 시점을 예측할 수 있다.
프로그램 실행 중 특정 명령어에서 Trap이 발생한다.
트랩의 예시 :
* 0으로 나누기
* 잘못된 명령어
* 세그먼테이션 폴트 (허용안된 메모리 접근시도)
* protection fault (읽기 전용인데 쓰려고할때)
> page fault 실행해야할 부분이 메모리에 안 올라와 있을 때.
page fault를 제외하면 모두 강제 종료 시킨다.
트랩의 처리 방식은 인터럽트와 비슷하게 IDT에서 ISR을 실행하여 처리한다.
3. 시스템콜
시스템콜은 프로세스가 커널 기능을 요청하는 것
내부적으로는 인터럽트를 발생시켜 IDT에서 system_call() 이라는 부분으로 들어가고,
그 안에 sys call table에서 요청한 기능을 수행한다.
3-2. OS가 I/O 제어하는 3가지 방법
1. Polling
2. Interrupt Driven I/O
3. Direct Memory Access (DMA)
1. Polling (=Programmed I/O)
프로그램을 실행함으로써 I/O 상태를 확인하는 방식.
디바이스 드라이버가 디바이스에게 일을 시키는데,
디바이스의 컨트롤러 부분에 4가지 주요 레지스터가 있음
1. Status register : 디바이스의 상태 저장 (Commend-ready, Busy, Error)
2. Control register : 디바이스 드라이버가 어떤 명령을 할 지 저장
3. Data-in register : 장치를 통해 입력할 내용
4. Data-out register : 장치를 통해 출력할 내용
결국 디바이스 드라이버가 일을 시키고 계속 컨트롤러의 상태 레지스터를 확인하면서
상태 레지스터의 Busy가 1에서 0이 될 때를 기다린다! -> busy waitng 함
I/O가 일찍 끝나는 경우, 문맥전환이 없는 방식이기 때문에 효과적이다.
하지만 오래 걸리는 I/O의 경우에는 그동안 CPU가 계속 상태 확인만 하기 때문에 다른일을 할 수 없다.
2. Interrupt Driven I/O
인터럽트를 통해 해당 I/O가 끝났는지 확인하는 방식
1. 유저 프로세스에서 systemcall로 요청하면
polling방식과 똑같이 디바이스 드라이버가 장치에게 명령을 함!
2. 그 다음에 프로세스 관리로 넘어가서 해당 프로세스를 block상태로 바꾸고,
문맥전환을 통해 다른 프로세스를 실행한다.
3. 만약 해당 I/O가 끝나면 CPU에게 인터럽트를 건다!
4. CPU는 커널로 들어가 해당 인터럽트를 ISR로 처리하고, 상태를 ready로 바꿔준다.
5. 이후 스케쥴러를 실행하여 우선순위에 따라 실행될 프로세스가 결정된다!
3. Direct Memory Access (DMA)
DMA는 위와 좀 다른 성격이다.
DMA를 안 쓰면 장치의 결과를 CPU가 직접 레지스터를 통해 메모리로 옮겨야 한다.
그러면 CPU가 거기에 시간을 많이 잡아먹기 때문에 다른 일을 할 수가 없다..
> 그래서 이를 대신 해줄 DMA 컨트롤러 라는게 나왔고, CPU는 DMA 컨트롤러에게 대신 메모리에 저장하라고 명령한다.
이를 통해 CPU는 이 일을 하지 않고 DMA가 입출력 장치 <-> 메모리(RAM) 사이에 데이터를 전달해준다.
그리고 일을 다 하면 다 했다고 CPU에게 인터럽트를 건다!
3-3. 디스크 스케쥴링
OS의 논리적인 디스크의 최소 단위는 블록이고,
실제 디스크의 물리적인 최소 단위는 섹터이다.
< 디스크 구조 알아보기 >
> 섹터 : 디스크의 물리적 최소 단위
> 트랙 : 디스크의 한 줄 (원), 트랙에는 여러 섹터가 있다. 그리고 하나의 디스크 표면에는 여러 트랙이 있다.
> 헤드 : 디스크 면을 읽어서 섹터의 데이터를 읽는다.
> 디스크 암 : 디스크를 읽는 팔 역할로, 끝에는 헤드가 있다.
디스크 드라이버는 논리 블록의 1차원 배열로 주소를 지정한다.
실제로는 여러 디스크면, 트랙, 섹터들 등 여러 차원으로 구성되지만, 이를 간단하게
하나의 배열로 블록 주소를 지정한다.
섹터 크기 = 블록 크기라고 할때
1. 바깥쪽트랙부터 다 읽고
2. 아랫면도 같은 트랙 읽고
3. 그 아래 디스크의 같은 트랙 읽고 . . 모두 읽으면
4. 첫 디스크의 하나 안쪽 트랙으로 넘어가서 읽는다! 반복
위 과정을 배열로 담아서 쓰는거임!!
< 디스크 I/O의 시간 >
디스크를 읽거나 쓸 때 걸리는 시간에 대해 알아보자.
1. Seek Time : 탐색시간, 헤드가 트랙에 찾아가는 시간 (가장길다)
2. Rotational Delay : 회전지연, 헤드에 읽어야할 섹터부분이 회전하며 오는데 걸리는 시간
3. Data Tranfer Time : 실제 데이터 전송이 발생하고, 데이터를 읽거나 쓰는 시간
이 중 Seek Time이 성능 차이의 주 원인이 됨!!
단일 디스크에 대해서 여러 I/O 요청이 발생하는데, 이때 무작위로 선택하면 성능저하가 발생하니
디스크 스케쥴링이 필요한 것이다...!!
< 디스크 스케쥴링 방식 3가지 >
1. FIFO : 요청이 들어온대로 접근하기 > 랜덤에 가까워서 오래걸림
2. SSTF (Shortest Seek Time First) : 현재 헤드가 있는 부분에서 가장 가까운 트랙을 우선으로 접근하여 처리
-> 대신 우선순위를 기반으로 하기 때문에 기아상태 문제 발생 가능
3. SCAN(엘리베이터, LOOK) : 한쪽 방향으로 쭉 이동하면서 처리하기 > 기아상태 없어!
-> 이동 방향에 더이상 읽을 섹터가 없으면 방향을 바꾼다!
< 디스크 캐시 > = page cache, buffer cache
프로그램이 같은 블록을 요청할 수 있으니, 최근 불러온 블록을 캐시에 저장하여 사용한다.
< 캐시가 가득 찰 때 대체하는 방식 >
1. LRU - Least Recently Used : 가장 오래된 블록을 버리자.
2. LFU - 가장 적게 사용된 블록을 버리자.