1. 程式人生 > >呼叫sleep後,我做了一個噩夢

呼叫sleep後,我做了一個噩夢

sleep系統呼叫

我是一個執行緒,生活在Linux帝國。一直以來辛勤工作,日子過得平平淡淡,可今天早上發生了一件事讓我回想起來都後怕。

早上,我還是如往常一樣執行著人類編寫的程式碼指令,不多時走到了一個冷門的分支,一個sleep()函式呼叫擺在了我的面前。

終於可以去休息了!聽老一輩的執行緒們說,執行了這個函式就可以休息休息了。我瞄了一眼引數,足足有5秒鐘的休息時間,我簡直樂壞了,沒有猶豫,趕緊執行了這個呼叫。

進入sleep()函式後,又來到了nano_sleep()函式,接著看到了一個syscall系統呼叫指令,我繼續執行,來到了核心空間。

進入核心空間後,我接連穿過了

  • --> nano_sleep()
  • --> hrtimer_nanosleep()
  • --> do_nanosleep()
  • --> freezable_schedule()

把我累得夠嗆,說好讓我休息,沒想到休息之前還有這麼多事要做。

終於,我來到了一個叫schedule()的函式面前。

執行緒排程

進入schedule()後,迎面走來一位髮鬚皆已花白的長者。

“小夥子,這是要來休息了,我是負責執行緒排程的使者,讓我看下你佔用的CPU號碼”,一邊說一邊查詢著什麼。

“哦,是2號CPU,來,跟我到這邊來”,在他的指引下,我又來到了一個函式面前。

“你先去pick_next_task()找到一個接盤俠,哦不,找到下一個需要執行的執行緒,這是2號CPU的就緒佇列

,你可拿好了,等你辦完回來我再帶你去辦理交接手續”,說完給我手裡塞了一個引數rq,隨即便離開了,留下我不知所措。

我只好按他說的照辦,邁進了pick_next_task()函式的大門,一位美女接待映入眼簾。

“先生您好,您來此想必是要尋找接班執行緒吧”,見我到來,美女起身招呼。

“你猜的不錯,要麻煩你幫我處理一下,多謝了”

“您別客氣,把就緒佇列給我看看吧”

我先是愣了一下,反應過來後將手裡的rq引數給了她。

struct rq {
    raw_spinlock_t lock;
    ...
    unsigned int nr_running;
    ...
    struct cfs_rq cfs;
    struct rt_rq rt;
    struct dl_rq dl;
    ...
    struct task_struct *curr, *idle, *stop;
    ...
    struct mm_struct *prev_mm;
    ...
    struct list_head cfs_tasks;
};

美女拿著rq一陣端詳,說到:“您運氣不錯哦,rq->nr_running和rq->cfs.h_nr_running相等,看來沒有實時執行緒,全是普通執行緒,您直接去那邊的公平排程CFS視窗fair_sched_class那裡去辦理吧。”

我順著美女指向的方向看去,那邊一共有5個視窗:

  • stop_sched_class
  • dl_sched_class
  • rt_sched_class
  • fair_sched_class
  • idle_sched_class

“唉,美女,那要是不相等該去哪個視窗辦理呢?你告訴我一下,下次來我就知道了”

“不相等的話那就說明就緒佇列裡除了普通執行緒還有其他優先順序更高的執行緒,就得按照優先順序從stop_sched_class視窗挨個向後詢問,直到找到一個執行緒。不過我在這幹了這麼久,就實時執行緒所在的rt_sched_class視窗和普通執行緒所在的fair_sched_class最常用”,美女耐心的給我解釋到。

聽了她的解釋,我想到了一個問題:“那要是都找不到執行緒需要執行怎麼辦,比如他們都在等待IO事件之類的?那我怎麼交差啊”

“放心吧,最後那個idle_sched_class視窗絕對不會讓你空手而歸的”,美女笑著說。

原來如此,我點了點頭。

來到fair_sched_class視窗的旁邊,遞交了我的rq引數,只見工作人員取出了其中的cfs_rq

struct cfs_rq {
	struct load_weight load;
	unsigned int nr_running, h_nr_running;
	...
	struct rb_root tasks_timeline;
	struct rb_node *rb_leftmost;
	...
	struct sched_entity *curr, *next, *last, *skip;
	...
	struct rq *rq;
};

