午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

十三、Linux驅(qū)動(dòng)程序開發(fā)(7) - 高級(jí)字符驅(qū)動(dòng)程序(1)

 garget 2010-07-20
十三、Linux驅(qū)動(dòng)程序開發(fā)(7) - 高級(jí)字符驅(qū)動(dòng)程序(1)

高級(jí)字符驅(qū)動(dòng)程序操作

Linux設(shè)備驅(qū)動(dòng)程序(第3版)》第六章和《Linux設(shè)備驅(qū)動(dòng)開發(fā)詳解》

這一節(jié)將講到以下內(nèi)容:

ioctl 接口

阻塞與非阻塞IO

poll select

異步通知與異步IO

移位一個(gè)設(shè)備

 

一、ioctl 接口

大部分驅(qū)動(dòng)需要 -- 除了讀寫設(shè)備的能力 -- 通過設(shè)備驅(qū)動(dòng)進(jìn)行各種硬件 控制的能力. 大部分設(shè)備可進(jìn)行超出簡單的數(shù)據(jù)傳輸之外的操作; 用戶空間必須常常能夠請(qǐng)求, 例如, 設(shè)備鎖上它的門, 彈出它的介質(zhì), 報(bào)告錯(cuò)誤信息, 改變波特率, 或者自我銷毀. 這些操 作常常通過 ioctl 方法來支持, 它通過相同名子的系統(tǒng)調(diào)用來實(shí)現(xiàn).

在用戶空間, ioctl 系統(tǒng)調(diào)用有下面的原 型:

int ioctl(int fd, unsigned long cmd, ...); 

這些點(diǎn)常常表示函數(shù)有數(shù)目不定的參數(shù)。

ioctl 驅(qū)動(dòng)方法有和用戶空間版本不同的原型:

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

inode filp 指針是對(duì)應(yīng)應(yīng)用程序傳遞的文件描述符 fd 的值, 和傳遞給 open 方法的相同參數(shù). cmd 參數(shù)從用戶那里不改變地 傳下來, 并且可選的參數(shù) arg 參數(shù)以一個(gè) unsigned long 的形式傳遞, 不管它是否由用戶給定為一個(gè)整數(shù)或一個(gè)指針. 如果調(diào)用程序不傳遞第 3 個(gè)參數(shù), 被驅(qū)動(dòng)操作收到的 arg 值是無定義的. 因?yàn)轭愋蜋z查在這個(gè)額外參數(shù)上被關(guān)閉, 編譯器不能警告你如果一個(gè)無效的參數(shù)被傳遞給 ioctl, 并且任何關(guān)聯(lián)的錯(cuò)誤將難以查找.

 

選擇 ioctl 命令

為 了防止向錯(cuò)誤的設(shè)備使用正確的命令,命令號(hào)應(yīng)該在系統(tǒng)范圍內(nèi)唯一。為方便程序員創(chuàng)建唯一的 ioctl 命令代號(hào), 每個(gè)命令號(hào)被劃分為多個(gè)位字段。要按 Linux 內(nèi)核的約定方法為驅(qū)動(dòng)選擇 ioctl 的命令號(hào), 應(yīng)該首先看看 include/asm/ioctl.h Documentation/ioctl-number.txt 要 使用的位字段符號(hào)定義在 <linux/ioctl.h> :

定 義 ioctl 命 令號(hào)的正確方法使用 4 個(gè)位段, 它們有下列的含義. 這個(gè)列表中介紹的新符號(hào)定義在 <linux/ioctl.h>.

type

魔數(shù). 只 是選擇一個(gè)數(shù)(在 參考了 ioctl-number.txt之后)并且使用它在整個(gè)驅(qū)動(dòng)中. 這個(gè)成員是 8 位寬(_IOC_TYPEBITS).

number

(順 序)號(hào). 它是 8 (_IOC_NRBITS).

direction

