shared ring 분석
페이지 정보
작성자 조희승 댓글 0건 조회 6,565회 작성일 12-08-17 15:05본문
### Shared Ring 분석 ###
-김환주
-김환주
1) 예제 코드
struct netif_tx_sring *txs;
txs = (struct netif_tx_sring *)get_zeroed_page(GFP_KERNEL);
SHARED_RING_INIT(txs);
FRONT_RING_INIT(&info->tx, txs, PAGE_SIZE);
FRONT_RING_INIT(&info->tx, txs, PAGE_SIZE);
err = xenbus_grant_ring(dev, virt_to_mfn(txs));
2) 링 타입의 정의 ( xen/include/public/io/ring.h )
DEFINE_RING_TYPES(netif_tx, struct netif_tx_request, struct netif_tx_response);
DEFINE_RING_TYPES(netif_rx, struct netif_rx_request, struct netif_rx_response);
DEFINE_RING_TYPES(netif_tx, struct netif_tx_request, struct netif_tx_response);
DEFINE_RING_TYPES(netif_rx, struct netif_rx_request, struct netif_rx_response);
#define DEFINE_RING_TYPES(__name, __req_t, __rsp_t) \
\
/* Shared ring entry */ \
union __name##_sring_entry { \
__req_t req; \
__rsp_t rsp; \
}; \
/* Shared ring page */ \
struct __name##_sring { \
RING_IDX req_prod, req_event; \
RING_IDX rsp_prod, rsp_event; \
uint8_t pad[48]; \
union __name##_sring_entry ring[1]; /* variable-length */ \
}; \
\
/* "Front" end's private variables */ \
struct __name##_front_ring { \
RING_IDX req_prod_pvt; \
RING_IDX rsp_cons; \
unsigned int nr_ents; \
struct __name##_sring *sring; \
}; \
\
/* "Back" end's private variables */ \
struct __name##_back_ring { \
RING_IDX rsp_prod_pvt; \
RING_IDX req_cons; \
unsigned int nr_ents; \
struct __name##_sring *sring; \
}; \
\
/* Syntactic sugar */ \
typedef struct __name##_sring __name##_sring_t; \
typedef struct __name##_front_ring __name##_front_ring_t; \
typedef struct __name##_back_ring __name##_back_ring_t
\
/* Shared ring entry */ \
union __name##_sring_entry { \
__req_t req; \
__rsp_t rsp; \
}; \
/* Shared ring page */ \
struct __name##_sring { \
RING_IDX req_prod, req_event; \
RING_IDX rsp_prod, rsp_event; \
uint8_t pad[48]; \
union __name##_sring_entry ring[1]; /* variable-length */ \
}; \
\
/* "Front" end's private variables */ \
struct __name##_front_ring { \
RING_IDX req_prod_pvt; \
RING_IDX rsp_cons; \
unsigned int nr_ents; \
struct __name##_sring *sring; \
}; \
\
/* "Back" end's private variables */ \
struct __name##_back_ring { \
RING_IDX rsp_prod_pvt; \
RING_IDX req_cons; \
unsigned int nr_ents; \
struct __name##_sring *sring; \
}; \
\
/* Syntactic sugar */ \
typedef struct __name##_sring __name##_sring_t; \
typedef struct __name##_front_ring __name##_front_ring_t; \
typedef struct __name##_back_ring __name##_back_ring_t
즉, netif_tx의 경우
union netif_tx_sring_entry {
struct netif_tx_request req;
struct netif_tx_response rsp;
};
과 같은 형식으로 링의 엔트리가 정의 되고,
union netif_tx_sring_entry {
struct netif_tx_request req;
struct netif_tx_response rsp;
};
과 같은 형식으로 링의 엔트리가 정의 되고,
struct netif_tx_sring {
RING_IDX req_prod, req_event;
RING_IDX rsp_prod, rsp_event;
uint8_t pad[48];
union netif_tx_sring_entry ring[1]; /* variable-length */
};
의 다중 엔트리를 가질 수 있는 shared ring이 정의 된다.
RING_IDX req_prod, req_event;
RING_IDX rsp_prod, rsp_event;
uint8_t pad[48];
union netif_tx_sring_entry ring[1]; /* variable-length */
};
의 다중 엔트리를 가질 수 있는 shared ring이 정의 된다.
또한 front/back 각각에 해당 하는 private variable을 저장하는 구조체를 선언한다.
/* "Front" end's private variables */
struct netif_tx_front_ring {
RING_IDX req_prod_pvt; // 요청 생성자 (front에서 요청)
RING_IDX rsp_cons; // 응답 소비자 (front에서는 응답을 받아서 처리)
unsigned int nr_ents;
struct netif_tx_sring *sring;
};
/* "Back" end's private variables */
struct netif_tx_back_ring {
RING_IDX rsp_prod_pvt; // 응답 생성자 (back에서는 응답)
RING_IDX req_cons; // 요청 소비자 (back에서는 요청을 받아서 서비스)
unsigned int nr_ents;
struct netif_tx_sring *sring;
};
/* "Front" end's private variables */
struct netif_tx_front_ring {
RING_IDX req_prod_pvt; // 요청 생성자 (front에서 요청)
RING_IDX rsp_cons; // 응답 소비자 (front에서는 응답을 받아서 처리)
unsigned int nr_ents;
struct netif_tx_sring *sring;
};
/* "Back" end's private variables */
struct netif_tx_back_ring {
RING_IDX rsp_prod_pvt; // 응답 생성자 (back에서는 응답)
RING_IDX req_cons; // 요청 소비자 (back에서는 요청을 받아서 서비스)
unsigned int nr_ents;
struct netif_tx_sring *sring;
};
위에서 선언한 entry를 제외한 나머지 세개의 타입에 대해서는 _t를 붙여 타입의 이름으로도 사용할 수 있게 한다.
3) 초기화 함수
/* Initialising empty rings */
#define SHARED_RING_INIT(_s) do { \
(_s)->req_prod = (_s)->rsp_prod = 0; \
(_s)->req_event = (_s)->rsp_event = 1; \
memset((_s)->pad, 0, sizeof((_s)->pad)); \
} while(0)
XXX_sring의 멤버인
request producer와 response producer를 0으로 설정
request event와 response event를 1로 설정
pad를 0으로 채움
request producer와 response producer를 0으로 설정
request event와 response event를 1로 설정
pad를 0으로 채움
#define FRONT_RING_INIT(_r, _s, __size) do { \
(_r)->req_prod_pvt = 0; \
(_r)->rsp_cons = 0; \
(_r)->nr_ents = __RING_SIZE(_s, __size); \
(_r)->sring = (_s); \
} while (0)
(_r)->req_prod_pvt = 0; \
(_r)->rsp_cons = 0; \
(_r)->nr_ents = __RING_SIZE(_s, __size); \
(_r)->sring = (_s); \
} while (0)
_r은 front/back의 고유 구조체(XXX_front[back]_ring)이고 _s는 XXX_sring이다.
_size는 주로 PAGE_SIZE가 넘어오고, _s는 이미 PAGE_SIZE 크기의 페이지를 할당 받은 상태이다.
__RING_SIZE는
#define __RING_SIZE(_s, _sz) \
(__RD32(((_sz) - (long)(_s)->ring + (long)(_s)) / sizeof((_s)->ring[0])))
_sz에서 앞의 메타데이터 부분(링 인덱스와 pad)을 제외한 크기를 링 엔트리의 크기로 나누어 _sz에 수용가능한
링 엔트리의 개수를 반환한다.
_size는 주로 PAGE_SIZE가 넘어오고, _s는 이미 PAGE_SIZE 크기의 페이지를 할당 받은 상태이다.
__RING_SIZE는
#define __RING_SIZE(_s, _sz) \
(__RD32(((_sz) - (long)(_s)->ring + (long)(_s)) / sizeof((_s)->ring[0])))
_sz에서 앞의 메타데이터 부분(링 인덱스와 pad)을 제외한 크기를 링 엔트리의 크기로 나누어 _sz에 수용가능한
링 엔트리의 개수를 반환한다.
3) xenbus에 ring을 grant
int xenbus_grant_ring(struct xenbus_device *dev, unsigned long ring_mfn)
{
int err = gnttab_grant_foreign_access(dev->otherend_id, ring_mfn, 0);
if (err < 0)
xenbus_dev_fatal(dev, err, "granting access to ring page");
return err;
}
backend device driver를 가지고 있는 driver domain에게 초기화를 끝낸 shared ring을 읽기/쓰기 권한으로 접근할 수 있게
access grant를 내린다. ( 이 함수는 각 정보를 shared grant table의 빈 ref에 해당하는 엔트리에 할당 한다.)
access grant를 내린다. ( 이 함수는 각 정보를 shared grant table의 빈 ref에 해당하는 엔트리에 할당 한다.)
4) 호출 과정
network은 backend 디바이스가 준비 완료되면, front 드라이버의 backend_changed로부터 network_connect 함수가 호출 됨.
disk는 probe과정 후에 호출
cf) backend_changed는 xenbus_driver 구조체 변수의 otherend_changed 멤버 함수에 할당
otherend_changed는 어떻게 호출 되는가? -> xenbus를 살펴보자( driver/xen/xenbus/xenbus_probe.c )
-> xenbus_register_driver_common에서 netfront->driver.probe에 등록된 xenbus_dev_probe가 호출되어 watch_otherend -> ... -> xenbus_watch_path
를 통해 otherend_changed가 callback으로 등록된 watch를 xenbus watch에 추가한다.
- netfront의 예
backend_changed -> network_connect -> talk_to_backend -> setup_device
- blkfront의 예
blkfront_probe -> talk_to_backend -> setup_blkring
backend_changed -> network_connect -> talk_to_backend -> setup_device
- blkfront의 예
blkfront_probe -> talk_to_backend -> setup_blkring
각 setup_XXX에서 위에서 언급한 링의 초기화를 수행한다.
5) backend driver의 연결
backend driver도 역시 front driver가 준비되면 frontend_chagned라는 함수로부터 connect_rings( connect_ring )이 호출된다.
connect_ring[s]는 xenbus_gather로 shared ring에 대한 페이지의 grant reference와 event channel 등을 가져온 뒤
(네트워크 드라이버는 더 많은 정보를 가져옴 e.g. copy/flip , scatter/gather / TSO / checksum offload )
netif_map( or blkif_map )을 호출한다.
backend driver도 역시 front driver가 준비되면 frontend_chagned라는 함수로부터 connect_rings( connect_ring )이 호출된다.
connect_ring[s]는 xenbus_gather로 shared ring에 대한 페이지의 grant reference와 event channel 등을 가져온 뒤
(네트워크 드라이버는 더 많은 정보를 가져옴 e.g. copy/flip , scatter/gather / TSO / checksum offload )
netif_map( or blkif_map )을 호출한다.
- netback의 예
frontend_changed -> connect -> connect_rings -> netif_map
- blkback의 예
frontend_changed -> connect_ring -> blkif_map
frontend_changed -> connect -> connect_rings -> netif_map
- blkback의 예
frontend_changed -> connect_ring -> blkif_map
*** netif_map은 무엇을 하는가? ***
크게 두가지 작업을 한다. 첫번째는 grant operation을 통해 front에서 할당하고 grant access를 준 shared ring에 대해 접근권한을 획득한다.
두번째는 front에게 인터럽트를 발생시키기 위한 이벤트 채널을 binding하는 작업을 한다.
(1) Shared Ring Mapping
netif->tx_comms_area와 netif_rx_comms_area에 각각 PAGE_SIZE만큼의 vitual memory 영역을 할당하기 위하여 alloc_vm_area(PAGE_SIZE)를 각각
호출한다. 이 함수는 vmalloc에서 주로 사용되는 함수로 get_vm_area(VM_IOREMAP) 호출을 하여 IO remmaping 가능한 영역에 PAGE_SIZE 만큼의
가상 주소 영역을 할당하고, apply_to_page_range 함수를 통해 init_mm, 즉 마스터 커널 디렉토리의 해당 영역의 페이지 테이블 영역을 할당한다.
그 후 map_frontend_pages를 호출하여 다음과 같은 작업을 수행한다.
netif->tx_comms_area->addr 는 tx를 위한 shared ring영역을 가리킬 가상 주소를 나타낸다. netif->domid는 frontend driver를 가지고 있는
도메인을 타나내고, tx_ring_ref를 가지고 HYPERVISOR_grant_table_op 하이퍼콜을 통해 해당 링영역을 매핑하여 접근 권한을 얻게 된다.
리턴된 op.handle값은 netif->tx_shmem_handle에 저장하고, tx_ring_ref또한 netif->tx_shmem_ref에 저장한다.
같은 작업을 Rx에도 똑같이 한다.
netif->tx_comms_area와 netif_rx_comms_area에 각각 PAGE_SIZE만큼의 vitual memory 영역을 할당하기 위하여 alloc_vm_area(PAGE_SIZE)를 각각
호출한다. 이 함수는 vmalloc에서 주로 사용되는 함수로 get_vm_area(VM_IOREMAP) 호출을 하여 IO remmaping 가능한 영역에 PAGE_SIZE 만큼의
가상 주소 영역을 할당하고, apply_to_page_range 함수를 통해 init_mm, 즉 마스터 커널 디렉토리의 해당 영역의 페이지 테이블 영역을 할당한다.
그 후 map_frontend_pages를 호출하여 다음과 같은 작업을 수행한다.
netif->tx_comms_area->addr 는 tx를 위한 shared ring영역을 가리킬 가상 주소를 나타낸다. netif->domid는 frontend driver를 가지고 있는
도메인을 타나내고, tx_ring_ref를 가지고 HYPERVISOR_grant_table_op 하이퍼콜을 통해 해당 링영역을 매핑하여 접근 권한을 얻게 된다.
리턴된 op.handle값은 netif->tx_shmem_handle에 저장하고, tx_ring_ref또한 netif->tx_shmem_ref에 저장한다.
같은 작업을 Rx에도 똑같이 한다.
(2) Bind interdomain
서로 다른 도메인간의 이벤트 채널을 형성한다. HYPERVISOR_event_channel_op가 사용되며 EVTCHNOP_bind_interdomain 가 operation으로 사용된다.
인자로서 <remote domid,remote port>를 넘기고, 리턴값으로 빈 port를 할당하여 local port로 받는다. remote id는 netif->domid로 얻을수 있고,
remote port는 xenbus_gather로 얻어온 값을 사용한다. 리턴 받은 local port는 netif->evtchn에 할당하여 front에 인터럽트를 발생시키고자 할 때
사용된다. backend도 역시 local port에 대한 irq handler를 등록한다. 이는 front에서 가상 인터럽트를 받아서 처리할 루틴을 명시해 주는 것으로
양쪽 모두 서로의 notification에 대한 핸들러를 가지고 있게 된다. netif_be_int가 backend의 인터럽트 핸들러이다.
서로 다른 도메인간의 이벤트 채널을 형성한다. HYPERVISOR_event_channel_op가 사용되며 EVTCHNOP_bind_interdomain 가 operation으로 사용된다.
인자로서 <remote domid,remote port>를 넘기고, 리턴값으로 빈 port를 할당하여 local port로 받는다. remote id는 netif->domid로 얻을수 있고,
remote port는 xenbus_gather로 얻어온 값을 사용한다. 리턴 받은 local port는 netif->evtchn에 할당하여 front에 인터럽트를 발생시키고자 할 때
사용된다. backend도 역시 local port에 대한 irq handler를 등록한다. 이는 front에서 가상 인터럽트를 받아서 처리할 루틴을 명시해 주는 것으로
양쪽 모두 서로의 notification에 대한 핸들러를 가지고 있게 된다. netif_be_int가 backend의 인터럽트 핸들러이다.
나머지 작업은 BACK_RING_INIT을 통해 netif->tx와 netif->rx를 초기화 하고, netif->refcnt를 증가시킨 후 carrier를 ON하고, netif를 UP한다.
6) 링을 통해 통신하기
/* Direct access to individual ring elements, by index. */
#define RING_GET_REQUEST(_r, _idx) \
(&((_r)->sring->ring[((_idx) & (RING_SIZE(_r) - 1))].req))
#define RING_GET_RESPONSE(_r, _idx) \
(&((_r)->sring->ring[((_idx) & (RING_SIZE(_r) - 1))].rsp))
(&((_r)->sring->ring[((_idx) & (RING_SIZE(_r) - 1))].rsp))
/* Loop termination condition: Would the specified index overflow the ring? */
#define RING_REQUEST_CONS_OVERFLOW(_r, _cons) \
(((_cons) - (_r)->rsp_prod_pvt) >= RING_SIZE(_r))
#define RING_REQUEST_CONS_OVERFLOW(_r, _cons) \
(((_cons) - (_r)->rsp_prod_pvt) >= RING_SIZE(_r))
#define RING_PUSH_REQUESTS(_r) do { \
wmb(); /* back sees requests /before/ updated producer index */ \
(_r)->sring->req_prod = (_r)->req_prod_pvt; \
} while (0)
wmb(); /* back sees requests /before/ updated producer index */ \
(_r)->sring->req_prod = (_r)->req_prod_pvt; \
} while (0)
#define RING_PUSH_RESPONSES(_r) do { \
wmb(); /* front sees responses /before/ updated producer index */ \
(_r)->sring->rsp_prod = (_r)->rsp_prod_pvt; \
} while (0)
wmb(); /* front sees responses /before/ updated producer index */ \
(_r)->sring->rsp_prod = (_r)->rsp_prod_pvt; \
} while (0)
댓글목록
등록된 댓글이 없습니다.