自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

內(nèi)核如何阻塞與喚醒進(jìn)程?

商務(wù)辦公
我們先從 Linux 的進(jìn)程談起,操作系統(tǒng)要運(yùn)行一個(gè)可執(zhí)行程序,首先要將程序文件加載到內(nèi)存,然后 CPU 去讀取和執(zhí)行程序指令,而一個(gè)進(jìn)程就是“一次程序的運(yùn)行過(guò)程”,內(nèi)核會(huì)給每一個(gè)進(jìn)程創(chuàng)建一個(gè)名為task_struct的數(shù)據(jù)結(jié)構(gòu),而內(nèi)核也是一段程序,系統(tǒng)啟動(dòng)時(shí)就被加載到內(nèi)存中了。

  [[328663]]

進(jìn)程和線程

我們先從 Linux 的進(jìn)程談起,操作系統(tǒng)要運(yùn)行一個(gè)可執(zhí)行程序,首先要將程序文件加載到內(nèi)存,然后 CPU 去讀取和執(zhí)行程序指令,而一個(gè)進(jìn)程就是“一次程序的運(yùn)行過(guò)程”,內(nèi)核會(huì)給每一個(gè)進(jìn)程創(chuàng)建一個(gè)名為task_struct的數(shù)據(jù)結(jié)構(gòu),而內(nèi)核也是一段程序,系統(tǒng)啟動(dòng)時(shí)就被加載到內(nèi)存中了。

進(jìn)程在運(yùn)行過(guò)程中要訪問(wèn)內(nèi)存,而物理內(nèi)存是有限的,比如 16GB,那怎么把有限的內(nèi)存分給不同的進(jìn)程使用呢?跟 CPU 的分時(shí)共享一樣,內(nèi)存也是共享的,Linux 給每個(gè)進(jìn)程虛擬出一塊很大的地址空間,比如 32 位機(jī)器上進(jìn)程的虛擬內(nèi)存地址空間是 4GB,從 0x00000000 到 0xFFFFFFFF。但這 4GB 并不是真實(shí)的物理內(nèi)存,而是進(jìn)程訪問(wèn)到了某個(gè)虛擬地址,如果這個(gè)地址還沒(méi)有對(duì)應(yīng)的物理內(nèi)存頁(yè),就會(huì)產(chǎn)生缺頁(yè)中斷,分配物理內(nèi)存,MMU(內(nèi)存管理單元)會(huì)將虛擬地址與物理內(nèi)存頁(yè)的映射關(guān)系保存在頁(yè)表中,再次訪問(wèn)這個(gè)虛擬地址,就能找到相應(yīng)的物理內(nèi)存頁(yè)。每個(gè)進(jìn)程的這 4GB 虛擬地址空間分布如下圖所示:


 

 

進(jìn)程的虛擬地址空間總體分為用戶(hù)空間和內(nèi)核空間,低地址上的 3GB 屬于用戶(hù)空間,高地址的 1GB 是內(nèi)核空間,這是基于安全上的考慮,用戶(hù)程序只能訪問(wèn)用戶(hù)空間,內(nèi)核程序可以訪問(wèn)整個(gè)進(jìn)程空間,并且只有內(nèi)核可以直接訪問(wèn)各種硬件資源,比如磁盤(pán)和網(wǎng)卡。那用戶(hù)程序需要訪問(wèn)這些硬件資源該怎么辦呢?答案是通過(guò)系統(tǒng)調(diào)用,系統(tǒng)調(diào)用可以理解為內(nèi)核實(shí)現(xiàn)的函數(shù),比如應(yīng)用程序要通過(guò)網(wǎng)卡接收數(shù)據(jù),會(huì)調(diào)用 Socket 的 read 函數(shù):

  1. ssize_t read(int fd,void *buf,size_t nbyte) 

CPU 在執(zhí)行系統(tǒng)調(diào)用的過(guò)程中會(huì)從用戶(hù)態(tài)切換到內(nèi)核態(tài),CPU 在用戶(hù)態(tài)下執(zhí)行用戶(hù)程序,使用的是用戶(hù)空間的棧,訪問(wèn)用戶(hù)空間的內(nèi)存;當(dāng) CPU 切換到內(nèi)核態(tài)后,執(zhí)行內(nèi)核代碼,使用的是內(nèi)核空間上的棧。

