今天看了一個(gè)關(guān)于中斷例程為什么不能休眠的文章,,引發(fā)了我的思考。其實(shí)這個(gè)問(wèn)題在學(xué)習(xí)驅(qū)動(dòng)的時(shí)候早就應(yīng)該解決了,,但是由于5年前學(xué)驅(qū)動(dòng)的時(shí)候?qū)儆?/span>Linux初學(xué)者,,能力有限,,所以對(duì)這個(gè)問(wèn)題就知其然,,沒(méi)有能力知其所以然?,F(xiàn)在回頭看這個(gè)問(wèn)題的時(shí)候,感覺(jué)應(yīng)該可以有一個(gè)較為清晰的認(rèn)識(shí)了,。 首先必須意識(shí)到:休眠是一種進(jìn)程的特殊狀態(tài)(即task->state= TASK_UNINTERRUPTIBLE | TASK_INTERRUPTIBLE)]
簡(jiǎn)單的說(shuō),休眠是為在一個(gè)當(dāng)前進(jìn)程等待暫時(shí)無(wú)法獲得的資源或者一個(gè)event的到來(lái)時(shí)(原因),,避免當(dāng)前進(jìn)程浪費(fèi)CPU時(shí)間(目的),,將自己放入進(jìn)程等待隊(duì)列中,,同時(shí)讓出CPU給別的進(jìn)程(工作),。休眠就是為了更好地利用CPU。 一旦資源可用或event到來(lái),,將由內(nèi)核代碼(可能是其他進(jìn)程通過(guò)系統(tǒng)調(diào)用)喚醒某個(gè)等待隊(duì)列上的部分或全部進(jìn)程,。從這點(diǎn)來(lái)說(shuō),休眠也是一種進(jìn)程間的同步機(jī)制,。 休眠是針對(duì)進(jìn)程,,也就是擁有task_struct的獨(dú)立個(gè)體。 當(dāng)進(jìn)程執(zhí)行某個(gè)系統(tǒng)調(diào)用的時(shí)候,,暫時(shí)無(wú)法獲得的某種資源或必須等待某event的到來(lái),,在這個(gè)系統(tǒng)調(diào)用的底層實(shí)現(xiàn)代碼就可以通過(guò)讓系統(tǒng)調(diào)度的手段讓出CPU,讓當(dāng)前進(jìn)程處于休眠狀態(tài),。
- 進(jìn)程什么時(shí)候會(huì)被休眠,?
進(jìn)程進(jìn)入休眠狀態(tài),必然是他自己的代碼中調(diào)用了某個(gè)系統(tǒng)調(diào)用,,而這個(gè)系統(tǒng)調(diào)用中存在休眠代碼,。這個(gè)休眠代碼在某種條件下會(huì)被激活,從而讓改變進(jìn)程狀態(tài),,說(shuō)到底就是以各種方式包含了: 1,、條件判斷語(yǔ)句 2、進(jìn)程狀態(tài)改變語(yǔ)句 3,、schedule();
進(jìn)程被置為休眠,意味著它被標(biāo)識(shí)為處于一個(gè)特殊的狀態(tài)(TASK_UNINTERRUPTIBLE或 TASK_INTERRUPTIBLE),,并且從調(diào)度器的運(yùn)行隊(duì)列中移走,。這個(gè)進(jìn)程將不在任何 CPU 上被調(diào)度,即不會(huì)被運(yùn)行,。 直到發(fā)生某些事情改變了那個(gè)狀態(tài)(to TASK_WAKING),。這時(shí)處理器重新開(kāi)始執(zhí)行此進(jìn)程,此時(shí)進(jìn)程會(huì)再次檢查是否需要繼續(xù)休眠(資源是否真的可用,?),,如果不需要就做清理工作,并將自己的狀態(tài)調(diào)整為TASK_RUNNING,。過(guò)程如下圖所示:
- 四,、誰(shuí)來(lái)喚醒休眠進(jìn)程
進(jìn)程在休眠后,就不再被調(diào)度器執(zhí)行,,就不可能由自己?jiǎn)拘炎约?,也就是說(shuō)進(jìn)程不可能“睡覺(jué)睡到自然醒”。喚醒工作必然是由其他進(jìn)程或者內(nèi)核本身來(lái)完成的,。喚醒需要改變進(jìn)程的task_struct中的狀態(tài)等,,代碼必然在內(nèi)核中,所以喚醒必然是在系統(tǒng)調(diào)用的實(shí)現(xiàn)代碼中(如你驅(qū)動(dòng)中的read,、write方法)以及各種形式的中斷代碼(包括軟,、硬中斷)中。 如果在系統(tǒng)調(diào)用代碼中喚醒,則說(shuō)明是由其他的某個(gè)進(jìn)程來(lái)調(diào)用了這個(gè)系統(tǒng)調(diào)用喚醒了休眠的進(jìn)程,。 如果是中斷中喚醒,,那么喚醒的任務(wù)可以說(shuō)是內(nèi)核完成了。 · 如何找到需要喚醒的進(jìn)程:等待隊(duì)列 上面其實(shí)已經(jīng)提到了:休眠代碼的一個(gè)工作就是將當(dāng)前進(jìn)程信息放入一個(gè)等待隊(duì)列中,。它其實(shí)是一個(gè)包含等待某個(gè)特定事件的所有進(jìn)程相關(guān)信息的鏈表,。一個(gè)等待隊(duì)列由一個(gè)wait_queue_head_t 結(jié)構(gòu)體來(lái)管理,其定義在<linux/wait.h>中,。 wait_queue_head_t 類(lèi)型的數(shù)據(jù)結(jié)構(gòu)如下:
- struct __wait_queue_head {
- spinlock_t lock;
- struct list_head task_list;
- };
- typedef struct __wait_queue_head wait_queue_head_t;
它包含一個(gè)自旋鎖和一個(gè)鏈表,。這是一個(gè)等待隊(duì)列鏈表頭,鏈表中的元素被聲明做wait_queue_t,。自旋鎖用于包含鏈表操作的原子性,。 wait_queue_t包含關(guān)于睡眠進(jìn)程的信息和喚醒函數(shù)。
- typedef struct __wait_queue wait_queue_t;
- typedef int (*wait_queue_func_t)(wait_queue_t *wait, unsigned mode, int flags, void *key);
- int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);
- struct __wait_queue {
- unsigned int flags;
- #define WQ_FLAG_EXCLUSIVE 0x01 /* 表示等待進(jìn)程想要被獨(dú)占地喚醒 */
- void *private; /* 指向等待進(jìn)程的task_struct結(jié)構(gòu)圖 */
- wait_queue_func_t func; /* 用于喚醒等待進(jìn)程的處理例程,,在其中實(shí)現(xiàn)了進(jìn)程狀態(tài)的改變和將自己從等待隊(duì)列中刪除的工作 */
- struct list_head task_list; /* 雙向鏈表結(jié)構(gòu)體,,用于將wait_queue_t鏈接到wait_queue_head_t */
- };
他們?cè)趦?nèi)存中的結(jié)構(gòu)大致如下圖所示: 等待隊(duì)列頭wait_queue_head_t一般是定義在模塊或內(nèi)核代碼中的全局變量,而其中鏈接的元素 wait_queue_t的定義被包含在了休眠宏中,。 休眠和喚醒的過(guò)程如下圖所示:
五,、休眠和喚醒的代碼簡(jiǎn)要分析
下面我們簡(jiǎn)單分析一下休眠與喚醒的內(nèi)核原語(yǔ)。 1,、休眠:wait_event - /**
- * wait_event - 休眠,,直到 condition 為真
- * @wq: 所休眠的等待隊(duì)列
- * @condition: 所等待事件的一個(gè)C表達(dá)式
- *
- * 進(jìn)程被置為等待狀態(tài) (TASK_UNINTERRUPTIBLE) 直到
- * @condition 評(píng)估為真. @condition 在每次等待隊(duì)列@wq 被喚醒時(shí)
- * 都被檢查。
- *
- * wake_up() 必須在改變?nèi)魏慰赡苡绊懙却龡l件結(jié)果
- * 的變量之后被調(diào)用,。
- */
- #define wait_event(wq, condition) \
- do { \
- if (condition) \
- break; \
- __wait_event(wq, condition); \
- } while (0)
-
- #define __wait_event(wq, condition) \
- do { \
- DEFINE_WAIT(__wait); \
- 定義一個(gè)插入到等待隊(duì)列中的等待隊(duì)列結(jié)構(gòu)體,注意.private = current,(即當(dāng)前進(jìn)程)
- #define DEFINE_WAIT_FUNC(name, function) \
- wait_queue_t name = { \
- .private = current, \
- .func = function, \
- .task_list = LIST_HEAD_INIT((name).task_list), \
- }
- #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
- \
- for (;;) { \
- prepare_to_wait(&wq, &__wait, TASK_UNINTERRUPTIBLE); \
- 將上面定義的結(jié)構(gòu)體__wait放入wq等待隊(duì)列中,,并設(shè)置當(dāng)前進(jìn)程狀態(tài)為T(mén)ASK_UNINTERRUPTIBLE
- if (condition) \
- break; \
- 測(cè)試條件狀態(tài),,看看是否真的需要休眠調(diào)度
- schedule(); \
- 開(kāi)始調(diào)度,程序停于此處,,直到有其他進(jìn)程喚醒本進(jìn)程,,就從此處繼續(xù)......
- } \
- finish_wait(&wq, &__wait); \
- 由于測(cè)試條件狀態(tài)為假,跳出以上循環(huán)后執(zhí)行休眠后的掃尾工作:
- 設(shè)置當(dāng)前進(jìn)程狀態(tài)為T(mén)ASK_RUNNING
- 將上面定義的__wait從等待隊(duì)列鏈表中刪除,。
} while (0)
2,、喚醒:wake_up
- #define wake_up(x) __wake_up(x, TASK_NORMAL, 1, NULL)
- /**
- * __wake_up - 喚醒阻塞在等待隊(duì)列上的線程.
- * @q: 等待隊(duì)列
- * @mode: which threads
- * @nr_exclusive: how many wake-one or wake-many threads to wake up
- * @key: is directly passed to the wakeup function
- *
- * It may be assumed that this function implies a write memory barrier before
- * changing the task state if and only if any tasks are woken up.
- */
- void __wake_up(wait_queue_head_t *q, unsigned int mode,
- int nr_exclusive, void *key)
- {
- unsigned long flags;
- spin_lock_irqsave(&q->lock, flags);
- __wake_up_common(q, mode, nr_exclusive, 0, key);
- spin_unlock_irqrestore(&q->lock, flags);
- }
- EXPORT_SYMBOL(__wake_up);
kernel/sched.c - /*
- * 核心喚醒函數(shù).非獨(dú)占喚醒(nr_exclusive == 0) 只是
- * 喚醒所有進(jìn)程. If it's an exclusive wakeup (nr_exclusive == small +ve
- * number) then we wake all the non-exclusive tasks and one exclusive task.
- *
- * There are circumstances in which we can try to wake a task which has already
- * started to run but is not in state TASK_RUNNING. try_to_wake_up() returns
- * zero in this (rare) case, and we handle it by continuing to scan the queue.
- */
- static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
- int nr_exclusive, int wake_flags, void *key)
- {
- wait_queue_t *curr, *next;
- list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
- 遍歷指定等待隊(duì)列中的wait_queue_t.
- unsigned flags = curr->flags;
- if (curr->func(curr, mode, wake_flags, key) &&
- (flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
- break;
- 調(diào)用喚醒函數(shù),也就是創(chuàng)建wait_queue_t時(shí)的 autoremove_wake_function
}- }
- int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
- {
- int ret = default_wake_function(wait, mode, sync, key);
- if (ret)
- list_del_init(&wait->task_list);
- 從等待隊(duì)列中刪除這個(gè)進(jìn)程
return ret;- }
- EXPORT_SYMBOL(autoremove_wake_function);
-
- int default_wake_function(wait_queue_t *curr, unsigned mode, int wake_flags,
- void *key)
- {
- return try_to_wake_up(curr->private, mode, wake_flags);
- 主要是要改變進(jìn)程狀態(tài)為 TASK_WAKING,,讓調(diào)度器可以重新執(zhí)行此進(jìn)程,。
}- EXPORT_SYMBOL(default_wake_function);
上面分析的休眠函數(shù)是最簡(jiǎn)單的休眠喚醒函數(shù),其他類(lèi)似的函數(shù),,如后綴為_timeout,、_interruptible,、_interruptible_timeout的函數(shù)其實(shí)都是在喚醒后的條件判斷上有些不同,,多判斷一些喚醒條件而已,。這里就不再贅述了。
六,、使用休眠的注意事項(xiàng) (1) 永遠(yuǎn)不要在原子上下文中進(jìn)入休眠,,即當(dāng)驅(qū)動(dòng)在持有一個(gè)自旋鎖、seqlock或者 RCU 鎖時(shí)不能睡眠,;關(guān)閉中斷也不能睡眠,,終端例程中也不可休眠。 持有一個(gè)信號(hào)量時(shí)休眠是合法的,,如果代碼在持有一個(gè)信號(hào)量時(shí)睡眠,,任何其他的等待這個(gè)信號(hào)量的線程也會(huì)休眠。發(fā)生在持有信號(hào)量時(shí)的休眠必須短暫,,而且決不能阻塞那個(gè)將最終喚醒你的進(jìn)程,。 (2)當(dāng)進(jìn)程被喚醒,它并不知道休眠了多長(zhǎng)時(shí)間以及休眠時(shí)發(fā)生什么,;也不知道是否另有進(jìn)程也在休眠等待同一事件,,且那個(gè)進(jìn)程可能在它之前醒來(lái)并獲取了所等待的資源。所以不能對(duì)喚醒后的系統(tǒng)狀態(tài)做任何的假設(shè),,并必須重新檢查等待條件來(lái)確保正確的響應(yīng),。 (3)除非確信其他進(jìn)程會(huì)在其他地方喚醒休眠的進(jìn)程,否則也不能睡眠,。使進(jìn)程可被找到意味著:需要維護(hù)一個(gè)等待隊(duì)列的數(shù)據(jù)結(jié)構(gòu),。它是一個(gè)進(jìn)程鏈表,其中包含了等待某個(gè)特定事件的所有進(jìn)程的相關(guān)信息,。
七,、不可在中斷例程中休眠的原因 如果在某個(gè)系統(tǒng)調(diào)用中把當(dāng)前進(jìn)程休眠,是有明確目標(biāo)的,,這個(gè)目標(biāo)就是過(guò)來(lái)call這個(gè)系統(tǒng)調(diào)用的進(jìn)程(注意這個(gè)進(jìn)程正在running),。 但是中斷和進(jìn)程是異步的,在中斷上下文中,,當(dāng)前進(jìn)程大部分時(shí)候和中斷代碼可能一點(diǎn)關(guān)系都沒(méi)有,。要是在這里調(diào)用了休眠代碼,把當(dāng)前進(jìn)程給休眠了,,那就極有可能把無(wú)關(guān)的進(jìn)程休眠了,。再者,如果中斷不斷到來(lái),,會(huì)殃及許多無(wú)辜的進(jìn)程,。 在中斷中休眠某個(gè)特定進(jìn)程是可以實(shí)現(xiàn)的,通過(guò)內(nèi)核的task_struct鏈表可以找到的,不論是根據(jù)PID還是name,。但是只要這個(gè)進(jìn)程不是當(dāng)前進(jìn)程,,休眠它也可能沒(méi)有必要??赡苓@個(gè)進(jìn)程本來(lái)就在休眠,;或者正在執(zhí)行隊(duì)列中但是還沒(méi)執(zhí)行到,如果執(zhí)行到他了可能又無(wú)須休眠了,。 還有一個(gè)原因是中斷也是所謂的原子上下文,,有的中斷例程中會(huì)禁止所有中斷,有的中斷例程還會(huì)使用自旋鎖等機(jī)制,,在其中使用休眠也是非常危險(xiǎn)的,。 下面會(huì)介紹。 八,、不可在持有自旋鎖,、seqlock、RCU 鎖或關(guān)閉中斷時(shí)休眠的原因 其實(shí)自旋鎖,、seqlock,、RCU 鎖或關(guān)閉中斷期間的代碼都稱(chēng)為原子上下文,比較有代表性的就是自旋鎖spinlock,。 對(duì)于UP系統(tǒng),,如果A進(jìn)程在擁有spinlock時(shí)休眠,這個(gè)進(jìn)程在擁有自旋鎖后主動(dòng)放棄了處理器,。其他的進(jìn)程就開(kāi)始使用處理器,,只要有一個(gè)進(jìn)程B去獲取同一個(gè)自旋鎖,B必然無(wú)法獲取,,并做所謂的自旋等待,。由于自旋鎖禁止所有中斷和搶占,B的自旋等待是不會(huì)被打斷的,,并且B也永遠(yuǎn)獲得不了鎖,。因?yàn)?/span>B在CPU中運(yùn)行,沒(méi)有其他進(jìn)程可以運(yùn)行并喚醒A并釋放鎖,。系統(tǒng)就此鎖死,,只能復(fù)位了。 對(duì)于SMP系統(tǒng),,如果A進(jìn)程在擁有spinlock時(shí)休眠,,這個(gè)進(jìn)程在擁有自旋鎖后主動(dòng)放棄了處理器。如果所有處理器都為了獲取這個(gè)鎖而自旋等待,,由于自旋鎖禁止所有中斷和搶占,,,,就不會(huì)有進(jìn)程可能去喚醒A了,系統(tǒng)也就鎖死了,。 并不是所一旦系統(tǒng)獲得自旋鎖休眠就會(huì)死,,而是有這個(gè)可能。但是注意了計(jì)算機(jī)的運(yùn)行速度之快,,只要有億分之一的可能,也是很容易發(fā)生,。 所有的原子上下文都有這樣的共性:不可在其中休眠,,否則系統(tǒng)極有可能鎖死。 如果你對(duì)此還有懷疑,,眼見(jiàn)為實(shí),。我編寫(xiě)了一個(gè)故意鎖死系統(tǒng)的及其簡(jiǎn)單的驅(qū)動(dòng): 只要對(duì)其設(shè)備節(jié)點(diǎn)做兩次讀寫(xiě)操作,系統(tǒng)必死,。我在X86 的SMP系統(tǒng),,ARMv5、ARMv6,、ARMv7中都做了如下的實(shí)驗(yàn)(單核(UP)系統(tǒng)必須配置CONFIG_DEBUG_SPINLOCK,,否則自旋鎖是沒(méi)有實(shí)際效果(起碼不會(huì)有“自旋”), 系統(tǒng)可以多次獲取自旋鎖,,沒(méi)有實(shí)驗(yàn)效果,。之后博文中有詳細(xì)描述)。現(xiàn)象都和上面敘述的死法相同,,看了源碼就知道(關(guān)鍵在read\write方法),。以下是實(shí)驗(yàn)記錄:- # insmod spin_lock_sleep.ko
- spin_lock sleep module loaded!
- # cat /proc/devices
- Character devices:
- 1 mem
- 4 /dev/vc/0
- 4 tty
- 4 ttyS
- 5 /dev/tty
- 5 /dev/console
- 5 /dev/ptmx
- 7 vcs
- 10 misc
- 13 input
- 14 sound
- 21 sg
- 29 fb
- 81 video4linux
- 89 i2c
- 90 mtd
- 116 alsa
- 128 ptm
- 136 pts
- 252 spin_lock_sleep
- 253 ttyO
- 254 rtc
- Block devices:
- 1 ramdisk
- 259 blkext
- 7 loop
- 8 sd
- 11 sr
- 31 mtdblock
- 65 sd
- 66 sd
- 67 sd
- 68 sd
- 69 sd
- 70 sd
- 71 sd
- 128 sd
- 129 sd
- 130 sd
- 131 sd
- 132 sd
- 133 sd
- 134 sd
- 135 sd
- 179 mmc
- # mknod spin_lock_sleep c 252 0
- # cat spin_lock_sleep
- spin_lock_sleep_read:prepare to get spin_lock! PID:1227
- spin_lock_sleep_read:have got the spin_lock! PID:1227
- spin_lock_sleep_read:prepare to sleep! PID:1227
- spin_lock_sleep_write:prepare to get spin_lock! PID:1229
- BUG: spinlock cpu recursion on CPU#0, sh/1229
- lock: dd511c3c, .magic: dead4ead, .owner: cat/1227, .owner_cpu: 0
- Backtrace:
- [<c005e9fc>] (dump_backtrace+0x0/0x118) from [<c04335e4>] (dump_stack+0x20/0x24)
- r7:00000002 r6:dd511c3c r5:dd511c3c r4:dd7ef000
- [<c04335c4>] (dump_stack+0x0/0x24) from [<c02178d0>] (spin_bug+0x94/0xa8)
- [<c021783c>] (spin_bug+0x0/0xa8) from [<c0217a58>] (do_raw_spin_lock+0x6c/0x160)
- r5:bf04c408 r4:dd75e000
- [<c02179ec>] (do_raw_spin_lock+0x0/0x160) from [<c0435ec4>] (_raw_spin_lock+0x18/0x1c)
- [<c0435eac>] (_raw_spin_lock+0x0/0x1c) from [<bf04c1a4>] (spin_lock_sleep_write+0xb4/0x190 [spin_lock_sleep])
- [<bf04c0f0>] (spin_lock_sleep_write+0x0/0x190 [spin_lock_sleep]) from [<c011ee90>] (vfs_write+0xb8/0xe0)
- r6:dd75ff70 r5:400d7000 r4:dd43bf00
- [<c011edd8>] (vfs_write+0x0/0xe0) from [<c011ef8c>] (sys_write+0x4c/0x78)
- r7:00000002 r6:dd43bf00 r5:00000000 r4:00000000
- [<c011ef40>] (sys_write+0x0/0x78) from [<c005a380>] (ret_fast_syscall+0x0/0x48)
- r8:c005a5a8 r7:00000004 r6:403295e8 r5:400d7000 r4:00000002
- 此時(shí)在另一個(gè)終端(ssh、telnet等)中執(zhí)行命令:
- echo 'l' > spin_lock_sleep
- BUG: spinlock lockup on CPU#0, sh/1229, dd511c3c
- Backtrace:
- [<c005e9fc>] (dump_backtrace+0x0/0x118) from [<c04335e4>] (dump_stack+0x20/0x24)
- r7:dd75e000 r6:dd511c3c r5:00000000 r4:00000000
- [<c04335c4>] (dump_stack+0x0/0x24) from [<c0217b0c>] (do_raw_spin_lock+0x120/0x160)
- [<c02179ec>] (do_raw_spin_lock+0x0/0x160) from [<c0435ec4>] (_raw_spin_lock+0x18/0x1c)
- [<c0435eac>] (_raw_spin_lock+0x0/0x1c) from [<bf04c1a4>] (spin_lock_sleep_write+0xb4/0x190 [spin_lock_sleep])
- [<bf04c0f0>] (spin_lock_sleep_write+0x0/0x190 [spin_lock_sleep]) from [<c011ee90>] (vfs_write+0xb8/0xe0)
- r6:dd75ff70 r5:400d7000 r4:dd43bf00
- [<c011edd8>] (vfs_write+0x0/0xe0) from [<c011ef8c>] (sys_write+0x4c/0x78)
- r7:00000002 r6:dd43bf00 r5:00000000 r4:00000000
- [<c011ef40>] (sys_write+0x0/0x78) from [<c005a380>] (ret_fast_syscall+0x0/0x48)
- r8:c005a5a8 r7:00000004 r6:403295e8 r5:400d7000 r4:00000002
而你在這樣原子環(huán)境中休眠調(diào)度,,內(nèi)核一旦檢測(cè)到(主要是檢測(cè)到關(guān)閉了搶占),,你可能會(huì)看到如下信息,警告你: - # cat spin_lock_sleep
- spin_lock_sleep_read:prepare to get spin_lock! PID:540
- spin_lock_sleep_read:have got the spin_lock! PID:540
- spin_lock_sleep_read:prepare to sleep! PID:540
- BUG: scheduling while atomic: cat/540/0x00000002
- Modules linked in: spin_lock_sleep
- [<c0070364>] (unwind_backtrace+0x0/0xe4) from [<c03304a4>] (schedule+0x74/0x36c)
- [<c03304a4>] (schedule+0x74/0x36c) from [<bf0062bc>] (spin_lock_sleep_read+0xe8/0x1bc [spin_lock_sleep])
- [<bf0062bc>] (spin_lock_sleep_read+0xe8/0x1bc [spin_lock_sleep]) from [<c00dd370>] (vfs_read+0xac/0x154)
- [<c00dd370>] (vfs_read+0xac/0x154) from [<c00dd454>] (sys_read+0x3c/0x68)
- [<c00dd454>] (sys_read+0x3c/0x68) from [<c006ae60>] (ret_fast_syscall+0x0/0x2c)
|