數(shù)據(jù)傳送的方向,如果這個(gè)特殊的命令涉及數(shù)據(jù)傳送. 可能的值是 _IOC_NONE(沒有數(shù)據(jù)傳輸), _IOC_READ, _IOC_WRITE, _IOC_READ|_IOC_WRITE (數(shù)據(jù)在2個(gè)方向被傳送). 數(shù)據(jù)傳送是從應(yīng)用程序的觀點(diǎn)來看待的; _IOC_READ 意思是從設(shè)備讀, 因此設(shè)備必須寫到用戶空間. 注意這個(gè)成員是一個(gè)位掩碼, 因此 _IOC_READ _IOC_WRITE 可使用一個(gè)邏輯 AND 操作來抽取.

size

涉及到的用戶數(shù)據(jù)的大小. 這個(gè)成員的寬度是依賴體系的, 但是常常是 13 或者 14 . 你可為你的特定體系在宏 _IOC_SIZEBITS 中 找到它的值. 你 使用這個(gè) size 成員不是強(qiáng)制的 - 內(nèi)核不檢查它 -- 但是它是一個(gè)好主意. 正確使用這個(gè)成員可幫助檢測(cè)用戶空間程序的錯(cuò)誤并使你實(shí)現(xiàn)向后兼容, 如果你曾需要改變相關(guān)數(shù)據(jù)項(xiàng)的大小. 如果你需要更大的數(shù)據(jù)結(jié)構(gòu), 但是, 你可忽略這個(gè) size 成員. 我們很快見到如何使用這個(gè)成員.

頭 文件 <asm/ioctl.h>, 它包含在 <linux/ioctl.h> , 定義宏來幫助建立命令號(hào), 如下:

_IO(type,nr)(給沒有參數(shù)的命令),

_IOR(type, nre, datatype)(給從驅(qū)動(dòng)中讀數(shù)據(jù)的),

_IOW(type,nr,datatype)(給寫數(shù)據(jù)),

_IOWR(type,nr,datatype)(給雙向傳送),

type number 成員作為參數(shù)被傳遞, 并且 size 成員通過應(yīng)用 sizeof datatype 參數(shù)而得到.

這 個(gè)頭文件還定義宏, 可被用在你的驅(qū)動(dòng)中來解碼這個(gè)號(hào):

_IOC_DIR(nr),

_IOC_TYPE(nr),

_IOC_NR(nr),

_IOC_SIZE(nr). 我們不進(jìn)入任何這些宏的細(xì)節(jié), 因?yàn)轭^文件是清楚的。

返回值

POSIX 標(biāo)準(zhǔn):

如果一個(gè)不合適的 ioctl 命令被發(fā)出, 那么 -ENOTTY 應(yīng)當(dāng)被返回. 這個(gè)錯(cuò)誤碼被 C 庫解釋為"設(shè)備的不適當(dāng)?shù)?/span> ioctl", 這常常正是程序員需要聽到的. 然而, 它仍然是相當(dāng)普遍的來返回 -EINVAL, 對(duì)于響應(yīng) 一個(gè)無效的 ioctl 命令.

 

預(yù)定義命令

有 一些ioctl命 令是由內(nèi)核識(shí)別的,當(dāng)這些命令用于自己的設(shè)備時(shí),他們會(huì)在我們自己的文件操作被調(diào)用之前被解碼. 因此, 如果你選擇一個(gè)ioctl命令編號(hào)和系統(tǒng)預(yù)定義的相同時(shí),你永遠(yuǎn)不會(huì)看到該命令的請(qǐng)求,而且因 為ioctl 號(hào) 之間的沖突,應(yīng)用程序的行為將無法預(yù)測(cè)。預(yù)定義命令分為 3 :

1)用于任何文件(常規(guī), 設(shè)備, FIFOsocket) 的命令

2)只用于常規(guī)文件的命令

3)特定于文件系統(tǒng)類型的命令 

下 列 ioctl 命 令是預(yù)定義給任何文件,包括設(shè)備特定文件:

FIOCLEX :設(shè)置 close-on-exec 標(biāo)志(File IOctl Close on EXec)

