Contenu connexe Similaire à Device Driver - Chapter 3字元驅動程式 (20) Plus de ZongYing Lyu (12) Device Driver - Chapter 3字元驅動程式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
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)
釋放裝置編號
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
11. 嵌入式及平行系統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. 嵌入式及平行系統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() 索取目錄變更通知,便會呼叫此作業方法。
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 {
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. 嵌入式及平行系統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()
27. 嵌入式及平行系統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. 嵌入式及平行系統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;
}
30. 嵌入式及平行系統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. 嵌入式及平行系統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;
}