filesystem mutex

Sziasztok!

Egy erdekes problema a kovetkezo (a topic nem utal halozatra, de majd megiscsak fog): van egy gep, van benne manapsag mar tobb proci (vagyis tobb mag, de persze lehet tobb proci is). Ezt hasznaljuk adatfeldolgozasra: egy nagy file diszkrol beolvas, sokaig csamcsog, valami adat (kisebb-nagyobb file) kiir; es ez jol parhuzamosithato mert a fileok egymastol teljesen fuggetlenul feldolgozhatoak. A tapasztalat az, hogyha egyszerre sok proci nekiesik parhuzamosan a problemanak, akkor -- mivel a fileok ugyanazon a diszken vannak -- nagyon-nagyon leesik a hatekonysag, foleg akkor, ha a partico NFS-en keresztul fel van masik gepre mount-olva, gyakorlatialg nagysagrenddel lassabb, mintha szekvencialisan olvasna. Az NFS az tenyleg vad, nem mondom hogy hasznalhatatlan, de nagyon tud blokkolni. Gyakorlatban ~50-100 megas fileok i/o-jarol van szo, gigabites ethernet, ~100-200mega/sec-es diszk i/o (hw raid) 4-6 gep es osszesen kb. 30-40 proci/mag (a gepek kozott van sima ketmagos, dual ketmagos, quad ketmagos, 2 x quad ketmagos, elegge vegyes allatkert).

Kerdes, hogy letezik-e erre a problemara valami mutex-szeru" ke'sz megoldas, amire azt mondom, hogy egy adott backend filerendszert mondjuk N-en (N=1 vagy max 2) hasznalhatnak, es egy sima kis progi blokkol, ameddig valaki fogja a filerendszert? Persze nem feltetlenul ennyire trivialis a problema, mert lehet hogy egy file-t egyetlen stock binaris dolgoz fel (es akkor nyilvan vagy nagyon bele kell turni a programba). De egy ilyesmit el tudok kepzelni:


( fsmutex --lock ; cat $bemenet.dat ; fsmutex --unlock ) | nagybonyaprogram >$kimenet.dat

