Archivo

Entradas Etiquetadas ‘memoria’

Bailando con bits: Ver y modificar los bits de un número

Jueves, 10 de Junio de 2010 Gaspar Fernández 2 comentarios

Hay muchas formas para hacer esto, pero quizás la más visual (tal vez también útil algunas veces) es la siguiente (se explica más abajo):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#include <stdio.h>

typedef struct {
    unsigned int b1:1;
    unsigned int b2:1;
    unsigned int b3:1;
    unsigned int b4:1;
    unsigned int b5:1;
    unsigned int b6:1;
    unsigned int b7:1;
    unsigned int b8:1;
    unsigned int b9:1;
    unsigned int b10:1;
    unsigned int b11:1;
    unsigned int b12:1;
    unsigned int b13:1;
    unsigned int b14:1;
    unsigned int b15:1;
    unsigned int b16:1;
    unsigned int b17:1;
    unsigned int b18:1;
    unsigned int b19:1;
    unsigned int b20:1;
    unsigned int b21:1;
    unsigned int b22:1;
    unsigned int b23:1;
    unsigned int b24:1;
    unsigned int b25:1;
    unsigned int b26:1;
    unsigned int b27:1;
    unsigned int b28:1;
    unsigned int b29:1;
    unsigned int b30:1;
    unsigned int b31:1;
    unsigned int b32:1;

} Tint_bits;

int main()
{
  int numero;
  Tint_bits *bitpack;

  bitpack=(Tint_bits*)&numero;
  numero=63;
  printf ("Dir cos: %X\nDir paq: %X\n", &numero, bitpack);  // Vemos que las direcciones de memoria son idénticas.
  printf ("Numero: %d\n", numero);
  printf ("Bit 1: %d\n", bitpack->b1);
  printf ("Bit 2: %d\n", bitpack->b2);
  printf ("Bit 3: %d\n", bitpack->b3);
  printf ("Bit 4: %d\n", bitpack->b4);
  printf ("Bit 5: %d\n", bitpack->b5);
  printf ("Bit 6: %d\n", bitpack->b6);
  printf ("Bit 7: %d\n", bitpack->b7);
  printf ("Bit 8: %d\n", bitpack->b8);
  printf ("Bit 9: %d\n", bitpack->b9);
  printf ("Bit10: %d\n", bitpack->b10);
  printf ("Bit11: %d\n", bitpack->b11);
  printf ("Bit12: %d\n", bitpack->b12);
  printf ("Bit13: %d\n", bitpack->b13);
  printf ("Bit14: %d\n", bitpack->b14);
  printf ("Bit15: %d\n", bitpack->b15);
  printf ("Bit16: %d\n", bitpack->b16);
  printf ("Bit17: %d\n", bitpack->b17);
  printf ("Bit18: %d\n", bitpack->b18);
  printf ("Bit19: %d\n", bitpack->b19);
  printf ("Bit20: %d\n", bitpack->b20);
  printf ("Bit21: %d\n", bitpack->b21);
  printf ("Bit22: %d\n", bitpack->b22);
  printf ("Bit23: %d\n", bitpack->b23);
  printf ("Bit24: %d\n", bitpack->b24);
  printf ("Bit25: %d\n", bitpack->b25);
  printf ("Bit26: %d\n", bitpack->b26);
  printf ("Bit27: %d\n", bitpack->b27);
  printf ("Bit28: %d\n", bitpack->b28);
  printf ("Bit29: %d\n", bitpack->b29);
  printf ("Bit30: %d\n", bitpack->b30);
  printf ("Bit31: %d\n", bitpack->b31);
  printf ("Bit32: %d\n", bitpack->b32);
 
  bitpack->b9=1;
  printf("Numero= %d\n", numero);
}

Primero creamos un espacio de bits en nuestro registro Tint_bits, con ello, hacemos 32 variables de tamaño 1 bit (con un espacio en memoria de 32bits (lo que es un entero de 32bits). Lo que hacemos para poder consultar los bits, es crear un puntero de este tipo (Tint_bits) que apunte a la dirección de memoria de nuestro número, con ello podremos leer en memoria, los distintos bits del número accediendo a los diferentes miembros (b1->b32), y lo que es mejor, podemos modificarlos (como hacemos al final del programa).

Lo malo de este método es que no podremos acceder a los bits como si de un array se tratase, y que este método no es muy portable, para enteros de 64 bits habría que hacer otro registro por ejemplo, y tendremos problemas para pasar de un sistema big-endian a un little-endian y viceversa; pero, como yo digo podemos ver la memoria directamente, que a veces es interesante.

Cuando un proceso “se come” la memoria de nuestro sistema

Martes, 20 de Abril de 2010 Gaspar Fernández 4 comentarios

pacmanHoy en día no se le suele ver la cara, dado que la memoria de nuestro sistema suele ser grande, pero cuando por ejemplo, a un proceso se le va la mano y reserva más memoria de la que tiene nuestro sistema, entra en marcha un proceso especial del núcleo de Linux; el OOM Killer (Out Of Memory Killer), que se encarga de detectar qué proceso es el peor del sistema y matarlo.
Atendiendo a la documentación (oom_kill.c) tendremos tres opciones cuando nos quedamos sin memoria:

  • Matar un proceso al azar (malo)
  • Congelar el sistema (peor)
  • Matar un proceso de forma inteligente

En fin, si el sistema no está configurado para congelarse (sysctl.kernel_panic_on_oom), intentaremos ver qué proceso tiene más papeletas para morir (el más dañino), y se hace asignando puntuaciones a procesos, en definitiva queremos matar un proceso malo en beneficio para el sistema, no queremos fastidiar diez horas de trabajo frente al ordenador (a menos que sea necesario).

Se sigue el siguiente algoritmo:

  • Los primeros puntos a sumar son la cantidad de memoria del proceso
  • Sumamos la memoria independiente de sus procesos hijos
  • Los procesos con prioridad (nice) duplican la puntuación. Un proceso con nice si se come nuestra memoria compromete el sistema.
  • Los procesos de superusuario dividen por 4 la puntuación. Los procesos de sistema o de superusuario no deberían dar ningún problema, y si root ejecutara algo… él/ella sabe lo que hace ;)
  • Miramos el valor de /proc/ /oom_adj (comprendido entre -17 y +15). Como usuarios, podemos aumentar o disminuir la probabilidad de que un proceso salga elegido para su sacrificio (por el bien del sistema). Si escribimos en ese fichero un -17, nuestro proceso no va a matarlo nunca el OOM killer