我這才注意到,原來這個cfs_rq中指向了一棵紅黑樹,再仔細一看,這樹上的每個節點都是一個執行緒task_struct

工作人員很快就取出了一個task_struct交給我,一個年紀輕輕的執行緒小T,我帶著小T告別了美女接待,回到了schedule()

context_switch

看到我回來,長者起身言道:“小夥子,回來啦,走,帶你們去context_switch()

進入這個context_switch()之後,長者又帶著我又做了一些準備工作,比如把當前的程序地址空間換成了小T的,最終我們來到了一個叫switch_to的地方。

“小夥子,再往前走幾步就是換班的地方了,就可以休息了,我就不送你了,感謝你這段時間的辛苦工作”,長者一邊說一邊拍拍我的肩膀。

告別了長者,我和小T踏上了這神祕的switch_to,跟隨著一步一步的指令,我把自己執行緒上下文的暫存器都儲存到了我的核心棧上面,然後將棧指標指向了小T的核心棧,最後把小T儲存在他核心棧的指令地址載入進指令暫存器,終於完成了交接工作。

“小T,接下來就該你工作了,我要去休息了”,我和小T握手告別,來到旁邊準備眯一會兒。

神祕的喚醒

“醒醒,醒醒”,睡夢中聽到有人喚我。

我揉揉睡眼,看了下時間,這才睡了2s,時間還沒到,難道現在是在做夢?

“總算把你叫醒了,快起來,換班時間到了,該你上了”,我抬起頭才發現另外一個執行緒小H站在面前。

“我休息時間還沒夠啊,怎麼選中了我啊,讓我再睡會兒”,說罷我就要躺下。

小H一把拉住了我,“別磨嘰了,就是你,快走”。

在小H的帶領下,我們又來到了那個叫switch_to地方,只不過這一次我的角色變了。

小H一頓和我之前一樣的操作,把執行流程交給了我。

我再次獲得了執行程式碼的能力,隨後回到了context_switch(),又回到了schedule(),看到了熟悉的長者。

我和長者再次告了別,繼續返回,最後通過sysret蟲洞,回到了使用者態空間。

不過我馬上意識到事情不對勁,這裡並不是我最開始呼叫sleep()的地方,而是一片完全陌生的區域,難道哪裡出了問題,我這是到了哪裡?

這一定是在做夢,我還在sleep()呢,時間還沒夠,我只好這樣安慰自己。

我小心翼翼的執行了這裡的程式碼,只是簡單輸出了一行日誌,然後來到了一個叫__restore_rt()的函式,又一條syscall指令擺在了我的面前,我沒有猶豫再一次一頭扎進了核心空間。

經過一番折騰,又來到了sysret蟲洞面前,不知道這一次又將帶我去到哪裡。我閉上了眼睛跳了進去···

等我睜開眼睛,竟然奇蹟般的回到了最開始呼叫sleep()的地方,這場夢終於醒了,慶幸我回到了這裡。

我看了一眼sleep()的返回值,竟然是3。我又看了一眼日誌檔案,竟看到了夢裡輸出的那一行日誌。

難道那不是夢?這究竟是怎麼一回事?

未完待續······

彩蛋

“奇怪,這個TCP資料包的ACK和SEQ怎麼和剛才那個一樣,估計是重傳了吧”,帝國網路部的小Q丟掉了這個重複的資料包。

不過,同樣的事情接二連三的出現,經歷了上次那件事的小Q不敢大意,趕緊向安全部長彙報了情況。

預知後事如何,請關係後續精彩······


本文關聯前作

核心地址空間大冒險1:系統呼叫

核心地址空間大冒險2:中斷與異常

核心地址空間大冒險3:許可權管理


往期熱門回顧

震撼!全網第一張原始碼分析全景圖揭祕Nginx

一個整數+1引發的災難

一網打盡!每個程式猿都該瞭解的黑客技術大彙總

看過無數Java GC文章,這5個問題你也未必知道!

一個Java物件的回憶錄:垃圾回收

誰動了你的HTTPS流量?

路由器裡的廣告祕密

DDoS攻擊:無限戰爭

一條SQL注入引出的驚天大案

一個HTTP資料包的奇幻之旅

一個DNS資料包的驚險之旅

我是一個流氓軟體執行緒

掃碼關注,更多精彩