device driver - chapter 6字元驅動程式的進階作業

33
Chapter 6 字字字字字字字字字字字 Speaker 字字字 Adviser 字字字 字字 Date 2007/1/29

Upload: zongying-lyu

Post on 13-Feb-2017

444 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Device Driver - Chapter 6字元驅動程式的進階作業

Chapter 6 字元驅動程式的進階作業Speaker :呂宗螢Adviser :梁文耀 老師Date : 2007/1/29

Page 2: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

ioctl

驅動程式需提供控制硬體的各種能力,例如格式化軟碟、鎖住機門、退片、狀態回報、改變通訊模式或傳輸速率..等。就是利用 ioctl 來實現int ioctl(int fd, unsigned long cmd, …);int (*ioctl) (struct inode *inode, struct file *filp,

unsigned int cmd, unsigned long arg); cmd : user-space 要求執行的 ioctl 指令,等於

ioctl() 第二引數,用 switch 實做 如 ioctl() 系統呼叫有第三個引數,則可自 arg 取得該引數

Page 3: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

選擇 ioctl 指令編號 (1)

cmd 與各指令之間的對應關係,需獨一無二,以免正確指令下達到錯誤裝置 查閱 include/asm/ioctl.h 和 Documentation/ioctl-number.txt 選出沒有使用的魔數 #include <linux/ioctl.h>

type• 魔數 (magic number)• 長度為 _IOC_TYPEBITS(8 bit)

number• 流水號 (ordinal number) 或稱序號 (sequential number)• 長度為 _IOC_NRBITS(8 bit)

direction• 傳輸方向• _IOC_NONE( 不傳輸資料 ) 、 _IOC_READ( 資料從裝置讀

出 ) 、 IOC_WRITE( 資料流入裝置 ) 、 _IOC_READ | _IOC_WRITE 、( 雙向 )

• 以應用程式觀點來看 size

• 資料量• 長度為 _IOC_SIZEBITS(13 of 14 bit)

Page 4: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

選擇 ioctl 指令編號 (2)

#include <linux/ioctl.h>(<asm/ioctl.h>)編制指令編號的巨集:

_IO( type, number) :沒有引數指令 _IOR( type, number, datatype) :從驅動程式讀出資料 _IOW( type, number, datatype) :傳輸資料到驅動程式 _IOWR( type, number, datatype) :雙向傳輸

解碼巨集:_IOC_DIR(nr) 、 _IOC_TYPE(nr) 、 _IOC_NR(nr) 、 _IOC_SIZE(nr)

整數引數傳遞方式有:透過指標,直接給明確數值按照 ioctl() 的慣例,應用指標來交換數值

Page 5: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

選擇 ioctl 指令編號 (3)

/* 使用” k” 為魔數,你的驅動程式應該另選一個不同的 8-bits 數值 */#define SCULL_IOC_MAGIC 'k'#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)/* * S 代表 “ Set” ( 設定 ) ,需要一個指標 * T 代表 “ Tell” ( 通知 ) ,直接使用引數值 * G 代表 “ Get“ ( 取得 ) ,以指向查詢結果的一個指標回覆 * Q 代表 “ Query“ ( 查詢 ) ,以回傳值答覆查詢結果 * X 代表 “ eXchange” ( 交換 ) ,連續執行 G 和 S * H 代表 “ sHift” ( 移位 ) ,連續執行 T 和 Q */#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC, 3)#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC, 4)#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC, 5, int)#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC, 6, int)#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC, 7)#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC, 8)#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC, 9, int)#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10, int)#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC, 11)#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC, 12)

#define SCULL_IOC_MAXNR 14

Page 6: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

ioctl 的回傳值如果 cmd 不符合任何命令編號,則 default 應做?

許多核心合適採取的行為是回傳 -EINVAL(Invalid Argument)

但, POSIX 標準規定回傳 -ENOTTY(Not a typewriter) , C 函式庫的解釋為 Inappropriate ioctl for device

常用為 -EINVAL

Page 7: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

預先定義 ioctl 指令 (1)

雖然 ioctl( ) 系統呼叫的主要作用對象是硬體裝置,但是核心本身仍能辨認少數幾個命令 ( 預設指令 ) 。因此,當你挑選的 ioctl 指令編號剛好與預定指令相同,則你寫出來的 ioctl 作業方法將永遠收不到該指令,而應用程式也會因為發出衝突的 ioctl 指令而遭遇到意外。預設指令分為三大類:

