728x90

5. Asynchronous I/O


Write
의 동기화 특질

동기화(디스크까지 보장)

비동기화(버퍼까지 보장)

동기식

(쓰기연산 완료 후 결과 return)

쓰기 연산은 자료를 디스크에 강제로 쓰기 전까지 결과를 반환하지 않는다. (파일을 열 때 O_SYNC를 명세한 경우)

TM기 연산은 자료가 커널 버퍼에 저장될 때까지 결과를 반환하지 않는다.(일반적인 동작)

비동기식

(요청 큐에 들어가자마자 결과 return)

쓰기 연산은 요청이 큐에 들어가자마자 결과를 바로 반호나한다. 일단 쓰기 연산이 최종적으로 수행되면, 자료는 디스크네 확실히 저장됨

쓰기 연산은 요청이 큐에 들어가자마자 결과를 바로 반환, 일단 쓰기 연산이 최종적으로 수행되면, 자료는 최소한 커널 버퍼에 확실히 저장됨

Read의 동기화 특질

동기화

동기식

읽기 연산은 최신 자료가 지정된 버퍼에 저장될 때까지 결과를 반환하지 않는다(일반적인 동작방식)

비동기식

읽기 연산은 요청이 큐에 들어가면 바로 결과를 반환(읽기 연산이 최종적으로 수행될 때 최신 자료를 반환)


aio interface (linux
에서는 O_DIRECT flag로 연 파일에서만 aio를 지원)

#include <aio.h>

/* asynchronous I/O control block */

struct aiocb {

int aio_filedes; /* file descriptor *

int aio_lio_opcode; /* operation to perform */

int aio_reqprio; /* request priority offset *

volatile void *aio_buf; /* pointer to buffer */

size_t aio_nbytes; /* length of operation */

struct sigevent aio_sigevent; /* signal number and value */

/* internal, private members follow... */

};

int aio_read (struct aiocb *aiocbp);

int aio_write (struct aiocb *aiocbp);

int aio_error (const struct aiocb *aiocbp);

int aio_return (struct aiocb *aiocbp);

int aio_cancel (int fd, struct aiocb *aiocbp);

int aio_fsync (int op, struct aiocb *aiocbp);

int aio_suspend (const struct aiocb * const cblist[],

int n,

const struct timespec *timeout);


thread
기반 (aio interface와 비슷하지만 thread관리에 더 큰 overhead가 발생)
 

1. worker thread pool 생성

2. work queue I/O operation을 넣는 인터페이스 집합을 구현

3. 각 인터페이스가 유일한 I/O id return하도록 구현(operation 구분), 개별 작업자 스레드(worker thread) queue 앞부분에서 request submit(제출) 후 완료될 때까지 대기

4. 완료되면 연산결과(반환값, 오류코드, 읽은 자료 등)를 결과 큐에 추가

5. 결과 큐에서 상태정보를 조회하는 인터페이스 집합을 구현.

 

728x90
728x90

4. File advice (일반적인 파일입출력을 위한 advice)

posix_fadvise (커널 2.5.60부터 등장)

#include <fcntl.h>

int posix_fadvise(int fd, off_t offsert, off_t len, int advice);

advice 옵션 (POSIX_FADV_NOREUSE가 추가되며 나머지는 madvice와 같다; 커널이 하는 동작을 application에서 한다는 차이가 있다.)
 

POSIX_FADV_NOREUSE application이 당분간은 정해진 범위에 있는 자료에 단 한번만 접근하려고 한다.

advice 옵션 (2.6 커널에서의 동작)

POSIX_FADV_NORMAL 커널이 적절히 미리읽기를 수행(평상시와 동일)

POSIX_FADV_RANDOM 커널이 미리읽기를 비활성화, 매번 물리적 연산이 일어날 때마다 최소 자료만 읽음

POSIX_FADV_SEQUENTIAL 커널이 공격적인 미리읽기 수행, 미리읽기 범위 크기를 두배로 늘림

POSIX_FADV_WILLNEED 커널이 미리읽기 작업을 시작해서 페이지를 메모리로 읽음

POSIX_FADV_NOREUSE 현재는 POSIX_FADV_WILLNEED와 동일(향후 한번만 사용한다는 방식을 이용하여 추가적인 최적화를 추가할 가능성 있음)

