2016-10-20 31 views
2

Linuxカーネルモジュールの作成中に、セマフォがロック解除されるのを待つ間に起きることができないkthreadに関する問題に直面しました。これにより、モジュールをアンロードしようとするとスレッドが止まらなくなり、rmmodがフリーズします。Linuxカーネル - kthreadがセマフォーを待つのを止めるには?

ご注意:注:このモジュールは3.10カーネルで動作します。これを新しいバージョン(3.10カーネルを搭載した在庫CentOS 7で稼動する顧客の要求)に更新する方法はありません。

以下は、モジュールのソースコードから興味深い部分です。シンプルなプロデューサの消費者問題です。リストのサイズに制限はなく(したがって、プロデューサセマフォは不要です)、ミューテックスによって保護されています。リストから何かを取る機能は、プロデューサによってアップされ、消費者が落としたセマフォによって保護されます。プロデューサ関数は、このコードスニペットには表示されていない外部イベント(実際にはcharデバイス)から呼び出され、できるだけ小さくします。モジュールのアンロードを除いて、プロセスは完全に機能します。

フリーズの原因となる部分には、コードスニペットにコメントが付きます。私がkthreadを停止するために知っている唯一の方法は、kthread_stopを呼び出すことです。この場合は、スリープしているスレッドを目覚めることができないため、失敗します。スレッドが終了するのを待つので、呼び出しは返されず、モジュールはアンロードされません。

セマフォがモジュールを正常にアンロードするのを待つkthreadを起動して停止するにはどうすればよいですか?

List実装:

#include <linux/mutex.h> 
#include <linux/list.h> 
#include <linux/semaphore.h> 

static LIST_HEAD(list); 
DEFINE_MUTEX(list_lock); 
DEFINE_SEMAPHORE(sem_list_consumer); 

void add_to_list(struct *some_struct) { 
    int rv = mutex_lock_interruptible(&list_lock); 
    if(rv != 0) { 
     return; 
    } 

    list_add(&some_struct->list, &list); 
    mutex_unlock(&list_lock); 
    up(&sem_list_consumer); 
} 

struct some_struct * take_from_list() { 
    int rv; 
    some_struct *entry; 

    /* this is where the kthread will freeze when module is unloaded */ 
    rv = down_interruptible(&sem_list_consumer); 
    if(rv != 0) { 
     return NULL; 
    } 

    rv = mutex_lock_interruptible(&list_lock); 
    if(rv != 0) { 
     up(&sem_list_consumer); 
     return NULL; 
    } 

    if (list_empty(&list)) { 
     mutex_unlock(&list_lock); 
     return NULL; 
    } else { 
     entry = list_last_entry(&list, struct some_struct, list); 
     if (entry) { 
      list_del(&entry->list); 
     } 
    } 

    mutex_unlock(&list_lock); 
    return entry; 
} 

消費者kthread実装:

#include <linux/kthread.h> 
#include <linux/sched.h> 

int consumer_kthread(void *data) { 
    struct some_struct *entry; 

    set_current_state(TASK_INTERRUPTIBLE); 
    while (!kthread_should_stop()) { 
     /* Here the function including the semaphore is called */ 
     entry = take_from_list(); 
     if(entry != NULL) { 
      /* Do something with 'entry' here */ 
     } else { 
      /* Some handling of returned NULL pointers */ 
     } 

     set_current_state(TASK_INTERRUPTIBLE); 
    } 
    set_current_state(TASK_RUNNING); 

    return 0; 
} 

モジュールの実装:

#include <linux/init.h> 
#include <linux/kthread.h> 
#include <linux/module.h> 
#include <linux/sched.h> 

static struct task_struct *consumer_task; 

static int __init initModule(void) { 
    consumer_task = kthread_run(consumer_kthread, NULL, "list-consumer"); 

    return 0; 
} 

static void __exit exitModule(void) { 
    /* this call will cause rmmod to freeze forever */ 
    kthread_stop(consumer_task); 
} 

module_init(initModule); 
module_exit(exitModule); 

MODULE_LICENSE("GPL v2"); 
MODULE_DESCRIPTION("My Module"); 
+0

'take_from_list'関数には、' NULL'を返す場所が3つあります。これらの3つの場所のうちの1つでは、 'sem_list_consumer'セマフォをまだ保持しています。私はこの矛盾があなたの問題と関係していると考えています。さもなければ、呼び出し元はセマフォを解放する必要があるかどうかをどのように伝えることができますか? –

+0

これは後で問題を引き起こす可能性がありますが、プロデューサコードを呼び出すことなくモジュールをロードしたりアンロードしたりするだけで問題が発生します。また、問題が発生するのは、何かがkthreadを中断/起動する場合にのみ発生しますが、これは私が達成できないものです。 – marandus

+0

