Na postagem anterior do blog, descrevi a inicialização e a destruição do mecanismo. Esta postagem do blog abordará o modelo de alocação de memória na interface do mecanismo.
O núcleo do memcached é responsável por alocar toda a memória necessária para suas conexões (buffers de envio/recebimento etc.), e o mecanismo é responsável por alocar (e liberar) toda a memória necessária para manter o controle dos itens. Os mecanismos não devem se preocupar com a memória que o núcleo aloca (e usa), mas o núcleo acessará a memória gerenciada pelo mecanismo.
Quando o núcleo do memcached está prestes a armazenar um novo item, ele precisa obter um buffer (até o momento, contínuo) para armazenar os dados do item. O núcleo tentará alocar esse buffer chamando a função alocar na API. Portanto, vamos começar a estender nosso código de exemplo adicionando nossa própria implementação da função alocar função. A primeira coisa que precisamos fazer é adicioná-la ao nosso descritor de mecanismo que retornamos de create_instance. Vamos adicionar várias funções na entrada de hoje, portanto, vamos mapear todas elas enquanto fazemos isso:
ENGINE_ERROR_CODE create_instance(uint64_t interface,
GET_SERVER_API get_server_api,
ENGINE_HANDLE **handle)
{
[ ... cortar ... ]
/*
* Mapear os pontos de entrada da API para nossas funções que os implementam.
*/
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;
A próxima coisa que precisamos fazer é criar uma estrutura de dados para manter as informações de que precisamos. O objetivo deste tutorial não é criar uma implementação eficiente em termos de memória, mas exercitar a API. Portanto, vamos criar a seguinte estrutura:
void *key;
size_t nkey;
void *data;
size_t ndata;
int flags;
rel_time_t exptime;
};
Nossa implementação de alocar teria a seguinte aparência:
const void* cookie,
item **item,
const void* key,
const size_t nkey,
const size_t nbytes,
const int flags,
const rel_time_t exptime)
{
struct fs_item *it = malloc(sizeof(struct fs_item));
Se (it == NULL) {
return ENGINE_ENOMEM;
}
it->flags = flags;
it->exptime = exptime;
it->nkey = nkey;
it->ndata = nbytes;
it->key = malloc(nkey);
it->data = malloc(nbytes);
Se (it->key == NULL || it->data == NULL) {
free(it->key);
free(it->data);
free(it);
return ENGINE_ENOMEM;
}
memcpy(it->key, key, nkey);
*item = it;
retornar ENGINE_SUCCESS;
}
Se você observar a implementação acima, verá que não retornamos o ponteiro para a memória real do armazenamento de dados para o núcleo do memcached. Para obter esse endereço, o memcached chamará get_item_info na API. Então, vamos implementar isso:
const item* item, item_info *item_info)
{
struct fs_item* it = (struct fs_item*)item;
if (item_info->nvalue < 1) {
retornar falso;
}
item_info->cas = 0; /* Não suportado */
item_info->clsid = 0; /* Não suportado */
item_info->exptime = it->exptime;
item_info->flags = it->flags;
item_info->key = it->key;
item_info->nkey = it->nkey;
item_info->nbytes = it->ndata; /* Comprimento total dos dados do item */
item_info->nvalue = 1; /* Número de fragmentos usados */
item_info->value[0].iov_base = it->data; /* ponteiro para o fragmento 1 */
item_info->value[0].iov_len = it->ndata; /* Comprimento do fragmento 1 */
retornar verdadeiro;
}
O get_item_info é importante e merece mais informações. Se você observar a API do mecanismo, o "item" é definido como um ponteiro vazio, e nós definimos nossa própria estrutura de item para manter o controle das informações de que precisamos por item. No entanto, o núcleo do memcached precisará saber
onde ler/gravar a memória para a chave e os dados que vão/vem de um clinet. Para isso, invocaremos get_item_info. Se você observar atentamente nossa implementação do fs_get_item_info Você verá que a primeira coisa que estou fazendo é verificar se item_info->nvalue contém pelo menos 1
elemento. No momento, ele irá sempre mas a intenção é que apoiemos a IO dispersa.
Quando o núcleo terminar de mover os dados que recebeu pelo cabo para o item, ele tentará armazenar o item em nosso mecanismo chamando store. Portanto, vamos criar uma implementação simples (vamos ampliá-la mais adiante no tutorial):
const void *cookie,
item* item,
uint64_t *cas,
Operação ENGINE_STORE_OPERATION,
uint16_t vbucket)
{
struct fs_item* it = item;
char fname[it->nkey + 1];
memcpy(fname, it->key, it->nkey);
fname[it->nkey] = '
FILE *fp = fopen(fname, "w");
Se (fp == NULL) {
return ENGINE_NOT_STORED;
}
size_t nw = fwrite(it->data, 1, it->ndata, fp);
fclose(fp);
se (nw != it->ndata) {
remove(fname);
return ENGINE_NOT_STORED;
}
*cas = 0;
retornar ENGINE_SUCCESS;
}
Se você observar a implementação acima, verá que ela não implementa a semântica correta para adicionar/substituir/definir etc., e isso bloqueará o memcached enquanto estivermos fazendo o IO do arquivo. Não se preocupe com isso agora, pois voltaremos a esse assunto.
Quando o núcleo terminar de usar o item alocado, ele liberará o item chamando a função liberação na API. O mecanismo pode reutilizar o armazenamento de itens para outra coisa nesse momento. Então, vamos conectar nosso liberação implementação:
const void *cookie,
item* item)
{
struct fs_item *it = item;
free(it->key);
free(it->data);
free(it);
}
Agora criamos todo o código para armazenar itens com sucesso em nosso mecanismo, mas não podemos ler nenhum deles de volta. Então, vamos implementar obter
const void* cookie,
item** item,
const void* key,
const int nkey,
uint16_t vbucket)
{
char fname[nkey + 1];
memcpy(fname, key, nkey);
fname[nkey] = '
struct stat st;
se (stat(fname, &st) == -1) {
return ENGINE_NOT_STORED;
}
struct fs_item* it = NULL;
ENGINE_ERROR_CODE ret = fs_allocate(handle, cookie, (void**)&it, key, nkey,
st.st_size, 0, 0);
Se (ret != ENGINE_SUCCESS) {
return ENGINE_ENOMEM;
}
FILE *fp = fopen(fname, "r");
Se (fp == NULL) {
fs_release(handle, cookie, it);
retornar ENGINE_FAILED;
}
size_t nr = fread(it->data, 1, it->ndata, fp);
fclose(fp);
se (nr != it->ndata) {
fs_release(handle, cookie, it);
retornar ENGINE_FAILED;
}
*item = it;
retornar ENGINE_SUCCESS;
}
Vamos adicionar uma implementação fictícia para o restante da API e tentar carregar e testar o mecanismo:
{
static engine_info info = {
.description = "Mecanismo do sistema de arquivos v0.1",
.num_features = 0
};
return &info;
}
static ENGINE_ERROR_CODE fs_item_delete(ENGINE_HANDLE* handle,
const void* cookie,
const void* key,
const size_t nkey,
uint64_t cas,
uint16_t vbucket)
{
return ENGINE_KEY_ENOENT;
}
static ENGINE_ERROR_CODE fs_get_stats(ENGINE_HANDLE* handle,
const void* cookie,
const char* stat_key,
int nkey,
ADD_STAT add_stat)
{
retornar ENGINE_SUCCESS;
}
static ENGINE_ERROR_CODE fs_flush(ENGINE_HANDLE* handle,
const void* cookie, time_t when)
{
retornar ENGINE_SUCCESS;
}
static void fs_reset_stats(ENGINE_HANDLE* handle, const void *cookie)
{
}
static ENGINE_ERROR_CODE fs_unknown_command(ENGINE_HANDLE* handle,
const void* cookie,
protocol_binary_request_header *request,
Resposta ADD_RESPONSE)
{
retornar ENGINE_ENOTSUP;
}
static void fs_item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
item* item, uint64_t val)
{
}
Então, vamos testar nosso mecanismo:
De outro terminal em que estou digitando:
Tentando ::1...
Conectado ao opensolaris.
O caractere de escape é '^]'.
adicionar teste 0 0 4
teste
ARMAZENADO
obter teste
VALUE test 0 4
teste
FIM
sair
Conexão com a tempestade fechada pelo host externo.
Encerre o memcached pressionando ctrl-ce procurar no diretório atual:
-rw-r-r- 1 trond users 6 Oct 8 12:56 test
trond@opensolaris> cat test
teste
Isso é tudo por enquanto.