從上面這張圖我們看到,用戶(hù)空間從低到高依次是代碼區(qū)、數(shù)據(jù)區(qū)、堆、共享庫(kù)與 mmap 內(nèi)存映射區(qū)、棧、環(huán)境變量。其中堆向高地址增長(zhǎng),棧向低地址增長(zhǎng)。

請(qǐng)注意用戶(hù)空間上還有一個(gè)共享庫(kù)和 mmap 映射區(qū),Linux 提供了內(nèi)存映射函數(shù) mmap, 它可將文件內(nèi)容映射到這個(gè)內(nèi)存區(qū)域,用戶(hù)通過(guò)讀寫(xiě)這段內(nèi)存,從而實(shí)現(xiàn)對(duì)文件的讀取和修改,無(wú)需通過(guò) read/write 系統(tǒng)調(diào)用來(lái)讀寫(xiě)文件,省去了用戶(hù)空間和內(nèi)核空間之間的數(shù)據(jù)拷貝,Java 的 MappedByteBuffer 就是通過(guò)它來(lái)實(shí)現(xiàn)的;用戶(hù)程序用到的系統(tǒng)共享庫(kù)也是通過(guò) mmap 映射到了這個(gè)區(qū)域。

我在開(kāi)始提到的task_struct結(jié)構(gòu)體本身是分配在內(nèi)核空間,它的vm_struct成員變量保存了各內(nèi)存區(qū)域的起始和終止地址,此外task_struct中還保存了進(jìn)程的其他信息,比如進(jìn)程號(hào)、打開(kāi)的文件、創(chuàng)建的 Socket 以及 CPU 運(yùn)行上下文等。

在 Linux 中,線程是一個(gè)輕量級(jí)的進(jìn)程,輕量級(jí)說(shuō)的是線程只是一個(gè) CPU 調(diào)度單元,因此線程有自己的task_struct結(jié)構(gòu)體和運(yùn)行棧區(qū),但是線程的其他資源都是跟父進(jìn)程共用的,比如虛擬地址空間、打開(kāi)的文件和 Socket 等。

阻塞與喚醒

我們知道當(dāng)用戶(hù)線程發(fā)起一個(gè)阻塞式的 read 調(diào)用,數(shù)據(jù)未就緒時(shí),線程就會(huì)阻塞,那阻塞具體是如何實(shí)現(xiàn)的呢?

Linux 內(nèi)核將線程當(dāng)作一個(gè)進(jìn)程進(jìn)行 CPU 調(diào)度,內(nèi)核維護(hù)了一個(gè)可運(yùn)行的進(jìn)程隊(duì)列,所有處于TASK_RUNNING狀態(tài)的進(jìn)程都會(huì)被放入運(yùn)行隊(duì)列中,本質(zhì)是用雙向鏈表將task_struct鏈接起來(lái),排隊(duì)使用 CPU 時(shí)間片,時(shí)間片用完重新調(diào)度 CPU。所謂調(diào)度就是在可運(yùn)行進(jìn)程列表中選擇一個(gè)進(jìn)程,再?gòu)?CPU 列表中選擇一個(gè)可用的 CPU,將進(jìn)程的上下文恢復(fù)到這個(gè) CPU 的寄存器中,然后執(zhí)行進(jìn)程上下文指定的下一條指令。

 

而阻塞的本質(zhì)就是將進(jìn)程的task_struct移出運(yùn)行隊(duì)列,添加到等待隊(duì)列,并且將進(jìn)程的狀態(tài)的置為T(mén)ASK_UNINTERRUPTIBLE或者TASK_INTERRUPTIBLE,重新觸發(fā)一次 CPU 調(diào)度讓出 CPU。

