Linux 進程的喚醒與睡眠
在 Linux 系統中,只有等待 CPU 時間的進程才稱為就緒進程。它們被放置在運行佇列中,狀態標誌位元為 TASK_RUNNING
。當一個運行中的進程用完時間片後,Linux 核心調度器會剝奪它對 CPU 的控制權,並從運行隊列中選擇一個合適的進程進行運行。
#當然,流程也可以主動放棄對 CPU 的控制權。 schedule()
函數是一個調度函數,可以被進程主動調用,以便調度其他進程佔用 CPU。一旦這個主動放棄 CPU 的程序重新被調度佔用 CPU,它將從上次停止執行的位置開始執行,也就是從呼叫 schedule()
的下一行程式碼開始執行。
有時,進程需要等待某個特定事件發生,例如裝置初始化完成、I/O 操作完成或計時器到時等。在這種情況下,進程必須從運行佇列中移除,並加入等待佇列中,此時進程進入睡眠狀態。
Linux 中的進程睡眠狀態有兩種:
- # 一種是可中斷的睡眠狀態,其狀態標記TASK_INTERRUPTIBLE。
- 另一種是不可中斷 的睡眠狀態,其狀態標誌位元為TASK_UNINTERRUPTIBLE。
可中斷的睡眠狀態的進程會睡眠直到某個條件變成真,比如說產生一個硬體中斷、釋放 進程正在等待的系統資源或是傳遞一個訊號都可以是喚醒進程的條件。不可中斷睡眠狀態與可中斷睡眠狀態類似,但是它有一個例外,那就是把訊號傳遞到這種睡眠 狀態的進程不能改變它的狀態,也就是說它不回應訊號的喚醒。不可中斷睡眠狀態一般較少用到,但在某些特定情況下這種狀態還是很有用的,比如說:進程必須等 待,不能被中斷,直到某個特定的事件發生。
在現代的 Linux 作業系統中,進程一般都是用呼叫 schedule()
的方法進入睡眠狀態的,下面的程式碼示範如何讓正在運行的進程進入睡眠狀態。
sleeping_task = current; set_current_state(TASK_INTERRUPTIBLE); schedule(); func1(); /* Rest of the code ... */
在第一個語句中,程式儲存了一個進程結構指標 sleeping_task
,current
是一個宏,它指向正在執行的進程結構。
set_current_state()
將該行程的狀態從執行狀態 TASK_RUNNING
變成睡眠狀態 TASK_INTERRUPTIBLE
。如果 schedule()
是被一個狀態為 TASK_RUNNING
的進程調度,那麼 schedule()
將調度另外一個進程佔用CPU。
如果 schedule()
是被一个状态为 TASK_INTERRUPTIBLE
或 TASK_UNINTERRUPTIBLE
的进程调度,那么还有一个附加的步骤将被执行:当前执行的进程在另外一个进程被调度之前会被从运行队列中移出,这将导致正在运行的那个进程进入睡眠,因为它已经不在运行队列中了。
我们可以使用下面的这个函数将刚才那个进入睡眠的进程唤醒。
wake_up_process(sleeping_task);
在调用了 wake_up_process()
以后,这个睡眠进程的状态会被设置为 TASK_RUNNING
,而且调度器会把它加入到运行队列中去。当然,这个进程只有在下次被调度器调度到的时候才能真正地投入运行。
无效唤醒
几乎在所有的情况下,进程都会在检查了某些条件之后,发现条件不满足才进入睡眠。可是有的时候进程却会在判定条件为真后开始睡眠,如果这样的话进程就会无限期地休眠下去,这就是所谓的无效唤醒问题。
在操作系统中,当多个进程都企图对共享数据进行某种处理,而 最后的结果又取决于进程运行的顺序时,就会发生竞争条件,这是操作系统中一个典型的问题,无效唤醒恰恰就是由于竞争条件导致的。
设想有两个进程A 和B,A 进程正在处理一个链表,它需要检查这个链表是否为空,如果不空就对链表里面的数据进行一些操作,同时B进程也在往这个链表添加节点。当这个链表是空的时候,由于无数据可操作,这时A进程就进入睡眠,当B进程向链表里面添加了节点之后它就唤醒A 进程,其代码如下:
A进程:
1 spin_lock(&list_lock); 2 if (list_empty(&list_head)) { 3 spin_unlock(&list_lock); 4 set_current_state(TASK_INTERRUPTIBLE); 5 schedule(); 6 spin_lock(&list_lock); 7 } 8 9 /* Rest of the code ... */ 10 spin_unlock(&list_lock);
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); /* condition 是等待的条件 */ while (!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 内核中的实例来看看其是如何避免无效睡眠的,这段代码出自 Linux2.6 的内核 (/kernel/sched.c):
/* Wait for kthread_stop */ set_current_state(TASK_INTERRUPTIBLE); while (!kthread_should_stop()) { schedule(); set_current_state(TASK_INTERRUPTIBLE); } __set_current_state(TASK_RUNNING); return 0;
上面的这些代码属于迁移服务线程 migration_thread
,这个线程不断地检查 kthread_should_stop()
,直到 kthread_should_stop()
返回 1 它才可以退出循环,也就是说只要 kthread_should_stop()
返回 0 该进程就会一直睡眠。
从代码中我们可以看出,检查 kthread_should_stop()
确实是在进程的状态被置为 TASK_INTERRUPTIBLE
后才开始执行的。因此,如果在条件检查之后但是在 schedule()
之前有其他进程试图唤醒它,那么该进程的唤醒操作不会失效。
小結
透過上面的討論,可以發現在Linux 中避免進程的無效喚醒的關鍵是在進程檢查條件之前就將進程的狀態置為TASK_INTERRUPTIBLE
或TASK_UNINTERRUPTIBLE
,並且如果檢查的條件滿足的話就應該將其狀態重新設定為TASK_RUNNING
。
這樣無論進程等待的條件是否滿足,進程都不會因為被移出就緒佇列而錯誤地進入睡眠狀態,從而避免了無效喚醒問題。
以上是Linux 進程的喚醒與睡眠的詳細內容。更多資訊請關注PHP中文網其他相關文章!

介紹 Linux是一個強大的操作系統,由於其靈活性和效率,開發人員,系統管理員和電源用戶都喜歡。但是,經常使用長而復雜的命令可能是乏味的

Linux適用於服務器、開發環境和嵌入式系統。 1.作為服務器操作系統,Linux穩定高效,常用於部署高並發應用。 2.作為開發環境,Linux提供高效的命令行工具和包管理系統,提升開發效率。 3.在嵌入式系統中,Linux輕量且可定制,適合資源有限的環境。

簡介:通過基於Linux的道德黑客攻擊數字邊界 在我們越來越相互聯繫的世界中,網絡安全至關重要。 道德黑客入侵和滲透測試對於主動識別和減輕脆弱性至關重要

Linux基礎學習從零開始的方法包括:1.了解文件系統和命令行界面,2.掌握基本命令如ls、cd、mkdir,3.學習文件操作,如創建和編輯文件,4.探索高級用法如管道和grep命令,5.掌握調試技巧和性能優化,6.通過實踐和探索不斷提陞技能。

Linux在服務器、嵌入式系統和桌面環境中的應用廣泛。 1)在服務器領域,Linux因其穩定性和安全性成為託管網站、數據庫和應用的理想選擇。 2)在嵌入式系統中,Linux因其高度定制性和高效性而受歡迎。 3)在桌面環境中,Linux提供了多種桌面環境,滿足不同用戶需求。

