ホームページ  >  記事  >  システムチュートリアル  >  Linux プロセスのスリープとウェイクアップ: システムをより省エネルギーかつ効率的にします。

Linux プロセスのスリープとウェイクアップ: システムをより省エネルギーかつ効率的にします。

PHPz
PHPz転載
2024-02-14 10:36:121101ブラウズ

Linux システムは、マルチタスクの同時実行をサポートするオペレーティング システムであり、複数のプロセスを同時に実行できるため、システムの使用率と効率が向上します。ただし、すべてのプロセスが常にプロセッサ リソースを占有する必要はなく、一部のプロセスは、特定の条件下で一時的にプロセッサを放棄し、スリープ状態に入り、条件が満たされるのを待ってからウェイクアップして実行を継続できます。この利点は、プロセッサのリソースを節約し、実行する必要がある他のプロセスに多くの機会を与えることができること、またシステムの消費電力と発熱を削減し、システムの安定性と寿命を向上させることができることです。この記事では、Linux システムにおけるプロセスのスリープとウェイクアップの方法 (スリープの理由、スリープの種類、スリープ機能、ウェイクアップ関数、プロセスのウェイクアップ メカニズムなど) を紹介します。

もちろん、プロセスは CPU の制御をアクティブに解放することもできます。関数schedule()は、他のプロセスがCPUを占有するようにスケジュールするために、プロセスによってアクティブに呼び出すことができるスケジューリング関数です。自発的に CPU を放棄したプロセスが CPU を占有するように再スケジュールされると、最後に停止した場所から実行を開始します。つまり、schedule() を呼び出すコードの次の行から実行を開始します。
場合によっては、デバイスの初期化、I/O 操作の完了、タイマーの期限切れなど、特定のイベントが発生するまでプロセスを待機する必要があります。この場合、プロセスは実行キューから削除され、待機キューに追加される必要があり、この時点でプロセスはスリープ状態になります。

Linux プロセスのスリープとウェイクアップ: システムをより省エネルギーかつ効率的にします。

Linux におけるプロセスのスリープ状態の分類

1 つは割り込み可能なスリープ状態で、そのステータス フラグは TASK_INTERRUPTIBLE;
もう 1 つは中断不可能なスリープ状態であり、そのステータス フラグは TASK_UNINTERRUPTIBLE です。割り込み可能なスリープ状態のプロセスは、特定の条件が true になるまでスリープします。たとえば、ハードウェア割り込みの生成、プロセスが待機しているシステム リソースの解放、シグナルの送信などが、プロセスをウェイクアップする条件になる可能性があります。中断不可能なスリープ状態は中断可能なスリープ状態と似ていますが、例外が 1 つあります。つまり、このスリープ状態に信号を送信するプロセスはその状態を変更できません。つまり、ウェイクアップする信号に応答しません。通常、中断不可能なスリープ状態はあまり使用されませんが、特定のイベントが発生するまでプロセスを待機する必要があり、中断できないなど、特定の状況では依然として非常に役立ちます。
最新の Linux オペレーティング システムでは、通常、プロセスは、schedule() を呼び出すことによってスリープ状態に入ります。次のコードは、
を実行します。 は、実行中のプロセスをスリープ状態にする方法を示しています。

リーリー

最初のステートメントでは、プログラムはプロセス構造体ポインター sleep_task を格納します。current はマクロであり、実行中のプロセスを指します。
プロセス構造。 set_current_state() は、プロセスの状態を実行状態 TASK_RUNNING からスリープ状態に変更します
タスク_中断可能。ステータス TASK_RUNNING のプロセスによってスケジュールされた場合、schedule() は CPU を占有する別のプロセスをスケジュールします; ステータス TASK_INTERRUPTIBLE または TASK_UNINTERRUPTIBLE のプロセスによってスケジュールされた場合、追加の手順があります。実行中: 現在実行中のプロセスは、別のプロセスがスケジュールされる前に実行キューから削除されます。これにより、実行中のプロセスは実行キューに存在しなくなるためスリープ状態になります。
次の関数を使用して、スリープ状態になったプロセスを復帰させることができます。
wake_up_process(sleeping_task);
wake_up_process() を呼び出した後、スリープ状態のプロセスのステータスは TASK_RUNNING に設定され、スケジューラ
実行キューに追加されます。もちろん、このプロセスは、スケジューラによって次回スケジュールされたときにのみ実行できます。

無効なウェイクアップ

ほとんどの場合、プロセスは特定の条件をチェックし、条件が満たされていないことが判明するとスリープ状態になります。でも時々### しかし、判定条件が成立した後にプロセスがスリープを開始してしまうと、プロセスはいつまでもスリープしてしまう、いわゆる無効ウェイクアップ問題となります。オペレーティング システムでは、複数のプロセスが共有データに対して何らかの処理を実行しようとし、最終結果がプロセスの実行順序に依存する場合、競合状態が発生します。これはオペレーティング システムの一般的な問題です。 up それはまさに競争条件によるものです。
2 つのプロセス A と B があるとします。プロセス A はリンク リストを処理しています。リンク リストが空かどうかを確認する必要があります。空でない場合は、リンクを処理します
テーブル内のデータはいくつかの操作を受けており、プロセス B もリンク リストにノードを追加しています。リンク リストが空の場合、操作するデータがないため、プロセス A はスリープ状態になります。プロセス B がリンク リストにノードを追加すると、プロセス A が起動します。コードは次のとおりです。 プロセス:### リーリー
Bプロセス:

100 spin_lock(&list_lock);
101 list_add_tail(&list_head, new_node);
102 spin_unlock(&list_lock);
103 wake_up_process(processa_task);

