2010-11-06 13 views
2

J'écris un code pour le module noyau Linux et j'y vois un comportement étrange. Voici mon code:Comportement étrange de printk dans le module noyau Linux

int data = 0; 
void threadfn1() 
{ 
    int j; 
    for(j = 0; j < 10; j++) 
     printk(KERN_INFO "I AM THREAD 1 %d\n",j); 
    data++; 
} 

void threadfn2() 
{ 
    int j; 
    for(j = 0; j < 10; j++) 
     printk(KERN_INFO "I AM THREAD 2 %d\n",j); 
    data++; 
} 
static int __init abc_init(void) 
{ 
     struct task_struct *t1 = kthread_run(threadfn1, NULL, "thread1"); 
     struct task_struct *t2 = kthread_run(threadfn2, NULL, "thread2"); 
     while(1) 
     { 
     printk("debug\n"); // runs ok 
      if(data >= 2) 
      { 
       kthread_stop(t1); 
       kthread_stop(t2); 
       break; 
      } 
     } 
     printk(KERN_INFO "HELLO WORLD\n"); 

} 

Fondamentalement, je tentais d'attendre les discussions pour terminer, puis imprimer quelque chose après. Le code ci-dessus n'atteint cette cible mais avec "printk("debug\n");" pas commenté. Dès que je commente printk("debug\n"); pour exécuter le code sans débogage et charger le module via la commande insmod, le module se bloque et il semble qu'il se perd dans la récursivité. Je ne sais pas pourquoi printk affecte mon code d'une telle manière?

Toute aide serait appréciée.

ce qui concerne.

Répondre

1

Avec l'appel à printk() supprimé le compilateur optimise la boucle en while (1);. Lorsque vous ajoutez l'appel à printk() le compilateur n'est pas sûr que data n'est pas modifié et vérifie donc la valeur chaque fois par le biais de la boucle.

Vous pouvez insérer une barrière dans la boucle, ce qui force le compilateur à réévaluer data à chaque itération. par exemple:

while (1) { 
     if (data >= 2) { 
       kthread_stop(t1); 
       kthread_stop(t2); 
       break; 
     } 

     barrier(); 
} 
0

Peut-être que les données devraient être déclarées volatiles? Il se peut que le compilateur ne soit pas en mémoire pour obtenir des données dans la boucle.

+1

volatile fonctionnerait quelque peu, mais c'est toujours une mauvaise idée car vous ne pouvez pas vous assurer que data ++ est une instruction atomique. Sur un système multiprocesseur avec plusieurs threads luttant pour la même variable, cela vous garantit une condition de course garantie. –

+0

Grand point. Ne pas utiliser volatile ici. Besoin de plus de café. ;-) –

4

Vous ne synchronisez pas l'accès à la variable de données. Ce qui se passe, c'est que le compilateur va générer une boucle infinie. Voici pourquoi:

while(1) 
     { 
      if(data >= 2) 
      { 
       kthread_stop(t1); 
       kthread_stop(t2); 
       break; 
      } 
     } 

Le compilateur peut détecter que la valeur des données ne change jamais dans la boucle while. Par conséquent, il peut complètement déplacer le départ de la boucle et vous finirez par un simple

while (1) {} 

Si vous insérez printk le compilateur doit supposer que les données variables globales peuvent changer (après tout - le compilateur a aucune idée de ce que printk fait en détail) donc votre code commencera à travailler à nouveau (dans une sorte de comportement non défini de façon ..)

Comment résoudre ce problème:

Utilisez des primitives de synchronisation de fil appropriée. Si vous enveloppez l'accès aux données dans une section de code protégée par un mutex, le code fonctionnera. Vous pouvez également remplacer les données de variable et utiliser un sémaphore compté à la place.

Edit:

Ce lien explique comment le verrouillage fonctionne dans le noyau Linux:

http://www.linuxgrill.com/anonymous/fire/netfilter/kernel-hacking-HOWTO-5.html

0

La réponse de Nils Pipenbrinck est parfaite. Je vais juste ajouter quelques pointeurs.

Rusty's Unreliable Guide to Kernel Locking (chaque hacker du noyau devrait lire celui-ci).

Goodbye semaphores?, The mutex API (lwn.net articles sur la nouvelle API mutex introduite début 2006, avant que le noyau Linux n'utilise les sémaphores comme mutex).De plus, puisque vos données partagées sont un simple compteur, vous pouvez simplement utiliser l'API atomique (en principe, déclarez votre compteur comme atomic_t et accédez-y en utilisant les fonctions atomic_ *).

0

Volatile pourrait ne pas toujours être "mauvaise idée". Il est nécessaire de séparer le cas où une volatilité est nécessaire et lorsqu'un mécanisme d'exclusion mutuelle est nécessaire. Il n'est pas optimal quand on utilise ou abuse un mécanisme pour l'autre. Dans le cas ci-dessus. Je suggère pour la solution optimale, que les deux mécanismes sont nécessaires: mutex à fournir une exclusion mutuelle, volatile pour indiquer au compilateur que "info" doit être lu à partir du matériel. Sinon, dans certaines situations (optimisation -O2, -O3), les compilateurs pourraient par inadvertance omettre les codes nécessaires.