Modules noyaux
Exercice #1: Générez un module noyau out of tree pour la cible NanoPi :
- Créez le squelette d’un module noyau et générez-le en dehors des sources du noyau à l’aide d’un Makefile. Le module devra afficher un message lors de son enregistrement et lors de sa désinstallation.
- Testez sur la machine hôte la commande
modinfo
1 sur votre squelette de module et comparez les informations retournées avec celles du code source. - Installez le module (
insmod
) et contrôlez le log du noyau (dmesg
) - Comparez les résultats obtenus par la commande
lsmod
avec ceux obtenus avec la commandecat /proc/modules
- Désinstallez le module (
rmmod
). - Adaptez le
Makefile
du module pour autoriser l’installation du module avec les autres modules du noyau permettant l’utilisation de la commandemodprobe
. Le module devra être installé dans le root filesystem utilisé en cifs par la cible.
Exercice #2: Adaptez le module de l’exercice précédent afin qu’il puisse recevoir deux ou trois paramètres de
votre choix. Ces paramètres seront affichés dans la console. Adaptez également le rootfs afin de
pouvoir utiliser la commande modprobe
.
Solution
CVER := aarch64-buildroot-linux-gnu-
KVER := 5.15.148
CPU := arm64
KDIR := /buildroot/output/build/linux-$(KVER)/
TOOLS := /buildroot/output/host/usr/bin/$(CVER)
MODPATH := /rootfs
#MODPATH := /buildroot/output/target
export PATH := /buildroot/output/host/usr/sbin$\
:/buildroot/output/host/usr/bin/$\
:/buildroot/output/host/sbin$\
:/buildroot/output/host/bin/$\
:$(PATH)
# Part executed when called from kernel build system:
ifneq ($(KERNELRELEASE),)
obj-m += mymodule.o ## name of the generated module
mymodule-objs := skeleton.o ## list of objects needed for that module
CFLAGS_skeleton.o := -DDEBUG
# Part executed when called from standard make in module source directory:
else
include ../../kernel_settings
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(CPU) CROSS_COMPILE=$(TOOLS) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
install:
$(MAKE) -C $(KDIR) M=$(PWD) INSTALL_MOD_PATH=$(MODPATH) modules_install
endif
// skeleton.c
#include <linux/module.h> // needed by all modules
#include <linux/init.h> // needed for macros
#include <linux/kernel.h> // needed for debugging
#include <linux/moduleparam.h> // needed for module parameters
static char* text = "dummy text";
module_param(text, charp, 0664);
static int elements = 1;
module_param(elements, int, 0);
static int __init skeleton_init(void)
{
pr_info ("Linux module 01 skeleton loaded\n");
pr_debug (" text: %s\n elements: %d\n", text, elements);
return 0;
}
static void __exit skeleton_exit(void)
{
pr_info ("Linux module skeleton unloaded\n");
}
module_init (skeleton_init);
module_exit (skeleton_exit);
MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");
Exercice #3: Trouvez la signification des 4 valeurs affichées lorsque l’on tape la commande
cat /proc/sys/kernel/printk
Gestion de la mémoire, bibliothèque et fonction utile
Exercice #4: Créez dynamiquement des éléments dans le noyau. Adaptez un module noyau, afin que l’on puisse lors de son installation spécifier un nombre d’éléments à créer ainsi qu’un texte initial à stocker dans les éléments précédemment alloués. Chaque élément contiendra également un numéro unique. Les éléments seront créés lors de l’installation du module et chaînés dans l’une liste. Ces éléments seront détruits lors de la désinstallation du module. Des messages d’information seront émis afin de permettre le debugging du module.
Solution
export PATH := /buildroot/output/host/usr/sbin$\
:/buildroot/output/host/usr/bin/$\
:/buildroot/output/host/sbin$\
:/buildroot/output/host/bin/$\
:$(PATH)
# Part executed when called from kernel build system:
ifneq ($(KERNELRELEASE),)
obj-m += mymodule.o ## name of the generated module
mymodule-objs := skeleton.o ## list of objects needed for that module
# Part executed when called from standard make in module source directory:
else
include ../../kernel_settings
PWD := $(shell pwd)
all:
$(MAKE) -C $(KDIR) M=$(PWD) ARCH=$(CPU) CROSS_COMPILE=$(TOOLS) modules
clean:
$(MAKE) -C $(KDIR) M=$(PWD) clean
install:
$(MAKE) -C $(KDIR) M=$(PWD) INSTALL_MOD_PATH=$(MODPATH) modules_install
endif
/* skeleton.c */
#include <linux/module.h> /* needed by all modules */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/moduleparam.h> /* needed for module parameters */
#include <linux/slab.h> /* needed for dynamic memory allocation */
#include <linux/list.h> /* needed for linked list processing */
#include <linux/string.h> /* needed for string handling */
static char* text = "dummy text";
module_param(text, charp, 0);
static int elements = 0;
module_param(elements, int, 0);
struct element {
char text[100];
int ele_nr;
struct list_head node;
};
static LIST_HEAD (my_list);
static int __init skeleton_init(void)
{
int i;
pr_info ("Linux module 04 skeleton loaded\n");
pr_info (" text: %s\n elements: %d\n", text, elements);
for (i = 0; i < elements; i++) {
struct element* ele = kzalloc (sizeof(*ele), GFP_KERNEL);
if (ele != 0) {
strncpy (ele->text, text, 99);
ele->ele_nr = i;
list_add_tail (&ele->node, &my_list);
}
}
return 0;
}
static void __exit skeleton_exit(void)
{
struct element* ele;
int nb_eles = 0;
list_for_each_entry (ele, &my_list, node) {
pr_info ("ele [%d/%d/%d] = %s\n", nb_eles, ele->ele_nr, elements, ele->text);
nb_eles++;
}
while (!list_empty (&my_list)) {
ele = list_entry (my_list.next, struct element, node);
list_del (&ele->node);
kfree (ele);
}
pr_info ("All elements (%d/%d) of the list have been removed and deleted!\n", nb_eles, elements);
pr_info ("Linux module skeleton unloaded\n");
}
module_init (skeleton_init);
module_exit (skeleton_exit);
MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");
Accès aux entrées/sorties
Exercice #5: À l’aide d’un module noyau, afficher le Chip-ID du processeur, la température du CPU et la MAC adresse du contrôleur Ethernet.
- Les 4 registres de 32 bits du Chip-ID sont aux adresses
0x01c1'4200
à0x01c1'420c
- Le registre de 32 bits du senseur de température du CPU est à l’adresse
0x01c2'5080
- Les 2 registres de 32 bits de la MAC adresse sont aux adresses
0x01c3'0050
et0x01c3'0054
Pour obtenir la température du CPU, il faut, après lecture du registre, appliquer la formule suivante :
Avant d’accéder aux registres du Chip-ID, veuillez réserver la zone mémoire correspondante aux
registres du microprocesseur. Validez cette réservation à l’aide de la commande cat /proc/iomem
.
La commande cat /sys/class/thermal/thermal_zone0/temp
permet de valider la bonne lecture
de la température. La commande ifconfig
permet de valider la bonne lecture de la MAC
adresse.
Solution
/* skeleton.c */
#include <linux/module.h> /* needed by all modules */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/moduleparam.h> /* needed for module parameters */
#include <linux/slab.h> /* needed for dynamic memory allocation */
#include <linux/list.h> /* needed for linked list processing */
#include <linux/string.h> /* needed for string handling */
#include <linux/ioport.h> /* needed for memory region handling */
#include <linux/io.h> /* needed for mmio handling */
static struct resource* res[3]={[0]=0,};
static int __init skeleton_init(void)
{
unsigned char* regs[3]={[0]=0,};
unsigned int chipid[4]={[0]=0,};
long temp = 0;
unsigned int addr[2] = {[0]=0,};
pr_info ("Linux module 05 skeleton loaded\n");
res[0] = request_mem_region (0x01c14000, 0x1000, "allwiner h5 sid");
//res[1] = request_mem_region (0x01C25000, 0x1000, "allwiner h5 ths");
//res[2] = request_mem_region (0x01C30000, 0x1000, "allwiner h5 emac");
if ((res[0] == 0))// || (res[1] == 0) ||(res[2] == 0))
pr_info ("Error while reserving memory region... [0]=%d, [1]=%d, [2]=%d\n", res[0]==0, res[1]==0, res[2]==0);
regs[0] = ioremap (0x01c14000, 0x1000);
regs[1] = ioremap (0x01C25000, 0x1000);
regs[2] = ioremap (0x01C30000, 0x1000);
if ((regs[0] == 0) || (regs[1] == 0) ||(regs[2] == 0)) {
pr_info ("Error while trying to map processor register...\n");
return -EFAULT;
}
chipid[0] = ioread32 (regs[0]+0x200);
chipid[1] = ioread32 (regs[0]+0x204);
chipid[2] = ioread32 (regs[0]+0x208);
chipid[3] = ioread32 (regs[0]+0x20c);
pr_info("chipid=%08x'%08x'%08x'%08x\n",
chipid[0], chipid[1], chipid[2], chipid[3]);
temp = -1191 * (int)ioread32(regs[1]+0x80) / 10 + 223000;
pr_info ("temperature=%ld (%d)\n", temp, ioread32(regs[1]+0x80));
addr[0]=ioread32(regs[2]+0x50);
addr[1]=ioread32(regs[2]+0x54);
pr_info("mac-addr=%02x:%02x:%02x:%02x:%02x:%02x\n",
(addr[1]>> 0) & 0xff,
(addr[1]>> 8) & 0xff,
(addr[1]>>16) & 0xff,
(addr[1]>>24) & 0xff,
(addr[0]>> 0) & 0xff,
(addr[0]>> 8) & 0xff
);
iounmap (regs[0]);
iounmap (regs[1]);
iounmap (regs[2]);
return 0;
}
static void __exit skeleton_exit(void)
{
pr_info ("Linux module skeleton unloaded\n");
if (res[0] != 0) release_mem_region (0x01c14000, 0x1000);
//release_mem_region (0x01C25000, 0x1000);
//release_mem_region (0x01C30000, 0x1000);
}
module_init (skeleton_init);
module_exit (skeleton_exit);
MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");
Threads du noyau
Exercice #6: Développez un petit module permettant d’instancier un thread dans le noyau. Ce thread affichera
un message toutes les 5 secondes. Il pourra être mis en sommeil durant ces 5 secondes à l’aide de
la fonction ssleep(5)
provenant de l’interface <linux/delay.h>
.
Solution
/* skeleton.c */
#include <linux/module.h> /* needed by all modules */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/moduleparam.h> /* needed for module parameters */
#include <linux/slab.h> /* needed for dynamic memory allocation */
#include <linux/list.h> /* needed for linked list processing */
#include <linux/string.h> /* needed for string handling */
#include <linux/ioport.h> /* needed for memory region handling */
#include <linux/io.h> /* needed for mmio handling */
#include <linux/kthread.h> /* needed for kernel thread management */
#include <linux/delay.h> /* needed for delay fonctions */
static struct task_struct* my_thread;
static int skeleton_thread (void* data)
{
pr_info ("skeleton thread is now active...\n");
while(!kthread_should_stop()) {
ssleep (5);
pr_info ("skeleton thread is kick every 5 seconds...\n");
}
return 0;
}
static int __init skeleton_init(void)
{
pr_info ("Linux module 06 skeleton loaded\n");
my_thread = kthread_run (skeleton_thread, 0, "s/thread");
return 0;
}
static void __exit skeleton_exit(void)
{
kthread_stop (my_thread);
pr_info ("Linux module skeleton unloaded\n");
}
module_init (skeleton_init);
module_exit (skeleton_exit);
MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");
Mise en sommeil
Exercice #7: Développez un petit module permettant d’instancier deux threads dans le noyau. Le premier thread attendra une notification de réveil du deuxième thread et se remettra en sommeil. Le 2ème thread enverra cette notification toutes les 5 secondes et se rendormira. On utilisera les waitqueues pour les mises en sommeil. Afin de permettre le debugging du module, chaque thread affichera un petit message à chaque réveil.
Solution
/* skeleton.c */
#include <linux/module.h> /* needed by all modules */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/kthread.h> /* needed for kernel thread management */
#include <linux/wait.h> /* needed for waitqueues handling */
#include <linux/delay.h> /* needed for delay fonctions */
static struct task_struct* my_thread[2];
DECLARE_WAIT_QUEUE_HEAD (queue_1);
static atomic_t is_kicked;
static int skeleton_thread_1 (void* data)
{
pr_info ("skeleton thread_1 is now active...\n");
while(!kthread_should_stop()) {
int status = wait_event_interruptible
(queue_1, (atomic_read(&is_kicked) != 0)
|| kthread_should_stop());
if (status == -ERESTARTSYS) {
pr_info ("skeleton thread_1 has been interrupted\n");
break;
}
atomic_dec (&is_kicked);
pr_info ("skeleton thread_1 has been kicked\n");
}
return 0;
}
static int skeleton_thread_2 (void* data)
{
wait_queue_head_t queue;
pr_info ("skeleton thread_2 is now active...\n");
init_waitqueue_head (&queue);
while(!kthread_should_stop()) {
ssleep(5);
/*
int status = wait_event_interruptible_timeout
(queue, kthread_should_stop(), 5*HZ);
if (status == -ERESTARTSYS) {
pr_info ("skeleton thread_2 has been interrupted\n");
break;
}
*/
pr_info ("skeleton thread_2 timout elapsed...\n");
atomic_set (&is_kicked, 1);
wake_up_interruptible (&queue_1);
}
return 0;
}
static int __init skeleton_init(void)
{
pr_info ("Linux module 07 skeleton loaded\n");
atomic_set (&is_kicked, 0);
my_thread[0] = kthread_run (skeleton_thread_1, 0, "s/thread/%d", 1);
my_thread[1] = kthread_run (skeleton_thread_2, 0, "s/thread/2");
return 0;
}
static void __exit skeleton_exit(void)
{
kthread_stop (my_thread[1]);
kthread_stop (my_thread[0]);
pr_info ("Linux module skeleton unloaded\n");
}
module_init (skeleton_init);
module_exit (skeleton_exit);
MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");
Interruptions
Exercice #8: Développez un petit module permettant de capturer les pressions exercées sur les switches de la carte d’extension par interruption. Afin de permettre le debugging du module, chaque capture affichera un petit message.
Quelques informations pour la réalisation du module :
- Acquérir la porte GPIO avec le service
gpio_request (<io_nr>, <label>);
- Obtenir le vecteur d’interruption avec le service
gpio_to_irq (<io_nr>);
- Informations sur les switches de la carte d’extension
- k1 -
gpio
: A,pin_nr
=0,io_nr
=0 - k2 -
gpio
: A,pin_nr
=2,io_nr
=2 - k3 -
gpio
: A,pin_nr
=3,io_nr
=3
- k1 -
Solution
/* skeleton.c */
#include <linux/module.h> /* needed by all modules */
#include <linux/init.h> /* needed for macros */
#include <linux/kernel.h> /* needed for debugging */
#include <linux/interrupt.h> /* needed for interrupt handling */
#include <linux/gpio.h> /* needed for i/o handling */
#define K1 0
#define K2 2
#define K3 3
static char* k1="gpio_a.0-k1";
static char* k2="gpio_a.2-k2";
static char* k3="gpio_a.3-k3";
irqreturn_t gpio_isr(int irq, void* handle)
{
pr_info ("interrupt %s raised...\n", (char*)handle);
return IRQ_HANDLED;
}
static int __init skeleton_init(void)
{
int status = 0;
// install k1
if (status == 0) status = gpio_request (K1, "k1");
if (status == 0)
status = request_irq(gpio_to_irq(K1), gpio_isr,
IRQF_TRIGGER_FALLING | IRQF_SHARED, k1, k1);
// install k2
if (status == 0) status = gpio_request (K2, "k2");
if (status == 0)
status = request_irq(gpio_to_irq(K2), gpio_isr,
IRQF_TRIGGER_FALLING | IRQF_SHARED, k2, k2);
// install k3
if (status == 0) status = gpio_request (K3, "k3");
if (status == 0)
status = request_irq(gpio_to_irq(K3), gpio_isr,
IRQF_TRIGGER_FALLING | IRQF_SHARED, k3, k3);
pr_info ("Linux module 08 skeleton loaded\n");
return status;
}
static void __exit skeleton_exit(void)
{
gpio_free(K1);
free_irq(gpio_to_irq(K1), k1);
gpio_free(K2);
free_irq(gpio_to_irq(K2), k2);
gpio_free(K3);
free_irq(gpio_to_irq(K3), k3);
pr_info ("Linux module skeleton unloaded\n");
}
module_init (skeleton_init);
module_exit (skeleton_exit);
MODULE_AUTHOR ("Daniel Gachet <daniel.gachet@hefr.ch>");
MODULE_DESCRIPTION ("Module skeleton");
MODULE_LICENSE ("GPL");
Archives 2021/2022
-
Pour installer
modinfo
, ajoutez le package “kmod utilities” : Target Packages → System Tools → kmod et kmod utilities. Vous pouvez ensuite mettre à jour le root file system avec la commandeextract-rootfs.sh
, mais attention, vous allez remplacer des fichiers tels que/etc/fstab
. Sauvegardez vos fichiers importants! ↩