Categorías: Tutoriales

Controlar el tamaño de la pila del sistema con Ulimit

Esta tarde, haciendo un programita en C usando hilos con memoria compartida, he obtenido un error de segmentación (segmentation fault) al multiplicar unas matrices de un tamaño considerable. Este error se producía por la cantidad de recursos utilizados y más en concreto por un desbordamiento de la pila (stack overflow), en este caso la solución es bien fácil. Usando ulimit podemos consultar el tamaño máximo actual y ponerlo a nuestro gusto, en mi caso lo he fijado a 50MB y es más que suficiente.

Para mostrar el tamaño de pila:

$ulimit -s

Para modificarlo:

$ulimit -s [tamaño en bytes]

Alberto Hornero Luque

Contínuamente relacionado con el procesamiento de imágenes y el análisis numérico, se encuentra actualmente trabajando como Ingeniero Técnico en el laboratorio de Métodos Cuantitativos de Teledetección del CSIC. Administrador del portal Linux Hispano centra sus intereses en tecnologías abiertas, desarrollos en la nube y GNU/Linux, y hace poco fundó junto a Javier Carazo una startup, Codection. Puedes seguir sus updates en @ahornero y LinkedIn.

Ver comentarios

  • Realmente eso no es una solución. El problema es que estás pasando por valor un array demasiado grande, cuando lo correcto es pasar los datos por referencia.

    Tu "solución" al segfault no es si no una chapuza que oculta el problema.
    La solución correcta sería trabajar con referencias.

    El usar threads y "memoria compartida" no parece relevante al problema (aunque quede cool) por lo que cuentas... aunque sin datos, poco podemos ver.

    Un saludo,

  • @LaintalAy: Siento decirte que eso sí es una solución, de hecho es la más correcta entre las posibles, si tengo que operar con unas matrices de X tamaño no voy a capar ese tamaño por culpa de un desbordamiento del stack, se hace evidente que tengo que fijar este tamaño a un nuevo valor. No se trata de un error del sistema operativo ni de la aplicación, simplemente se trata de entender lo que tenemos y lo que queremos.

    El hecho de usar hilos y memoria compartida se debe a que incluso separando el problema en diferentes hilos (lo cual reduce el tamaño usado por cada uno) seguimos obteniendo el problema de llenar una pila con un tamaño inicial, en algunos casos, un poco limitado.

    Por cierto, ya que afirmas que esto NO es una solución me gustaría conocer cómo lo harías tú, y no me digas que usando "referencias", que si tuvieras unos conocimientos mínimos sobre C sabrás que un vector, un matriz y cualquier array N-dimensional no deja de ser un puntero y hay que tratarlo como tal, así que "referencias" no aporta nada nuevo al problema.

    Un saludo.

  • Vamos a razonar esto... la pila de un programa es el lugar en el que, entre otras cosas:
    * Se almancenan las variables locales de una función
    * Se pasan los parámetros a la siguiente función

    Ref: http://en.wikipedia.org/wiki/Call_stack#Functions_of_the_call_stack

    Ergo, o bien estás llamando recursivamente muchas veces para llenar el stack, o bien el almacenamiento que haces no está siendo por referencia, si no por valor (variables locales de longitud fija). Una combinación de ambos también vale.

    Por ejemplo,

    #include
    #define MAX_SIZE 15500

    void dealWithMadness(int loop) {
    char madness[MAX_SIZE][MAX_SIZE];

    fprintf(stdout, "This seems to work... ");
    while (1) {
    fprintf(stdout, "%d\n", loop);
    fflush(stdout);
    dealWithMadness(++loop);
    }
    }

    int main() {
    dealWithMadness(0);
    return 0;
    }

    Si compilas ese código no lo podrás ejecutar, te dará segfault a la primera. Si pones a ilimitada la pila con ulimit conseguirás ocho vueltas. Por lo que el problema no está solucionado, lo que haces es aplazarlo. Igual para tu caso particular es suficiente, pero NO es solución.

    ¿Cuál es la solución?

    Pues no declarar las variables locales de tamaño demencial, si no reservarlas en memoria dinámica (via malloc) o declarar las matrices como estructuras estáticas globales (que ocupan espacio en la imagen de memoria del programa -zona de datos-, no en la pila).
    Para terminar de completar, en el caso de matrices, las llamadas anidadas a malloc pueden suponer una sobrecarga considerable y sería más eficiente una llamada única a malloc con el tamaño total a reservar y crear la estructura de referencias mediante aritmética de punteros.

    Ejemplo:

    #include
    #include

    #define MAX_SIZE 15500
    #define ITERATIONS 10
    int gIt = 0;

    void dealWithMadnessMallocHell(int loop) {
    char** madness;
    int i = 0;
    madness = malloc(MAX_SIZE * sizeof(char*));
    for (; i < MAX_SIZE; i++) {
    madness[i] = malloc(MAX_SIZE * sizeof(char));
    }

    while (gIt++ <= ITERATIONS) {
    fprintf(stdout, "This seems to go very slow... ");
    fprintf(stdout, "%d\n", loop);
    fflush(stdout);
    dealWithMadnessMallocHell(++loop);
    }

    /* Free stuff */
    for (i = 0; i < MAX_SIZE; i++) {
    free(madness[i]);
    }
    free(madness);
    return;
    }

    void dealWithMadnessArithmetic(int loop) {
    char** madness;
    int i = 0;
    madness = malloc(MAX_SIZE * MAX_SIZE * sizeof(char));
    for (; i < MAX_SIZE; i++) {
    madness[i] = (char*)(madness + MAX_SIZE * i * sizeof(char));
    }

    while (gIt++ <= ITERATIONS) {
    fprintf(stdout, "This seems to work... ");
    fprintf(stdout, "%d\n", loop);
    fflush(stdout);
    dealWithMadnessArithmetic(++loop);
    }

    free(madness);
    return;
    }

    int main() {
    gIt = 0;
    dealWithMadnessMallocHell(0);
    gIt = 0; /* Sorry... recursion needed :) */
    dealWithMadnessArithmetic(0);
    return 0;
    }

    De todos modos la discusión vale de poco si no enseñas un esbozo de tu código y la interfaz de la función en dónde te explotaba el programa. El tamaño de pila de unix por defecto son 10MB ¿un poco limitado? WTF? ¿Cuánto quieres que te den para pasar parámetros?

    Además te quedas tan ancho diciendo que esa solución es "la más correcta". Con dos cojones. ¿Entonces los desbordamientos de pila sólo se solucionan ampliando la pila? Cachondo...

    En fin, al menos que esto sirva para no esparcir (aún más) esta falsedad de "solución" por más sitios, que ya me toca bastante sufrirla en el trabajo.

    Saludos,

    PD: Excelente referencia, con ejemplos, http://en.wikipedia.org/wiki/Stack_overflow - Impagable la línea "This class of software bug is usually caused by one of two types of programming errors."

    • Estimado LaintalAy, he de recomendarte la lectura del siguiente enlace: Bell-Labs.

      Me parece incoherente todo lo que dices, según "idea", el tamaño de la pila no influye para nada en la programación, pues he de decirte que te equivocas, es un parámetro que la mayoría de las veces no ha de ser modificado, es más se puede confundir su modificación con un error de código, pero existen los casos en los que aun estando correctamente programado el tamaño de pila ha de ser mayor que el establecido por defecto. Amigo creo que deberías de aprender a escuchar y a documentarte antes sobre lo que hablas. Y no, no voy a pegar aquí el fuente del que hablo porque no está abierto.

      Un saludo, y espero que trates de entenderlo algún día.

  • Espectacular, enlazas un artículo sobre los orígenes de C. La única referencia al stack en el artículo....

    "Similarly, C itself provides two durations of storage: `automatic' objects that exist while control resides in or below a procedure, and `static,' existing throughout execution of a program. Off-stack, dynamically-allocated storage is provided only by a library routine and the burden of managing it is placed on the programmer."

    Bah, que me podías haber enlazado a un artículo sobre Enigma también. De paso, en tu caso estás abusando del entorno ·automático· de C, utilizándolo para algo que no está diseñado. Amigo mío, si no distingues cuando una estructura de datos tiene que ser guardada en pila y cuándo guardada en el heap, eres un peligro como programador (de C, benditos los lenguajes que te abstraen de todo esto eh?).

    El que ni siquiera quieras hacer un esbozo del problema te pone en evidencia. Nadie te está pidiendo que publiques tu código fuente, si no que pongas el esqueleto del problema que tan fantásticamente has resuelto con "ulimit".
    Y a diferencia de tí, yo he proporcionado enlaces que describen tu problema, explican su causa y muestran cómo se solucionan. Tú has aportado un lamentable y anacrónico enlace y vaguedades.

    Ah! Y algún ejemplo de "esos casos" en los que hay que aumentar la pila impepinablemente tampoco estarían mal.... no se, ahora mismo no recuerdo ninguna aplicación que en su manual de instrucciones ponga que se tiene que hacer "ulimit -s unlimited" antes de ejecutar. Y así, mientras a mi me iluminas, a mucha gente que aterrice por aquí le aclararás cuándo están enmascarando un error y cuando están en ese caso "correcto" del que hablas.

    Me repito, "ulimit -s" es un WORKAROUND para hacer funcionar un programa que, a nivel de código fuente, hace un uso abusivo del stack del programa. No es la solución a nada.

    Un saludo.

    Fe de errores:
    * En mi primer post me refiero a "referencias" y "valor" de un modo incorrecto en C que da lugar a confusión. Con "valor" me refiero a declaraciones del tipo "char cadena[200];" que implican asignación directa de espacio en pila. Con referencias me refiero a espacio asignado dinámicamente en heap via malloc.
    * En el segundo post, el código de la parte de aritmética de punteros, que genera una matriz a partir de un único malloc, está (muy) mal. Lo que pasa es que no tiene mucho sentido republicar código que resulta ilegible en los comentarios.... probando, probando

    int main() {
    fprintf(stdout, "Hello world");
    }

    • Lo que resulta espectacular es la ignorancia de algunos. Dado que prefiero que los lectores conozcan el porqué y cuando solucionarlo, planteo el siguiente código y para resolverlo lo propuesto arriba, aunque propongo las diferentes soluciones para sistemas IBM AIX y HP/Compaq Tru64.

      #include
      #include
      #include
      #define N 1048

      int main (int argc, char *argv[])
      {
      int nthreads, tid, i, j;
      double a[N][N];

      /* Fork a team of threads with explicit variable scoping */
      #pragma omp parallel shared(nthreads) private(i,j,tid,a)
      {

      /* Obtain/print thread info */
      tid = omp_get_thread_num();
      if (tid == 0)
      {
      nthreads = omp_get_num_threads();
      printf("Number of threads = %d\n", nthreads);
      }
      printf("Thread %d starting...\n", tid);

      /* Each thread works on its own private copy of the array */
      for (i=0; i<N; i++)
      for (j=0; j<N; j++)
      a[i][j] = tid + i + j;

      /* For confirmation */
      printf("Thread %d done. Last element= %f\n",tid,a[N-1][N-1]);

      } /* All threads join master thread and disband */

      }

      Y la solución para los sitemas mencionados:

      # This is for IBM AIX systems
      setenv XLSMPOPTS "stack=50000000"
      # This is for HP/Compaq Tru64 systems
      setenv MP_STACK_SIZE 50000000

  • Bueno, mi guessing no anduvo nada mal, gracias por confirmarlo :))

    >double a[N][N];

    Muy grande, una barbaridad, y eso no se debería declarar en pila.

    La pila no se usa para eso. El apaño de la copia automática de las variables en los threads con OpenMP te explota en la jeta, porque no se usa para lo que estás intentando hacer. Y por eso tienes que recurrir a la chapuza del ulimit.

    Pero nada, se feliz, siempre podrás agrandar la pila hasta lo que te direccione la máquina. Que para eso está ¿no?

    Por último, y también para que los lectores lo sepan, el comando "ulimit" en algún unix está restringido a root por razones de seguridad (RHEL en concreto... debian y fedora por ejemplo no).

    Un saludo,

  • Interesante discusión la que habéis tenido, en realidad quizás los dos tengáis razón o estéis equivocados, la solución sería buscar algo intermedio entre lo que ambos decís.

    Yo actualmente me he encontrado con varios problemas de este tipo en java, ya que tengo que comparar a nivel de bytes ficheros binarios de gran tamaño (varios gigas) para determinar si son iguales, iguales pero desordenados, iguales pero uno contiene basura, etc...

    Lo que hago es determinar el tamaño máximo de memoria disponible en la máquina para crear la tabla de comparación en memoria, conforme se va llegando al límite vuelco el contenido de la tabla en un fichero y ya mezclo en el algoritmo lo que sigo almacenando en memoria y el contenido del fichero.

    Igual para operar con matrices no vale y es algo más lento al tener que escribir/leer en ficheros pero es la solución menos comprometida que he encontrado.

    Saludos!

  • es muy interesante todo esto pero tengo una gran pregunta que no se si valla al caso pero quiero saber aunque sea la respuesta a mi pregunta espero tenerla graciias

    que es pila de sistema?

    un saludo :p

  • Hay veces que hay que aumentar el tamaño de pila o de heap, el tamaño que puede reservarse un programa en memoria a veces no es infinito. Habia una practica muy interesante en la universidad con factorización recursiva que desbordaba la pila, en fin un saludo.

Entradas recientes

DeepSeek

3 días hace

Contacto

2 semanas hace

Smart-tv mute

2 semanas hace

STEAM OS

3 semanas hace

2025

1 mes hace

El podcast de Linux Hispano – #072 – El hardware libre debe consolidarse como el software libre

https://www.youtube.com/embed/z-xGk9c_eOw Guionista y locutor: Manuel Ignacio López Quintero.Fecha de publicación: 31 de diciembre de 2024.

1 mes hace