?? ch07s04.html
字號(hào):
<html xmlns:cf="http://docbook.sourceforge.net/xmlns/chunkfast/1.0"><head><meta http-equiv="Content-Type" content="text/html; charset=gb2312"><title>7.4. 內(nèi)核定時(shí)器-Linux設(shè)備驅(qū)動(dòng)第三版(中文版)- - </title><meta name="description" content="驅(qū)動(dòng)開發(fā)- - " /><meta name="keywords" content="Linux設(shè)備驅(qū)動(dòng),中文版,第三版,ldd,linux device driver,驅(qū)動(dòng)開發(fā),電子版,程序設(shè)計(jì),軟件開發(fā), " /><meta name="author" content=" www.21cstar.com QQ:610061171" /> <meta name="verify-v1" content="5asbXwkS/Vv5OdJbK3Ix0X8osxBUX9hutPyUxoubhes=" /><link rel="stylesheet" href="docbook.css" type="text/css"><meta name="generator" content="DocBook XSL Stylesheets V1.69.0"><link rel="start" href="index.html" title="Linux 設(shè)備驅(qū)動(dòng) Edition 3"><link rel="up" href="ch07.html" title="第 7 章 時(shí)間, 延時(shí), 和延后工作"><link rel="prev" href="ch07s03.html" title="7.3. 延后執(zhí)行"><link rel="next" href="ch07s05.html" title="7.5. Tasklets 機(jī)制"></head><body bgcolor="white" text="black" link="#0000FF" vlink="#840084" alink="#0000FF"><div class="navheader"><table width="100%" summary="Navigation header"><tr><th colspan="3" align="center">7.4. 內(nèi)核定時(shí)器</th></tr><tr><td width="20%" align="left"><a accesskey="p" href="ch07s03.html">上一頁(yè)</a> </td><th width="60%" align="center">第 7 章 時(shí)間, 延時(shí), 和延后工作</th><td width="20%" align="right"> <a accesskey="n" href="ch07s05.html">下一頁(yè)</a></td></tr></table><hr></div><div class="sect1" lang="zh-cn"><div class="titlepage"><div><div><h2 class="title" style="clear: both"><a name="KernelTimers.sect"></a>7.4. 內(nèi)核定時(shí)器</h2></div></div></div><p>無論何時(shí)你需要調(diào)度一個(gè)動(dòng)作以后發(fā)生, 而不阻塞當(dāng)前進(jìn)程直到到時(shí), 內(nèi)核定時(shí)器是給你的工具. 這些定時(shí)器用來調(diào)度一個(gè)函數(shù)在將來一個(gè)特定的時(shí)間執(zhí)行, 基于時(shí)鐘嘀噠, 并且可用作各類任務(wù); 例如, 當(dāng)硬件無法發(fā)出中斷時(shí), 查詢一個(gè)設(shè)備通過在定期的間隔內(nèi)檢查它的狀態(tài). 其他的內(nèi)核定時(shí)器的典型應(yīng)用是關(guān)閉軟驅(qū)馬達(dá)或者結(jié)束另一個(gè)長(zhǎng)期終止的操作. 在這種情況下, 延后來自 close 的返回將強(qiáng)加一個(gè)不必要(并且嚇人的)開銷在應(yīng)用程序上. 最后, 內(nèi)核自身使用定時(shí)器在幾個(gè)情況下, 包括實(shí)現(xiàn) schedule_timeout.</p><p>一個(gè)內(nèi)核定時(shí)器是一個(gè)數(shù)據(jù)結(jié)構(gòu), 它指導(dǎo)內(nèi)核執(zhí)行一個(gè)用戶定義的函數(shù)使用一個(gè)用戶定義的參數(shù)在一個(gè)用戶定義的時(shí)間. 這個(gè)實(shí)現(xiàn)位于 <linux/timer.h> 和 kernel/timer.c 并且在"內(nèi)核定時(shí)器"一節(jié)中詳細(xì)介紹.</p><p>被調(diào)度運(yùn)行的函數(shù)幾乎確定不會(huì)在注冊(cè)它們的進(jìn)程在運(yùn)行時(shí)運(yùn)行. 它們是, 相反, 異步運(yùn)行. 直到現(xiàn)在, 我們?cè)谖覀兊睦域?qū)動(dòng)中已經(jīng)做的任何事情已經(jīng)在執(zhí)行系統(tǒng)調(diào)用的進(jìn)程上下文中運(yùn)行. 當(dāng)一個(gè)定時(shí)器運(yùn)行時(shí), 但是, 這個(gè)調(diào)度進(jìn)程可能睡眠, 可能在不同的一個(gè)處理器上運(yùn)行, 或者很可能已經(jīng)一起退出.</p><p>這個(gè)異步執(zhí)行類似當(dāng)發(fā)生一個(gè)硬件中斷時(shí)所發(fā)生的( 這在第 10 章詳細(xì)討論 ). 實(shí)際上, 內(nèi)核定時(shí)器被作為一個(gè)"軟件中斷"的結(jié)果而實(shí)現(xiàn). 當(dāng)在這種原子上下文運(yùn)行時(shí), 你的代碼易受到多個(gè)限制. 定時(shí)器函數(shù)必須是原子的以所有的我們?cè)诘?1 章"自旋鎖和原子上下文"一節(jié)中曾討論過的方式, 但是有幾個(gè)附加的問題由于缺少一個(gè)進(jìn)程上下文而引起的. 我們將介紹這些限制; 在后續(xù)章節(jié)的幾個(gè)地方將再次看到它們. 循環(huán)被調(diào)用因?yàn)樵由舷挛牡囊?guī)則必須認(rèn)真遵守, 否則系統(tǒng)會(huì)發(fā)現(xiàn)自己陷入大麻煩中.</p><p>為能夠被執(zhí)行, 多個(gè)動(dòng)作需要進(jìn)程上下文. 當(dāng)你在進(jìn)程上下文之外(即, 在中斷上下文), 你必須遵守下列規(guī)則:</p><div class="itemizedlist"><ul type="disc"><li><p>沒有允許存取用戶空間. 因?yàn)闆]有進(jìn)程上下文, 沒有和任何特定進(jìn)程相關(guān)聯(lián)的到用戶空間的途徑.</p></li><li><p>這個(gè) current 指針在原子態(tài)沒有意義, 并且不能使用因?yàn)橄嚓P(guān)的代碼沒有和已被中斷的進(jìn)程的聯(lián)系.</p></li><li><p>不能進(jìn)行睡眠或者調(diào)度. 原子代碼不能調(diào)用 schedule 或者某種 wait_event, 也不能調(diào)用任何其他可能睡眠的函數(shù). 例如, 調(diào)用 kmalloc(..., GFP_KERNEL) 是違犯規(guī)則的. 旗標(biāo)也必須不能使用因?yàn)樗鼈兛赡芩?</p></li></ul></div><p>內(nèi)核代碼能夠告知是否它在中斷上下文中運(yùn)行, 通過調(diào)用函數(shù) in_interrupt(), 它不要參數(shù)并且如果處理器當(dāng)前在中斷上下文運(yùn)行就返回非零, 要么硬件中斷要么軟件中斷.</p><p>一個(gè)和 in_interrupt() 相關(guān)的函數(shù)是 in_atomic(). 它的返回值是非零無論何時(shí)調(diào)度被禁止; 這包含硬件和軟件中斷上下文以及任何持有自旋鎖的時(shí)候. 在后一種情況, current 可能是有效的, 但是存取用戶空間被禁止, 因?yàn)樗軐?dǎo)致調(diào)度發(fā)生. 無論何時(shí)你使用 in_interrupt(), 你應(yīng)當(dāng)真正考慮是否 in_atomic 是你實(shí)際想要的. 2 個(gè)函數(shù)都在 <asm/hardirq.h> 中聲明.</p><p>內(nèi)核定時(shí)器的另一個(gè)重要特性是一個(gè)任務(wù)可以注冊(cè)它本身在后面時(shí)間重新運(yùn)行. 這是可能的, 因?yàn)槊總€(gè) timer_list 結(jié)構(gòu)在運(yùn)行前從激活的定時(shí)器鏈表中去連接, 并且因此能夠馬上在其他地方被重新連接. 盡管反復(fù)重新調(diào)度相同的任務(wù)可能表現(xiàn)為一個(gè)無意義的操作, 有時(shí)它是有用的. 例如, 它可用作實(shí)現(xiàn)對(duì)設(shè)備的查詢.</p><p>也值得了解在一個(gè) SMP 系統(tǒng), 定時(shí)器函數(shù)被注冊(cè)時(shí)相同的 CPU 來執(zhí)行, 為在任何可能的時(shí)候獲得更好的緩存局部特性. 因此, 一個(gè)重新注冊(cè)它自己的定時(shí)器一直運(yùn)行在同一個(gè) CPU.</p><p>不應(yīng)當(dāng)被忘記的定時(shí)器的一個(gè)重要特性是, 它們是一個(gè)潛在的競(jìng)爭(zhēng)條件的源, 即便在一個(gè)單處理器系統(tǒng). 這是它們與其他代碼異步運(yùn)行的一個(gè)直接結(jié)果. 因此, 任何被定時(shí)器函數(shù)存取的數(shù)據(jù)結(jié)構(gòu)應(yīng)當(dāng)保護(hù)避免并發(fā)存取, 要么通過原子類型( 在第 1 章的"原子變量"一節(jié)) 要么使用自旋鎖( 在第 9 章討論 ).</p><div class="sect2" lang="zh-cn"><div class="titlepage"><div><div><h3 class="title"><a name="TheTimerAPI.sect"></a>7.4.1. 定時(shí)器 API</h3></div></div></div><p>內(nèi)核提供給驅(qū)動(dòng)許多函數(shù)來聲明, 注冊(cè), 以及去除內(nèi)核定時(shí)器. 下列的引用展示了基本的代碼塊:</p><pre class="programlisting">#include <linux/timer.h>struct timer_list{ /* ... */ unsigned long expires; void (*function)(unsigned long); unsigned long data;};void init_timer(struct timer_list *timer);struct timer_list TIMER_INITIALIZER(_function, _expires, _data);void add_timer(struct timer_list * timer);int del_timer(struct timer_list * timer);</pre><p>這個(gè)數(shù)據(jù)結(jié)構(gòu)包含比曾展示過的更多的字段, 但是這 3 個(gè)是打算從定時(shí)器代碼自身以外被存取的. 這個(gè) expires 字段表示定時(shí)器期望運(yùn)行的 jiffies 值; 在那個(gè)時(shí)間, 這個(gè) function 函數(shù)被調(diào)用使用 data 作為一個(gè)參數(shù). 如果你需要在參數(shù)中傳遞多項(xiàng), 你可以捆綁它們作為一個(gè)單個(gè)數(shù)據(jù)結(jié)構(gòu)并且傳遞一個(gè)轉(zhuǎn)換為 unsiged long 的指針, 在所有支持的體系上的一個(gè)安全做法并且在內(nèi)存管理中相當(dāng)普遍( 如同 15 章中討論的 ). expires 值不是一個(gè) jiffies_64 項(xiàng)因?yàn)槎〞r(shí)器不被期望在將來很久到時(shí), 并且 64-位操作在 32-位平臺(tái)上慢.</p><p>這個(gè)結(jié)構(gòu)必須在使用前初始化. 這個(gè)步驟保證所有的成員被正確建立, 包括那些對(duì)調(diào)用者不透明的. 初始化可以通過調(diào)用 init_timer 或者 安排 TIMER_INITIALIZER 給一個(gè)靜態(tài)結(jié)構(gòu), 根據(jù)你的需要. 在初始化后, 你可以改變 3 個(gè)公共成員在調(diào)用 add_timer 前. 為在到時(shí)前禁止一個(gè)已注冊(cè)的定時(shí)器, 調(diào)用 del_timer.</p><p>jit 模塊包括一個(gè)例子文件, /proc/jitimer ( 為 "just in timer"), 它返回一個(gè)頭文件行以及 6 個(gè)數(shù)據(jù)行. 這些數(shù)據(jù)行表示當(dāng)前代碼運(yùn)行的環(huán)境; 第一個(gè)由讀文件操作產(chǎn)生并且其他的由定時(shí)器. 下列的輸出在編譯內(nèi)核時(shí)被記錄:</p><pre class="screen">phon% cat /proc/jitimer time delta inirq pid cpu command 33565837 0 0 1269 0 cat 33565847 10 1 1271 0 sh 33565857 10 1 1273 0 cpp0 33565867 10 1 1273 0 cpp0 33565877 10 1 1274 0 cc1 33565887 10 1 1274 0 cc1 </pre><p>在這個(gè)輸出, time 字段是代碼運(yùn)行時(shí)的 jiffies 值, delta 是自前一行的 jiffies 改變值, inirq 是由 in_interrupt 返回的布爾值, pid 和 command 指的是當(dāng)前進(jìn)程, 以及 cpu 是在使用的 CPU 的數(shù)目( 在單處理器系統(tǒng)上一直為 0).</p><p>如果你讀 /proc/jitimer 當(dāng)系統(tǒng)無負(fù)載時(shí), 你會(huì)發(fā)現(xiàn)定時(shí)器的上下文是進(jìn)程 0, 空閑任務(wù), 它被稱為"對(duì)換進(jìn)程"只要由于歷史原因.</p><p>用來產(chǎn)生 /proc/jitimer 數(shù)據(jù)的定時(shí)器是缺省每 10 jiffies 運(yùn)行一次, 但是你可以在加載模塊時(shí)改變這個(gè)值通過設(shè)置 tdelay ( timer delay ) 參數(shù).</p><p>下面的代碼引用展示了 jit 關(guān)于 jitimer 定時(shí)器的部分. 當(dāng)一個(gè)進(jìn)程試圖讀取我們的文件, 我們建立這個(gè)定時(shí)器如下:</p><pre class="programlisting">
?? 快捷鍵說明
復(fù)制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號(hào)
Ctrl + =
減小字號(hào)
Ctrl + -