Chapter 3 字元裝置驅動程式
(char device driver)
Speaker :呂宗螢
Adviser :梁文耀 老師
Date : 2007/1/29
嵌入式及平行系統2
裝置編號
字元裝置的存取,是透過它們在檔案系統裡上的名
稱
 特殊檔 (special file)
• 位於 /dev/ 下,用 ls -l 查看時,第一欄, c 代表字元
裝置, b 代表區塊裝置
 裝置檔 (device file)
 檔案系統樹的節點 (node)
 內定裝置編號列表於 /proc/devices
嵌入式及平行系統3
裝置編號:主編號 & 次編號
 主編號 (major number) :裝置所配合驅動程
式。 ex , /dev/null 與 /dev/zero 都是由 driver 1 控管
 核心容許多個驅動程式共用同一個主編號
 次編號 (minor number) :你的驅動程式所實作的裝置
 在舊版主次編號範圍均為 0~255 , kernel 2.6 則沒有
主 次
crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null
crw------- 1 root root 10, 1 Apr 11 2002 psaux
crw-------   1 root root 4, 1 Oct 28 03:04 tty1
crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0
crw-rw---- 1 root uucp 4, 65 Apr 11 2002 ttyS1
crw--w---- 1 vcsa tty 7, 1 Apr 11 2002 vcs1
crw--w---- 1 vcsa tty 7, 129 Apr 11 2002 vcsa1
crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero
嵌入式及平行系統4
建立裝置編號
裝置編號型別: dev_t
#include <linux/types.h>
總長 32bit 無號數,其中 12bit 主編號, 20bit 次編
號
建立裝置編號
#include <linux/kdev_t.h>
 MAJOR(dev_t dev);
 MINOR(dev_t dev);
• 將 dev_t 的主次編號取出來
 MKDEV(int major, int minor)
• 建立裝置編號
嵌入式及平行系統5
裝置編號的配置與釋放
 #include <linux/fs.h>
 int register_chrdev_region(dev_t first, unsigned int count, char
*name);
 配置裝置編號 ( 自定範圍 )
• first :想配置的裝置編號的起點
• count :申請的連續裝置編號總數
• name :獲得此編號範圍的裝置名稱
• 回傳值 0 成功,負值錯誤代碼
 int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned
int count, char *name)
 動態配置裝置編號
• dev :取得配置範圍的第一個裝置編號
• firstminor :申請的第一個次編號 (0)
 void unregister_chrdev_region(dev_t first, unsigned int count)
 釋放裝置編號
嵌入式及平行系統6
file 結構
#include <linux/fs.h>
定義於 struct file
代表己開啟的檔案 (open file)
open() 時自動建立
close() 時才會釋放 ( 需沒有任何行程持有該 file 指
標 )
ps: 閱讀時, file 是結構, filp(file pointer) 是指向
struct file 的指標
嵌入式及平行系統7
file 結構欄位 (1)
 mode_t f_mode;
• 代表檔案的存取模式。
• 可讀: FMODE_READ ,可寫 FMODE_WRITE ,可讀可寫:
FMODE_READ | FMODE_WRITE
• 驅動程式在 open 或 ioctl 作業方法時會檢查此欄位來確定權限
,故不用在 read 或 write 時做檢查
 loff_t f_pos;
• 目前的讀寫位置。
• 64-bit
• read 和 write 作業方法應該透過引數傳來的指標 (loff_t *) 來更新
讀寫位置,不應直接修改 file->f_pos
 unsigned int f_flags;
• 檔案旗標的組合
• 定義於 <linux/fcntl.h> , ex : O_NONBLOCK 、 O_SYNC 、
O_RDONLY
• ex :只需檢查 O_NONBLOCK ,就知道是否要求進行
nonblocking operation
嵌入式及平行系統8
file 結構欄位 (2)
 struct file_operations *f_op;
 指向一個 file_operations 結構,該結構含有一組函式指標,
指向用在本 file 上的各項作業方法
 open 時,核心自動指向一個 file_operations
 核心不會快取 file->f_op ,意味著驅動程式隨時可抽換己註冊
的作業方法
 void *private_data;
 open() 系統呼叫時先設定為 NULL ,才去呼叫驅動程式的
open 作業方法
 可自由決定是否使用, ex 用來保存需要跨越多次系統呼叫的
狀態資訊
 release 作業方法記得釋放此指標指向的記憶體
 struct dentry *f_dentry;
 file 所屬的目錄項 (directory entry)
 file->f_dentry->d_inode