POSIX_FADV_DONTNEED 커널이 페이지 캐시에서 해당 영역에 있는 자료를 비움(madvise()의 DONTNEED와 다르게 동작)
 

readahead(리눅스용 인터페이스)

posix_fadvise()의 POSIX_FADV_WILLNEED와 동일한 동작: kernel 2.6 이전에는 posix_fadvise()대신 readahead() 사용함.
 

#include <fcntl.h>

ssize_t readahead(int fd, off64_t offset, size_t count);
 

해당 영역에 페이지 캐시가 자리 잡도록 만든다.
 

* 파일의 일부를 읽기에 앞서 POSIX_FADV_WILLNEED로 페이지 캐시에 파일을 읽어 들이도록 지시하면 입출력은 비동기식 background로 수행된다. application이 최종적으로 파일에 점근할 때, 입출력 차단 없이 연산이 완료될 수 있다.
 

*연속적으로 비디오를 디스크에 스트리밍하는 경우(많은 자료를 읽고 쓴 다음), POSIX_FADV_DONTNEED로 페이지 캐시에서 파일 조각을 비우라고 지시. application이 다시 접근할 의도가 없다면 페이지 캐시를 다른 용도로 사용할 수 있게 비워서 효율을 높임.
 

*파일 전체를 읽을 때는 POSIX_FADV_SEQUENTIAL을 제공

728x90

'Programming > linux왕초보' 카테고리의 다른 글

find 명령어(linux)  (4) 2012.04.03
ALSA SoC Layer  (0) 2012.04.03
ubuntu 바탕화면에 휴지통 꺼내놓기  (0) 2012.03.30
ALSA  (0) 2012.03.26
5. Asynchronous I/O  (0) 2012.02.24
3. Memory mapped I/O  (0) 2012.02.17
2. epoll(), select(), poll()  (0) 2012.02.08
make[1]: warning: Clock skew detected. Your build may be incomplete.  (0) 2012.02.03
1. Scatter/gather I/O(vectored I/O)  (0) 2012.01.29
리눅스 파일 입출력 (Linux File I/O)  (0) 2012.01.29
728x90

3. Memory mapped I/O
 

#include <sys/mman.h>

void * mmap(void* addr,//힌트일 뿐이며 보통 NULL을 넘긴다.

size_t len,

int prot,//접근권한

int flags,//추가적인 동작 방식

int fd,//대상

off_t offset);//시작위치

prot 옵션

PROT_NONE 권한없음

PROT_READ 읽기 가능한 페이지

PROT_WRITE 쓰기 가능한 페이지

PROT_EXEC 실행 가능한 페이지

flags 옵션

MAP_FIXED addr을 요구사항으로 취급하다록 지시. 해당주소에 mapping하지 못하면 호출실패

프로세스 주소공간에 대한 상세한 지식을 요구, 호환성이 떨어짐.

MAP_PRIVATE mapping을 공유하지 않음. 파일은 write후 복사로 mapping되며 변경된 메모리 속성은 실제 파일이나 다른 프로세스의 map에는 반영되지 않는다.

MAP_SHARED 동일파일을 mapping한 모든 프로세스들이 mapping을 공유.

read시 다른 프로세스가 write한 내용도 반영

mapping은 page단위로 구성됨 -> addr과 offset은 반드시 page size의 정수배가 되어야 함

ex)

void *p;

p = mmap (0, len, PROT_READ, MAP_SHARED, fd, 0);

if(p == MAP_FAILED) //ERROR

#include <unistd.h>

long sysconf(int name); // page size를 얻기위한 표준 method(POSIX)

int getpagesize(void); // page size를 얻기위한 표준 method(linux)

<asm/page.h>

PAGE_SIZE //정의된 정적 매크로, 실행시점이 아니라 컴파일 시점에서

시스템 페이지 크기를 조회

#include <sys/mman.h>

int munmap (void *addr, size_t len);

mmap()으로 만들어진 mapping 제거

ex)

#include <stdio.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcn시.h>

#include <unistd.h>

#include <sys/mman.h>

struct stat sb;

off_t len;

char *p;

int fd;

fd = open (filename, O_RDONLY);

if(fstat(fd, &sb) == -1) // ERROR

if(!S_ISREG (sb.st_mode)) //ERROR

p = mmap (0, sb.st_size, PROT_READ, MAP_SHARED, fd, 0);

if(p == MAP_FAILED) //ERROR