那線程怎么喚醒呢?線程在加入到等待隊(duì)列的同時(shí)向內(nèi)核注冊(cè)了一個(gè)回調(diào)函數(shù),告訴內(nèi)核我在等待這個(gè) Socket 上的數(shù)據(jù),如果數(shù)據(jù)到了就喚醒我。這樣當(dāng)網(wǎng)卡接收到數(shù)據(jù)時(shí),產(chǎn)生硬件中斷,內(nèi)核再通過(guò)調(diào)用回調(diào)函數(shù)喚醒進(jìn)程。喚醒的過(guò)程就是將進(jìn)程的task_struct從等待隊(duì)列移到運(yùn)行隊(duì)列,并且將task_struct的狀態(tài)置為T(mén)ASK_RUNNING,這樣進(jìn)程就有機(jī)會(huì)重新獲得 CPU 時(shí)間片。

這個(gè)過(guò)程中,內(nèi)核還會(huì)將數(shù)據(jù)從內(nèi)核空間拷貝到用戶(hù)空間的堆上。

 

當(dāng) read 系統(tǒng)調(diào)用返回時(shí),CPU 又從內(nèi)核態(tài)切換到用戶(hù)態(tài),繼續(xù)執(zhí)行 read 調(diào)用的下一行代碼,并且能從用戶(hù)空間上的 Buffer 讀到數(shù)據(jù)了。

小結(jié)

今天我們談到了一次 Socket read 系統(tǒng)調(diào)用的過(guò)程:首先 CPU 在用戶(hù)態(tài)執(zhí)行應(yīng)用程序的代碼,訪問(wèn)進(jìn)程虛擬地址空間的用戶(hù)空間;read 系統(tǒng)調(diào)用時(shí) CPU 從用戶(hù)態(tài)切換到內(nèi)核態(tài),執(zhí)行內(nèi)核代碼,內(nèi)核檢測(cè)到 Socket 上的數(shù)據(jù)未就緒時(shí),將進(jìn)程的task_struct結(jié)構(gòu)體從運(yùn)行隊(duì)列中移到等待隊(duì)列,并觸發(fā)一次 CPU 調(diào)度,這時(shí)進(jìn)程會(huì)讓出 CPU;當(dāng)網(wǎng)卡數(shù)據(jù)到達(dá)時(shí),內(nèi)核將數(shù)據(jù)從內(nèi)核空間拷貝到用戶(hù)空間的 Buffer,接著將進(jìn)程的task_struct結(jié)構(gòu)體重新移到運(yùn)行隊(duì)列,這樣進(jìn)程就有機(jī)會(huì)重新獲得 CPU 時(shí)間片,系統(tǒng)調(diào)用返回,CPU 又從內(nèi)核態(tài)切換到用戶(hù)態(tài),訪問(wèn)用戶(hù)空間的數(shù)據(jù)。

責(zé)任編輯:武曉燕 來(lái)源: 今日頭條
相關(guān)推薦

2017-03-01 16:40:12

Linux驅(qū)動(dòng)技術(shù)設(shè)備阻塞

2023-05-08 12:03:14

Linux內(nèi)核進(jìn)程

2017-08-02 15:28:58

Linux進(jìn)程睡眠和喚醒

2011-08-18 10:26:52

Linux 3.1內(nèi)核

2018-03-28 08:52:53

阻塞非阻塞I

2021-04-15 05:51:25

Linux

2012-10-10 10:00:27

同步異步開(kāi)發(fā)Java

2017-12-06 10:50:50

Linux自動(dòng)喚醒系統(tǒng)運(yùn)行時(shí)間

2011-01-14 14:49:05

2019-04-10 13:43:19

Linux內(nèi)核進(jìn)程負(fù)載

2010-04-21 12:54:46

Unix內(nèi)核

2010-01-20 18:10:48

2010-02-02 09:36:38

2021-06-24 08:37:34

網(wǎng)絡(luò)安全內(nèi)核代碼

2023-03-15 08:39:07

遠(yuǎn)程服務(wù)調(diào)用

2012-05-14 14:09:53

Linux內(nèi)核調(diào)度系統(tǒng)

2018-03-19 08:32:16

Linux 進(jìn)程睡眠喚醒

2010-04-30 16:19:17

Unix內(nèi)核

2010-07-20 11:18:12

SQL server阻

2017-03-03 09:40:52

Linux休眠喚醒
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)