嵌入式及平行系統9
inode 結構
代表檔案
一個檔案可被開啟很多次,而有很多個代表 FD(file
descriptor) 的 file 結構,但全指向同一個檔案,即
inode 結構
dev_t i_rdev;
 含有實際的裝置編號
struct cdev *i_cdev;
 指向一個 struct cdev ,該結構用來表示字元裝置
unsigned int iminor(struct inode *inode);
unsigned int imajor(struct inode *inode);
 用來取得 inode 的主次編號
嵌入式及平行系統10
File_operations
#include <linux/fs.h>
用 OOP 來比喻 file 是物件
(object) , file_operations 就是用來操作 file 物件
的方法 (method)
__user 字串代表指向一個 user-space 位址,不可
直接取值 (dereference)
嵌入式及平行系統11
File_operations 結構 (1)
struct module *owner
 指向擁有本 file_operations 結構的模組
 THIS_MODULE( 定義於 <linux/module.h>)
loff_t (*llseek) (struct file *, loff_t, int);
 定位作業:改變檔案存取的位置,下次讀寫從新位
置開始
 loff_t :至少 64bit
 回傳值:正值代表新存取位置,負值錯誤,
 此函式指標如指向 NULL ,則會改變檔案存取點的
系統呼叫,都有可能造成 file 中的位置指標改到無
法預期的位置
嵌入式及平行系統12
File_operations 結構 (2)
 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
 讀取作業
 回傳值,正值代表已成功讀取的位元組個數
 若函式指標指向 NULL ,呼叫 read 時,則會回傳 -
EINVAL(Invalid argument)
 ssize_t (*aio_read) (struct kiocb *, char __user *, size_t,
loff_t);
 異步讀取作業
 不一定能在函式返回前傳輸完畢所有要讀取的資料
 若函式指標指向 NULL ,則會改以 read 取代
 ssize_t (*write) (struct file *, const char __user *, size_t
,loff_t *);
 ssize_t (*aio_write) (struct kiocb *, const char __user *,
size_t, loff_t);
 意思同 read 作業,只是變成 write 作業
嵌入式及平行系統13
File_operations 結構 (3)
 int (*readdir) (struct file *, void *, filldir_t);
 讀取檔案系統上的目錄
 unsigned int (*poll) (struct file *, struct poll_table_struct *);
 查詢裝置的 I/O 狀態。
 poll() 、 epoll() 、 select() 系統呼叫都會作業此法;用來查詢某個已開啟的檔案下次讀寫
是否會造成停頓 (block)
 若函式指向 NULL ,則核心會假設裝置永遠都可以順暢讀寫,不會 block
 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
 執行裝置專屬指令( device-specific command) , ex :格式化軟碟
 若應用程式要求驅動程式沒提供的 ioctl 指令,則會回傳 -ENOTTY(No such ioctl for
device)
 int (*mmap) (struct file *, struct vm_area_struct *);
 記憶體映射操作 (memory mapping) ,將裝置上的記憶體映射到行程的位址空間
 若 NULL ,回傳 – ENODEY
 int (*open) (struct inode *, struct file *);
 開啟檔案,並沒強制要求
 NULL 時一樣可開啟檔案,但驅動程式收不到通知
 int (*flush) (struct file *);
 完工作業,當行程關閉它手中的裝置檔 FD 時,會觸動一次 flush 作業方法,而它應該執
行 ( 並等待 ) 任何常未完成的作業
 NULL 時,則會忽略應用程式要求
嵌入式及平行系統14
File_operations 結構 (4)
 int (*release) (struct inode *, struct file *);
 釋放裝置檔
 只在所有分身都被關閉時,最後一次 close() 才會觸動 releapse
 int (*fsync) (struct file *, struct dentry *, int datasync);
 int (*aio_fsync) (struct kiocb *, int datasync);
 出清留滯資料,讓應用程式用來將留滯在記憶體中的資料全數確實寫入裝置
 NULL 時,則 fsync() 系統呼叫,會傳回 -EINVAL
 int (*fasync) (int, struct file *, int);
 異步作業通知,核心發現 FASYNC 旗標有改變時,會呼叫此作業方法來通知驅
動程式
 int (*lock) (struct file *, int, struct file_lock *);
 檔案鎖定
 驅動程式而言,反而幾乎不必要
 ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);
 ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);
 分散式讀取 (scatter read) 與累積式寫出 (gather write)
 應用程式偶而需要一次讀入多個記憶區的資料,或是將資料一次寫到多個記憶