FIONCLEX :清除 close-no-exec 標(biāo)志(File IOctl Not CLose on EXec)。

FIOQSIZE :這個(gè)命令返回一個(gè)文件或者目錄的大小; 當(dāng)用作一個(gè)設(shè)備文件, 但是, 它返回一個(gè) ENOTTY 錯(cuò)誤。

FIONBIO"File IOctl Non-Blocking I/O"("阻塞和非阻塞操作"一節(jié)中描述)。 

 

使用ioctl參 數(shù)

在 使用ioctl的可選arg參數(shù)時(shí),如果傳遞的是一個(gè)整數(shù),它可以直接使用。如果是一個(gè)指針,,就必須小心。當(dāng)用一個(gè)指針引用用戶空間, 我們必須確保用戶地址是有效的,其校驗(yàn)(不傳送數(shù)據(jù))由函數(shù) access_ok 實(shí)現(xiàn),定 義在 <asm/uaccess.h> :

int access_ok(int type, const void *addr, unsigned long size);

第一個(gè)參數(shù)應(yīng)當(dāng)是 VERIFY_READ(讀)或VERIFY_WRITE(讀寫);addr 參 數(shù)為用戶空間地址,size 為字節(jié)數(shù),可使用sizeof()。access_ok 返回一個(gè)布爾值: 1 是成功(存取沒問題) 0 是失敗(存取有 問題)。如果它返回假,驅(qū)動(dòng)應(yīng)當(dāng)返回 -EFAULT 給調(diào)用者。

注意:首先, access_ok不做校驗(yàn)內(nèi)存存取的完整工作; 它 只檢查內(nèi)存引用是否在這個(gè)進(jìn)程有合理權(quán)限的內(nèi)存范圍中,且確保這個(gè)地址不指向內(nèi)核空間內(nèi)存。其次,大部分驅(qū)動(dòng)代碼不需要真正調(diào)用 access_ok,而直接使用put_user(datum, ptr)get_user(local, ptr),它們帶有校驗(yàn)的功能,確保進(jìn)程能夠?qū)懭虢o定的內(nèi)存地址,成功時(shí)返回 0, 并且在錯(cuò)誤時(shí)返回 -EFAULT.。

put_user(datum, ptr)
__put_user
(datum, ptr)

get_user
(local, ptr)

__get_user
(local, ptr)

這些宏它們相對(duì)copy_to_user copy_from_user, 并且 這些宏已被編寫來允許傳遞任何類型的指針,只要它是一個(gè)用戶空間地址. 傳 送的數(shù)據(jù)大小依賴 prt 參數(shù)的類型, 并且在編譯時(shí)使用 sizeof typeof 等編譯器內(nèi)建宏確定。他們只傳送1、248 個(gè)字節(jié)。如果使用以上函數(shù)來傳送一個(gè)大小不適合的值,結(jié)果常常是一個(gè)來自編譯器的奇怪消息,如"coversion to non-scalar type requested". 在 這些情況中,必須使用 copy_to_user 或者 copy_from_user

__put_user__get_user 進(jìn)行更少的檢查(不調(diào)用 access_ok), 但是仍然能夠失敗如果被指向的內(nèi)存對(duì)用戶是不可寫的,所以他們應(yīng)只用在內(nèi)存區(qū)已經(jīng)用 access_ok 檢查過的時(shí)候。作為通用的規(guī)則:當(dāng)實(shí)現(xiàn)一個(gè) read 方法時(shí),調(diào)用 __put_user 來節(jié)省幾個(gè)周期, 或 者當(dāng)你拷貝幾個(gè)項(xiàng)時(shí),因此,在第一次數(shù)據(jù)傳送之前調(diào)用 access_ok 一次。

權(quán)能與受限操作

Linux 內(nèi)核提供了一個(gè)更加靈活的系統(tǒng), 稱為權(quán)能(capability)。內(nèi)核專為許可管理上使用權(quán)能并導(dǎo)出了兩個(gè)系統(tǒng)調(diào)用 capget capset,這樣可以從用戶空間管理權(quán)能,其定義在 <linux/capability.h> 中。對(duì)設(shè)備驅(qū)動(dòng)編寫者有意義的權(quán)能如下:

CAP_DAC_OVERRIDE /*越過在文件和目錄上的訪問限制(數(shù)據(jù)訪問控制或 DAC)的能力。*/
CAP_NET_ADMIN
/*進(jìn)行網(wǎng)絡(luò)管理任務(wù)的能力, 包括那些能夠影響網(wǎng)絡(luò)接口的任務(wù)*/

CAP_SYS_MODULE
/*加載或去除內(nèi)核模塊的能力*/

CAP_SYS_RAWIO
/*進(jìn)行 "raw"(裸)I/O 操作的能力. 例子包括存取設(shè)備端口或者直接和 USB 設(shè)備通訊*/

CAP_SYS_ADMIN
/*截獲的能力, 提供對(duì)許多系統(tǒng)管理操作的途徑*/

CAP_SYS_TTY_CONFIG
/*執(zhí)行 tty 配置任務(wù)的能力*/

在進(jìn)行一個(gè)特權(quán)操作之前, 一個(gè)設(shè)備驅(qū)動(dòng)應(yīng)當(dāng)檢查調(diào)用進(jìn)程有合適的能力,檢查是通過 capable 函數(shù)來進(jìn)行的(定義在 <linux/sched.h> )范例如下:

if (! capable (CAP_SYS_ADMIN))
 
return -EPERM;

 

二、阻塞與非阻塞IO

阻塞的引入:

在實(shí)現(xiàn)read write 方法上,會(huì)出現(xiàn)這樣的情況:

一個(gè)驅(qū)動(dòng)當(dāng)它無法立刻滿足請(qǐng)求應(yīng)當(dāng)如何響應(yīng)? 一個(gè)對(duì) read 的調(diào)用可能當(dāng)沒有數(shù)據(jù)時(shí)到來, 而以后會(huì)期待更多的數(shù)據(jù). 或者一個(gè)進(jìn)程可能試圖寫, 但 是你的設(shè)備沒有準(zhǔn)備好接受數(shù)據(jù), 因?yàn)槟愕妮敵鼍彌_滿了. 調(diào)用進(jìn)程往往不關(guān)心這種問題; 程序員只希望調(diào)用 read write 并且使調(diào)用返回, 在必要的工作已完成后. 這 樣, 在這樣的情形中, 你 的驅(qū)動(dòng)應(yīng)當(dāng)(缺省地)阻塞進(jìn) 程, 使它進(jìn)入睡眠直到請(qǐng)求可繼續(xù)。

 

在展示如何使一個(gè)進(jìn)程睡眠并且之后再次喚醒它之前,我們必須首先解釋幾個(gè)概念:

睡眠的介紹

對(duì)于一個(gè)進(jìn)程"睡眠"意味著什么? 當(dāng)一個(gè)進(jìn)程被置為睡眠, 它被標(biāo)識(shí)為處于一個(gè)特殊的狀態(tài)并且 從調(diào)度器的運(yùn)行隊(duì)列中去除. 直到發(fā)生某些事情改變了那個(gè)狀態(tài), 這個(gè)進(jìn)程將不被在任何 CPU 上調(diào)度, 并且, 因此, 將不會(huì)運(yùn)行. 一個(gè)睡著的進(jìn)程已被擱置到系統(tǒng)的一邊, 等待以后發(fā)生事件.

對(duì) 于一個(gè) Linux 驅(qū)動(dòng)使一個(gè)進(jìn)程睡眠是 一個(gè)容易做的事情. 但是, 有幾個(gè)規(guī)則必須記住以安全的方式編碼睡眠.