Linux的缺點包括用戶體驗、軟件兼容性、硬件支持和學習曲線。 1.用戶體驗不如Windows或macOS友好,依賴命令行界面。 2.軟件兼容性不如其他系統,缺乏許多商業軟件的原生版本。 3.硬件支持不如Windows全面,可能需要手動編譯驅動程序。 4.學習曲線較陡峭,掌握命令行操作需要時間和耐心。

Linuxisnothardtolearn,butthedifficultydependsonyourbackgroundandgoals.ForthosewithOSexperience,especiallycommand-linefamiliarity,Linuxisaneasytransition.Beginnersmayfaceasteeperlearningcurvebutcanmanagewithproperresources.Linux'sopen-sourcenature,bas

Linux的五個基本組件是:1.內核,管理硬件資源;2.系統庫,提供函數和服務;3.Shell,用戶與系統交互的接口;4.文件系統,存儲和組織數據;5.應用程序,利用系統資源實現功能。


熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

SublimeText3 Linux新版
SublimeText3 Linux最新版

mPDF
mPDF是一個PHP庫,可以從UTF-8編碼的HTML產生PDF檔案。原作者Ian Back編寫mPDF以從他的網站上「即時」輸出PDF文件,並處理不同的語言。與原始腳本如HTML2FPDF相比,它的速度較慢,並且在使用Unicode字體時產生的檔案較大,但支援CSS樣式等,並進行了大量增強。支援幾乎所有語言,包括RTL(阿拉伯語和希伯來語)和CJK(中日韓)。支援嵌套的區塊級元素(如P、DIV),

Atom編輯器mac版下載
最受歡迎的的開源編輯器

DVWA
Damn Vulnerable Web App (DVWA) 是一個PHP/MySQL的Web應用程序,非常容易受到攻擊。它的主要目標是成為安全專業人員在合法環境中測試自己的技能和工具的輔助工具,幫助Web開發人員更好地理解保護網路應用程式的過程,並幫助教師/學生在課堂環境中教授/學習Web應用程式安全性。 DVWA的目標是透過簡單直接的介面練習一些最常見的Web漏洞,難度各不相同。請注意,該軟體中

VSCode Windows 64位元 下載
微軟推出的免費、功能強大的一款IDE編輯器