이전 블로그 게시물에서 엔진 초기화와 소멸에 대해 설명했습니다. 이번 블로그 게시물에서는 엔진 인터페이스의 메모리 할당 모델을 다룹니다.
멤캐시드 코어는 연결에 필요한 모든 메모리(송수신 버퍼 등)를 할당하고 엔진은 항목을 추적하는 데 필요한 모든 메모리를 할당(및 해제)할 책임이 있습니다. 엔진은 코어가 할당(및 사용)하는 메모리에 신경 쓸 필요가 없지만, 코어는 엔진이 관리하는 메모리에 액세스합니다.
멤캐시드 코어가 새 항목을 저장하려고 할 때 해당 항목의 데이터를 저장할 버퍼를 (현재 기준으로는 계속) 확보해야 합니다. 코어는 이 버퍼를 할당하려고 할 때 할당 함수를 호출할 수 있습니다. 이제 예제 코드를 직접 구현한 할당 함수를 추가합니다. 가장 먼저 해야 할 일은 반환하는 엔진 설명자에 이 함수를 추가하는 것입니다. create_instance. 오늘의 항목에 여러 가지 함수를 추가할 예정이므로 이왕 하는 김에 모든 함수를 매핑해 보겠습니다:
ENGINE_ERROR_CODE create_instance(uint64_t 인터페이스,
GET_SERVER_API GET_SERVER_API,
ENGINE_HANDLE **핸들)
{
[ ... 컷 ... ]
/*
* API 진입점을 이를 구현하는 함수에 매핑합니다.
*/
h->engine.initialize = fs_initialize;
h->engine.destroy = fs_destroy;
h->engine.get_info = fs_get_info;
h->engine.allocate = fs_allocate;
h->engine.remove = fs_item_delete;
h->engine.release = fs_item_release;
h->engine.get = fs_get;
h->engine.get_stats = fs_get_stats;
h->engine.reset_stats = fs_reset_stats;
h->engine.store = fs_store;
h->engine.flush = fs_flush;
h->engine.unknown_command = fs_unknown_command;
h->engine.item_set_cas = fs_item_set_cas;
h->engine.get_item_info = fs_get_item_info;
다음으로 해야 할 일은 필요한 정보를 보관할 데이터 구조를 만드는 것입니다. 이 튜토리얼의 목적은 메모리 효율적인 구현을 만드는 것이 아니라 API를 실습하는 것입니다. 따라서 다음 구조체를 만들어 보겠습니다:
void *key;
size_t nkey;
void *데이터;
size_t ndata;
int 플래그;
REL_TIME_T EXPTIME;
};
다음을 구현합니다. 할당 가 어떻게 보일까요?
const void* 쿠키,
항목 **항목,
const void* 키,
const size_t nkey,
const size_t n바이트,
const int 플래그,
const rel_time_t exptime)
{
구조체 fs_item *it = malloc(sizeof(구조체 fs_item));
if (it == NULL) {
엔진_이름을 반환합니다;
}
it->flags = 플래그;
IT->EXPTIME = EXPTIME;
it->nkey = nkey;
it->ndata = n바이트;
it->key = malloc(nkey);
it->data = malloc(nbytes);
if (it->key == NULL || it->data == NULL) {
free(it->key);
FREE(IT->DATA);
free(it);
엔진_이름을 반환합니다;
}
memcpy(it->key, key, nkey);
*아이템 = 그것;
엔진_성공을 반환합니다;
}
위의 구현을 보면 데이터 저장을 위한 실제 메모리에 대한 포인터를 멤캐시드 코어에 반환하지 않았음을 알 수 있습니다. 해당 주소를 얻으려면 memcached는 get_item_info 를 사용할 수 있습니다. 이제 이를 구현해 보겠습니다:
const item* item, item_info *item_info)
{
구조체 fs_item* it = (구조체 fs_item*)item;
if (item_info->nvalue < 1) {
거짓을 반환합니다;
}
item_info->cas = 0; /* 지원되지 않음 */
item_info->clsid = 0; /* 지원되지 않음 */
item_info->exemptime = it->exemptime;
item_info->flags = it->flags;
item_info->key = it->key;
item_info->nkey = it->nkey;
item_info->nbytes = it->ndata; /* 아이템 데이터의 총 길이 */
item_info->nvalue = 1; /* 사용된 조각 수 */
item_info->value[0].iov_base = it->데이터; /* 조각 1에 대한 포인터 */
item_info->value[0].iov_len = it->ndata; /* 조각 1의 길이 */
참을 반환합니다;
}
그리고 get_item_info 함수가 중요하고 더 많은 정보를 제공할 필요가 있다고 생각했습니다. 엔진 API를 살펴보면 "항목"은 보이드 포인터로 정의되어 있으며, 항목별로 필요한 정보를 추적하기 위해 자체 항목 구조를 정의했습니다. 그러나 멤캐시드 코어는 다음 사항을 알아야 합니다.
키에 대한 메모리를 읽거나 쓸 위치 및 캐비닛과 주고받는 데이터의 위치를 지정합니다. 이렇게 하려면 get_item_info. 의 구현을 자세히 살펴보면 fs_get_item_info 내가 가장 먼저 하는 일은 item_info->nvalue 1 이상 포함
요소입니다. 지금은 항상 하나로 통합하는 것이 아니라, 분산된 IO를 지원하겠다는 취지입니다.
코어가 유선으로 받은 데이터를 아이템으로 옮기는 작업이 완료되면, 엔진에 아이템을 저장하려고 시도합니다. store. 이제 간단한 구현을 만들어 보겠습니다(튜토리얼의 뒷부분에서 확장해 보겠습니다):
const void *쿠키,
항목* 항목,
uint64_t *cas,
엔진_스토어_운영 작업,
UINT16_T VBUCKET)
{
구조체 FS_ITEM* IT = 항목;
문자 fname[it->nkey + 1];
memcpy(fname, it->key, it->nkey);
fname[it->nkey] = '
FILE *fp = fopen(fname, "w");
if (fp == NULL) {
ENGINE_NOT_STORED를 반환합니다;
}
size_t nw = fwrite(it->data, 1, it->ndata, fp);
fclose(fp);
if (nw != it->ndata) {
remove(fname);
ENGINE_NOT_STORED를 반환합니다;
}
*cas = 0;
엔진_성공을 반환합니다;
}
위의 구현을 보면 다음과 같은 경우 올바른 시맨틱을 구현하지 않는다는 것을 알 수 있습니다. 추가/대체/set 등을 사용하면 파일 IO를 수행하는 동안 멤캐시가 차단됩니다. 이 부분은 나중에 다시 설명할 테니 지금은 신경 쓰지 마세요.
코어가 할당된 아이템 사용을 완료하면, 할당된 아이템이 완료되면 -release 함수를 호출할 수 있습니다. 현재 엔진은 아이템 저장소를 다른 용도로 재사용할 수 있습니다. 따라서 -release 구현:
const void *쿠키,
항목* 항목)
{
구조체 fs_item *it = item;
free(it->key);
FREE(IT->DATA);
free(it);
}
이제 엔진에 항목을 성공적으로 저장하기 위한 코드를 모두 만들었지만 그 중 어떤 것도 다시 읽을 수 없습니다. 따라서 get
const void* 쿠키,
항목** 항목,
const void* 키,
const int nkey,
UINT16_T VBUCKET)
{
문자 fname[nkey + 1];
memcpy(fname, key, nkey);
fname[nkey] = '
구조체 stat st;
if (stat(fname, &st) == -1) {
ENGINE_NOT_STORED를 반환합니다;
}
구조체 fs_item* it = NULL;
엔진 오류 코드 ret = fs_allocate(핸들, 쿠키, (void**)&it, 키, nkey),
st.st_size, 0, 0);
if (ret != ENGINE_SUCCESS) {
엔진_이름을 반환합니다;
}
FILE *fp = fopen(fname, "r");
if (fp == NULL) {
fs_release(handle, cookie, it);
엔진_실패 반환;
}
size_t nr = fread(it->data, 1, it->ndata, fp);
fclose(fp);
if (nr != it->ndata) {
fs_release(handle, cookie, it);
엔진_실패 반환;
}
*아이템 = 그것;
엔진_성공을 반환합니다;
}
나머지 API에 대한 더미 구현을 추가하고 엔진을 로드하고 테스트해 보겠습니다:
{
정적 엔진_정보 정보 = {
.description = "파일 시스템 엔진 v0.1",
.num_features = 0
};
반환 &info;
}
static ENGINE_ERROR_CODE fs_item_delete(ENGINE_HANDLE* handle,
const void* 쿠키,
const void* 키,
const size_t nkey,
UINT64_t CAS,
UINT16_T VBUCKET)
{
엔진_키_엔오엔트를 반환합니다;
}
static ENGINE_ERROR_CODE fs_get_stats(ENGINE_HANDLE* handle,
const void* 쿠키,
const char* stat_key,
int nkey,
ADD_STAT 추가_스탯)
{
엔진_성공을 반환합니다;
}
static ENGINE_ERROR_CODE fs_flush(ENGINE_HANDLE* handle,
const void* cookie, time_t when)
{
엔진_성공을 반환합니다;
}
static void fs_reset_stats(ENGINE_HANDLE* handle, const void *cookie)
{
}
static ENGINE_ERROR_CODE fs_unknown_command(ENGINE_HANDLE* handle,
const void* 쿠키,
프로토콜_바이너리_요청_헤더 *요청,
ADD_RESPONSE 응답)
{
엔진_엔오츠업을 반환합니다;
}
static void fs_item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
item* item, uint64_t val)
{
}
이제 엔진을 사용해 보겠습니다:
다른 터미널에서 입력 중입니다:
시도 중 ::1...
오픈솔라리스에 연결되었습니다.
이스케이프 문자는 '^]'입니다.
추가 테스트 0 0 4
테스트
저장됨
테스트 받기
VALUE 테스트 0 4
테스트
END
종료
외국 호스트에 의해 스톰 연결이 차단되었습니다.
를 눌러 멤캐시드를 종료합니다. ctrl-c를 클릭하고 현재 디렉토리를 찾습니다:
-rw-r-r- 1 트론 사용자 6 Oct 8 12:56 테스트
트론드@오픈솔라리스> 고양이 테스트
테스트
여기까지입니다.