Como buen Linux, que nos deja como usuarios poder tocar su funcionamiento, tenemos lo siguiente:

  • /proc/PID/oom_score : Puntuación de un proceso
  • /proc/PID/oom_adj : Asignar o quitar papeletas para que un proceso sea sacrificado (como dije en la lista anterior). Es buena idea que si sabemos que un proceso puede colgar nuestro sistema, le demos un +15; si el sistema se lo carga, mejor; y si es algo importante aunque nuestro sistema ande falto de memoria, le damos un -17
  • sysctl vm.panic_on_oom : En lugar de matar un proceso, lanza un kernel panic.
  • Hay más reglas para configuración si buscamos dentro de sysctl y /proc/

¿Queremos saber qué proceso tiene, ahora mismo, más papeletas para ser sacrificado?
Si disponemos de /proc/sys/vm/oom_victim lo podremos ver ahí, si no, podemos ejecutar este script:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
min=0

for pid in `ps axo pid`
do
        if [ -r /proc/$pid/oom_score ]
        then
                puntos=`cat /proc/$pid/oom_score`;
                if (( $puntos > $min ))
                then
                        proceso=$pid;
                        min=$puntos;
                fi
        fi
done

ps ax | grep $proceso

Y lo más importante, queremos lanzarlo a mano, es decir, hay un proceso gastando mucha memoria y procesador, tanta, que no podemos hacer login en el sistema desde consola. Podemos hacer: Alt + SysRq + F y saltará un OMM Manual (Si sysctl kernel.sysrq = 1), es normal que se muera Firefox :) (gasta muchos recursos y Linux lo considera dañino para el sistema)

Foto: Joe Shlabotnik (Flickr)

Por qué no debemos utilizar gets()

Lunes, 19 de Abril de 2010 Gaspar Fernández 3 comentarios

A veces me sorprendo (como profesor de programación) de que en muchos sitios siguen enseñando la función gets() para la entrada de datos desde teclado sin explicar lo que puede pasar.

gets() es una función peligrosa. Imaginemos que escribimos el siguiente programa:

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

int main()
{
  char cadena2[10];
  char cadena1[10];

  printf("c1: %x\nc2: %x\n", cadena1, cadena2);
  gets(cadena1);

  printf("Cadena 2: %s\n", cadena2);
}

Ahora la compilamos, ejecutamos e introducimos un texto de prueba:

$ gcc -o test3 test3.c
/tmp/ccK2P2ON.o: In function `main’:
test3.c:(.text+0×32): warning: the `gets’ function is dangerous and should not be used.
$ ./test3
c1: bfc81580
c2: bfc8158a
Escribo un texto de mas de 10 caracteres
Cadena 2: texto de mas de 10 caracteres
Violación de segmento

Ya de primeras nos avisa gcc de que la función no debería ser usada, a lo que muchos se sorprenden.
Luego, en la ejecución, tal como se indica en el código fuente, mostramos las direcciónes de memoria donde se ubicarán cadena1 (c1) y cadena2 (c2).
Tras ello escribimos: “Escribo un texto de mas de 10 caracteres“, que se introducirá en la variable cadena1 y mostramos la variable cadena2, que muestra parte de lo que escribimos antes… y además de regalo una violación de segmento, porque nuestro programa ha escrito en una región de memoria que no le pertenecía.

Todo esto sucede porque gets() no tiene ninguna manera de saber cuánto puede escribir, y por ello, escribirá siempre en memoria, todos los caracteres que introduzcamos (para un ejemplo rápido vale, pero ya), de todas formas no debemos darlo todo por perdido, tenemos una excelente función, muy parecida fgets(), aunque tiene más parámetros:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

#define MAX_LON_CADENA 10

int main()
{
  char cadena2[MAX_LON_CADENA];
  char cadena1[MAX_LON_CADENA];

  printf("c1: %x\nc2: %x\n", cadena1, cadena2);

/*   gets(cadena1); */
  fgets(cadena1, MAX_LON_CADENA, stdin);
  printf("Cadena 1: \"%s\"\n", cadena1);
  printf("Cadena 2: %s\n", cadena2);
}
  1. El primer parámetro de fgets() es dónde vamos a almacenar lo leído.
  2. El segundo parámetro es cuántos caracteres leeremos como máximo (incluyendo el terminador de cadena). Y si hemos definido un valor máximo para la cadena, podemos utilizar éste.
  3. El tercero es de dónde lo leemos, y si queremos hacerlo desde teclado, usaremos stdin
Categories: C/C++ Tags: , , , , ,

Visita otras webs de la red