調用sleep後,我做了一個噩夢

  • 2020 年 4 月 23 日
  • 筆記

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數據包的驚險之旅

我是一個流氓軟件線程

掃碼關注,更多精彩