區,就能讓應用程式直接進行多記憶區的資料傳輸,而不必分次傳輸
 若為 NULL ,則會以 read 和 write 來取代 
嵌入式及平行系統15
File_operations 結構 (5)
 ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);
 以最少的抄寫動作,將資料從一個 FD 傳輸到另一個 FD ,也能從
FD 讀出資料給核心
 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
 當核心從 sendfile 獲得一整個記憶頁的資料,便會呼叫目標檔的
sendpage ,將資料寫入該檔案。
 unsigned long (*get_unmapped_area)(struct file *, unsigned long,
unsigned long, unsigned long, unsigned long);
 在行程的位址空間中找出一個適當的位置,用來對映目標裝置上的
記憶區段
 int (*check_flags)(int);
 讓模組可檢查 fcntl(F_SETFL…) 收到旗標
 int (*dir_notify)(struct file *filp, unsigned long arg);
 應用程式使用 fcntl() 索取目錄變更通知,便會呼叫此作業方法。
嵌入式及平行系統16
File_operations 宣告
struct file_operations scull_fops = {
.owner = THIS_MODULE,
.llseek = scull_llseek,
.read = scull_read,
.write = scull_write,
.ioctl = scull_ioctl,
.open = scull_open,
.release = scull_release,
};
嵌入式及平行系統17
註冊字元裝置
 struct cdev ,代表字元裝置
 #include <linux/cdev.h>
 cdev_alloc();
 配置一個 cdev 的結構
struct cdev *my_codv = cdev_alloc();
my_cdev->ops = &my_fops;
my_cdev->owner = THIS_MODULE;
 void cdev_init(struct cdev *dev, struct file_operations *fops);
 如果驅動程式需要裝 cdev 嵌在你自己設計的一個特殊資料結構時使用
 int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
 將 cdev 加入核心
 cdev_add() 返回時,裝置就當場生效,核心隨時有可能呼叫它的作業方法,故
必須完全準備委當才使用
• dev :設定好的 cdev 結構
• num :是第一個裝置編號
• count :裝置編號的總數
 void cdev_del(struct cdev *dev);
 註銷 cdev
嵌入式及平行系統18
註冊字元裝置範例
struct scull_dev {
struct scull_qset *data; /* Pointer to first quantum set */
int quantum; /* the current quantum size */
int qset; /* the current array size */
unsigned long size; /* amount of data stored here */
unsigned int access_key; /* used by sculluid and scullpriv */
struct semaphore sem; /* mutual exclusion semaphore */
struct cdev cdev; /* Char device structure */
};
static void scull_setup_cdev(struct scull_dev *dev, int index)
{
int err, devno = MKDEV(scull_major, scull_minor + index);
cdev_init(&dev->cdev, &scull_fops);
dev->cdev.owner = THIS_MODULE;
dev->cdev.ops = &scull_fops;
err = cdev_add (&dev->cdev, devno, 1);
/* Fail gracefully if need be */
if (err)
printk(KERN_NOTICE "Error %d adding scull%d", err, index);
}
嵌入式及平行系統19
open 作業方法
 檢查裝置特有的錯誤 (ex :沒放光碟片 .. 等 )
 如果目標裝置首次被開啟,則應該進行初始化程序
 更新 f_op 指標
 配置任何要放在 filp->privated_data 的的資料結構
 container_of(pointer, container_type, container_field);( 因為 inode 引數的 i_cdev ,指向
一個 cdev 結構 )
 #include <linux/kernel.h>
 pointer :指向一個名為 container_field
 container_typer :該欄位所屬之型別
 回傳一個指向容器的結構的指標
 int (*open)(struct inode *inode, struct file *filp);
int scull_open(struct inode *inode, struct file *filp)
{
struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
/* now trim to 0 the length of the device if open was write-only */
if ( (filp->f_flags & O_ACCMODE) = = O_WRONLY) {
scull_trim(dev); /* ignore errors */
}
return 0; /* success */
}
嵌入式及平行系統20
release 作業方法
釋放 open 儲存於 filp->private_data 的任何東西
在最後一次關閉時,將目標裝置關機
只有在最後一次 close() 時,才會呼叫 release
int scull_release(struct inode *inode, struct file *filp)
{
return 0;
}
嵌入式及平行系統21
要求記憶體空間
void *kmalloc(size_t size, int flags);
 配置 size 個位元組記憶體
 成功回傳指向該記憶體指標,失敗為 NULL
 flag 引數是描述如何配置記憶體