可作用於任何檔案 ( 正常檔 , 裝置檔 , FTFO 或socket)

只對正常檔案有作用 只能用於特定檔案系統類型

Page 8: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

預先定義 ioctl 指令 (2)

以下是核心預先定義的 ioctl 指令,可作用於任何檔案: FIOCLEX

• 設立 close-on-exec 旗標 (File IOctl Close on Exec) 。當行程以exec() 系統呼叫執行另一個程式時,曾被該行程設立本旗標的檔案會被自動關閉

FIONCLEX• 撤銷 close-on-exec 旗標

FIOASYNC• 設立或撤銷檔案的“臨時通知” (asynchronous notification)

FIONBIO :• ”File IOctl Nonblock I/O” 此指令會修改 filp->f_flags裡的

O_NONBBOCK 旗標• 發出此指令的行程,必須在 ioctl( ) 的第三引數註明它到底想要「設立」或「撤銷」的動作• O_NONBBOCK 通常透過 fcntl() 系統呼叫的 F_SETFL 指令來改變

Page 9: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

ioctl 的額外引數之用法 (1)

在開始研究 scull 如何實作 ioctl 作業方法之前,有必要先搞清楚如何使用它的額外引數 ( 第三引數 ) 。 若該引數為整數時,那就直接拿來用。 如果是指標,就必須多費點心。 若指標指向 user-space 的位址,必須先確定該位址是有效的

int access_ok(int type, const void *addr, unsigned long size) #include <asm/uaccess.h>

• type :必須是 VERIFY_READ 或 VERIFY_WRITE(包含雙向 ) 的其中之一,取決於想對 user-space 進行的動作是讀入或寫出• addr :是被檢查的 user-space 位址• size :是檢查範圍 ( 以 byte 為計算單位 )• 回傳值: 1 代表成功 ( 可存取 ) , 0 代表失敗 ( 不能存取 ) ,如為失敗

ioctl 通常回傳 -EFAULT 它並非徹底檢驗指定範圍內的每一個位址,而是確認受檢位址是否在行程的合理存取範圍 ( 不會侵犯到 kernel-space) 大部份的驅動程式並不需要刻意呼叫 access_ok( )(記憶體存取程序會幫忙處理 )

Page 10: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

ioctl 的額外引數之用法 (2)

int scull_ioctl(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg){int err = 0, tmp;int retval = 0;

/* 分離出 type 和 number 位元欄位,如果遇到錯誤的 cmd ,直接傳回 ENOTTY*/ if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;

if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY; /* direction 是一個位元遮罩 , 而 VERIFY_WRITE 代表雙向傳輸 (R/W) * type 是從 user-space 來看 * access_ok卻是從 kernel 來看 * 所以“ read” 和“ write” 剛好相反 */ if (_IOC_DIR(cmd) & _IOC_READ) err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd)); else if (_IOC_DIR(cmd) & _IOC_WRITE) err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd)); if (err) return -EFAULT;}

Page 11: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

ioctl 的額外引數之用法 (3)

除了使用 copy_from_user 以及 copy_to_user 函式之外, <asm/uaccess.h>還提供了一組針對常用資料規格而設計的傳輸工具: put_user(datum, ptr); __put_user(datum, ptr);

將 datum 寫到 ptr所指的 user-space put_user() 會確認行程是否有資格寫入指定的記憶位址

( 會使用 access_ok()) ,傳輸成功回傳 0 ,失敗則 -EFAULT

__put_user()所作檢查較少 ( 不會呼叫 access_ok()) ,但所指位址是使用者無權寫入,會回傳 -EFAULT get_user(local, ptr); __get_user(local, ptr);

從 ptr所指的 user-space 位址取得單一資料項,並將得所得資料存放在 local

Page 12: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

機能管制 (1)

使用者能否存取裝置,需借助作業系統的權限控管機制 (限制對象為人 )對於限制對象是操作項目而言,驅動程式自己必須作一些額外檢查,判斷使用者是否有權操作要求機能機能 (capabilities) ,不在只分成「特權」與「非特權」,而細分成更多類細目:

可將某種開放給某特定程式 ( 或使用者 ) ,而不必將無關的其他權力也一併交出 核心只將機能用於權限管理,並釋出兩個 linux 特有的系統呼叫, capget() 與 capset()

Page 13: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

機能管制 (2)

#include <linux/capability.h> 機能分類:

CAP_DAC_OVERRIDE :改變檔案或目錄之存取權限的能力 (Data Access Control)

CAP_NET_ADMIN :執行網路控管工作的能力 (包括會影響網路介面的動作 ) CAP_SYS_MODULE :將模組載入,移出核心的能力 CAP_SYS_RAWIO :執行「原始 I/O 」 (raw I/O) 作業能力

• ex:存取裝置的 I/O port ,直接與 USB 裝置通訊 CAP_SYS_ADMIN :一種無所不能的能力,提供系統管理作業所需的一切存取能力 ( 給予 administrator 能力 ) CAP_SYS_TTY_CONFIG :設定 tty組態的能力

驅動程式應先檢查系統呼叫的行程是否有足夠的權限。 #include <sys/sched.h> int capable (int capability); ex

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

Page 14: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

實作 ioctl 指令範例 (1)

switch(cmd) {case SCULL_IOCRESET:

scull_quantum = SCULL_QUANTUM;scull_qset = SCULL_QSET;break;

case SCULL_IOCSQUANTUM: /* Set: arg points to the value */if (! capable (CAP_SYS_ADMIN))

return -EPERM;retval = __get_user(scull_quantum, (int __user *)arg);break;

case SCULL_IOCTQUANTUM: /* Tell: arg is the value */if (! capable (CAP_SYS_ADMIN))

return -EPERM;scull_quantum = arg;break;

case SCULL_IOCGQUANTUM: /* Get: arg is pointer to result */retval = __put_user(scull_quantum, (int __user *)arg);break;

case SCULL_IOCQQUANTUM: /* Query: return it (it's positive) */return scull_quantum;

Page 15: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

實作 ioctl 指令範例 (2)

case SCULL_IOCXQUANTUM: /* eXchange: use arg as pointer */if (! capable (CAP_SYS_ADMIN))

return -EPERM;tmp = scull_quantum;retval = __get_user(scull_quantum, (int __user *)arg);if (retval = = 0)

retval = __put_user(tmp, (int __user *)arg);break;

case SCULL_IOCHQUANTUM: /* sHift: like Tell + Query */if (! capable (CAP_SYS_ADMIN))

return -EPERM;tmp = scull_quantum;scull_quantum = arg;return tmp;

default: /* redundant, as cmd was checked against MAXNR */return -ENOTTY;

}return retval;

Page 16: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

實作 ioctl 指令範例 (3)

由 user-space 觀點來看int quantum;ioctl(fd,SCULL_IOCSQUANTUM, &quantum); /* Set by pointer */ioctl(fd,SCULL_IOCTQUANTUM, quantum); /* Set by value */ioctl(fd,SCULL_IOCGQUANTUM, &quantum); /* Get by pointer */quantum = ioctl(fd,SCULL_IOCQQUANTUM); /* Get by return value */ioctl(fd,SCULL_IOCXQUANTUM, &quantum); /* Exchange by pointer */quantum = ioctl(fd,SCULL_IOCHQUANTUM, quantum); /* Exchange by

value */

Page 17: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

除了 ioctl 之外的裝置控制法指令導向式控制法 (command-oriented)

優點:簡便,使用者只要寫入特殊資料 ( 指令 ) 就能控制裝置,而不需要使用額外的工具程式 缺點:在操作法則 (policy)上有所限制,才能避免將一般資料當成控制命令來處理

對於不會傳輸資料,只會對命令作出回應的裝置( 例如 : 機械手臂 ) ,指令導向式控制法就肯定是最理想的選擇

如果目標裝置適合使用指令導向式的控制法,驅動程式自然不必提供 ioctl ,而是一段能解讀控制指令的程式 (interpreter)

Page 18: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

Blocking I/O

read 被觸發時,還沒有資料可提供,但是即將有更多資料到齊 write 被觸發時,目標裝置還沒準備好接受資料,因為暫存區己滿 可用 block 的方式,使其進入休眠,直到滿足作業為止 休眠 (sleep) :休眠狀態的行程,會被移出排程器的運行佇列 (run queue)

