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);
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 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
즉, netif_tx의 경우
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이 정의 된다.
또한 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;
};
위에서 선언한 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으로 채움
#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은 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에 수용가능한
링 엔트리의 개수를 반환한다.

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에 해당하는 엔트리에 할당 한다.)

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
각 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 )을 호출한다.
- netback의 예
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에도 똑같이 한다.
(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의 인터럽트 핸들러이다.
나머지 작업은 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))
/* 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_PUSH_REQUESTS(_r) do { \
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)

댓글목록

등록된 댓글이 없습니다.