天天看點

LDD3 D10 補

  • 10:32-11:40 20p

    第6章 進階字元驅動操作

    ioctl 接口

    在使用者空間, ioctl 系統調用有下面的原型:

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

    原型中的點并不表示可變參數, 而是一個單個可選的參數, 傳統上辨別為 char *argp. 這些點在那裡隻是為了阻止在編譯時的類型檢查.

    第三個參數的類型,取決于第二個參數-指令。有的指令,不需要額外參數,有的需要整形,有的指令需要指針作為參數等等。

    【程式設計技巧】

    在核心空間,ioctl 驅動方法的原型:

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

    使用者空間的可選參數無論是整形還是指針,以一個unsigned long的形式傳遞。

    【程式設計技巧】

    選擇ioctl指令

     ioctl 指令數字應當在這個系統是唯一的, 這是為了阻止向錯誤的裝置發出正确的指令而引起的錯誤.

    【如果ioctl 指令數字不是唯一的,假設列印機 列印的指令字是0, 炸彈啟動的指令字也是0. 如果你想列印,結果不小心就炸了。是以每個裝置的指令字數字應該是系統内唯一的。】

    定義 ioctl 指令号的正确方法使用 4 個位段, 它們有下列的含義. 這個清單中介紹的新符号定義在 <linux/ioctl.h>.

    傳回值

    按慣例傳回 -ENOTTY, 因為這個錯誤碼被 C 庫解釋為"裝置的不适當的 ioctl", 這常常正是程式員需要聽到的。但是 -ENIVAL 也常見。

    預定義的指令

    預定義指令分為 3 類:

    •     可對任何檔案發出的(正常, 裝置, FIFO, 或者 socket) 的那些.
    •     隻對正常檔案發出的那些.
    •     對檔案系統類型特殊的那些.

    裝置驅動編寫者隻對第一類指令感興趣, 它們的魔數是 "T".

    使用ioctl參數

    如果它是一個整數, 這很容易: 它可以直接使用.

    如果它是一個指針, 必須小心些.

    當用一個指針引用使用者空間, 我們必須確定使用者位址是有效的. 試圖存取一個沒驗證過的使用者提供的指針可能導緻不正确的行為, 一個核心 oops, 系統崩潰, 或者安全問題. 這是驅動的責任來對每個它使用的使用者空間位址進行正确的檢查, 并且傳回一個錯誤如果它是無效的.

    copy_from_user 和 copy_to_user 函數, 它們可用來安全地移動資料到和從使用者空間. 這些函數也可用在 ioctl 方法中。

    ioctl 調用常常包含小資料項, 更有效地操作有:

    位址校驗(不傳送資料)

    由函數 access_ok 實作, 它定義在 <asm/uaccess.h>:

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

    傳輸資料

    put_user() , get_user()

    相容性和受限制操作

    在進行一個特權操作之前, 一個裝置驅動應當檢查調用程序有合适的能力; 不這樣做可能導緻使用者程序進行非法的操作, 對系統的穩定和安全有壞的後果. 能力檢查是通過 capable 函數來進行的(定義在 <linux/sched.h>):

     int capable(int capability);

     ioctl 指令的實作

    不用 ioctl 的裝置控制

    有時控制裝置最好是通過寫控制序列到裝置自身來實作. 例如, 這個技術用在控制台驅動中, 這裡所謂的 escape 序列被用來移動光标, 改變預設的顔色, 或者進行其他的配置任務. 這樣實作裝置控制的好處是使用者可僅僅通過寫資料控制裝置, 不必使用(或者有時候寫)隻為配置裝置而建立的程式. 當裝置可這樣來控制, 發出指令的程式甚至常常不需要運作在和它要控制的裝置所在的同一個系統上.

    【使用ioctl來控制裝置, 需要使用者自己編寫一個程式來調用 ioctl 接口。而寫控制序列則不用】

    直接裝置控制的好處是你可以使用 cat 來移動錄影機, 而不必寫和編譯特殊的代碼來發出 ioctl 調用.

    阻塞I/O

    一個重要的問題:一個驅動當它無法立刻滿足請求應當如何響應?

    在這樣的情形中, 你的驅動應當(預設地)阻塞程序, 使它進入睡眠直到請求可繼續.

    休眠的介紹

    對于一個程序"休眠"意味着什麼? 當一個程序被置為休眠, 它被辨別為處于一個特殊的狀态并且從排程器的運作隊列中去除. 直到發生某些事情改變了那個狀态, 這個程序将不被在任何 CPU 上排程, 并且, 是以, 将不會運作. 一個睡着的程序已被擱置到系統的一邊, 等待以後發生事件.

    對于一個 Linux 驅動使一個程序休眠是一個容易做的事情. 但是, 有幾個規則必須記住以安全的方式編碼休眠.

    • 當你運作在原子上下文時不能睡眠
    • 持有一個自旋鎖, seqlock, 或者 RCU 鎖時不能睡眠
    • 已關閉中斷你也不能睡眠

    擁有信号量時休眠,是合法的。 如果代碼在持有一個信号量時休眠, 任何其他的等待這個信号量的線程也将休眠. 是以發生在持有信号量時的任何休眠應當短暫, 并且你應當說服自己, 由于持有這個信号量, 你不能阻塞那個将最終喚醒你的程序.

    另一件要記住的事情是, 當你醒來, 你從不知道你的程序離開 CPU 多長時間或者同時已經發生了什麼改變。結果是你不能關于你醒後的系統狀态做任何的假設, 并且你必須檢查來確定你在等待的條件是, 确實, 真的.

    簡單休眠

    Linux 核心中最簡單的休眠方式是, wait_event。

    首選的選擇是 wait_event_interruptible, 它可能被信号中斷

    喚醒,用 wake_up

    阻塞和非阻塞操作

    有時還有調用程序通知你他不想阻塞, 不管它的 I/O 是否繼續. 明确的非阻塞 I/O 由 filp->f_flags 中的 O_NONBLOCK 标志來訓示.

    增加一個輸出緩沖可允許驅動在每個寫調用中接收大的資料塊, 性能上有相應的提高.

    如你可能期望的, 非阻塞操作立刻傳回, 允許這個應用程式輪詢資料. 應用程式當使用 stdio 函數處理非阻塞檔案中, 必須小心, 因為它們容易搞錯一個的非阻塞傳回為 EOF. 它們始終必須檢查 errno.

    一個阻塞 I/O 的例子

    【了解】

    進階休眠

    一個程序如何休眠

    手動睡眠

    互斥等待

    進行互斥等待的程序被一次喚醒一個, 以順序的方式, 并且沒有引起驚群問題. 但核心仍然每次喚醒所有的非互斥等待者.

    喚醒細節

    以前的曆史

    不要再使用:

    void sleep_on(wait_queue_head_t *queue);

    void interruptible_sleep_on(wait_queue_head_t *queue);

    測試 scullpipe 驅動