• GFP_KERNEL :核心記憶體配置。可能休眠
• GFP_USER :配置記憶體給 user-space 行程。可能
休眠 ( 可參閱,第 8 章 p236)
void kfree(void *ptr);
 釋放 kmalloc() 配置的記憶體
嵌入式及平行系統22
read 與 write
 ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t
*offp);
 ssize_t write(struct file *filp, const char __user *buff,size_t count, loff_t
*offp);
 filp : file 結構的指標
 count :是要被傳輸的資料量
 buff :指向一塊 user-space 暫存區
• read 用於存放裝置讀出的資料
• write 含有要被寫入至裝置的資料
 f_pos(long offset type) :使用者正在存取檔案的位置
 回傳值 (signed size type)
 核心不能夠直接取值 (dereference)
 user-space 指標在 kernel 模式下不一樣是有效的
 即使 user-space 與 kernel-space 在同一位址空間,但 user-space
記憶體是換頁式的,發生系統呼叫時,可能導致 page fault ,而這
在 kernel 模式是不充許的
 user-space 指標必定來自某一個 user-space 的應用程式,該程式
可能有錯,甚至是惡意的
read=kernel->user
write=user->kernel
嵌入式及平行系統23
read 與 write(2)
 # include <asm/uaccess.h>
 unsigned long copy_to_user(void __user *to, const void
*from, unsigned long count);
 將資料從 kernel-space -> user-space
 unsigned long copy_from_user(void *to, const void __user
*from, unsigned long count);
 將資料從 user-space -> kernel-space
 這兩個在傳輸時會檢查 user-space 的指標是否有效。如果無
效,則不會傳輸動作。傳輸時才遇到無效位址,則只有部份
資料會完成傳輸
 回傳值 0 為成功完成,正值為尚未完成傳輸的資料量
 如不想檢查 user-space 指標
 __copy_to_user()
 __copy_from_user()
嵌入式及平行系統24
read 作業方法引數
嵌入式及平行系統25
read 作業方法
若回傳值等於當初傳給 read() 系統呼叫的 count 引
數,那表示當初要求傳的資料己經全數傳輸成功
若回傳值大於 0 ,但小於 count ,則表示只順利傳
輸部份資料
若回傳值為 0 ,代表檔案結尾 EOF
若回傳值為 -1 ,則發生某種錯誤
 (<linux/errno.h> 定義各種錯誤代碼 )
嵌入式及平行系統26
Scull 裝置的記憶佈局
嵌入式及平行系統27
read 作業方法範例 (1)
ssize_t scull_read(struct file *filp, char user *buf, size_t count, loff_t *f_pos){
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr; /* the first listitem */
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset; /* how many bytes in the listitem */
int item, s_pos, q_pos, rest;
ssize_t retval = 0;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
if (*f_pos >= dev->size)
goto out;
if (*f_pos + count > dev->size)
count = dev->size - *f_pos;
/* find listitem, qset index, and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
嵌入式及平行系統28
read 作業方法範例 (2)
/* follow the list up to the right position (defined elsewhere) */
dptr = scull_follow(dev, item);
if (dptr = = NULL || !dptr->data || ! dptr->data[s_pos])
goto out; /* don't fill holes */
/* read only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
out:
up(&dev->sem);
return retval;
}
嵌入式及平行系統29
write 作業方法
若回傳值等於 count ,表示要求的資料量己全數寫
入 scull 裝置
若回傳值大於 0 ,但小於 count ,表示只有部份資
料被寫入裝置
若回傳值為0,表示沒寫入任何資料
若回傳值為負,表示發生錯誤
嵌入式及平行系統30
write 作業方法範例 (1)
ssize_t scull_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos){
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum, qset = dev->qset;
int itemsize = quantum * qset;
int item, s_pos, q_pos, rest;
ssize_t retval = -ENOMEM; /* value used in "goto out" statements */
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
/* find listitem, qset index and offset in the quantum */
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum; q_pos = rest % quantum;
/* follow the list up to the right position */
dptr = scull_follow(dev, item);
if (dptr = = NULL)
goto out;
if (!dptr->data) {
dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
if (!dptr->data)
goto out;
memset(dptr->data, 0, qset * sizeof(char *));
}
嵌入式及平行系統31
write 作業方法範例 (2)
if (!dptr->data[s_pos]) {
dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
if (!dptr->data[s_pos])
goto out;
}
/* write only up to the end of this quantum */
if (count > quantum - q_pos)
count = quantum - q_pos;
if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) {
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
/* update the size */
if (dev->size < *f_pos)
dev->size = *f_pos;
out:
up(&dev->sem);
return retval;
}