if(close (fd) == -1)//ERROR

for(len = 0; len < sb.st_size; len++)

putchar(p[len]);

if(munmap(p, sb.st_size) == -1) //ERROR



장점

- 사용이 편리(다른 작업없이 메모리에 접근하기만 하면 된다. 포인터로 접근)

- 메모리 절약(추가적인 버퍼 메모리가 필요 없다.)

- 속도가 빠르다. (파일이 클수록, OS영역에 캐싱되어 있을수록 속도차이가 난다.)

- 다중 프로세스가 동일 객체를 메모리에 mapping할 때, 모든 프로세스가 자료를 공유한다

단점

- memory와 I/O가 주소공간을 공유(사용 가능한 주소 공간이 제한됨)

- page size의 정수배만 가능하다.(여분의 공간이 낭비됨)

- 연속적인 공간이 있어야 하므로 크기가 다른 mapping이 많으면 단편화 때문에 적당한 공간을 찾지 못할 수도 있다.(32bit일때. 64bit 주소공간에서는 크게 문제되지 않는다)

추가

- 파일이 모두 메모리에 올라오는 것이 아니라 메모리 access시 메모리를 할당해주고 I/O가 발생
 

mmap size 조정 // 리눅스 전용

#define _GNU_SOURCE

#include <unistd.h>

#include <sys/mman.h>

void * mremap (void *addr, size_t old_size,

size_t new_size, unsigned long flags);

접근 권한 변경

#include <sys/mman.h>

int mprotect (const void *addr, size_t len, int prot);

성공하면 0을 return.

mapping으로 파일 동기화 // fsync()와 유사

#incldue <sys/mman.h>

int msync (void *addr, size_t len, int flags);

flags 설정(OR연산 가능)

MS_ASYNC 동기화가 비동기식으로 일어나야 함

MS_INVALIDATE 캐시된 복사본이 유효하지 않음을 명세, 향후 이 파일의 map에 접근할 경우 새롭게 동기화된 디스크 내용을 반영

MS_SYNC 동기화가 동기식으로 일어나야 함,

msync() 호출은 모든 페이지를 드스크에 다시 쓰기 전까지 결과를 반환하지 않음.


madvise

memorymap에 포함된 page와 관련된 동작방식을 조언. 리눅스 커널은 기본적으로 동적으로 동작방식을 조율하며 성능을 최적화하지만 madvise는 부하상황에서 바람직한 캐시와 미리읽기 동작방식을 보장한다.

#include <sys/mman.h>

int madvise (void *addr, size_t len, int advice);

advice 설정(POSIX에서 정의한 의미)

MADV_NORMAL 특별한 조언을 하지 않는다.

MADV_RANDOM page에 random 순서로 접근하려고 함

MADV_SEQUENTIAL 낮은 주소에서 높은 주소의 순서대로 페이지 접근

MADV_WILLNEED 당분간은 지정된 영역에 있는 페이지에 접근하려고 함

MADV_DONTNEED 당분간은 지정된 영역에 있는 페이지에 접근하지 않음

advice 옵션(linux 2.6 kernel에서의 동작)

MADV_NORMAL 커널이 적절히 미리읽기 작업을 수행(평상시와 동일)

MADV_RANDOM 커널이 미리읽기를 비활성화함, 매번 물리적인 연산이 일어날 때마다 최소 자료만 읽음

MADV_SEQUENTIAL 커널이 공격적인(적극적인) 미리읽기 작업을 수행

MADV_WILLNEED 커널이 미리읽기 작업을 시작해서 페이지를 메모리로 읽어 들인다.

MADV_DONTNEED 커널이 페이지와 관련된 자원을 해제하고, 변경되었지만 아직 동기화되지 않은 페이지는 버린다. 이후 mapping된 자료에 대한 접근이 있으면 파일에서 페이지를 읽어들인다.

728x90
728x90

2. Epoll

Event Poll: poll()과 select()를 개선한 형태

* select()

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

int select(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);

FD_SET(int fd, fd_set *set); // fd를 set(검사대상)에 추가

FD_CLR(int fd, fd_set *set); // fd를 set에서 제거

FD_ZERO(fd_set *set);// set에 지정된 모든 대상 제거,select호출 전 항상 호출함

FD_ISSET(int fd, fd_set *set); //select()호출 후 fd가 사용 가능한지 확인 return bool

ex)

