Cómo usar /proc desde un módulo del kernel Linux
Ya he hablado sobre el directorio /proc en Linux, y también he dedicado un artículo a cómo programar un módulo para el kernel. Hoy vamos a dar una vuelta más de tuerca y vamos a ver cómo usar /proc desde un módulo del kernel Linux.
Para crear una entrada (ya sea un archivo o un directorio) en el filesystem /proc (procfs) nos valemos de la función create_proc_entry():
struct proc_dir_entry *create_proc_entry(const char *name, umode_t mode, struct proc_dir_entry *parent)
El primer parámetro es el nombre del archivo o directorio, el segundo es el modo (permisos y tipo) y finalmente el padre (si no tiene será NULL). La función devuelve una estructura llamada proc_dir_entry, que tiene la siguiente definición en proc_fs.h.
struct proc_dir_entry {
unsigned int low_ino;
umode_t mode;
nlink_t nlink;
kuid_t uid;
kgid_t gid;
loff_t size;
const struct inode_operations *proc_iops;
const struct file_operations *proc_fops;
struct proc_dir_entry *next, *parent, *subdir;
void *data;
read_proc_t *read_proc;
write_proc_t *write_proc;
atomic_t count; /* use count */
int pde_users;
struct completion *pde_unload_completion;
struct list_head pde_openers;
spinlock_t pde_unload_lock;
u8 namelen;
char name[];
};
Por ahora sólo vamos a usar los campos read_proc y write_proc, que son punteros a la función encargada de la lectura y la escritura en el fichero. Sin más preámbulos vamos a presentar un ejemplo completo que seguidamente pasaremos a analizar. Lo situaremos en el directorio ~kerneldev/src/modulo2 y lo llamaremos hello_proc.c.
#include <linux/module.h>
/* importar función copy_from_user() */
#include <asm/uaccess.h>
#include <linux/proc_fs.h>
/* importar función find_task_by_pid_ns() */
#include <linux/sched.h>
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("Modulo Hola proc");
MODULE_AUTHOR("Alberto Garcia");
/* nombre del archivo: /proc/helloproc */
#define PROC_ENTRY "helloproc"
#define MAX_LEN 7
pid_t pid = 0;
/* Muestra el nombre el pid introducido */
/* La información se almacena en memoria (empezando en *buf) */
static int read_proc(char *buf, char **start, off_t off,
int count, int *eof, void *data)
{
struct task_struct *task = NULL;
unsigned len = 0;
if (pid) {
task = pid_task(find_vpid(pid), PIDTYPE_PID);
if (!task)
return -EINVAL;
len +=
snprintf(buf + len, count - len,
"Datos sobre el proceso %u\n", pid);
len +=
snprintf(buf + len, count - len, " Padre: %u\n",
task->parent->pid);
return (len);
}
printk(KERN_INFO "No se ha indicado un pid\n");
return 0;
}
/* Obtenemos el pid del proceso */
static ssize_t write_proc(struct file *file, const char __user * buff,
unsigned long len, void *data)
{
char c[MAX_LEN + 1];
if (len > MAX_LEN)
return -EINVAL;
if (copy_from_user(c, buff, len))
return -EFAULT;
c[len] = '\0';
if (!sscanf(c, "%d\n", &pid))
return
printk(KERN_INFO "Solicitud para pid %d\n", pid);
return len;
}
/* Registrar la entrada en el fs /proc */
static int register_proc(void)
{
struct proc_dir_entry *hello_entry;
hello_entry = create_proc_entry(PROC_ENTRY, S_IFREG, NULL);
if (!hello_entry)
return -1;
hello_entry->read_proc = read_proc;
hello_entry->write_proc = write_proc;
return 0;
}
/* eliminar la enreada en el fs /proc */
static void unregister_proc(void)
{
remove_proc_entry(PROC_ENTRY, NULL);
}
static int __init hello_init(void)
{
if (register_proc() == -1) {
printk(KERN_INFO "No se ha podido crear la entrada en /proc\n");
return -1;
}
printk(KERN_INFO "Entrada creada en /proc.\n");
return 0;
}
static void __exit hello_exit(void)
{
unregister_proc();
printk(KERN_INFO "Hasta otra.\n");
}
module_init(hello_init);
module_exit(hello_exit);
Para compilar este programa podemos usar el siguiente Makefile.
ifneq ($(KERNELRELEASE),)
obj-m := hello_proc.o
else
KERNEL_VERSION = linux-3.5.0
KERNELDIR := ../$(KERNEL_VERSION)
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.[oas] .*.flags *.ko .*.cmd .*.d .*.tmp \
*.mod.c .tmp_versions Module.symvers
Tras compilarlo, obtendremos un módulo instalable con insmod, que crea un archivo llamado /proc/helloproc. El fichero soporta lectura y escritura. Su funcionamiento es el siguiente:
Escribimos sobre el fichero el pid de un proceso:
echo "927" > /proc/helloproc
Seguidamente podemos leer del fichero para obtener información sobre el proceso. En este caso, y por simplificar, sólo se mostrará el PID del proceso padre.
cat /proc/helloproc