在連動環境 (atomic context) 下,絕對不能休眠, 醒來之後,人事全非。你不可能知道自己被 cpu撇下多久,也不知道休眠過程中發生什麼變化 有把握不會一睡不醒。得確定要等待的事件一定會發生,或至少某人會在某處喚醒你

待命佇列 (wait queue) ,一系列的休眠行程,由待命佇列首項 (wait queue head, wqh) 來管理 #include <linux/wait.h> 編釋期的靜態方式

DECLARE_WAIT_QUEUE_HEAD(name); 執行程的動態配置方式

wait_queue_head_t name;Init_waitqueue_head(&name);

Page 19: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

簡易休眠 wait_event(queue, condition)( 不可中斷 ) wait_event_interruptible(queue, condition)( 可中斷 )

queue 是所要使用的待命佇列頭 (pass by value) condition 是休眠條件,可以是任何布林結果運算,條件成立則結束休眠;會在休眠之前與之後各被執行一次 wait_event_interruptible 會回傳值,若該值不為 0 表示有某種中斷,則驅動程式或許應該回傳 -ERESTARTSYS

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

會等待一段有限的時間,不管 condition 的計算結果如何,傳回值固定是 0 Timeout 是以 jiffies 為單位 (開機至今,計時器中斷的次數 )

void wake_up(wait_queue_head_t *queue); 喚醒在 queue 的所有行程

void wake_up_interruptible(wait_queue_head_t *queue); 只喚醒當初願意因中斷事件而甦醒的行程

Page 20: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

簡易休眠範例static DECLARE_WAIT_QUEUE_HEAD(wq);static int flag = 0;ssize_t sleepy_read (struct file *filp, char __user *buf, size_t count, loff_t *pos){

printk(KERN_DEBUG “process %i (%s) going to sleep\n”, current->pid, current->comm);

wait_event_interruptible(wq, flag != 0);flag = 0;printk(KERN_DEBUG "awoken %i (%s)\n", current->pid, current->comm);return 0; /* EOF */

}ssize_t sleepy_write (struct file *filp, const char __user *buf, size_t count,loff_t *pos){

printk(KERN_DEBUG "process %i (%s) awakening the readers...\n",current->pid, current->comm);

flag = 1;wake_up_interruptible(&wq);return count; /* succeed, to avoid retrial */

}此程式可能有相競現象

Page 21: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

遲滯 (blocking) 作業模式 (1)

檢查 filp->f_flags 的 O_NONBLOCK 旗標 定義在 <linux/fs> 引入的 <linux/fcntl.h> 可藉由此旗標來表達是否同意休眠的意願 O_NDELAY 同義 O_NONBLOCK 預設值為 0( 代表 blocking)

read 作業方法 當輸入緩衝區空了,還沒有資料可提供給 user-space ,必須讓行程休眠 當資料到達,必須立刻喚醒行程,將資料傳到 user-space ,資料量可少於行程要求的量

write 作業方法 當輸出緩衝區己沒有空間了,必須讓行程休眠,但不能與

read 同一個待命佇列 當輸出緩衝區挪出空間,必須立刻喚醒行程,將 user-space的資料傳輸到輸出緩衝區,傳輸資料量可少於要求的量

Page 22: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

遲滯 (blocking) 作業模式 (2)

驅動程式自備 I/O緩衝區輸入緩衝區

用來暫存來自硬體的資料,其作用讓行程不必等待就可取走資料 也避免漏失了硬體進來的資料

輸出緩衝區 較不重要,因為 write 不會丟失資料 但其效率增益比輸入緩衝區大,可以大幅減少

context switch 和 user-level / kernel-level 過渡

Page 23: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

非遲滯 (non-blocking) 作業模式主要讓應用程式可以輪詢 (poll) 資料O_NONBLOCK 被設立時, read() 無法即時提供資料 ( 輸入緩衝區空 ) 和 write 輸出動作會造成休眠

( 輸出緩衝區滿 ) ,則須回傳 -EAGAIN(try again)只有 read 、 write 、 open 會被影響

Page 24: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

輪詢作業 (poll 與 select)(1)

會使用 non-blocking I/O 的應用程式,通常會使用到 select() 、 poll() 、epoll() 系統呼叫。 同樣都是判斷下次對於特定檔案的 I/O 作業會不會 blocking 也可以阻檔一個行程,直到給定一組 FD成為可供讀寫的狀態為止