(mondjuk ebben a peldaban a

$kimenet.dat

kicsi/elhanyagolhato meretu", de a $bemenet.dat nem)

Udv, A.

Hozzászólások

procmail-lel együtt jár egy lockfile nevű program, továbbá van egy lockfile-progs csomag is.


#define _XOPEN_SOURCE
#define _XOPEN_SOURCE_EXTENDED 1

#include <errno.h>    /* errno */
#include <limits.h>   /* SHRT_MIN */
#include <stdio.h>    /* fprintf() */
#include <stdlib.h>   /* EXIT_FAILURE */
#include <string.h>   /* strrchr */
#include <sys/ipc.h>  /* ftok() */
#include <sys/sem.h>  /* semget() */
#include <sys/stat.h> /* S_IRUSR */


static const char *pname;


static key_t
x_ftok(const char *pathname, int proj_id)
{
  key_t key = ftok(pathname, proj_id);

  if ((key_t)-1 == key) {
    (void)fprintf(stderr, "%s: ftok(): %s\n", pname, strerror(errno));
    exit(EXIT_FAILURE);
  }

  return key;
}


static int
x_semget(key_t key)
{
  int semid = semget(key, 1, 0);

  if (-1 == semid) {
    (void)fprintf(stderr, "%s: semget(): %s\n", pname, strerror(errno));
    exit(EXIT_FAILURE);
  }

  return semid;
}


static void
x_semcreat(key_t key, int initval)
{
  int semid = semget(key, 1, IPC_CREAT | IPC_EXCL | S_IRUSR | S_IWUSR);

  if (-1 == semid)
    (void)fprintf(stderr, "%s: semget(IPC_CREAT | IPC_EXCL): %s\n", pname,
        strerror(errno));
  else {
    union semun {
      int val;
      struct semid_ds *buf;
      unsigned short *array;
    } arg;

    arg.val = initval;
    if (0 == semctl(semid, 0, SETVAL, arg)) {
      (void)fprintf(stderr, "%s: initialized semid %d\n", pname, semid);
      return;
    }

    (void)fprintf(stderr, "%s: semctl(SETVAL): %s\n", pname, strerror(errno));
    if (-1 == semctl(semid, 0, IPC_RMID))
      (void)fprintf(stderr, "%s: semctl(%d, IPC_RMID): %s\n", pname,
          semid, strerror(errno));
  }

  exit(EXIT_FAILURE);
}


static void
x_semadd(int semid, short incr)
{
  struct sembuf buf;

  buf.sem_num = 0;
  buf.sem_op = incr;
  buf.sem_flg = 0;
  if (-1 == semop(semid, &buf, 1)) {
    (void)fprintf(stderr, "%s: semop(): %s\n", pname, strerror(errno));
    exit(EXIT_FAILURE);
  }
}


static long
x_long(const char *str, const char *desc, long min, long max)
{
  long ret;
  char *endptr;

  errno = 0;
  ret = strtol(str, &endptr, 0);
  if (0 != errno || '\0' == *str || '\0' != *endptr
      || min > ret || max < ret) {
    (void)fprintf(stderr, "%1$s: failed to parse \"%2$s\" as %3$s or"
        " %3$s not in range [%4$ld .. %5$ld]\n", pname, str, desc, min, max);
    exit(EXIT_FAILURE);
  }

  return ret;
}


int
main(int argc, char **argv)
{
  pname = strrchr(argv[0], '/');
  pname = pname ? pname + 1 : argv[0];

  if (2 == argc && 0 == strcmp(argv[1], "help"))
    (void)fprintf(stderr,
        "%1$s: perform basic semaphore operations\n"
        "Copyright (C) 2007 lacos, GNU GPLv2+\n"
        "%2$s\n"
        "\n"
        "usage: \n"
        "%1$s help - print this help and exit successfully\n"
        "%1$s create PATHNAME PROJ_ID INITVAL - create new semaphore\n"
        "%1$s op     PATHNAME PROJ_ID DIFF - modify existing semaphore\n",
        pname, "$Id: sem.c,v 1.2 2007/11/15 23:29:44 lacos Exp $");
  else
    if (5 == argc && 0 == strcmp(argv[1], "create"))
      x_semcreat(
        x_ftok(
          argv[2],
          x_long(argv[3], "PROJ_ID", 1, 255)
        ),
        x_long(argv[4], "INITVAL", 0, INT_MAX)
      );
    else
      if (5 == argc && 0 == strcmp(argv[1], "op"))
        x_semadd(
          x_semget(
            x_ftok(
              argv[2],
              x_long(argv[3], "PROJ_ID", 1, 255)
            )
          ),
          x_long(argv[4], "DIFF", SHRT_MIN, SHRT_MAX)
        );
      else {
        (void)fprintf(stderr, "%1$s: invalid parameters, run \"%1$s help\""
            " for help\n", pname);
        return EXIT_FAILURE;
      }

  return EXIT_SUCCESS;
}

A fenti példádban az

fsmutex --lock

-ot cseréld ki például

ssh fileserver sem op / 1 -1

-re, az

fsmutex --unlock

-ot pedig cseréld ki

ssh fileserver sem op / 1 +1

-re. Az egész előtt egyszer hozd létre / inicializáld a szemafort (pontosabban: egyelemű szemafortömböt):

ssh fileserver sem create / 1 2

(ha egyszerre legfeljebb kettőt akarsz tudni futtatni).

A

PATHNAME

és

PROJ_ID

jelentéséhez az

ftok()

man-ját ajánlom. (Annak is különösen az ütközésekre vonatkozó részét.) Szerintem maradj egy megbízható file-nál (pl.

/

), és a

PROJ_ID

-vel variálj, ha szükséges. Esetleg a "zárolni kívánt" filerendszer mount-point-ját add meg mindig

PATHNAME

-ként, és a

PROJ_ID

legyen 1.

Ajánlom még:

ipcs

,

ipcrm -s

.

Az inicializációnál a program kiírja a szemafor(tömb) azonosítóját, hogy az

ipcrm -s

-sel könnyen kiirthasd, ha akarod. Mivel az inicializáció két lépésből áll (létrehozás, érték beállítása), azért ha a második lépés bedöglik (próbáld meg 32768-cal pl.), "visszavonja" az elsőt is. Ha ez a visszavonás nem sikerül neki, akkor is megmondja az azonosítót, hátha te majd tudod törölni.

Az ssh-val belépegetés nem valami tetszetős, de így bármilyen shell script-ből lehet használni (helyileg is), illetve azt mondtad, hogy a file-ok jó sok megásak, ill. a kliensen a számolgatás CPU-igényes, úgyhogy azok mellett az ssh el fog törpülni. A szerveren az sshd-nek valóban több dolga lesz egy kicsit.

Kiegészítés: a down-ok és up-ok szétcsúszásának valószínűségét csökkentendő, a program köré a shell script-et valahogy így érdemes kanyarítani:


#!/bin/bash
set -e -C

PATHNAME=...
PROJ_ID=...
INPUT=...
OUTPUT=...

ssh fileserver sem op "$PATHNAME" "$PROJ_ID" -1
trap 'ssh fileserver sem op "$PATHNAME" "$PROJ_ID" +1' EXIT
work <"$INPUT" >"$OUTPUT"

Koszi, ezt a megoldast fontolora veszem. Egyelore egy cel-demont probalok osszedobni, ami az egesz parhuzamositott futtatast osszefogja es ez fogja kezelni a mutex-eket is. Maga a demon megcsinalja a tunnelt a gepek kozott, igy a mutex-hivasokat nem kell ssh-n keresztul meg extra modon atvinni, hanem mehetnek localhoston is, illetve igy egy "lockable cat" is egyszerubben implementalhato. Par nap, es szerintem fel is teszem valahova a produktumot, hha mast is erdekel.