あなたはすでにプロデューサコードが問題ではないことを確認しているので、エラーはコンシューマコードにあります。あなたの 'take_from_list'関数で、' mutex_unlock(&list_lock);と 'return NULL;'の行の間に 'up(&sem_list_consumer);を呼び出すだけでいいのではないかと思われます。これは、 'take_from_list'が' NULL'を返すときでさえ、 'sem_list_consumer'セマフォーが保持されることを期待しない限りです。この場合、セマフォーを保持せずに' take_from_list'をどういうわけか 'NULL'を返す必要があります。 –

答えて

2

不足しているコードは、この回答に教育的な推測しか使用できないことを意味します。ここで

はあなたの不足しているコードについての私の仮定です:take_from_listは有効なエントリを返す場合

  1. consumer_kthreadはエントリーで何かをして、take_from_listdown_interruptible(&sem_list_consumer)への呼び出しを一致させるためにup(&sem_list_consumer)を呼び出します。

  2. NULL戻りtake_from_list場合、consumer_kthreadNULLポインタのいくつかの処理を行い、そしてsem_list_consumerセマフォが元の状態にある前提としています。これらの仮定を考えると

それは時々最初up(&sem_list_consumer)を呼び出すことなくNULLを返すので、take_from_listにバグがあります。つまり、その後にtake_from_listを呼び出すと、信号によって中断されるまでdown_interruptible(&sem_list_consumer)への呼び出しがブロックされます。追加consumer_kthreadの不足しているコード内のいくつかの場所がある場合

struct some_struct * take_from_list() { 
    int rv; 
    some_struct *entry; 

    rv = down_interruptible(&sem_list_consumer); 
    if(rv != 0) { 
     return NULL; 
    } 

    rv = mutex_lock_interruptible(&list_lock); 
    if(rv != 0) { 
     up(&sem_list_consumer); 
     return NULL; 
    } 

    if (list_empty(&list)) { 
     mutex_unlock(&list_lock); 
     up(&sem_list_consumer); /* <-- this line was missing */ 
     return NULL; 
    } else { 
     entry = list_last_entry(&list, struct some_struct, list); 
     if (entry) { 
      list_del(&entry->list); 
     } 
    } 

    mutex_unlock(&list_lock); 
    return entry; 
} 

改正

:そのバグを修正するには、常にそれがNULLを返したときにそれを残した状態にセマフォを残すためにtake_from_listを変更自分自身を待ち行列に入れてスリープ状態に入るときは、起床条件にkthread_should_stop()への呼び出しを含める必要があります。ウェイクアップ条件は、他の条件(||kthread_should_stop()によって満たされる必要があります。

exitModule関数のkthread_stop(condition_thread)を呼び出すと、消費者スレッドが起動します。イベントを待っている場合は、まず起床条件を確認し、条件が満たされていない場合はスリープ状態に戻ります。可能な起床条件の1つとしてkthread_should_stop()を含めることによって、消費者スレッドがすぐにスリープ状態に戻らないことを保証します。

+0

あなたが間違っている行が問題を引き起こす可能性があります。しかし、根本的な原因ではありません。デザイン全体は、リストが空のときにkthreadがスリープし、リストに何かがあればプロデューサーが目覚めるというアイデアに基づいています(これが私がプロデューサ - 消費者問題を明示した理由です)。したがって、リストが空の場合、スレッドは、欠落した行を追加したその時点まで到達することはありません。 カーネル内のkthreadにシグナルを送る方法の例を教えてください。私は私の問題を解決するだろうと思う、私は中断信号を逃して、それを送信する方法がわからないので。 – marandus

+0

あなたの 'exitModule'関数は、プロデューサスレッドが目を覚ますのと同じ方法でコンシューマスレッドを起動することができます。コンシューマスレッドに信号を送るための 'exitModule'は必要ないはずですが、コンシューマスレッドを起動する前にフラグを設定し、コンシューマスレッドはそのフラグがセットされたときに適切なアクションを取るべきです。 –

+0

別のフラグを使用する代わりに、待ちイベントの起床条件の一部として 'kthread_should_stop()'の呼び出しを含めることができます。それはおそらく実際にそれを行う最も簡単な方法です。私はそれに応じて私の答えを修正します。信号を忘れる - それは問題に対する鈍いハンマーのアプローチです。 –

1

あなたは、待機中のプロセスを送信する必要がありますシグナル。プロセスはTASK_INTERRUPTABLEからTASK_RUNNINGに変更され、スケジュールされ、EINTRを返すdown_interruptableで実行されます。

+0

ありがとうございます。 kthreadにシグナルを送る方法の簡単な例を教えてください。私はカーネルソースを掘り下げてどのように動作しているのかを調べましたが、正確にやり方がわからず、例えばsiginfo構造体が必要です。 – marandus

関連する問題