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

騰訊云內(nèi)核團(tuán)隊(duì)修復(fù)Linux關(guān)鍵Bug
嵌入式學(xué)習(xí)-飛凌嵌入式ElfBoard ELF 1板卡-Linux內(nèi)核移植之內(nèi)核簡(jiǎn)介
飛凌嵌入式ElfBoard ELF 1板卡-Linux內(nèi)核移植之內(nèi)核簡(jiǎn)介
嵌入式工程師都在找的【Linux內(nèi)核調(diào)試技術(shù)】建議收藏!
國(guó)產(chǎn)實(shí)時(shí)操作系統(tǒng):和RT-Linux,Zephyr的實(shí)時(shí)性對(duì)比

Linux系統(tǒng)中shell命令解析
一文搞懂Linux進(jìn)程的睡眠和喚醒
Linux用戶管理詳解
deepin社區(qū)亮相第19屆中國(guó)Linux內(nèi)核開發(fā)者大會(huì)
詳解linux內(nèi)核的uevent機(jī)制
linux驅(qū)動(dòng)程序如何加載進(jìn)內(nèi)核
Linux內(nèi)核測(cè)試技術(shù)

Linux內(nèi)核中的頁(yè)面分配機(jī)制

評(píng)論