struct timeval tv;

fd_set readfds;

int ret;

FD_ZERO(&readfds);

FD_SET(STDIN_FILENO, &readfds);

tv.tv_sec = 0;

tv.tv_use = 500;

ret = select (STDIN_FILENO + 1, &readfds, NULL, NULL, &tv);

if(FD_ISSET(STDIN_FILENO, &readfds)){ /* 'fd' is readable without blocking! */ }

* poll()

#include <sys/poll.h>

int poll (struct pollfd *fds, unsigned int nfds, int timeout);

struct pollfd {

int fd; /* file descriptor */

short events; /* requested events to watch */

short revents; /* returned events witnessed */

};

ex)

struct pollfd fds[2];

int ret;

fds[0].fd = STDIN_FILENO;

fds[0].events = POLLIN;

fds[1].fd = STDOUT_FILENO;

fds[1].events = POLLOUT;

ret = poll(fds, 2, TIMEOUT);

if(fds[0].revents & POLLIN){ /* stdin is readable */ }

if(fds[0].revents & POLLOUT){ /* stdout is writable */ }

poll() vs select()

poll  select 
매개변수에 +1(FILENO+1)이 필요없다   
fd의 숫자가 큰 경우 select보다 효율적  fd_set값이 크면 비효율적이다. bitmask를 모두 검사하며 특히 bit가 분산된 경우 더욱 비효율적이다 
   매번 set을 초기화해야 한다.(FD_ZERO)
   이식성을 높이기 위해 timeout parameter(timeval)를 매번 초기화 해야함
 몇몇 UNIX에서는 poll을 지원하지 않는다  이식성이 높다
   microsecond단위까지는 timeout resolution이 더 좋다
 

*epoll()

2.6 Linux kernel 이후부터 사용 가능

select()/poll()은 호출될 때마다 fdset을 모두 확인하나 epoll은 실제 감시작업과 감시 등록작업을 분리하는 방법으로 이런 문제를 해결.

#include <sys/epoll.h>

int epoll_create(int size)

//size는 감시대상의 개수를 알려주는 힌트이며 정확한 수를 요구하지는 않는다.

return value : file descriptor or error(-1)

file descriptor는 poll이 끝난 다음 close()를 이용하여 닫아야 한다.

ex)

int epfd;

epfd = epoll_create(100);

if(epfd < 0) // errorclose(epfd);

int epoll_ctl(int epfd,

int op,

int fd,

struct epoll_event *event);

struct epoll_event{

__u32 events; /* events */

union {

void *ptr;

int fd;

__u32 u32;

--u64 u64;

}data;

};

op parameter

EPOLL_CTL_ADD : 감시대상에 fd 추가

EPOLL_CTL_DEL : 감시대상에서 fd 제거

EPOLL_CTL_MOD : 기존 등록된 fd의 event를 수정

ex)

struct epoll_event event;

int ret;

event.data.fd = fd;

event.events = EPOLLIN | EPOLLOUT;

ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);

if(ret) //ERROR

event.data.fd = fd;

event.events = EPOLLIN;

ret = epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &event);

ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &event);

** EPOLL_CTL_DEL을 사용할 때 event를 NULL로 보내면 2.6.9이전 버전에서 호환성이 맞지 않는다.

event 매개변수의 events필드에 EPOLLET값을 설정하면, fd에 대한 감시가 레벨트리거에서 에지트리거로 바뀐다. 에지트리거는 일반적으로 비차단 입출력을 활용하도록 다른 프로그래밍 접근방식을 요구하므로(만약 read를 요청하는 시점에 읽을 수 있는 상태라고 해도 write가 진행중이면 write가 끝날 때까지 기다린다) EAGAIN을 주의깊게 검사해야 한다.

#include <sys/epoll.h>

int epoll_wait (int epfd,

struct epoll_event *events,

int maxevents,

int timeout);

ex)

#define MAX_EVENTS64

struct epoll_event *events;

int nr_events, i, epfd;

events = malloc (sizeof(struct epoll_event) * MAX_EVENTS);

nr_events = epoll_wait (epfd, events, MAX_EVENTS, -1);

if(nr_events < 0) {

free(events);

return 1; //ERROR

}

for(i =0; i < nr_events; I++) {

//events[i].events마다 events[i].data.fd에 대한 작업 진행가능

}

free(events);

728x90

+ Recent posts