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