4.用戶態睡眠
以sleep為例來說明任務在用戶態是如何睡眠的。
首先我們通過strace工具來看下其調用的系統調用:
$ strace sleep 1
...
close(3) = 0
clock_nanosleep(CLOCK_REALTIME, 0, {tv_sec=1, tv_nsec=0}, NULL) = 0
close(1) = 0
...
可以發現sleep主要調用clock_nanosleep系統調用來進行睡眠(也就是說用戶態任務睡眠需要調用系統調用陷入內核)。
下面我們來研究下clock_nanosleep的實現(這里集中到睡眠的實現,先忽略掉定時器等諸多的技術細節):
SYSCALL_DEFINE4(clock_nanosleep
-》const struct k_clock *kc = clockid_to_kclock(which_clock); //根據時鐘類型得到內核時鐘結構
return kc-》nsleep(which_clock, flags, &t); //調用內核時鐘結構的nsleep回調
我們傳遞過來的時鐘類型為CLOCK_REALTIME,則調用鏈為:
kc-》nsleep(CLOCK_REALTIME, flags, &t)
-》clock_realtime.nsleep
-》common_nsleep
-》hrtimer_nanosleep //kernel/time/hrtimer.c
-》hrtimer_init_sleeper_on_stack
-》__hrtimer_init_sleeper
-》__hrtimer_init(&sl-》timer, clock_id, mode); //初始化高精度定時器
sl-》timer.function = hrtimer_wakeup; //設置超時回調函數
sl-》task = current;。//設置超時時要喚醒的任務
-》do_nanosleep //睡眠操作
可以看到,睡眠函數最終調用到hrtimer_nanosleep,它調用了兩個主要函數:__hrtimer_init_sleeper和do_nanosleep,前者主要設置高精度定時器,后者就是真正的睡眠,主要來看下do_nanosleep:
kernel/time/hrtimer.c
do_nanosleep
-》
do {
set_current_state(TASK_INTERRUPTIBLE); //設置可中斷的睡眠狀態
hrtimer_sleeper_start_expires(t, mode); //開啟高精度定時器
if (likely(t-》task))
freezable_schedule(); //主動調度
hrtimer_cancel(&t-》timer);
mode = HRTIMER_MODE_ABS;
} while (t-》task && !signal_pending(current)); //是否記錄的有任務且沒有掛起的信號
__set_current_state(TASK_RUNNING); //設置為可運行狀態
do_nanosleep函數是睡眠的核心實現:首先設置任務的狀態為可中斷的睡眠狀態,然后開啟了之前設置的高精度定時器,隨即調用freezable_schedule進行真正的睡眠。
來看下freezable_schedule:
//include/linux/freezer.h
freezable_schedule
-》schedule()
-》__schedule(false);
可以看到最終調用主調度器__schedule進行主動調度。
當任務睡眠完成,定時器超時,會調用之前在__hrtimer_init_sleeper設置的超時回調函數hrtimer_wakeup將睡眠的任務喚醒(關于進程喚醒在這里就不在贅述,在后面的進程喚醒專題文章在進行詳細解讀),然后就可以再次獲得處理器的使用權了。
總結:處于用戶態的任務,如果想要睡眠一段時間必須向內核請求服務(如調用clock_nanosleep系統調用),內核中會設置一個高精度定時器,來記錄要睡眠的任務,然后設置任務狀態為可中斷的睡眠狀態,緊接著發生主動調度,這樣任務就發生睡眠了。
5.內核態睡眠
當任務處于內核態時,有時候也需要睡眠一段時間,不像任務處于用戶態需要發生系統調用來請求內核進行睡眠,在內核態可以直接調用睡眠函數。當然,內核態中,睡眠有兩種場景:一種是睡眠特定的時間的延遲操作(喚醒條件為超時),一種是等待特定條件滿足(如IO讀寫完成,可睡眠的鎖被釋放等)。
下面分別以msleep和mutex鎖為例講解內核態睡眠:
5.1 msleep
msleep做ms級別的睡眠延遲。
//kernel/time/timer.c
void msleep(unsigned int msecs)
{
unsigned long timeout = msecs_to_jiffies(msecs) + 1; //ms時間轉換為jiffies
while (timeout)
timeout = schedule_timeout_uninterruptible(timeout); //不可中斷睡眠
}
下面看下schedule_timeout_uninterruptible:
這里涉及到一個重要數據結構process_timer
struct process_timer {
struct timer_list timer; //定時器結構
struct task_struct *task; //定時器到期要喚醒的任務
};
schedule_timeout_uninterruptible
-》 __set_current_state(TASK_UNINTERRUPTIBLE); //設置任務狀態為不可中斷睡眠
return schedule_timeout(timeout);
-》expire = timeout + jiffies; //計算到期時的jiffies值
timer.task = current; //記錄定時器到期要喚醒的任務 為當前任務
timer_setup_on_stack(&timer.timer, process_timeout, 0); //初始化定時器 超時回調為process_timeout
__mod_timer(&timer.timer, expire, MOD_TIMER_NOTPENDING); //添加定時器
schedule(); //主動調度
再看下超時回調為process_timeout:
process_timeout
-》struct process_timer *timeout = from_timer(timeout, t, timer); //通過定時器結構獲得process_timer
wake_up_process(timeout-》task); //喚醒其管理的任務
可以看到,msleep實現睡眠也是通過定時器,首先設置當前任務狀態為不可中斷睡眠,然后設置定時器超時時間為傳遞的ms級延遲轉換的jiffies,超時回調為process_timeout,然后將定時器添加到系統中,最后調用schedule發起主動調度,當定時器超時的時候調用process_timeout來喚醒睡眠的任務。
5.2 mutex鎖
mutex鎖是可睡眠鎖的一種,當申請mutex鎖時發現其他內核路徑已經持有這把鎖,當前任務就會睡眠等待在這把鎖上。
下面我們來看他的實現,主要看睡眠的部分:
kernel/locking/mutex.c
mutex_lock
-》__mutex_lock_slowpath
-》__mutex_lock(lock, TASK_UNINTERRUPTIBLE, 0, NULL, _RET_IP_) //睡眠的狀態為不可中斷睡眠
-》__mutex_lock_common
-》
...
waiter.task = current; //記錄需要喚醒的任務為當前任務
set_current_state(state); //設置睡眠狀態
for (;;) {
if (__mutex_trylock(lock)) //嘗試獲得鎖
goto acquired;
schedule_preempt_disabled();
-》schedule(); //主動調度
}
acquired:
__set_current_state(TASK_RUNNING);//設置狀態為可運行狀態
可以看到mutex鎖實現睡眠套路和之前是一樣的:申請mutex鎖的時候,如果其他內核路徑已經持有這把鎖,首先通過mutex鎖的相關結構來記錄下當前任務,然后設置任務狀態為不可中斷睡眠,接著在一個for循環中調用schedule_preempt_disabled發生主動調度,于是當前任務就睡眠在這把鎖上。
當其他內核路徑釋放了這把鎖,就會喚醒等待在這把鎖上的任務,當前任務就獲得了這把鎖,然后進入鎖的臨界區,喚醒操作就完成了(關于喚醒的技術細節,后面的喚醒專題會詳細講解)。
6.總結
進程睡眠按照應用場景可以分為:延遲睡眠和等待某些特定條件而睡眠,實際上都可以歸于等待某些特定條件而睡眠,因為延遲特定時間也可以作為特定條件。
進程睡眠按照進程所處的特權級別可以分為:用戶態進程睡眠和內核態進程睡眠,用戶態進程睡眠需要進程通過系統調用陷入內核來發起睡眠請求。對于進程睡眠,內核主要需要做三大步操作:
1.設置任務狀態為睡眠狀態 2.記錄睡眠的任務 3.發起主動調度。這三大步操作都是非常有必要,第一步設置睡眠狀態為后面調用主調度器做必要的標識準備;第二步記錄下睡眠的任務是為了以后喚醒任務來準備的;第三步是睡眠的主體部分,這里會將睡眠的任務從運行隊列中踢出,選擇下一個任務運行。
原文標題:深入理解Linux內核之進程睡眠(下)
文章出處:【微信公眾號:Linux閱碼場】歡迎添加關注!文章轉載請注明出處。
責任編輯:haq
-
內核
+關注
關注
3文章
1372瀏覽量
40280 -
Linux
+關注
關注
87文章
11293瀏覽量
209338
原文標題:深入理解Linux內核之進程睡眠(下)
文章出處:【微信號:gh_6fde77c41971,微信公眾號:FPGA干貨】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論