《1》在內核中, dev_t 類型(<linux/types.h> 中定義)用來保存設備編號-----包括主設備號和次設備號。在內核2.6.0版本中,dev_t是一個32位的數,其中的12位用來表示主設備號,而其餘20位用來表示次設備號。當然我們的代碼不應該對設備編號的組織做任何假定。而應該始終使用<linux/kdev_t.h> 中定義的宏。比如要獲得dev_t 的主設備號或次設備號,應使用: MAJOR(dev_t dev) ; MINOR(dev_t dev) ; 相反, 如果需要將主設備號和次設備號轉換成dev_t 類型,則使用: MKDEV(int major , int minor) ;
《2》在建立一個字符設備之前,我們首先需要獲得一個或者多個設備編號。靜態分配設備號需要的函數是:
int register_chrdev_region(dev_t first , unsigned int count , char * name);
first : 是要分配的設備號範圍的起始值。first的次設備號經常被設置爲0, 但對該函數來講並不是必需的。
count :所請求的連續設備編號的個數。如果count非常大,則所請求的範圍可能和下一個主設備號重疊, 但只要我們所請求的編號範圍是可用的,則不會帶來任何問題。
name : 是和該編號範圍關聯的設備名稱, 它將出現在 /proc/devices 和 sysfs中。
成功分配時返回0, 錯誤情況返回一個錯誤碼,並且不能請求所請求的區域。
當我們不知道設備號的時候,我們可以通過動態分配來實現,使用函數是:
int alloc_chrdev_region(dev_t *dev , unsigned int firstminor , unsigned int count , char *name) ;
dev : 是僅用於輸出的參數, 在成功完成調用後將保存已分配範圍的第一個編號。
firstminor 應該是要使用的被請求的第一個次設備號, 它通常是0 。count 和 name 參數與register_chrdev_region 函數是一樣的。
《3》無論採用什麼樣的方法分配設備號,都應該在不再使用它們時釋放這些設備編號。使用下面的函數:
void unregister_chrdev_region(dev_t first , unsigned int count ) ;
《4》在書本中例子主要是講解scull, 下面這個scull_load 腳本是發佈scull的一部分, 使用模塊形式發行的驅動程序的用戶可以在系統的rc.local 文件中調用這個腳本,或是在需要模塊時手工調用。
#!/bin/sh
module = “scull”
device=“scull”
mode=“664”
#使用傳入到該腳本的所有參數調用insmod , 同時使用路徑名來指定模塊位置,
#這是因爲新的modutils 默認不會在當前目錄中查找模塊。
/sbin/insmod ./$module.ko $* || exit 1
#刪除原有節點
rm -f /dev/${device} [0-3]
major = $(awk “\$2 = = \“$module\ ” {print \$1 } ” /proc/devices )
mknod /dev/${device}0 c $major 0
mknod /dev/${device}1 c $major 1
mknod /dev/${device}2 c $major 2
mknod /dev/${device}3 c $major 3
#給定適當的組屬性及許可,並修改屬組。
#並非所有的發行版都具有staff組, 有些有wheel 組。
group =“staff”
grep -q ' ^staff : ' / etc / group || group = “ wheel ”
chgrp $group /dev/${device}[ 0 -3 ]
chmod $mode /dev/ ${device}[ 0 -3]
這個腳本同樣可以適用於其他的驅動程序,只要重新定義變量並調整mknod 那幾行語句就可以了。
這個腳本必須由超級用戶運行,所以新創建的設備文件自然屬於root。默認權限位只屬於root對其有寫訪問權,而其他用戶只有讀訪問權,所以我們需要做相應的修改。
《5》大部分基本驅動程序操作涉及到三個重要的內核數據結構,分別是file_operations , file 和 inode 。迄今爲止,我們已經爲自己保留了一些設備編號,但未將任何驅動程序操作連接到這些編號。file_operations 結構就是用來建立這種連接的。這種結構定義在<linux / fs.h>, 其中包含了一組函數指針。每個打開的文件(在內部由一個file 結構表示) 和一組函數關聯(通過包含指向一個file_operations 結構的f_op字段) 。
這三個結構體主要如下:
struct file {
union {
struct list_head fu_list; 文件對象鏈表指針linux/include/linux/list.h
struct rcu_head fu_rcuhead; RCU(Read-Copy Update)是Linux 2.6內核中新的鎖機制
} f_u;
struct path f_path; 包含dentry和mnt兩個成員,用於確定文件路徑
#define f_dentry f_path.dentry f_path的成員之一,當前文件的dentry結構
#define f_vfsmnt f_path.mnt 表示當前文件所在文件系統的掛載根目錄
const struct file_operations *f_op; 與該文件相關聯的操作函數
atomic_t f_count; 文件的引用計數(有多少進程打開該文件)
unsigned int f_flags; 對應於open時指定的flag
mode_t f_mode; 讀寫模式:open的mod_t mode參數
off_t f_pos; 該文件在當前進程中的文件偏移量
struct fown_struct f_owner; 該結構的作用是通過信號進行I/O時間通知的數據。
unsigned int f_uid, f_gid; 文件所有者id,所有者組id
struct file_ra_state f_ra; 在linux/include/linux/fs.h中定義,文件預讀相關
unsigned long f_version;
#ifdef CONFIG_SECURITY
void *f_security;
#endif
/* needed for tty driver, and maybe others */
void *private_data;
#ifdef CONFIG_EPOLL
/* Used by fs/eventpoll.c to link all the hooks to this file */
struct list_head f_ep_links;
spinlock_t f_ep_lock;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
};
struct inode {
struct hlist_node i_hash; 哈希表
struct list_head i_list; 索引節點鏈表
struct list_head i_dentry; 目錄項鍊表
unsigned long i_ino; 節點號
atomic_t i_count; 引用記數
umode_t i_mode; 訪問權限控制
unsigned int i_nlink; 硬鏈接數
uid_t i_uid; 使用者id
gid_t i_gid; 使用者id組
kdev_t i_rdev; 實設備標識符
loff_t i_size; 以字節爲單位的文件大小
struct timespec i_atime; 最後訪問時間
struct timespec i_mtime; 最後修改(modify)時間
struct timespec i_ctime; 最後改變(change)時間
unsigned int i_blkbits; 以位爲單位的塊大小
unsigned long i_blksize; 以字節爲單位的塊大小
unsigned long i_version; 版本號
unsigned long i_blocks; 文件的塊數
unsigned short i_bytes; 使用的字節數
spinlock_t i_lock; 自旋鎖
struct rw_semaphore i_alloc_sem; 索引節點信號量
struct inode_operations *i_op; 索引節點操作表
struct file_operations *i_fop; 默認的索引節點操作
struct super_block *i_sb; 相關的超級塊
struct file_lock *i_flock; 文件鎖鏈表
struct address_space *i_mapping; 相關的地址映射
struct address_space i_data; 設備地址映射
struct dquot *i_dquot[MAXQUOTAS];節點的磁盤限額
struct list_head i_devices; 塊設備鏈表
struct pipe_inode_info *i_pipe; 管道信息
struct block_device *i_bdev; 塊設備驅動
unsigned long i_dnotify_mask;目錄通知掩碼
struct dnotify_struct *i_dnotify; 目錄通知
unsigned long i_state; 狀態標誌
unsigned long dirtied_when;首次修改時間
unsigned int i_flags; 文件系統標誌
unsigned char i_sock; 套接字
atomic_t i_writecount; 寫者記數
void *i_security; 安全模塊
__u32 i_generation; 索引節點版本號
union {
void *generic_ip;文件特殊信息
} u;
};
inode 譯成中文就是索引節點。每個存儲設備或存儲設備的分區(存儲設備是硬盤、軟盤、U盤 ... ... )被格式化爲文件系統後,應該有兩部份,一部份是inode,另一部份是Block,Block是用來存儲數據用的。而inode呢,就是用來存儲這些數據的信息,這些信息包括文件大小、屬主、歸屬的用戶組、讀寫權限等。inode爲每個文件進行信息索引,所以就有了inode的數值。操作系統根據指令,能通過inode值最快的找到相對應的文件。
struct file_operations {
struct module *owner;
loff_t(*llseek) (struct file *, loff_t, int);
ssize_t(*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t(*aio_read) (struct kiocb *, char __user *, size_t, loff_t);
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);
int (*readdir) (struct file *, void *, filldir_t);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, struct dentry *, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
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 *);
ssize_t(*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void __user *);
ssize_t(*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area) (struct file *, unsigned long,
unsigned long, unsigned long,
unsigned long);
};
《6》下面對於一些操作的函數給予更爲詳細的介紹,file_operations結構體中的函數:stuct module *owner
第一個file_operations字段並不是一個操作;相反,它是指向“擁有”該結構的模塊的指針。內核使用這個字段以避免在模塊的操作正在被使用時卸載該模塊。幾乎在所有的情況下,該成員都會被初始化爲THIS_MODULE, 它是定義在<linux/module.h>中的一個宏。
loff_t ( * llseek)(struct file * , loff_t , int );
方法llseek用來修改文件的當前讀寫位置,並將新位置作爲(正的)返回值返回。參數loff_t一個“長偏移量” , 即使在32位平臺上也至少佔用64位的數據寬度。出錯時返回一個負的返回值。如果這個函數指針是NULL,對seek的調用將會以魔種不可預期的方式修改file結構中的位置計數器。
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 *);
向設備發送數據。如果沒有這個函數, write 系統調用會向程序返回一個 -EINVAL 。如果返回值非負, 則表示成功寫入的字節數。
ssize_t (* aio_write ) (struct kiocb * , const char __user * , size_t ,loff_t *) ;
初始化設備上的異步寫入操作。
int (*readdir) (struct file * , void * , filldir_t );
對於設備文件來說 , 這個字段應該爲NULL 。它僅是用於讀取目錄 , 只對文件系統有用 。
unsigned int (* poll ) ( struct file * , struct poll_table_struct * ) ;
poll 方法是poll , epoll 和 select 這三個系統調用的後端實現。 這三個系統調用可用來查詢某個或多個文件描述符上的讀取或寫入是否會被阻塞。 poll 方法應該返回一個位掩碼 , 用來指出非阻塞的讀取或寫入是否可能 , 並且也會向內核提供將調用進程置於休眠狀態直到I/O變爲可能時的信息。如果驅動程序將poll的方法定義爲NULL, 則設備會被認爲即可讀也可寫, 並且不會被阻塞。
int (* ioctl ) (struct inode * , struct file * , unsigned int , unsigned long ) ;
系統調用ioctl提供了一致執行設備特定命令的方法(如格式化軟盤的某個磁道,這既不是讀操作也不是寫操作)。另外, 內核還能識別一部分ioctl 命令, 而不必調用 fops 表中的ioctl 。如果設備不提供ioctl入口點, 則對於任何內核未預先定義的請求, ioctl 系統調用將返回錯誤(-ENOTTY ,“No such ioctl for device , 該設備無此ioctl 命令”)。
int( * mmap) (struct file * , struct vm_area_struct * ) ;
mmap 用於請求將設備內存隱射到進程地址空間。如果設備沒有實現這個方法, 那麼mmap 系統調用將返回 -ENODEV。
int (*open) (struct inode * , struct file * ) ;
儘管這個始終是對設備文件執行的第一個操作,然後卻並不要求驅動程序一定要聲明一個相應的方法。如果這個入口爲NULL , 設備的打開操作永遠成功, 但系統不會通知驅動程序。
int (*flush )(struct file *) ;
對flush 操作的調用發生在進程關閉設備文件描述符副本的時候, 它應該執行(並等待) 設備上尚未完結的操作。請不要將它同用戶使用的fsync操作相混淆。目前, flush 僅僅用於少數幾個驅動程序, 比如, SCSI磁帶驅動程序用它來確保設備被關閉之前所有數據都被寫到磁帶中。如果flush被設置爲NULL , 內核將簡單地忽略用戶應用程序的請求。
int (* release) (struct inode * , struct file *)
當file結構被釋放時, 將調用這個操作。與open相仿, 也可以將release 設置爲NULL。
int (* fsync ) (struct file * , struct dentry * , int) ;
該方法是fsync系統調用的後端實現, 用戶調用它來刷新待處理的數據。如果驅動程序沒有實現這一方法fsync 系統調用返回-EINVAL。
int (* aio_fsync)(struct kiocb * , int );
這個是fsync方法的異步版本。
int (* fasync )(int , struct file * , int ) ;
這個操作用來通知設備其FASYNC標誌發生了變化。異步通知是比較高級的問題, 將在第六章介紹。如果設備不支持異步通知, 該字段可以是NULL 。
int (* lock) (struct file * , int , struct file_lock * );
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 * );
這些方法用來實現分散 / 聚集型的讀寫操作 。應用程序有時需要進行涉及多個內存區域的單次讀或寫操作, 利用上面這些系統調用可完成這類工作, 而不必強加額外的數據拷貝操作。如果這些函數指針被設置成NULL ,就會調用read 和 write 方法(可能是多次)。
ssize_t (*sendfile ) (struct file * , loff_t * , size_t , read_actor_t , void * ) ;
這個方法實現sendfile 系統調用的讀取部分。sendfile 系統調用以最小的複製操作將數據從一個文件描述符移動到另一個。例如:Web服務器可以利用這個方法將某個設備的內容發送到網絡連接。設備驅動程序通常將sendfile設置爲NULL。
ssize_t (* sendpage) (struct file * , struct page * , int , size_t , loff_t *, int );
sendpage 是sendfile 系統調用的另外一半, 它由內核調用以將數據發送到對應的文件, 每次一個數據頁。設備驅動程序通常也不需要實現sendpage。
unsigned long (* get_unmapped_area ) (struct file * , unsigned long , unsigned long , unsigned long , unsigned long ) ;
該方法的目的是在進程的地址空間中找到一個合適的位置,以便將底層設備中的內存段映射到該位置。該任務通常由內存管理代碼完成, 但該方法的存在可允許驅動程序強制滿足特定設備需要的任何對齊要求。大部分驅動程序可設置該方法爲NULL。
int (* check_flags )(int )
該方法允許模塊檢查傳遞給fcntl(F_SETFL...)調用的標誌。
int (* dir_notify )(struct file * , unsigned long ) ;
當應用程序使用fcntl來請求目錄改變通知時, 該方法將被調用。該方法僅對文件系統有用,驅動程序不必實現dir_notify。
《7》通常scull設備驅動的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 ,
} ;
《8》struct file 是一個內核結構,