呼叫一或多次 poll_wait() ,將一或多個可能改變輪詢狀態的待命佇列放入輪詢表 (poll table) 。若當時還沒有可供 I/O 的 FD ,核心會使行程休眠於待命佇列,等待所有 FD都傳送給系統呼叫 傳回一個位元遮罩,描述哪些操作項目可立即執行而不會 blocking 輪詢狀態資訊只有驅動程式自己知道,核心無法得知 unsigned int (*poll) (struct file *filp, poll_table *wait);

wait :指向輪詢表 (poll table) 指標 ( 定義於 <linux/poll.h>) ,可以不必了解 poll_table 結構,只為配合 poll_wait 而需要 void poll_wait (struct file *filp, wait_queue_head_t *sync, poll_table

*pt); 讓驅動程式將每一個會喚醒行程 ( 或改變 poll 作業狀態 ) 的待命佇列項目 (sync)填入 pt所指的輪詢表

Page 25: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

輪詢作業 (poll 與 select)(2)

#incldude <linux/poll.h> 定義一系列旗標來建立狀態遮罩: POLLIN :裝置可被 read 而不 blocking POLLRENORM :平常資料己準備好可被讀取。可被正常讀取的裝置,應傳回 (POLLIN | POLLRDNORM) POLLPRI :可讀出高度優先度資料 (緊急資料 ) 而不必

blocking POLLHUP :每當讀取裝置的行程見到 EOF ,驅動程式就必須設立 POLLHUP 位元 (hang-up) POLLER :裝置發生錯誤狀況 POLLOUT :若裝置可被逕行寫入而不 blocking ,則應設此 POLLWRNORM :和 POLLOUT 意義相同。可寫入的裝置應該傳回 (POLLOUT | POLLWRNORM) POLLRDBAND :代表可從裝置讀出緊急 (out-of-band) 資料 POLLWRBAND :如同 POLLRDBAND ,代表優先度不為 0的資料可被寫入裝置

POLLRDBAND 和 POLLWRBAND 只對 socket 有意義

Page 26: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

輪詢作業 (poll 與 select)(3)

static unsigned int scull_p_poll(struct file *filp, poll_table *wait){

struct scull_pipe *dev = filp->private_data;unsigned int mask = 0;/*緩衝區是環狀的若 wp 與 rp 指向同一個位置,表示緩衝區是空的 */down(&dev->sem);poll_wait(filp, &dev->inq, wait);poll_wait(filp, &dev->outq, wait);if (dev->rp != dev->wp)

mask |= POLLIN | POLLRDNORM; /* readable */if (spacefree(dev))

mask |= POLLOUT | POLLWRNORM; /* writable */up(&dev->sem);return mask;

}

Page 27: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

讀、寫、輪詢的作業準則 從裝置讀出資料

如果資料己在輸入緩衝區裡, read 應該在感覺不出延遲的時間內,傳回資料。即使不能滿足應用程式要求的量或是即將有資料到達。 poll 應回傳 POLLIN | POLLRDNORM 輸入緩衝區為空的, read 的預設行為應該 blocking ,直到至少傳來

1byte 的資料為止。如設立了 O_NONBLOCK ,則應該立即 return –EAGAIN 。 poll 應回傳不可讀狀態 ( 將 POLLIN 與 POLLRDNORM歸零 )

如遇 EOF ,必須立即回傳 0 。 poll 應該回傳 POLLHUP 將資料寫入裝置

如果輸出緩衝區有可用空間, write 立刻完成 return ,毫無延遲。即使不能滿足應用程式要求的量。 poll 應回傳 POLLOUT | POLLWRNORM

輸出緩衝區為滿的, write 的預設行為應該 blocking ,直到緩衝區有空位為止。如設立了 O_NONBLOCK ,則應該立即 return –EAGAIN 。poll 應回傳不可寫狀態

即使在 blocking 作業模式下,也絕不可讓 write() 因為等待資料傳輸而可返回 user-space 。如果應用程式希望能確定排入輸出緩衝區的資料,己經全數寫到硬體裝置上,驅動程式應提供 fsync 作業方法

Page 28: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