1 永遠(yuǎn)不要在原子上下文中進(jìn)入休眠,即當(dāng)驅(qū)動(dòng) 在持有一個(gè)自旋鎖、seqlock或者 RCU 鎖時(shí)不能睡眠;關(guān)閉中斷也不能睡眠。持 有一個(gè)信號(hào)量時(shí)休眠是合法的,但你應(yīng)當(dāng)仔細(xì)查看代碼:如果代碼在持有一個(gè)信號(hào)量時(shí)睡眠,任何其他的等待這個(gè)信號(hào)量的線程也會(huì)休眠。因此發(fā)生在持有信號(hào)量時(shí) 的休眠必須短暫,而且決不能阻塞那個(gè)將最終喚醒你的進(jìn)程。

2)當(dāng)進(jìn)程被喚醒,它并不知道休眠了多長時(shí)間以及休眠時(shí)發(fā)生什么;也不知道是否另有進(jìn)程也在休眠等待同 一事件,且那個(gè)進(jìn)程可能在它之前醒來并獲取了所等待的資源。所以不能對(duì)喚醒后的系統(tǒng)狀態(tài)做任何的假設(shè), 并必須重新檢查等待條件來確保正確的響應(yīng)。

除非確信其他進(jìn)程會(huì)在其他地 方喚醒休眠的進(jìn)程,否則也不能睡眠。使進(jìn)程可被找到意味著:需要維護(hù)一個(gè)稱為等待隊(duì)列的數(shù)據(jù)結(jié)構(gòu)。它是一個(gè)進(jìn)程鏈表,其中飽含了等待某個(gè)特定事件的所有進(jìn) 程。在 Linux 中, 一個(gè)等待隊(duì)列由一個(gè)wait_queue_head_t 結(jié)構(gòu)體來管理,其定義在<linux/wait.h>中。wait_queue_head_t 類型的數(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è)鏈表是一個(gè)等待隊(duì)列入口,它被聲明做 wait_queue_t。wait_queue_head_t包含關(guān)于睡眠進(jìn)程的信息和它想怎樣被喚醒。

 簡單睡眠

當(dāng)一個(gè)進(jìn)程睡眠, 它這樣做以期望某些條件在以后會(huì) 成真. 如我們之前注意到的, 任何睡眠的進(jìn)程必須在它再次醒來時(shí)檢查來確保它在等待的條件真 正為真. Linux 內(nèi)核中睡眠的最簡單方式是一個(gè)宏定義, 稱為 wait_event(有幾個(gè)變 體); 它結(jié)合了處理睡眠的細(xì)節(jié)和進(jìn)程在等待的條件的檢查. wait_event 的形式是:

wait_event(queue, condition)
wait_event_interruptible(queue, condition)
wait_event_timeout(queue, condition, timeout)
wait_event_interruptible_timeout(queue, condition, timeout)

在所有上面的形式中, queue 是要用的等待隊(duì)列頭. 注意它是"通過值"傳遞的. 條件是一個(gè)被這個(gè)宏在睡眠前后所 求值的任意的布爾表達(dá)式; 直到條件求值為真值, 進(jìn)程繼續(xù)睡眠. 注意條件可能被任意次地求值, 因此它不應(yīng)當(dāng)有任何邊界效應(yīng).

如果你使用 wait_event, 你的進(jìn) 程被置為不可中斷地睡眠, 如同我們之前已經(jīng)提到的, 它常常不是你所要的. 首選的 選擇是 wait_event_interruptible, 它可能被信號(hào)中斷. 這個(gè)版本返回一個(gè)你應(yīng)當(dāng)檢查的整 數(shù)值; 一個(gè)非零值意味著你的睡眠被某些信號(hào)打斷, 并且你的驅(qū)動(dòng)可能應(yīng)當(dāng)返回 -ERESTARTSYS. 最 后的版本(wait_event_timeout wait_event_interruptible_timeout)等待一段有限的時(shí)間; 在這個(gè)時(shí)間期間(以嘀噠數(shù)表達(dá)的, 我們將在第 7 章討論)超時(shí)后, 這個(gè)宏返回一個(gè) 0 值而不管條件是如何求值的.