Al igual que en el módulo creado en el artículo anterior, aquí usamos module_init y module_exit para indicar cuáles son las funciones encargadas de la carga y descarga del módulo. Al cargar el módulo en memoria se invoca la función register_proc(), que crea la entrada en el procfs usando create_proc_entry(). La constante S_IFREG definida en sys/stat.h, indica que queremos crear un fichero normal. Si quisiéramos crear un directorio debemos usar S_IFDIR.
Sobre la estructura devuelta por esta función indicamos cuáles son las funciones encargadas de la gestión de la lectura/escritura del fichero:
hello_entry->read_proc = read_proc; hello_entry->write_proc = write_proc;
Así pues, read_proc() y write_proc() son las funciones en la que codificaremos lo que se debe hacer cuando se lee o escribe en el fichero.
La función write_proc() tiene la siguiente definición.
static ssize_t write_proc(struct file *file, const char __user * buff, unsigned long len, void *data)
Por ahora, para no complicarlo más, nos basta saber que buff contiene los datos que se están escribiendo en el fichero, y len la longitud en bytes de dichos datos.
Hay que tener en cuenta que buff es un puntero a una zona de memoria del espacio de usuario, pero como estamos en el espacio del kernel, no podemos acceder de forma directa, por lo que hay que usar la función copy_from_user() para copiar el dato en una variable del espacio del kernel. Tras esto simplemente usamos la función sscanf() para poner el valor introducido en formato entero en la variable pid.
La función para la lectura tiene la siguiente definición.
static int read_proc(char *buf, char **start, off_t off, int count, int *eof, void *data)
Cuando se invoca la función read_proc, el kernel reserva una zona de memoria para almacenar la salida. En este caso es buf, el primer parámetro, donde escribiremos la información que queremos devolver. Usamos la función snprintf() para ir poniendo la información en buf y, finalmente, retornamos en número de bytes que vamos a devolver.
Vamos a pararnos en la siguiente línea:
task = pid_task(find_vpid(pid), PIDTYPE_PID);
Tras la ejecución, la variable task apuntará a una estructura de datos llamada task_struct con información del proceso cuyo PID es el almacenado en la variable pid. Esta estructura almacena mucha información sobre el proceso (cada proceso tiene asociado un task_struct). Aquí puedes encontrar más información sobre la estructura task_struct (además de otras).
Nosotros hemos usado el campo parent, que es un puntero al task_struct del proceso padre, y de ahí hemos sacado su PID con task->parent->pid.
Si estuviéramos codificando directamente sobre el código del kernel podríamos haber usado las funciones find_task_by_pid_ns() o find_task_by_vpid() para obtener el proceso a partir del PID, pero desde un módulo no podemos usarlas directamente a no ser que las exportemos desde el propio código del kernel con, por ejemplo, EXPORT_SYMBOL(find_task_by_pid_ns).
Antes de terminar nos resta comentar la función unregister_proc(), que es invocada al descargar el módulo de la memoria con rmmod, y que se encarga de eliminar el archivo del procfs mediante la función remove_proc_entry().
Ahora que somos capaces de crear un módulo y usar el procfs, el siguiente paso lógico es hablar sobre los controladores o device drivers y cómo programarlos, pero será ya en un próximo artículo.
Tags: /proc, directorio proc, kernel, Linux, módulo, procfs, programación




[...] así que conviene leer los anteriores posts sobre programación de módulos del kernel Linux y cómo usar /proc desde un módulo del kernel Linux. Para compilar el módulo me he basado en el entorno para desarrollo para kernel Linux que [...]