내가 맡은 부분은 메모리 단편화, demane-zero memory, 이더넷.
전반적으로 보겠지만 설명을 맡은 부분은 그렇다.
[운영체제(OS)] 가상 메모리 - (1) 가상 메모리와 페이징 기법
각 프로세스에,
하나의 사적 주소 공간을 제공.
(이 주소공간에서
예외 제어 흐름, 커널, 메인 메모리, 들 간의
상호 작용이 일어난다.)
제공하는 세 개의 기능은 이렇다.
메인 메모리를 캐시, 일종의 중간 기억 장치로 간주, 메인 메모리의 활성화 영역만 관리하여, 필요에 따라 데이터를 디스크와 메인 메모리에 전송.
(디스크까지 관리하면 시간이 오래 걸리므로 메인 메모리만 사용하여 총체적으로 사용한다는 뜻 같다.)
각 프로세스에 통일된 주소 공간을 제공하여 메모리 관리를 단순화.
(프로세스마다 주소 공간 제공 시, 템플릿처럼 일관된 형식으로 제공하여 프로세스마다 따로 주소 공간에 따른 관리가 필요 없어진 듯 함.)
각 프로세스의 주소 공간을 다른 프로세스의 손상으로부터 보호.
(그래서 segement fault가..ㅠ)
가상 메모리를 알아야 할 이유는 무엇일까?
메인 메모리 M개의 연속적 바이트.
각 바이트는 고유의 물리주소 (PA, physical address)를 가짐.
이 물리주소 자체에 접근하는것을
물리 주소 방식(physical addressing) 이라고 한다.
CPU -로드 -> 메인 메모리(주소 생성) -> CPU(레지스터)
형식.
그러나 임베디드 등이 아니면 가상 주소 방식(virtual addressing)을 사용.
CPU -Virtual address(VA) ->
MMU, memory management unit(Address translation) -PA->
메인 메모리(주소 생성) -> CPU(레지스터)
가상 주소를 생성하여, MMU, 메모리 관리 유닛에게 넘기면,
MMU가 주소번역(address translation)을 하여 물리주소로 바꿔 넘긴다.
MMU는 메인메모리에 저장된 참조 테이블을 사용하여,
실행 중에 가상 주소를 번역하는데,
이 테이블을 OS가 관리한다.
주소 공간은
음수가 아닌 정수 주소의 정렬된 집합이다.
주소 공간 크기는
N = 2^n 이라면
n-비트 주소공간이라고 부르고
현대는 32비트, 64비트 가상 주소 공간을 지원하며,
M바이트의 물리메모리에 따라
물리 주소 공간 또한 있다.
그렇게 각 바이트에 따라,
가상 주소 공간의 채택된 가상 주소를 가진다.
(흠....)
(아마 실제 물리 메모리가 훨씬 크기때문에,)
(프로세스P - 가상 주소 - 물리메모리 A칸)
(프로세스Q - 가상 주소 - 물리메모리 A+1칸)
(같은 것인거같다.)
VM system이
가상 메모리를 분할한 블록으로 관리하는데,
그 블록을 가상 페이지 라고 부른다.
물리 메모리도 비슷하게 물리 페이지로 분리되어 사용한다.
(물리 페이지는 물리 프레임이라고도 한다.)
가상 페이지는 세 개의 부분 집합으로 이루어진다.
전자는 CPU와 메인 메모리 사이에 있는 중간 기억 장치고,
후자는 MMU가 갖는 캐시인가?
아아,
SRAM은 CPU와 메인 메모리 사이에 있는 중간 기억 장치고,
후자는 가상 주소 공간이 할당되는 중간 기억 장치고,
MMU는 주소 번역을 해서 DRAM에 저장하는 거구나.
DRAM에서 실수하면,
그걸 디스크에서 처리해야하기 때문에 치뤄야하는 값이 큰데,
또 그 디스크 속도가 상대적으로 느리기때문에....
새로운 가상 주소 공간을 할당하는 것보다는
가상 페이지 자체의 크기를 키워서
가상 페이지 내에서 해결 되는게 가장 좋기 때문에
가상 페이지는 4KB ~ 2MB 크기 까지 갖고 있다.
이러한 사유때문에
가상 페이지를 교체하는 알고리즘에 대한 연구가 이뤄지고 있다....
DRAM은 write-back 을 사용한다.
캐시 메모리를 쓸 때의 두 개의 전략.
전자는 데이터 업데이트시 캐시, 메인 메모리 양쪽 모두 기록.
신뢰성은 높아져도 시간이 오래 걸리거나 불필요한 쓰기 횟수 상승..
후자는 데이터 업데이트 시 캐시에만 기록,
메인 메모리는 다른 캐시 블록으로 넘어갈때 기록됨.
시간 걸리기는 좀 줄일수 있지만 데이터 불일치 문제 발생 가능..
가상 페이지가 DRAM 어디에 저장되었는지 알아야,
어떤 물리 페이지로 이어지는지,
미스가 발생한다면
물리 메모리 중에 어떤 희생자 페이지(victim page)를 가져올건지
결정해야하는데,
이를 페이지 테이블(page table)에서 결정한다.
페이지 테이블 형태로
DRAM에서 저장되어
물리 메모리 <-> 가상메모리
로 이어지는데....
페이지 테이블 엔트리,(page table entry)가
유효 비트(valid), n비트 주소 필드
로 구성되어,
유효 비트는 가상 페이지의 할당 여부 표시(0이면 할당 x, 1이면 할당 o)
n비트 주소 필드는 주소가 적혀있는데
로 이루어진다.
DRAM의 완전결합성(fully associative)을 강조하는데,
라고 하는데,
캐시 히트가 이미 캐시에 있다면 메인 메모리까지 갈 필요없는 걸 얘기하는건데,
오히려 아무 곳에 넣기때문에 항상 전체 캐시를 검사하기때문에
가능한 구조인 것같다...
페이지 테이블에서,
가상주소를 인덱스로 들어가서 읽고,
유효 비트로 캐시되어있는지 확인해서,
들어있는 물리 메모리 주소로
물리 주소 번역으로 들어간다.
페이지 오류는 DRAM 캐시 미스다.
예를 들어,
들어갔지만 유효비트로 캐시 안 되었다는걸 확인한다면,
그걸로 페이지 오류 예외를 발생시킴.
만약 캐시된줄 알았는데
아니었다면 예외를 발생시키면서 캐시 안되었다고 바꿈.
(그리고 희생자 페이지로 바꿔서 재진행함.)
아무튼,
이러한 디스크와 메모리 사이 페이지를 전송하는 동작을
스와핑, 페이징이라고 한다.
디스크 -페이지-> DRAM (페이징 되어 들어옴)
디스크 <-페이지- DRAM (페이징 되어 나감)
그래서 미스가 날때,
다른 페이지로 들어오는 그 마지막 순간까지 기다리는 전략을
요구 페이징(demand paging)이라고 한다.
지역성의 원리 덕에
서로 다른 페이지를 다루더라도
동작 집합(working set), 거주집합(resident set)으로 알려진
작은 활성화된 페이지 집합에서 작동하므로 괜찮다.
그러나... 동작 집합 크기가
물리 메모리 크기보다 크다면, 쓰래싱(thrashing)이 일어날 수도 있다..
사용자 모드에서는 한정된 메모리에만
읽고 쓰기 접근이 허용되는데,
다른 메모리에 접근이 허용되지 않은 상태로
간섭하게 된다면 발생.
(없는 인덱스에 넣으면 생기는 주된 오류라)
(아마 할당하지않은 메모리 썼음! 에 가까울 듯.)
그리고 9.9.1에서
free 함수의 메커니즘을 설명하는데
어차피 짜야할테니 간략히 적어두겠음...
....
일단 말록으로 배정할때마다
더블 워드 규칙(n진수 배로 할당)을 지킨다는 거랑
프리하면 포인터를 남겨뒀다가
새로 할당할 때 그 포인터 지점부터 다시 할당하지만
포인터를 재활용한다기보다는 지점만 활용하고
포인터 자체는 새로 만드네...
명시적 할당기들은 이러한 제한 사항에서 동작해야한다....
제한 사항.
임의의 요청 순서 처리
할당 받을 때 할당이 오면 반드시 free도 온다든가,
연속되어서 온다든가 하는 가정이 불가함.
요청에 즉시 응답
(영판에는 할당기는 재정렬, 또는 버퍼 등을 설정하기 힘들다고 되어있음.)
(한글판은 블록 정렬과 똑같은 문장이 써져있음.)
힙만 사용
할당기가 사용하는 비확장성 자료구조들은 힙 자체에 저장.
(선형 구조를 만드는 비선형 구조를 힙에 저장하여 구현해야한다는 말.)
블록 정렬하기(정렬 요건)
어떤 종류의 데이터 객체라도 저장할수 있도록 하는 방식으로 정렬.
할당된 블록을 수정하지 않기
가용 블록만 조작, 변경이 가능하다...
이미 할당된 블록을 압축 등이 불가하다.
목표는 이렇다.
처리량 극대화.
할당 요청의 최악 실행 시간은 가용 블록 수에 비례한다.
반환 요청 실행 시간은 보통 상수다.
메모리 이용도를 최대화하기.
가상 메모리는 유한한 자원이기때문에....
할당기가 힙을 유용하게 사용하는지 확인하는 기준은 최고 이용도, peak utilization 이다.
처리량을 극대화 하면 메모리 이용도가 떨어지거나 하므로
잘 균형을 잡아보자...
힙 이용도가 나쁜 주요 이유는 단편화.
가용 메모리가 있는데도 할당할 정도는 아닐 때를 가리키며,
두 종류가 있다.
할당된 블록이 데이터 자체보다 더 클 때.
(실제 넣으려는 데이터보다 할당하는 블록의 영역이 큰 현상인데)
(할당 블록의 최소 크기가 정해져있다면 발생할 수 있다.)
(실제로도 정렬 제한 사항, 즉 일정 워드 단위여야한다는 조건을 만족시키기위해)
(본래 데이터보다 더 큰 블록을 할당한다.)
할당 요청을 만족시킬 수 있는 메모리 공간이,
전체적으로는 충분히 존재하지만,
이 요청을 처리할 수 있는 단일 가용블록은 없는 경우.
(8개의 워드를 요청했을때,
실제로 존재는 하지만,
그 워드가 연속적으로 가용 가능하지는 않은 경우.)
...
외부 단편화의 측정이 더 어려운데,
미래에 어떻게 요청하느냐에 따라 외부 단편화의 양상이 나오기 때문이다.
(항상 4워드와 같거나 작은 블록만 요청한다면 괜찮겠지만,)
(그보다 큰 블록을 요청하면 발생할 수 있다.)
그래서 외부 단편화 측정은 어려우므로
할당기들은 대개 적은 수의 큰 가용 블록을 유지하는 방법을 채택한다.
이러한 이슈들은 서로 다른 가용 블록 구조와 가용되어있어,
다른 가용 블록 구조.. 묵시적 가용 리스트 등을 확인할 것이다.
충분히 큰 가용 블록이 있는 지
리스트에서 검색하는데,
이 검색 방법은 할당 정책에 따라 결정된다.
사용 되는 것이 first fit, next fit, best fit 세 가지다.
free list 를 처음부터 검색해서
크기가 맞는 첫번째 free 블록을 선택.
크기가 맞는 첫번째 free 블록을 선택하지만,
이전 검색이 종료된 지점에서 시작함.
first fit 보다 빠른 편.
(특히 앞부분이 작은 가용블록 더미면.)
그러나 first fit보다 메모리 이용도가 최악이다.
모든 가용 블록을 검사하며,
크기가 맞는 가장 작은 블록을 선택.
둘보다 좋은 메모리 이용도.
그러나 묵시적 가용 리스트 같은 단순 가용리스트에서는
힙을 완전히 다 검색해야한다는 단점.
힙을 모두 검색하지 않는 best-fit 정책을 단순화한
다단 가용 리스트조직(segregated free list)를 사용한다.
크기가 맞는 가용 블록을 찾았을 때,
가용 블록의 어느 정도를 쓸 것인가? 를 결정해야한다.
첫 번째는 이 가용 블록 전체를 쓰는 거지만,
그러면 내부 단편화가 생긴다...
일부 추가적 내부 단편화는 수용할 수도 있지만 말이다.
그렇지만 크기가 안 맞을 때는,
할당기는 대개 가용 블록을 두 부분으로 나눈다.
할당 블록 : 새로운 가용 블록!
요청한 블록을 찾을 수 없다면?
메모리에서 물리적으로 인접한 가용 블록들을 합쳐서(연결해서)
더 큰 가용 블록을 만드는 것이다....
이렇게 해도 충분히 큰 블록이 만들어지지 않거나,
가용 블록이 이미 최대로 연결되었다면,
할당기는 커널에게 sork 함수를 호출해서,
추가적 힙 메모리를 요청한다.
할당기는 추가 메모리를 한개의 더 큰 가용 블록으로 변환하여,
이 블록을 가용리스트에 삽입하여 쓴다!
할당 블록 반환시,
인접한 다른 가용 블록들이 있을 수 있다.
이러한 인접 가용 블록들이 false fragmentaiton, 오류 단편화 현상을 일으킬 수 있다. (작고 사용할 수 없는 가용 블록으로 쪼개진 많은 가용 메모리들.)
그래서 실용적 할당기라면 연결(coalescing)이라는 과정으로
가용 블록들을 통합한다.
언제 연결을 수행해야할지에 따른 정책이 필요하다.
즉시 연결이 간단하고 빨리 되지만,
일부 요청 패턴에 따라 연속적으로 연결되다가 잠시 후 분할되는 쓰레싱이
일어날 수 있다...(반복 할당, 반환 과정상 불필요한 분할과 연결 발생.)
어떻게 연결을 구현하나?
반환 하려는 블록을 현재 블록(current block)이라 하자.
그러면 다음 가용 블록을 메모리에 연결하는건 쉽다...
현재 블록 헤더 -> 다음 블록 헤더
를 가리키며,
다음 블록이 가용한지 결정하기 위해 체크될 수 있다...
...
...
음, 아마
헤더 자체가 연결되어있으니
다음 블록 헤더를 찾아갈 수 있으므로
연결할 수 있는지 아닌지를 찾기 쉽다는 말 같다.
그리고...
....
이전 블록과는 어떻게 연결하는가?
헤더를 사용하는 묵시적 가용 리스트의
유일한 방법은 전체 리스트를 검색해서 찾아내는 것이다...
그래서 free 호출이 힙의 크기에 비례하는 시간을 소모한다...
사실 이 보다 복잡한 가용리스트를 써도 상수 시간급이 될 순 없다.
이 때 나온 게 경계 태그다.
각 블록 끝부분에 풋터(footer, 경계 태그)를 추가한다.
이 풋터는 (이전 블록의) 헤더를 복사한 것이다.
....
블록이 풋터를 포함하고 있다면,
자신의 풋터를 조사해서 이전 블록 상태를 확인, 시작 위치를 결정할 수 있다.
(이 때의 시작 위치는 현재 블록 시작부분에서 한 워드 떨어진 곳에 위치.)
네 가지 경우를 생각해보자.
....
일단 가용 상태가 되면
앞, 뒤 헤더를 가용 상태로 변경하는데.
만약 뒷 블록의 헤더가 가용이면
헤더를 현재 블록 + 뒷 블록 크기로 바꿔주고.
앞 블록 헤더가 가용이면
헤더를 앞 블록 + 현재 블록 크기로 바꿔주고.
둘다 가용이면
헤더를 앞 + 현재 + 뒷 블록 크기로 바꿔준다.
....
잠재적 단점으로는 블록마다 헤더와 풋터를 유지해야해서
많은 작은 크기의 블록을 다룬다면 메모리 오버헤드가 생길 수 있다.
다행히,
최적화하는 방법이 존재한다.
(라고는 하지만 뭔소리인지 잘 모르겠군.)
(흠......)
(아마 가용 블록에 아예 풋터를 넣어주는게...)
(다음 가용 블록 할당시 경계 태그부터 읽을테니까
그때 경계 태그에 있는 사이즈를 읽고 이전 블록 크기가
이만큼이구나... 하고 이해하는 것 같다.)
(그 사이즈를 안다면 그 블록의 시작위치로 갈 수 있으니까.)
(그런데 할당했을 경우에는 사실 시작 위치로 돌아갈 이유가 없으니까)
(그 때는 할당 안하고)
(나중에 반환했을때,)
(비어진 블록 끝에만 풋터를 달아놔서 오버헤드를 줄이는 것 같다.)
대개 할당기는
블록 경계를 구분하며,
할당 블록과 가용 블록을 구분하는,
데이터 구조를 필요로 한다.
대부분 이 정보를 블록 내에 내장한다.
이러한 구조를 묵시적 리스트라고 하는데,
그 이유는 free 블록이 헤더 내 필드에 의해 묵시적으로 연결되기때문이다...
(따로 연결시키는 직접적 구조가 없지만 이러한 나열로
연결과 같은 형태를 띄기 때문인 것 같다.)
할당기는 간접적으로 Free 블록을
힙 내의 전체 블록을 다니면서 방문할 수 있다...
단, 특별히 표시한 마지막 블록이 필요하다는 점을 유의하자.
(종료하는 헤더는 할당 비트가 세트되었고, 크기는 0을 가짐.)
(0/1 < 크기 0이고, 1로 할당 처리됨.)
묵시적 가용 리스트의 장점은 단순성이다.
단, 단점은 가용리스트 탐색 비용이 전체 할당 블록과 가용 블록 수에 비례한다.
최소 블록 크기를 결정해야한다는 점이 중요하다.
묵시적 가용 리스트는 범용 할당기에는 적합하지 않다.
더 좋은 방법은,
가용 블록들을 명시적 자료구조로 구성하는 것이다.
가용 블록 본체는 프로그램에서
사용 중이지 않으므로,
자료구조를 구현하는 포인터를 가용 블록 본체 내에 저장하는 것이다.
예를 들어,
가용 블록 내 pred(predeccessor)와 succ(successor) 포인터를
포함하는 이중 연결 가용 리스트로 구성할 수 있다.
이렇게 하면
first fit에서 전체 블록 시간 비례를
가용 블록 수 비례로 줄일 수 있다.
반환하는 건 가용 리스트 내 블록 정렬 정책에 따라 달라진다.
한 가지 접근 법은,
리스트를 새롭게 반환한 블록을 리스트 시작 부분에 삽입하여,
후입선출 LIFO 순으로 유지한다.
LIFO와 first fit 배치를 사용하면,
할당기는 대부분 최근에 사용된 블록을 먼저 조사하고 반환한다.
여기서 경계 태그도 사용하면 연결도 상수 시간에 수행할 수 있다.
두번째 접근법은, 리스트를 주소 순으로 관리한다.
리스트 내 각 블록의 주소가 다음 블록 주소보다 작다.
이 경우 블록 반환시 적당한 선행블록을 찾는 선형 검색 시간이 든다.
대신 주소 순서를 사용하는 first fit이 best fit의 근접하는 메모리 이용도를 갖는다.
주소 순서가 따로 주소 테이블인지 뭔지 물어보니
이전 블록이 다음 블록으로 갈수 있는 포인터.. .next 같은게 있다고
이해하면 될 것 같다.
전자는 힙 포인터로만 관리되는 거고...
(솔직히 포인터로만 관리한다니 포인터 망가지는게
얼마나 큰일인지 좀 알것 같군.)
리눅스에서는,
가상 메모리 내용을 디스크 객체에 연결해서 초기화한다...
(....?)
다음 두 종류의 객체 중 하나로 매핑된다.
리눅스 파일 시스템 내 일반 파일
가상 메모리는 디스크 파일의 연속된 section으로 매핑되는데,
이 section은 page 크기의 조각들로 나누어지고,
이 page들은 가상 페이지의 초기 내용을 담고 있다.
CPU가 처음 접근까지는 어떤 물리 메모리와도 매핑되지 않는다.
(가상 메모리-section(물리메모리))
(가상메모리 페이지1-section_1)
(가상 메모리 페이지2-section_1)
(가상메모리 페이지k : 가상 페이지의 초기 내용)
무기명 파일(Anonymous file)
무기명 파일 매핑이 가능.
커널이 만든 것이며, 이진수로 0을 포함하고 있다.
CPU가 이 페이지에 접근 시,
(비어있으니까) 커널은 물리 메모리 내 적당한 희생자 페이지를 찾는데,
만약 희생자 페이지가 더럽다(dirty..) 희생자 페이지를 내보내고(swap out)
희생자 페이지를 이진수 0으로 덮어쓰고,
이 페이지가 존재한다고 페이지 테이블을 갱신한다.
하지만 실제로 어떤 데이터도 이동하지않았기 때문에,
무기명 파일로 매핑된 영역 내 패이지들은 demand-zero page 라고 불린다.
...
....
아아
알았다
객체 매핑할 때
int a같은걸 했다고 할때
무기명은 일단...
이 공간 쓸게!
라고 해두었지만 딱히 아직 뭐 배정받지도 않고 아무것도 안했는데
여기 쓰겠다! 라고 하는 순간
적당한 아무 물리 메모리 가져와서
더러우면 0으로 싹 초기화해서 페이지 할당했음! 하는 거구나.
흠...
무기명 파일을 사용한 프로세스 간 데이터 공유는
1)무기명 파일을 메모리에 매핑 2)mmap 함수로 무기명 파일을 가상 메모리에 매핑 3) 각 프로세스는 매핑된 메모리 영역 사용 가능 4)동기화 메커니즘으로 동기화, 세마포어, 뮤텍스 등 동기화 기법 사용.(상호배제나 동기화 구현)
흠, 일반 파일과 다른 점은.
일반 파일은 이미 그 파일이 존재해야!
그 부분을 읽거나 쓰기를 할 수 있는 거거든.
또 메모리가 해제되어도 이미 존재하고,
쓰는대로 메모리에 로드되고.
근데 무기명 파일은 엔트리가 딱히 없고,
메모리 해제되면 사라지고,
맨처음에는 가상 메모리에만 존재해서,
프로세스간 통신이나 프로세스 내 임시 데이터를 다룰 떄 사용.
#include <unistd.h>
#include <sys/mman.h>
void *mmap(void *start, size_t length,
int prot, int flag, int fd, off_t offset);
커널에 새 가상 메모리 영역 생성을 요청하는 함수.
start로 시작하는 영역을 선언,
새 영역을 가리키는 파일 식별자 fd로 연속된 객체들을 매핑해줄 것을 요청.
연속된 객체들은 바이트 길이를 가지는데,
offset 바이트의 오프셋으로 시작.
start 주소는 힌트에 가까운 것으로, 대개 NULL로 명시.
prot은 새롭게 매핑한 가상 메모리 영역의 접근 권한을 설명하는 비트다.
(대응되는 영역 구조체 vm_prot 비트인데)
(prot_exec : CPU가 실행 가능)
_read : 읽기 가능
_write 쓰기 가능
_none : 접근 불가.
flag는 매핑한 객체의 유형을 설명하는 비트를 포함.
예를들어, MAP_ANON 플래그로 세팅한다면 무기명 객체임을 가리킨다.
MAP_PRIVATE는 copy-on-write 객체를, shared는 공유 객체를 나타낸다.
그래서 대개 이러한 예시로 쓴다.
bufp = Mmap(NULL, size, PROT_READ, MAP_PRIVATE|MAP_ANON, 0,0);
....
int munmap(void *start, size_t length);
munmap은 가상 메모리 영역들을 삭제한다.
start에서 삭제하며, length 바이트로 이루어진 영역을 삭제하며,
이후 삭제된 영역을 참조하면 segement fault 를 일으킨다.
I/O 디바이스 동작과,
어떻게 이들이 프로그램이 되는가?
....
CPU가 디스크에서 데이터를 읽을 때
발생하는 단계들을 살펴보자.
CPU는 memory-mapped I/O 라 부르는 기술,
메모리 매핑 I/O 기술을 사용해서,
I/O 디바이스에 명령을 보낸다.
(여기서 이 기술 시스템의 주소 공간에
한 개의 주소 블록이 I/O 디바이스와
통신하기 위해 예약되어있다.
이 각각의 주소를 I/O 포트(port)라고 한다.
각 디바이스는 한 개 이상의 포트와 관련된다.)
예제로 살펴보자.
디스크 컨트롤러가 포트 0xa0에 매핑.
CPU가 디스크 읽기를 개시.
보내는 인스트럭션은 3개.
1 : 읽기를 개시하라. 그리고 interrupt로 쓸 매개 변수 첨부.
2 : 읽어야할 논리 블록 번호 지정.
3 : 디스크 sector 내용이 저장되어야할 메인 메모리 주소 지정.
(간단히 말하자면 디스크에서 읽어온 내용을 메모리에 저장해둬야할테니.)
그리고 읽기를 진행하면서
다른 작업도 수행한다.(시간이 오래 걸리기때문!)
디스크 컨트롤러는 CPU에게 명령을 받은 후,
논리블록 번호를 섹터 주소로 번역하고,
(가상메모리 블록 - 물리메모리 주소같은것인듯)
CPU의 개입없이 이걸 메인 메모리에 직접 전송한다.
이러한,
어떤 디바이스가 스스로 읽기, 또는 쓰기 버스 transaction 하는 걸
직접 메모리 접근, DMA 라고 한다.
또 이러한 데이터 전송을 DMA 전송이라고 한다.(DMA transfer)
전송을 완료한 후,
메인 메모리 저장까지 끝내면,
디스크 컨트롤러는 CPU에 인터럽트 신호를 보내서 알린다.
(인터럽트가 CPU 칩 외부 핀에 신호를 보낸다.
현재 하고 있는 일을 멈추고 운영 체제 루틴으로 이동하게 한다.
이 루틴에서 I/O가 끝났단 사실을 기록하고 다시 CPU로 제어를 넘긴다.)
디스크 읽기 쓰기 외에도,
네트워크, 그래픽, 오디오, 메모리 간 복사...에도 사용된다.
큰 데이터 블록을 메모리 복사에도 DMA를 쓴다는데...
흠, OS에서 쓰려나. 근데 사실 OS에서 쓴대도
디스크 입출력 디바이스 구현에 더 쓸 거같네.
물리적으로 네트워크는 기하학적 위치로 구성된 계층구조 시스템이다...
진짜 무슨 소리람.
하위 수준은 LAN, Local Area Network로
빌딩이나 캠퍼스에 설치된다.
가장 대중적인 LAN 기술은 현재까지는 이더넷, Ethernet이다.
Ethernet segement는
몇개의 전선들과 hub라고 부르는 작은 상자로 구성된다.
대개 방이나 빌딩 층 같이 작은 지역에 설치,
각 전선은 모두 같은, 최대 비트의 대역폭을 가지는데,
대개 100Mb/s~ 1Gb/s이다.
한쪽 끝은 호스트의 어댑터에,
다른 끝은 허브의 port에 연결된다.
허브는 각 포트에서 수신한 모든 비트를
다른 모든 포트로 복사한다...
그래서 모든 호스트는 모든 비트로 볼 수 있다..
엥? 그럼 내가 보낸걸 다른 호스트도 볼수 있다고?
했더니 맞다고 한다......
....
각 이더넷 어댑터는
어댑터의 비휘발성 메모리에 저장된
전체적으로 고유한 48비트 주소를 가진다.
(간단히 말하자면)
(연결하는 기기 자체에 안 날아가는 고유 48비트 주소가 있다는거군.)
호스트는 프레임(frame)이라고 부르는 비트를,
segment의 다른 호스트에게 보낼 수 있는데,
각 프레임은 헤더(header) 를 가지며,
프레임 소스, 목적지, 프레임 길이가 적혀있고,
그 뒤에 데이터 비트를 갖는다.
모든 호스트 어댑터가 이 프레임을 볼수 있지만
목적지 호스트만 실제로 이것을 읽는다.
...
그리고,
Bridge라는 작은 상자를 이용해
다수의 이더넷 세그먼트를 연결하여, 브릿지형 이더넷(bridged Ethernets)이라고 하는 더 큰 LAN을 구성할 수 있으며, 전체 빌딩이나 캠퍼스 규모로 설치가 가능하다.
일부 선은 브릿지와 브릿지를, 다른 선은 브릿지와 허브를 연결한다.
각 선의 대역폭은 다를 수 있다.(브릿지와 브릿지간이 더 빠르다.)
분산 알고리즘에 따라
기본적으로는 모든 포트 내용을 수신해도
브릿지가 여기까지 건널필요 없다면 무시하는 등이 가능하다.
그렇게 있다보면
서로 이어지지 않는 LAN들은
라우터(routers)라고 부르는 컴퓨터에 의해 연결된다.
라우터는 네트워크 간 연결을 구성한다.
(상호 연결 네트워크, interconnected network)
각 라우터는 연결되는 각 네트워크에 대한 어댑터, 포트를 가진다.
고속의 point-to-point 전화 연결이 가능하고,
이쯤 되면 WAN, Wide-Area-Network라 부른다.
라우터는 임의의 LAN과 WAN들로부터 internet을 만들기 위해
사용될 수 있다.
internet의 중요 특성은,
매우 다르고 호환이 안 되는 기술들이 여러가지 LAN과 WAN으로
구성되었다는 점이다.
물리적으로는 모든 호스트에 연결되어있다고 해도,
어떻게 소스 호스트가 모든 호환이 안되는 네트워크를 지나서
다른 목적지 호스트로 전송하는가?
호스트와 라우터에서 돌고 있는 소프트 웨어로,
여러가지 네트워크 간 차이를 줄여준다.
이 소프트웨어는 호스트들과 라우터들이
데이터를 전송하기 위해 어떻게 협력하는지 결정하는 프로토콜을 구현한다.
이 프로토콜은 두 가지 기본 기능을 제공한다.
명명법(Naming Scheme)
호스트에 주소 할당 시 LAN 기술에 따라 달라진다.
internet 프로토콜은 호스트 주소를 위한 통일된 포맷을 정의해서
이 차이를 줄인다.
각 호스트는, 자신을 유일하게 식별하는 Internet 주소 최소한 한개가 할당된다.
전달 기법(delivery mechanism)
서로 다른 네트워킹 기술은 비트 인코딩, 프레임 내 패키징 방법이 다르다.
internet 프로토콜은 데이터 비트를 패킷(packets) 이라 부르는
비연속적 단위로 묶는 통일된 방법을 정의해 이 차이를 줄인다.
패킷은 패킷 크기, 소스 및 목적지 호스트 주소를 포함하는 헤더와,
소스 호스트가 보낸 데이터 비트를 포함하는 데이터로 구성된다.
...
그래서 실제 이송 과정을 설명한다.
데이터 -시스템콜-> 프로토콜 소프트웨어 -
-데이터+internet 헤더+LAN 헤더(프레임헤더)->
LAN1 adapter -> 프로토콜 소프트웨어 ->
LAN2 adapter...
...
형식으로 넘어가는데,
LAN1 프레임에서 '데이터'에 해당하는게
internet 패킷, 즉 데이터+인터넷 주소 인데,
이런 것을 캡슐화, encapsulation 이라고 한다.
internetworking의 근본적 통찰이라고 하는데,
아마 서로 다른 비 호환성 기술로도
목적 호스트에 도달하는... 아이디어라고 볼 수 있을 것 같다.