出清延宕資料fsync 系統呼叫 return 時,就可認定先前用 write()寫出的資料己經全數出清 (flush) 到裝置上int (*fsync) (struct file *file, struct dentry *dentry,

int datasync); datasync :用於區別 fsync() 或 fdatasync() 系統呼叫

Page 29: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

臨時通知 (asynchronous notification)

可在輸入檔 (包括裝置檔 ) 有異動時,對應用程式發出SIGIO信號

要獲得臨時通知的程式1. 讓自己成為檔案的擁有者;可用 fcntl() 系統呼叫的

F_SETOWN 來完成,該指令會將行程的 PID存入 filp->f_owner

2. 啟動臨時通知:使用 fcntl() 系統呼叫發出 F_SETFL 指令,設定裝置的 FASYNC 指標 收到信號的行程,不知道信號的來源

signal(SIGIO, &input_handler); /* 簡單示範。 sigaction( ) 會更好 */fcntl(STDIN_FILENO, F_SETOWN, getpid( ));oflags = fcntl(STDIN_FILENO, F_GETFL);fcntl(STDIN_FILENO, F_SETFL, oflags | FASYNC);

Page 30: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

驅動程式對「臨時通知」的支援 (1)

從核心觀點來看 收到 fcntl() 系統呼叫的 F_SETOWN 指令時,將目前行程的 PID 設定給

filp->f_owner 收到 fcntl() 系統呼叫的 F_SETFL 指令來打開 filp->f_flags 的 FASYNC 旗標時,則呼叫 fasync 作業方法。每當 filp->f_flags 的 FASYNC 旗標出現變化時,核心就會觸發一次 fasync ,該驅程程式作出適當回應。以預設模式開啟檔案時, FASYNC 應為 0 當資料到達,必須送出 SIGIO信號給曾登記要求收到臨時通知的所有行程

Linux 實作通知機制主要由 struct fasync_struct 結構與兩個函式構成 #include <linux/fs.h> int fasync_helper(int fd, struct file *filp, int mode, struct fasync_struct **fa);

• 驅動程式發覺己開啟的裝置檔的 FASYNC 旗標出現變化,就可使用fasync_helper() 將行程移出或編入通知名單

void kill_fasync(struct fasync_struct **fa, int sig, int band);• 收到裝置傳來的資料時,則可用 kill_fasync() ,將信號送給通知名單的中的行程• sig :被送出的信號值 (SIGIO)• band :緊急度 (POLL_IN)

Page 31: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

驅動程式對「臨時通知」的支援 (2)

fasync作業方法static int scull_p_fasync(int fd, struct file *filp, int mode){

struct scull_pipe *dev = filp->private_data;return fasync_helper(fd, filp, mode, &dev->async_queue);

}當有新資料寫入時,送出 SIGIO信號給等待通知的所有行程

if (dev->async_queue)kill_fasync(&dev->async_queue, SIGIO, POLL_IN);

將這個 filp移出臨時通知名單scull_p_fasync(-1, filp, 0);

Page 32: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

定位作業發生 lseek() 或 llseek() 系統呼叫時,都會觸動驅動程式的 lseek 作業方法。如定位作業涉及實際硬體的動作,則必須提

供 lseek 作業方法對於只有串流的字元裝置 ( 序列埠、鍵盤 ) ,定位沒有意義,但不宣告 llseek 作業方法是沒用的。應呼叫 nonseekable_open() 該核心知道你的裝置不支援 llseek 作業方法 int nonseekable_open(struct inode *inode; struct file *filp);

會將指定的 filp 標示為無法定位 當核心收到 lseek() 或 llseek() 系統呼叫時,會回覆一個錯誤代碼

為完整起見,將 llseek 函式指向 no_llseek()( 定義於<linux/fs.h>)

Page 33: Device Driver - Chapter 6字元驅動程式的進階作業

嵌入式及平行系統實驗室

定位作業範例loff_t scull_llseek(struct file *filp, loff_t off, int whence){

struct scull_dev *dev = filp->private_data;loff_t newpos;switch(whence) {

case 0: /* SEEK_SET */newpos = off;break;

case 1: /* SEEK_CUR */newpos = filp->f_pos + off;break;

case 2: /* SEEK_END */newpos = dev->size + off;break;

default: /* can't happen */return -EINVAL;

}if (newpos < 0) return -EINVAL;filp->f_pos = newpos;return newpos;

}