En la entrada anterior describí la inicialización y destrucción del motor. Esta entrada del blog cubrirá el modelo de asignación de memoria en la interfaz del motor.
El núcleo de memcached es responsable de asignar toda la memoria que necesita para sus conexiones (búferes de envío/recepción, etc.), y el motor es responsable de asignar (y liberar) toda la memoria que necesita para realizar un seguimiento de los elementos. Los motores no deberían tener que preocuparse por la memoria que el núcleo asigna (y utiliza), pero el núcleo accederá a la memoria gestionada por el motor.
Cuando el núcleo de memcached está a punto de almacenar un nuevo elemento, necesita obtener un búfer (actualmente continuo) para almacenar los datos del elemento. El núcleo intentará asignar este búfer llamando a la función asignar de la API. Así que vamos a empezar a ampliar nuestro código de ejemplo añadiendo nuestra propia implementación de la función asignar función. Lo primero que tenemos que hacer es añadirlo a nuestro descriptor de motor que devolvemos de crear_instancia. Vamos a añadir una serie de funciones en la entrada de hoy, así que vamos a asignar todos ellos mientras estamos en ello:
ENGINE_ERROR_CODE crear_instancia(uint64_t interfaz,
GET_SERVER_API get_server_api,
ENGINE_HANDLE **mango)
{
[ ... corte ... ]
/*
* Asigna los puntos de entrada de la API a las funciones que los implementan.
*/
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;
Lo siguiente que tenemos que hacer es crear una estructura de datos para guardar la información que necesitamos. El propósito de este tutorial no es crear una implementación eficiente en memoria, sino ejercitar la API. Así que vamos a crear la siguiente estructura:
void *key;
size_t nkey;
void *data;
size_t ndata;
int banderas;
rel_time_t exptime;
};
Nuestra aplicación de asignar quedaría así:
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));
if (it == NULL) {
return ENGINE_ENOMEM;
}
it->flags = banderas;
it->exptime = exptime;
it->nkey = nkey;
it->ndata = nbytes;
it->key = malloc(nkey);
it->data = malloc(nbytes);
if (it->key == NULL || it->data == NULL) {
free(it->key);
free(it->datos);
libre(it);
return ENGINE_ENOMEM;
}
memcpy(it->clave, clave, nclave);
*item = it;
return ÉXITO_INGENIO;
}
Si te fijas en la implementación anterior verás que no hemos devuelto el puntero a la memoria real para el almacenamiento de datos al núcleo de memcached. Para obtener esa dirección memcached llamará a get_item_info en la API. Así que vamos a implementar eso:
const item* item, item_info *item_info)
{
struct fs_item* it = (struct fs_item*)item;
if (item_info->nvalue < 1) {
devolver falso;
}
item_info->cas = 0; /* No soportado */
item_info->clsid = 0; /* No soportado */
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; /* Longitud total de los datos del elemento */
item_info->nvalue = 1; /* Número de fragmentos utilizados */
item_info->value[0].iov_base = it->data; /* puntero al fragmento 1 */
item_info->value[0].iov_len = it->ndata; /* Longitud del fragmento 1 */
devuelve true;
}
En get_item_info es importante y merece más información. Si nos fijamos en la API del motor, el "elemento" se define como un puntero void, y hemos definido nuestra propia estructura de elementos para realizar un seguimiento de la información que necesitamos para cada elemento. Sin embargo, el núcleo de memcached necesitará saber
donde leer / escribir la memoria para la clave y los datos que van a / vienen de un clinet. Para ello en invocará get_item_info. Si observa detenidamente nuestra aplicación de fs_get_item_info verás que lo primero que hago es comprobar que item_info->nvalue contiene al menos 1
elemento. En este momento siempre ser uno, pero la intención es que vamos a apoyar IO dispersos.
Cuando el núcleo termine de mover los datos que recibió a través del cable al elemento, intentará almacenar el elemento en nuestro motor llamando a store. Así que vamos a crear una implementación sencilla (la ampliaremos más adelante en el tutorial):
const void *cookie,
item* item,
uint64_t *cas,
Operación ENGINE_STORE_OPERATION,
uint16_t vbucket)
{
struct fs_item* it = item;
char fname[it->nkey + 1];
memcpy(fname, it->clave, it->clave);
fname[it->nkey] = '
FILE *fp = fopen(fname, "w");
if (fp == NULL) {
return ENGINE_NOT_STORED;
}
size_t nw = fwrite(it->data, 1, it->ndata, fp);
fclose(fp);
if (nw != it->ndata) {
remove(fname);
return ENGINE_NOT_STORED;
}
*cas = 0;
return ÉXITO_INGENIO;
}
Si miras la implementación anterior verás que no implementa la semántica correcta para añada/sustituir/configure etc, y bloqueará memcached mientras estamos haciendo IO de archivos. No te preocupes por eso ahora, porque volveremos a eso.
Cuando el núcleo termine de utilizar el elemento que ha asignado, lo liberará llamando a la función liberación en la API. El motor puede reutilizar el almacenamiento de elementos para otra cosa en este momento. Así que vamos a conectar nuestro liberación aplicación:
const void *cookie,
artículo* artículo)
{
struct fs_item *it = item;
free(it->key);
free(it->datos);
libre(it);
}
Ahora hemos creado todo el código para almacenar con éxito elementos en nuestro motor, pero no podemos leer ninguno de ellos de vuelta. Así que vamos a implementar consiga
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;
if (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_tamaño, 0, 0);
if (ret != ENGINE_SUCCESS) {
return ENGINE_ENOMEM;
}
FILE *fp = fopen(fname, "r");
if (fp == NULL) {
fs_release(handle, cookie, it);
return ENGINE_FAILED;
}
size_t nr = fread(it->data, 1, it->ndata, fp);
fclose(fp);
if (nr != it->ndata) {
fs_release(handle, cookie, it);
return ENGINE_FAILED;
}
*item = it;
return ÉXITO_INGENIO;
}
Añadamos una implementación ficticia para el resto de la API e intentemos cargar y probar el motor:
{
estático engine_info info = {
.description = "Motor del sistema de archivos v0.1",
.número_características = 0
};
devolver &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)
{
return ÉXITO_INGENIO;
}
static ENGINE_ERROR_CODE fs_flush(ENGINE_HANDLE* handle,
const void* cookie, time_t when)
{
return ÉXITO_INGENIO;
}
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,
ADD_RESPONSE respuesta)
{
return ENGINE_ENOTSUP;
}
static void fs_item_set_cas(ENGINE_HANDLE *handle, const void *cookie,
item* item, uint64_t val)
{
}
Así que vamos a seguir adelante y probar nuestro motor:
Desde otro terminal en el que estoy escribiendo:
Intentando ::1...
Conectado a opensolaris.
El carácter de escape es '^]'.
añadir prueba 0 0 4
prueba
ALMACENADO
obtener prueba
VALOR prueba 0 4
prueba
FIN
abandone
Conexión a tormenta cerrada por host extranjero.
Finaliza memcached pulsando ctrl-cy buscar en el directorio actual:
-rw-r-r- 1 trond users 6 Oct 8 12:56 test
trond@opensolaris> cat test
prueba
Eso es todo por esta vez.