Device Driver - Chapter 3字元驅動程式

  • 1.
    Chapter 3 字元裝置驅動程式 (chardevice driver) Speaker :呂宗螢 Adviser :梁文耀 老師 Date : 2007/1/29
  • 2.
    嵌入式及平行系統2 裝置編號 字元裝置的存取,是透過它們在檔案系統裡上的名 稱  特殊檔 (specialfile) • 位於 /dev/ 下,用 ls -l 查看時,第一欄, c 代表字元 裝置, b 代表區塊裝置  裝置檔 (device file)  檔案系統樹的節點 (node)  內定裝置編號列表於 /proc/devices
  • 3.
    嵌入式及平行系統3 裝置編號:主編號 & 次編號 主編號 (major number) :裝置所配合驅動程 式。 ex , /dev/null 與 /dev/zero 都是由 driver 1 控管  核心容許多個驅動程式共用同一個主編號  次編號 (minor number) :你的驅動程式所實作的裝置  在舊版主次編號範圍均為 0~255 , kernel 2.6 則沒有 主 次 crw-rw-rw- 1 root root 1, 3 Apr 11 2002 null crw------- 1 root root 10, 1 Apr 11 2002 psaux crw-------   1 root root 4, 1 Oct 28 03:04 tty1 crw-rw-rw- 1 root tty 4, 64 Apr 11 2002 ttys0 crw-rw---- 1 root uucp 4, 65 Apr 11 2002 ttyS1 crw--w---- 1 vcsa tty 7, 1 Apr 11 2002 vcs1 crw--w---- 1 vcsa tty 7, 129 Apr 11 2002 vcsa1 crw-rw-rw- 1 root root 1, 5 Apr 11 2002 zero
  • 4.
    嵌入式及平行系統4 建立裝置編號 裝置編號型別: dev_t #include <linux/types.h> 總長32bit 無號數,其中 12bit 主編號, 20bit 次編 號 建立裝置編號 #include <linux/kdev_t.h>  MAJOR(dev_t dev);  MINOR(dev_t dev); • 將 dev_t 的主次編號取出來  MKDEV(int major, int minor) • 建立裝置編號
  • 5.
    嵌入式及平行系統5 裝置編號的配置與釋放  #include <linux/fs.h> int register_chrdev_region(dev_t first, unsigned int count, char *name);  配置裝置編號 ( 自定範圍 ) • first :想配置的裝置編號的起點 • count :申請的連續裝置編號總數 • name :獲得此編號範圍的裝置名稱 • 回傳值 0 成功,負值錯誤代碼  int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)  動態配置裝置編號 • dev :取得配置範圍的第一個裝置編號 • firstminor :申請的第一個次編號 (0)  void unregister_chrdev_region(dev_t first, unsigned int count)  釋放裝置編號
  • 6.
    嵌入式及平行系統6 file 結構 #include <linux/fs.h> 定義於struct file 代表己開啟的檔案 (open file) open() 時自動建立 close() 時才會釋放 ( 需沒有任何行程持有該 file 指 標 ) ps: 閱讀時, file 是結構, filp(file pointer) 是指向 struct file 的指標
  • 7.
    嵌入式及平行系統7 file 結構欄位 (1) mode_t f_mode; • 代表檔案的存取模式。 • 可讀: FMODE_READ ,可寫 FMODE_WRITE ,可讀可寫: FMODE_READ | FMODE_WRITE • 驅動程式在 open 或 ioctl 作業方法時會檢查此欄位來確定權限 ,故不用在 read 或 write 時做檢查  loff_t f_pos; • 目前的讀寫位置。 • 64-bit • read 和 write 作業方法應該透過引數傳來的指標 (loff_t *) 來更新 讀寫位置,不應直接修改 file->f_pos  unsigned int f_flags; • 檔案旗標的組合 • 定義於 <linux/fcntl.h> , ex : O_NONBLOCK 、 O_SYNC 、 O_RDONLY • ex :只需檢查 O_NONBLOCK ,就知道是否要求進行 nonblocking operation
  • 8.
    嵌入式及平行系統8 file 結構欄位 (2) struct file_operations *f_op;  指向一個 file_operations 結構,該結構含有一組函式指標, 指向用在本 file 上的各項作業方法  open 時,核心自動指向一個 file_operations  核心不會快取 file->f_op ,意味著驅動程式隨時可抽換己註冊 的作業方法  void *private_data;  open() 系統呼叫時先設定為 NULL ,才去呼叫驅動程式的 open 作業方法  可自由決定是否使用, ex 用來保存需要跨越多次系統呼叫的 狀態資訊  release 作業方法記得釋放此指標指向的記憶體  struct dentry *f_dentry;  file 所屬的目錄項 (directory entry)  file->f_dentry->d_inode
  • 9.
    嵌入式及平行系統9 inode 結構 代表檔案 一個檔案可被開啟很多次,而有很多個代表 FD(file descriptor)的 file 結構,但全指向同一個檔案,即 inode 結構 dev_t i_rdev;  含有實際的裝置編號 struct cdev *i_cdev;  指向一個 struct cdev ,該結構用來表示字元裝置 unsigned int iminor(struct inode *inode); unsigned int imajor(struct inode *inode);  用來取得 inode 的主次編號
  • 10.
    嵌入式及平行系統10 File_operations #include <linux/fs.h> 用 OOP來比喻 file 是物件 (object) , file_operations 就是用來操作 file 物件 的方法 (method) __user 字串代表指向一個 user-space 位址,不可 直接取值 (dereference)
  • 11.
    嵌入式及平行系統11 File_operations 結構 (1) structmodule *owner  指向擁有本 file_operations 結構的模組  THIS_MODULE( 定義於 <linux/module.h>) loff_t (*llseek) (struct file *, loff_t, int);  定位作業:改變檔案存取的位置,下次讀寫從新位 置開始  loff_t :至少 64bit  回傳值:正值代表新存取位置,負值錯誤,  此函式指標如指向 NULL ,則會改變檔案存取點的 系統呼叫,都有可能造成 file 中的位置指標改到無 法預期的位置
  • 12.
    嵌入式及平行系統12 File_operations 結構 (2) ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);  讀取作業  回傳值,正值代表已成功讀取的位元組個數  若函式指標指向 NULL ,呼叫 read 時,則會回傳 - EINVAL(Invalid argument)  ssize_t (*aio_read) (struct kiocb *, char __user *, size_t, loff_t);  異步讀取作業  不一定能在函式返回前傳輸完畢所有要讀取的資料  若函式指標指向 NULL ,則會改以 read 取代  ssize_t (*write) (struct file *, const char __user *, size_t ,loff_t *);  ssize_t (*aio_write) (struct kiocb *, const char __user *, size_t, loff_t);  意思同 read 作業,只是變成 write 作業
  • 13.
    嵌入式及平行系統13 File_operations 結構 (3) int (*readdir) (struct file *, void *, filldir_t);  讀取檔案系統上的目錄  unsigned int (*poll) (struct file *, struct poll_table_struct *);  查詢裝置的 I/O 狀態。  poll() 、 epoll() 、 select() 系統呼叫都會作業此法;用來查詢某個已開啟的檔案下次讀寫 是否會造成停頓 (block)  若函式指向 NULL ,則核心會假設裝置永遠都可以順暢讀寫,不會 block  int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);  執行裝置專屬指令( device-specific command) , ex :格式化軟碟  若應用程式要求驅動程式沒提供的 ioctl 指令,則會回傳 -ENOTTY(No such ioctl for device)  int (*mmap) (struct file *, struct vm_area_struct *);  記憶體映射操作 (memory mapping) ,將裝置上的記憶體映射到行程的位址空間  若 NULL ,回傳 – ENODEY  int (*open) (struct inode *, struct file *);  開啟檔案,並沒強制要求  NULL 時一樣可開啟檔案,但驅動程式收不到通知  int (*flush) (struct file *);  完工作業,當行程關閉它手中的裝置檔 FD 時,會觸動一次 flush 作業方法,而它應該執 行 ( 並等待 ) 任何常未完成的作業  NULL 時,則會忽略應用程式要求
  • 14.
    嵌入式及平行系統14 File_operations 結構 (4) int (*release) (struct inode *, struct file *);  釋放裝置檔  只在所有分身都被關閉時,最後一次 close() 才會觸動 releapse  int (*fsync) (struct file *, struct dentry *, int datasync);  int (*aio_fsync) (struct kiocb *, int datasync);  出清留滯資料,讓應用程式用來將留滯在記憶體中的資料全數確實寫入裝置  NULL 時,則 fsync() 系統呼叫,會傳回 -EINVAL  int (*fasync) (int, struct file *, int);  異步作業通知,核心發現 FASYNC 旗標有改變時,會呼叫此作業方法來通知驅 動程式  int (*lock) (struct file *, int, struct file_lock *);  檔案鎖定  驅動程式而言,反而幾乎不必要  ssize_t (*readv) (struct file *, const struct iovec *, unsigned long, loff_t *);  ssize_t (*writev) (struct file *, const struct iovec *, unsigned long, loff_t *);  分散式讀取 (scatter read) 與累積式寫出 (gather write)  應用程式偶而需要一次讀入多個記憶區的資料,或是將資料一次寫到多個記憶 區,就能讓應用程式直接進行多記憶區的資料傳輸,而不必分次傳輸  若為 NULL ,則會以 read 和 write 來取代 
  • 15.
    嵌入式及平行系統15 File_operations 結構 (5) ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *);  以最少的抄寫動作,將資料從一個 FD 傳輸到另一個 FD ,也能從 FD 讀出資料給核心  ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);  當核心從 sendfile 獲得一整個記憶頁的資料,便會呼叫目標檔的 sendpage ,將資料寫入該檔案。  unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);  在行程的位址空間中找出一個適當的位置,用來對映目標裝置上的 記憶區段  int (*check_flags)(int);  讓模組可檢查 fcntl(F_SETFL…) 收到旗標  int (*dir_notify)(struct file *filp, unsigned long arg);  應用程式使用 fcntl() 索取目錄變更通知,便會呼叫此作業方法。
  • 16.
    嵌入式及平行系統16 File_operations 宣告 struct file_operationsscull_fops = { .owner = THIS_MODULE, .llseek = scull_llseek, .read = scull_read, .write = scull_write, .ioctl = scull_ioctl, .open = scull_open, .release = scull_release, };
  • 17.
    嵌入式及平行系統17 註冊字元裝置  struct cdev,代表字元裝置  #include <linux/cdev.h>  cdev_alloc();  配置一個 cdev 的結構 struct cdev *my_codv = cdev_alloc(); my_cdev->ops = &my_fops; my_cdev->owner = THIS_MODULE;  void cdev_init(struct cdev *dev, struct file_operations *fops);  如果驅動程式需要裝 cdev 嵌在你自己設計的一個特殊資料結構時使用  int cdev_add(struct cdev *dev, dev_t num, unsigned int count);  將 cdev 加入核心  cdev_add() 返回時,裝置就當場生效,核心隨時有可能呼叫它的作業方法,故 必須完全準備委當才使用 • dev :設定好的 cdev 結構 • num :是第一個裝置編號 • count :裝置編號的總數  void cdev_del(struct cdev *dev);  註銷 cdev
  • 18.
    嵌入式及平行系統18 註冊字元裝置範例 struct scull_dev { structscull_qset *data; /* Pointer to first quantum set */ int quantum; /* the current quantum size */ int qset; /* the current array size */ unsigned long size; /* amount of data stored here */ unsigned int access_key; /* used by sculluid and scullpriv */ struct semaphore sem; /* mutual exclusion semaphore */ struct cdev cdev; /* Char device structure */ }; static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add (&dev->cdev, devno, 1); /* Fail gracefully if need be */ if (err) printk(KERN_NOTICE "Error %d adding scull%d", err, index); }
  • 19.
    嵌入式及平行系統19 open 作業方法  檢查裝置特有的錯誤(ex :沒放光碟片 .. 等 )  如果目標裝置首次被開啟,則應該進行初始化程序  更新 f_op 指標  配置任何要放在 filp->privated_data 的的資料結構  container_of(pointer, container_type, container_field);( 因為 inode 引數的 i_cdev ,指向 一個 cdev 結構 )  #include <linux/kernel.h>  pointer :指向一個名為 container_field  container_typer :該欄位所屬之型別  回傳一個指向容器的結構的指標  int (*open)(struct inode *inode, struct file *filp); int scull_open(struct inode *inode, struct file *filp) { struct scull_dev *dev; /* device information */ dev = container_of(inode->i_cdev, struct scull_dev, cdev); filp->private_data = dev; /* for other methods */ /* now trim to 0 the length of the device if open was write-only */ if ( (filp->f_flags & O_ACCMODE) = = O_WRONLY) { scull_trim(dev); /* ignore errors */ } return 0; /* success */ }
  • 20.
    嵌入式及平行系統20 release 作業方法 釋放 open儲存於 filp->private_data 的任何東西 在最後一次關閉時,將目標裝置關機 只有在最後一次 close() 時,才會呼叫 release int scull_release(struct inode *inode, struct file *filp) { return 0; }
  • 21.
    嵌入式及平行系統21 要求記憶體空間 void *kmalloc(size_t size,int flags);  配置 size 個位元組記憶體  成功回傳指向該記憶體指標,失敗為 NULL  flag 引數是描述如何配置記憶體 • GFP_KERNEL :核心記憶體配置。可能休眠 • GFP_USER :配置記憶體給 user-space 行程。可能 休眠 ( 可參閱,第 8 章 p236) void kfree(void *ptr);  釋放 kmalloc() 配置的記憶體
  • 22.
    嵌入式及平行系統22 read 與 write ssize_t read(struct file *filp, char __user *buff, size_t count, loff_t *offp);  ssize_t write(struct file *filp, const char __user *buff,size_t count, loff_t *offp);  filp : file 結構的指標  count :是要被傳輸的資料量  buff :指向一塊 user-space 暫存區 • read 用於存放裝置讀出的資料 • write 含有要被寫入至裝置的資料  f_pos(long offset type) :使用者正在存取檔案的位置  回傳值 (signed size type)  核心不能夠直接取值 (dereference)  user-space 指標在 kernel 模式下不一樣是有效的  即使 user-space 與 kernel-space 在同一位址空間,但 user-space 記憶體是換頁式的,發生系統呼叫時,可能導致 page fault ,而這 在 kernel 模式是不充許的  user-space 指標必定來自某一個 user-space 的應用程式,該程式 可能有錯,甚至是惡意的 read=kernel->user write=user->kernel
  • 23.
    嵌入式及平行系統23 read 與 write(2) # include <asm/uaccess.h>  unsigned long copy_to_user(void __user *to, const void *from, unsigned long count);  將資料從 kernel-space -> user-space  unsigned long copy_from_user(void *to, const void __user *from, unsigned long count);  將資料從 user-space -> kernel-space  這兩個在傳輸時會檢查 user-space 的指標是否有效。如果無 效,則不會傳輸動作。傳輸時才遇到無效位址,則只有部份 資料會完成傳輸  回傳值 0 為成功完成,正值為尚未完成傳輸的資料量  如不想檢查 user-space 指標  __copy_to_user()  __copy_from_user()
  • 24.
  • 25.
    嵌入式及平行系統25 read 作業方法 若回傳值等於當初傳給 read()系統呼叫的 count 引 數,那表示當初要求傳的資料己經全數傳輸成功 若回傳值大於 0 ,但小於 count ,則表示只順利傳 輸部份資料 若回傳值為 0 ,代表檔案結尾 EOF 若回傳值為 -1 ,則發生某種錯誤  (<linux/errno.h> 定義各種錯誤代碼 )
  • 26.
  • 27.
    嵌入式及平行系統27 read 作業方法範例 (1) ssize_tscull_read(struct file *filp, char user *buf, size_t count, loff_t *f_pos){ struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; /* the first listitem */ int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; /* how many bytes in the listitem */ int item, s_pos, q_pos, rest; ssize_t retval = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->size) goto out; if (*f_pos + count > dev->size) count = dev->size - *f_pos; /* find listitem, qset index, and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum;
  • 28.
    嵌入式及平行系統28 read 作業方法範例 (2) /*follow the list up to the right position (defined elsewhere) */ dptr = scull_follow(dev, item); if (dptr = = NULL || !dptr->data || ! dptr->data[s_pos]) goto out; /* don't fill holes */ /* read only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; out: up(&dev->sem); return retval; }
  • 29.
    嵌入式及平行系統29 write 作業方法 若回傳值等於 count,表示要求的資料量己全數寫 入 scull 裝置 若回傳值大於 0 ,但小於 count ,表示只有部份資 料被寫入裝置 若回傳值為0,表示沒寫入任何資料 若回傳值為負,表示發生錯誤
  • 30.
    嵌入式及平行系統30 write 作業方法範例 (1) ssize_tscull_write(struct file *filp, const char __user *buf, size_t count,loff_t *f_pos){ struct scull_dev *dev = filp->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t retval = -ENOMEM; /* value used in "goto out" statements */ if (down_interruptible(&dev->sem)) return -ERESTARTSYS; /* find listitem, qset index and offset in the quantum */ item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; /* follow the list up to the right position */ dptr = scull_follow(dev, item); if (dptr = = NULL) goto out; if (!dptr->data) { dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); if (!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); }
  • 31.
    嵌入式及平行系統31 write 作業方法範例 (2) if(!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; } /* write only up to the end of this quantum */ if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user(dptr->data[s_pos]+q_pos, buf, count)) { retval = -EFAULT; goto out; } *f_pos += count; retval = count; /* update the size */ if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return retval; }