圖片的另一半, 當(dāng)然, 是喚醒. 一些其他的執(zhí)行線程(一個(gè)不同的進(jìn)程, 或者一個(gè)中斷處理, 也許)必須為你進(jìn)行喚醒, 因?yàn)槟愕倪M(jìn)程, 當(dāng)然, 是在睡眠. 基本的喚醒睡眠進(jìn)程的函數(shù)稱為 wake_up. 它有幾個(gè)形式(但是我們現(xiàn)在只看其中 2 個(gè)):

void wake_up(wait_queue_head_t *queue);
void wake_up_interruptible(wait_queue_head_t *queue);

wake_up 喚醒所有的在給定隊(duì)列上等待的進(jìn)程(盡管這個(gè)情形比那個(gè)要復(fù)雜一些, 如同我們之后將見到的). 其他的形式(wake_up_interruptible)限制它自己到處理一個(gè)可中斷的睡眠. 通常, 2 個(gè)是不用區(qū)分的(如果你使用可中斷的睡眠); 實(shí)際上, 慣例是使用 wake_up 如果你在使用 wait_event , wake_up_interruptible 如果你在使用 wait_event_interruptible.

阻塞和非阻塞的定義:

阻塞:阻塞操作是指在執(zhí) 行設(shè)備操作時(shí)若不能獲得資源則掛起進(jìn)程,直到滿足可操作的條件后再進(jìn)行操作,被掛起的進(jìn)程進(jìn)入休眠狀態(tài),被從調(diào)度器的運(yùn)行隊(duì)列移走,直到等待的條件被滿 足。

非阻塞

非阻塞操作的進(jìn)程在不能進(jìn)行設(shè)備操作時(shí)并不掛 起,它或者放棄,或者不停的查詢,直至可以進(jìn)行操作為止。

 

阻塞從字面上聽起來似乎意味著效率低下,實(shí)則不然,如果設(shè)備驅(qū)動(dòng)不阻塞,則用戶想獲取設(shè)備資源只能不停的查詢,這反而會(huì)無謂的耗費(fèi)CPU資源,而阻塞訪問時(shí),不能獲取資源的進(jìn)程將進(jìn)入休眠,它將CPU資源讓給其它進(jìn)程。

因?yàn)樽枞倪M(jìn)程會(huì)進(jìn)入休眠狀態(tài),因此,必須確保有一個(gè)地方能夠喚醒休眠的進(jìn)程,喚醒進(jìn)程最大的地方最 大可能發(fā)生在中斷里面,因?yàn)橛布Y源獲得的同時(shí)往往伴隨著一個(gè)中斷。

 

linux驅(qū) 動(dòng)中,可以用等待隊(duì)列(wait queue)來實(shí)現(xiàn)阻塞進(jìn)程的喚醒,等待隊(duì)列可以用來同步對(duì)系統(tǒng)資源的訪問,之 前的講的信號(hào)量在內(nèi)核中也依賴等待隊(duì)列來實(shí)現(xiàn)。

全功能的 read write 方法涉及到進(jìn)程可以決定是進(jìn)行非阻塞 I/O還 是阻塞 I/O操作。明確的非阻塞 I/O filp->f_flags 中的 O_NONBLOCK 標(biāo)志來指示(定義再 <linux/fcntl.h> ,被 <linux/fs.h>自動(dòng)包含)。瀏覽源碼,會(huì)發(fā)現(xiàn)O_NONBLOCK 的另一個(gè)名字:O_NDELAY ,這是為了兼容 System V 代碼。O_NONBLOCK 標(biāo)志缺省地被清除,因?yàn)榈却龜?shù)據(jù)的進(jìn)程的正常行為只是睡眠.

其實(shí)不一定只有read write 方法有阻塞操作,open也可以有阻塞操作。

講到阻塞必須要詳細(xì)說到等待隊(duì)列的概念和各種操作,有關(guān)內(nèi)容可以參考linux內(nèi)核里《Linux內(nèi) 核wait_queue深入分析》《Linux內(nèi)核的等待隊(duì)列》兩篇文章。

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多