벡터 입출력
- 벡터 입출력은 한번의 시스템콜을 사용해서 여러개의 버퍼 벡터에 쓰거나 여러 개의 버퍼 벡터로 읽어 들일때 사용
- 2장의 표준 읽기 쓰기는 선형 입출력이라 하며 이에 비해 다음의 장점이 있다.
- 여러 필드에 걸쳐 데이터가 분리되어 있다면 직관적인 방법으로 조작 가능
- 여러번의 선형입출력 대체 가능하므로 효율적
- 시스템콜의 횟수를 줄이고 선형입출력 구현에 비해 최적화 되어 효율적
- 선형 입출력과 대조적으로 벡터 입출력 연산은 원자성을 보장
readv()와 writev()
|
|
- readv()는 파일 디스크립터 fd에서 데이터를 읽어서 count 개수만큼 iov버퍼에 저장
- writev()는 count 개수의 iov버퍼 데이터를 파일 디스크립터 fd에 씀
- iovec구조체는 세그멘트라고 하는 독립적으로 분리된 버퍼, 세그멘테이션 집합을 벡터라 부름
- readv(), writev()는 성공 시 읽거나 쓴 바이트 개수 반환, 에러 발생 시 -1을 반환 후 errno를 설정
epoll
- 커널 2.6에서 poll과 select의 한계를 극복하기 위한 epoll 도입
- poll과 select가 실행시 마다 전체 파일 디스크립터를 요구하는 문제, epoll은 실제 검사할 파일디스크립터를 등록 부분을 분리
epoll 인스턴스 생성
|
|
- epoll 인스턴스 생성 및 그 인스턴스와 연관된 파일 디스크립터를 반환, 에러 발생시 -1 반환 후 errno를 설정
- flag는 EPOLL_CLOSEXEC만 유효, 새 프로세스가 실행될 때 이 파일을 자동적으로 닫아 줌
- epoll_create1에서 반환되는 파일 디스크립터는 사용 완료후 close()로 닫아 줄 것
epoll 제어
|
|
- epoll context에 파일 디스크립터를 추가하거나 삭제할때 사용, 호출이 성공하면 0을 반환, 실패시 -1을 반환 후 errno를 설정
- epfd : 호출이 성공시 epoll 인스턴스는 epfd 와 연결
- op : 파일드스크립터 fd가 가리키는 파일에 대한 작업을 명시
- EPOLL_CTL_ADD 감시 추가, EPOLL_CTL_DEL 감시 삭제, EPOLL_CTL_MOD 감시 이벤트 갱신
- epoll_event 구조체 event 필드는 감시할 이벤트 목록으로 OR연산으로 묶을 수 있음
- EPOLLIN 지연되지 않고 읽기 가능 감시, EPOLLOUT 지연되지 않고 파일 쓰기 가능 감시, EPOLLPRI 읽어야할 OOB데이터 존재 여부 감시
- EPOLLERR 에러 상황 감시(default), EPOLLHUP 행업 감시(default)
- EPOLLET 감시 시 에지 트리거 사용, EPOLLONESHOT 한번 만 감시(다시 활성화 하려면 EPOLL_CTL_MOD로 다시 설정 필요)
- epoll_evnet 구조체 data 필드는 사용자를 위한 필드로 이벤트가 발생해서 사용자에게 반환될때 함께 반환, 보통 data.fd를 fd로 채워씀
epoll 이벤트 대기
|
|
- epoll_wait()를 호출하면 timeout 밀리 초 동안 epfd와 연관된 파일의 이벤트 대기, 호출이 성공 시 발생 이벤트 갯수를 실패시 -1을 반환 후 errno를 기록
- 호출 성공 events에 해당 이벤트를 기록, 최대 maxevent만큼의 이벤트 기록
- timeout시 반환값음 0
에지 트리거와 레벨트리거
- 트리거 종류, 당신이 생가하는 그거
메모리에 파일 매핑
mmap()
|
|
- mmap()을 호출하면 파일디스크립터 fd가 가리키는 파일의 offset위치에서 len 바이트만큼 메모리에 매핑하도록 요청
- addr이 포함되면 메모리에서 해당주소를 선호한다고 커널에 알림(단지 제안일 뿐)
- mmap()은 성공시 맵핑의 실제 시작주소를 반환, 실패 시 MAP_FAILED 반환 후 errno를 설정
- prot인자는 맵핑에 원하는 메모리 보호 정책을 명시, OR 연산으로 묶어서 설정 가능
- PROT_NONE 접근불가(거의 미사용), PROT_READ 읽기 가능, PROT_WRITE 쓰기가능, PROT_EXEC 실행가능
- flags 인자에는 맵핑의 유형 및 동작 요소
- MAP_FIXED : addr인자를 제안이 아닌 요구사항으로 취급, 커널이 해당주소를 확보 못할 시 호출 실패
- MAP_PRIVATE : 맵핑 미공유, 파일은 copy-on-write로 매핑, 매핑된 내용에 변경이 발생하더라도 실제파일이나 다른 프로세스에 미영향
- MAP_SHARED 같은 파일을 맵핑한 모든 프로세스와 맵핑 공유, 변경이 일어나면 실제 파일에서도 동일한 내용 기록
- mmap()시스템콜은 페이지를 다루며 addr과 offset은 페이지 크기로 정렬되어야 함(정렬이 되지않은 값이 전달되면 정렬된 값으로 할당)
- 페이지의 크기를 구하는 방법은
sysconf(_SC_PAGESIZE)
,getpagesize()
등이 있음(이식성을 고려 시 sysconf를 사용) - mmap()관련 시그널은 SIGBUS(프로세스가 유효하지 않은 맵핑영역 접근), SIGSEGV(읽기전용 매핑영역에 쓰려고 할 때)가 있음
munmap()
|
|
- munmap()은 addr에서 len 바이트만큼 이어지는 프로세스 주소 공간 내 존재하는 페이지를 포함하는 맵핑을 해제
- 성공시 0을 반환, 실패 시 -1을 반환하고 errno를 적절한 값으로 설정
mmap 장점
- read/write 시스템콜을 사용할 때 발생하는 불필요한 복사 방지
- 잠재적인 페이지 폴트를 제외하면 매핑된 파일을 읽고 쓰는데 다른 시스템 콜이나 컨택스트 스위칭이 발생하지 않음
- 여러 프로세스가 같은 객체를 메모리에 매핑한다면 데이터는 모든 프로세스 사이에서 공유
- lseek()같은 시스템콜을 사용하지 않고 매핑영역 탐색 가능
mmap 단점
- 메모리 매핑은 페이지 크기의 정수배만 가능하므로 작은 파일이 많다면 공간이 낭비됨
- 32비트 주소공간에서 다양한 크기의 수많은 매핑 사용시 주소공간이 파편화 됨(64비트 주소공간에서는 상관없음)
- 메모리 매핑과 관련된 자료구조를 커널 내부에서 생성, 유지하는 오버헤드가 있음
매핑 크기 변경
|
|
- mremap()은 [addr, addr+old_size)에 매핑된 영역을 new_size만큼의 크기로 변경
- flags인자는 0이거나 MREMAP_MAYMOVE(크기변경 수행하는데 필요시 맵핑의 위치가 변경되도 됨)가 될 수 있다.
- 성공 시 크기가 조정된 메모리 맵핑의 시작주소 반환, 실패시 MAP_FAILED 반환 후 errno를 설정
매핑의 보호모드 변경
|
|
- mremap()은 [addr, addr+len)내에 포함된 메모리의 페이지 보호모드를 prot로 변경(추가 아님)
- 성공시 0을 반환, 실패 시 -1을 반환하고 errno를 설정
파일과 매핑의 동기화
|
|
- msync()는 addr에서 len 바이트 만큼 매핑된 파일이나 파일의 일부를 디스크로 동기화
- msync()를 호출하지 않으면 매핑이 해제되기 전까지는 맵핑된 메모리에 쓰여진 내용이 디스크로 반영됨을 보장할 수 없다.
- flags는 동기화 방식 제어
- MS_SYNC 동기화 호출, MS_ASYNC 비동기식 동기화, MS_INVALIDATE 맵핑의 캐시 복사본을 모두 무효화
- 성공시 0을 반환, 실패 시 -1을 반환하고 errno를 설정
맵핑의 사용처 알려주기
|
|
- madvice() 시스템콜은 주어진 힌트에 따라 맵핑의 동작 방식을 최적화(캐시 및 미리읽기 방식) 가능
- advice 인자는 커널에 알려줄 힌트 기술
- MADV_NOMAL : 일반적 영역, 적당양 미리 읽기
- MADV_RANDOM : 랜덤하게 접근하는 영역, 미리읽기 미사용(최소한의 데이터만 가져옴)
- MADV_SEQUENTIAL : 순차적으로 접근하는 영역, 공격적인 미리 일기 수행
- MADV_WILLNEDD : 곧 접근하는 영역, 미리읽기 활성화 후 주어진 페이지를 메모리로 읽음
- MADV_DONTNEED : 당분간 접근하지 않는 영역, 페이지와 곤련된 자원 해제 후 동기화 되지 않은 페이지 버림
- 성공시 0을 반환, 실패 시 -1을 반환하고 errno를 설정
일반 파일 입출력에 대한 힌트
posix_fadvise() 시스템콜
|
|
- fd의 [offset, offset+len] 범위에 대한 흰트를 제공
- 주로 len을 0으로 넘겨서 파일 전체에 대한 힌트를 제공하는 방식으로 사용
- 힌트에 대해 커널이 등으하는 방식은 구현에 따라 커널버전에 따라 다름
- POSIX_FADV_NORMAL : 힌트없음, 적당한 미리읽기
- POSIX_FADV_RANDOM : 랜덤 접근, 미리읽기를 하지 않고 매번 일기마다 최소한의 데이터만 읽음
- POSIX_FADV_SEQUENTIAL : 순차접근, 미리읽기 윈도우 크기를 두배로 늘림
- POSIX_FADV_WILLNEED : 곧 접근함, 미리읽기 활성화하고 주어진 페이지를 메모리로 읽음
- POSIX_FADV_NOREUSE : 곧 한번만 접근, POSIX_FADV_WILLNEED와 동일하게 동작
- POSIX_FADV_DONTNEED : 당분간 안 접근함, 범위내의 캐싱 중인 데이터를 페이지 캐시에서 제거
- 성공 시 0 반환, 실패 시 -1 반환 후 errno 값을 설정
readahead() 시스템콜
|
|
- 리눅스 전용으로 POSIX_FADV_WILLNEED와 동일한 방식을 제공하기 위해 사용
- fd가 가리키는 파일의 [offset, offset+counter) 영역의 페이지를 캐시 생성
부담없이 힌트를 사용하자
- 책에서는 힌트는 도움이 많이 된다고 기술
- POSIX_FADV_WILLNEED를 이용하여 읽으려는 파일을 미리 캐시에 넣어 어플리케이션에서 블로킹을 방지하거나
- 비디오 스트림 기록 같은 경우 POSIX_FADV_DONTNEED로 캐시에서 제거 할 수 있음
동기화, 동기식, 비동기식 연산
- 동기식, 비동기식 용어는 입출력 연산이 반환하기 전 어떤 이벤트(ex. 데이터 저장)을 기다리는지 여부
- 동기화, 비동기화 용어는 정학한 어떤 이벤트(ex. 데이터를 디스크에 기록)이 발생해야 함을 나타냄
- 책에 표를 읽어보자
비동기식 입출력
- aio라이브러리는 비동기식 입출력을 요청하고 작업이 완료되면 알림을 받는 함수를 제공
|
|
입출력 스케줄러와 성능
디스크 주소 지정 방식
- 하드디스크는 CHS(cylinder, Head, Sector) 주소 지정방식 사용하며 요즘은 이 값대신 유일한 블록 번호를 CHS에 맵핑
입출력 스케줄러 동작 방식
- 입출력 스케줄러는 병합과 정렬이라는 두가지 기본 동작 수행(하드디스크의 입출력 성능 극대화를 위함)
- 병합은 둘 이상의 인접한 입출력 요청을 단일 요청으로 합침
- 대기 중인 입출력 요청을 블록 순서의 오름 차순으로 정렬
읽기 개선
- 입출력 스케줄러에는 다음과 같은 방식이 있다.
- 데드라인 입출력 스케줄러
- 예측 입출력 스케줄러
- CFQ 입출력 스케줄러
- Noop 입출력 스케줄러
입출력 스케줄러 선택과 설정
- 기본 입출력 스케줄러는 부팅 시 커널 명령행 인자인 iosched를 통해서 선택 가능
- `/sys/block/[device]/queue/scheduler 값을 변경해서 입출력 스케줄러 선택가능
입출력 성능 최적화
- 자잘한 연산을 묶어 입출력 연산을 최소화 하거나 입출력을 블록 크기에 정렬되도록 수행하거나 3장의 사용자 버퍼링, 벡터 입출력, 2장의 위치를 지정한 입출력, 비동기식 입출력 등을 고려할 수 있다.
- 사용자 영역 어플리케이션에서 커널과 유사한 방식을 사용하여 더 나은 성능을 얻을 수 있도록 노력해야함
- 사용자 영역에서 입출력 스케줄링 하기
- 경로로 정렬하기
- inode로 정렬하기
- 물리 블록으로 정렬하기