这里会出现一个问题,假如当A进程执行到第3行后第4行前的时候,B进程被另外一个处理器调度
投 入运行。在这个时间片内,B进程执行完了它所有的指令,因此它试图唤醒A进程,而此时的A进程还没有进入睡眠,所以唤醒操作无效。在这之后,A 进程继续执行,它会错误地认为这个时候链表仍然是空的,于是将自己的状态设置为TASK_INTERRUPTIBLE然后调用schedule()进入睡 眠。由于错过了B进程唤醒,它将会无限期的睡眠下去,这就是无效唤醒问题,因为即使链表中有数据需要处理,A 进程也还是睡眠了。

避免无效唤醒

如何避免无效唤醒问题呢?我们发现无效唤醒主要发生在检查条件之后和进程状态被设置为睡眠状
态之前, 本来B进程的wake_up_process()提供了一次将A进程状态置为TASK_RUNNING 的机会,可惜这个时候A进程的状态仍然是TASK_RUNNING,所以wake_up_process()将A进程状态从睡眠状态转变为运行状态的努力 没有起到预期的作用。要解决这个问题,必须使用一种保障机制使得判断链表为空和设置进程状态为睡眠状态成为一个不可分割的步骤才行,也就是必须消除竞争条 件产生的根源,这样在这之后出现的wake_up_process ()就可以起到唤醒状态是睡眠状态的进程的作用了。
找到了原因后,重新设计一下A进程的代码结构,就可以避免上面例子中的无效唤醒问题了。
A进程:

1 set_current_state(TASK_INTERRUPTIBLE);
2 spin_lock(&list_lock);
3 if(list_empty(&list_head)) {
4 spin_unlock(&list_lock);
5 schedule();
6 spin_lock(&list_lock);
7 }
8 set_current_state(TASK_RUNNING);
9
10 /* Rest of the code ... */
11 spin_unlock(&list_lock);

可以看到,这段代码在测试条件之前就将当前执行进程状态转设置成TASK_INTERRUPTIBLE了,并且在链表不为空的情况下又将自己置为TASK_RUNNING状态。这样一来如果B进程在A进程进程检查
了链表为空以后调用wake_up_process(),那么A进程的状态就会自动由原来TASK_INTERRUPTIBLE
变成TASK_RUNNING,此后即使进程又调用了schedule(),由于它现在的状态是TASK_RUNNING,所以仍然不会被从运行队列中移出,因而不会错误的进入睡眠,当然也就避免了无效唤醒问题。

Linux内核的例子

在Linux操作系统中,内核的稳定性至关重要,为了避免在Linux操作系统内核中出现无效唤醒问题,
Linux内核在需要进程睡眠的时候应该使用类似如下的操作:
/* ‘q’是我们希望睡眠的等待队列 /
DECLARE_WAITQUEUE(wait,current);
add_wait_queue(q, &wait);
set_current_state(TASK_INTERRUPTIBLE);
/
或TASK_INTERRUPTIBLE /
while(!condition) /
‘condition’ 是等待的条件*/
schedule();
set_current_state(TASK_RUNNING);
remove_wait_queue(q, &wait);
上面的操作,使得进程通过下面的一系列步骤安全地将自己加入到一个等待队列中进行睡眠:首先调
用DECLARE_WAITQUEUE ()创建一个等待队列的项,然后调用add_wait_queue()把自己加入到等待队列中,并且将进程的状态设置为 TASK_INTERRUPTIBLE 或者TASK_INTERRUPTIBLE。然后循环检查条件是否为真:如果是的话就没有必要睡眠,如果条件不为真,就调用schedule()。当进程 检查的条件满足后,进程又将自己设置为TASK_RUNNING 并调用remove_wait_queue()将自己移出等待队列。
从上面可以看到,Linux的内核代码维护者也是在进程检查条件之前就设置进程的状态为睡眠状态,
然后才循环检查条件。如果在进程开始睡眠之前条件就已经达成了,那么循环会退出并用set_current_state()将自己的状态设置为就绪,这样同样保证了进程不会存在错误的进入睡眠的倾向,当然也就不会导致出现无效唤醒问题。
下面让我们用linux 内核中的实例来看看Linux 内核是如何避免无效睡眠的,这段代码出自Linux2.6的内核(linux-2.6.11/kernel/sched.c: 4254):
4253 /* Wait for kthread_stop */
4254 set_current_state(TASK_INTERRUPTIBLE);
4255 while (!kthread_should_stop()) {
4256 schedule();
4257 set_current_state(TASK_INTERRUPTIBLE);
4258 }
4259 __set_current_state(TASK_RUNNING);
4260 return 0;
上面的这些代码属于迁移服务线程migration_thread,这个线程不断地检查kthread_should_stop(),
直 到kthread_should_stop()返回1它才可以退出循环,也就是说只要kthread_should_stop()返回0该进程就会一直睡 眠。从代码中我们可以看出,检查kthread_should_stop()确实是在进程的状态被置为TASK_INTERRUPTIBLE后才开始执行 的。因此,如果在条件检查之后但是在schedule()之前有其他进程试图唤醒它,那么该进程的唤醒操作不会失效。

この記事では、Linux システムにおけるプロセスのスリープおよびウェイクアップの方法 (スリープの理由、スリープの種類、スリープ機能、ウェイクアップ関数、プロセスのウェイクアップ メカニズムなど) を紹介します。この知識を理解して習得することで、Linux システムのプロセスをより適切に管理および制御できるようになり、システムをより省エネかつ効率的に実行できるようになります。もちろん、Linux システムのスリープおよびウェイクアップのプロセスには多くの詳細とテクニックがあり、継続的に学習し実践する必要があります

以上がLinux プロセスのスリープとウェイクアップ: システムをより省エネルギーかつ効率的にします。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はlxlinux.netで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。