結構體file_operations在頭檔案 linux/fs.h中定義,用來存儲驅動核心子產品提供的對裝置進行各種操作的函數的指針。該結構體的每個域都對應着驅動核心子產品用來處理某個被請求的 事務的函數的位址。
舉個例子,每個字元裝置需要定義一個用來讀取裝置資料的函數。結構體 file_operations中存儲着核心子產品中執行這項操作的函數的位址。一下是該結構體 在核心2.6.5中看起來的樣子:
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);
};
驅動核心子產品是不需要實作每個函數的。像視訊卡的驅動就不需要從目錄的結構 中讀取資料。那麼,相對應的file_operations重的項就為 NULL。
gcc還有一個友善使用這種結構體的擴充。你會在較現代的驅動核心子產品中見到。新的使用這種結構體的方式如下:
struct file_operations fops = {
read: device_read,
write: device_write,
open: device_open,
release: device_release
};
同樣也有C99文法的使用該結構體的方法,并且它比GNU擴充更受推薦。我使用的版本為 2.95為了友善那些想移植你的代碼的人,你最好使用這種文法。它将提高代碼的相容性:
struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
這種文法很清晰,你也必須清楚的意識到沒有顯示聲明的結構體成員都被gcc初始化為NULL。
指向結構體struct file_operations的指針通常命名為fops。
關于file結構體
每一個裝置檔案都代表着核心中的一個file結構體。該結構體在頭檔案linux/fs.h定義。注意,file結構體是核心空間的結構體, 這意味着它不會在使用者程式的代碼中出現。它絕對不是在glibc中定義的FILE。 FILE自己也從不在核心空間的函數中出現。它的名字确實挺讓人迷惑的。它代表着一個抽象的打開的檔案,但不是那種在磁盤上用結構體inode表示的檔案。
指向結構體struct file的指針通常命名為filp。你同樣可以看到struct file file的表達方式,但不要被它誘惑。
去看看結構體file的定義。大部分的函數入口,像結構體struct dentry沒有被裝置驅動子產品使用,你大可忽略它們。這是因為裝置驅動子產品并不自己直接填充結構體file:它們隻是使用在别處建立的結構體file中的資料。
注冊一個裝置
如同先前讨論的,字元裝置通常通過在路徑/dev下的裝置檔案進行通路。主裝置号告訴你哪些驅動子產品是用來操縱哪些硬體裝置的。從裝置号是驅動子產品自己使用來差別它操縱的不同裝置,當此驅動子產品操縱不隻一個裝置時。
将核心驅動子產品加載入核心意味着要向核心注冊自己。這個工作是和驅動子產品獲得主裝置号時初始化一同進行的。你可以使用頭檔案linux/fs.h中的函數register_chrdev來實作。
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
其中unsigned int major是你申請的主裝置号,const char *name是将要在檔案/proc/devices中顯示的名稱,struct file_operations *fops是指向你的驅動子產品的file_operations表的指針。負的傳回值意味着注冊失敗。注意注冊并不需要提供從裝置号。核心本身并不在意從裝置号。
現在的問題是你如何申請到一個沒有被使用的主裝置号?最簡單的方法是檢視檔案 Documentation/devices.txt從中挑選一個沒有被使用的。這不是一勞永逸的方法因為你無法得知該主裝置号在将來會被占用。最終的方法是讓核心為你動态配置設定一個。
如果你向函數register_chrdev傳遞為0的主裝置号,那麼傳回的就是動态配置設定的主裝置号。副作用就是既然你無法得知主裝置号,你就無法預先建立一個裝置檔案。有多種解決方法:第一種方法是新注冊的驅動子產品會輸出自己新配置設定到的主裝置号,是以我們可以手工建立需要的裝置檔案。第二種是利用檔案/proc/devices新注冊的驅動子產品的入口,要麼手工建立裝置檔案,要麼編一個腳本去自動讀取該檔案并且生成裝置檔案。第三種是在我們的子產品中,當注冊成功時,使用mknod系統調用建立裝置檔案并且在驅動子產品調用函數cleanup_module前,調用rm删除該裝置檔案。
登出一個裝置
即使是root也不能允許随意解除安裝核心子產品。當一個程序已經打開一個裝置檔案時我們解除安裝了該裝置檔案使用的核心子產品,我們此時再對該檔案的通路将會導緻對已解除安裝的核心子產品代碼記憶體區的通路。幸運的話我們最多獲得一個讨厭的錯誤警告。如果此時已經在該記憶體區加載了另一個子產品,倒黴的你将會在核心中跳轉執行意料外的代碼。結果是無法預料的,而且多半是不那麼令人愉快的。
平常,當你不允許某項操作時,你會得到該操作傳回的錯誤值(一般為負值)。但對于無傳回值的函數cleanup_module這是不可能的。然而,卻有一個計數器跟蹤着有多少程序正在使用該子產品。你可以通過檢視檔案 /proc/modules的第三列來擷取這些資訊。如果該值非零,則解除安裝就會失敗。你不需要在你子產品中的函數cleanup_module中檢查該計數器,因為該項檢查由頭檔案linux/module.c中定義的系統調用sys_delete_module完成。你也不應該直接對該計數器進行操作。你應該使用在檔案linux/modules.h定義的宏來增加,減小和讀取該計數器:
try_module_get(THIS_MODULE): Increment the use count.
try_module_put(THIS_MODULE): Decrement the use count.
保持該計數器時刻精确是非常重要的;如果你丢失了正确的計數,你将無法解除安裝子產品,那就隻有重新開機了。不過這種情況在今後編寫核心子產品時也是無法避免的。
chardev.c
下面的代碼示範了一個叫做chardev的字元裝置。你可以用cat輸出該裝置檔案的内容(或用别的程式打開它)時,驅動子產品會将該裝置檔案被讀取的次數顯示。目前對裝置檔案的寫操作還不被支援(像echo "hi" > /dev/hello),但會捕捉這些操作并且告訴使用者該操作不被支援。不要擔心我們對讀入緩沖區的資料做了什麼;我們什麼都沒做。我們隻是讀入資料并輸出我們已經接收到的資料的資訊。
Example 4-1. chardev.c
/*
* chardev.c: Creates a read-only char device that says how many times
* you've read from the dev file
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <asm/uaccess.h> /* for put_user */
/*
* Prototypes - this would normally go in a .h file
int init_module(void);
void cleanup_module(void);
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char *, size_t, loff_t *);
#define SUCCESS 0
#define DEVICE_NAME "chardev" /* Dev name as it appears in /proc/devices */
#define BUF_LEN 80 /* Max length of the message from the device */
/*
* Global variables are declared as static, so are global within the file.
static int Major; /* Major number assigned to our device driver */
static int Device_Open = 0; /* Is device open?
* Used to prevent multiple access to device */
static char msg[BUF_LEN]; /* The msg the device will give when asked */
static char *msg_Ptr;
static struct file_operations fops = {
.read = device_read,
.write = device_write,
.open = device_open,
.release = device_release
};
* Functions
int init_module(void)
{
Major = register_chrdev(0, DEVICE_NAME, &fops);
if (Major < 0) {
printk("Registering the character device failed with %d/n",
Major);
return Major;
}
printk("<1>I was assigned major number %d. To talk to/n", Major);
printk("<1>the driver, create a dev file with/n");
printk("'mknod /dev/hello c %d 0'./n", Major);
printk("<1>Try various minor numbers. Try to cat and echo to/n");
printk("the device file./n");
printk("<1>Remove the device file and module when done./n");
return 0;
}
void cleanup_module(void)
/*
* Unregister the device
*/
int ret = unregister_chrdev(Major, DEVICE_NAME);
if (ret < 0)
printk("Error in unregister_chrdev: %d/n", ret);
* Methods
* Called when a process tries to open the device file, like
* "cat /dev/mycharfile"
static int device_open(struct inode *inode, struct file *file)
static int counter = 0;
if (Device_Open)
return -EBUSY;
Device_Open++;
sprintf(msg, "I already told you %d times Hello world!/n", counter++);
msg_Ptr = msg;
try_module_get(THIS_MODULE);
return SUCCESS;
* Called when a process closes the device file.
static int device_release(struct inode *inode, struct file *file)
Device_Open--; /* We're now ready for our next caller */
* Decrement the usage count, or else once you opened the file, you'll
* never get get rid of the module.
module_put(THIS_MODULE);
* Called when a process, which already opened the dev file, attempts to
* read from it.
static ssize_t device_read(struct file *filp, /* see include/linux/fs.h */
char *buffer, /* buffer to fill with data */
size_t length, /* length of the buffer */
loff_t * offset)
/*
* Number of bytes actually written to the buffer
int bytes_read = 0;
* If we're at the end of the message,
* return 0 signifying end of file
if (*msg_Ptr == 0)
return 0;
* Actually put the data into the buffer
while (length && *msg_Ptr) {
/*
* The buffer is in the user data segment, not the kernel
* segment so "*" assignment won't work. We have to use
* put_user which copies data from the kernel data segment to
* the user data segment.
*/
put_user(*(msg_Ptr++), buffer++);
length--;
bytes_read++;
* Most read functions return the number of bytes put into the buffer
return bytes_read;
* Called when a process writes to dev file: echo "hi" > /dev/hello
static ssize_t
device_write(struct file *filp, const char *buff, size_t len, loff_t * off)
printk("<1>Sorry, this operation isn't supported./n");
return -EINVAL;
為多個版本的核心編寫核心子產品
系統調用,也就是核心提供給程序的接口,基本上是保持不變的。也許會添入新的系統調用,但那些已有的不會被改動。這對于向下相容是非常重要的。在多數情況下,裝置檔案是保持不變的。但核心的内部在不同版本之間還是會有差別的。
Linux核心分為穩定版本(版本号中間為偶數)和試驗版本(版本号中間為奇數)。試驗版本中可以試驗各種各樣的新而酷的主意,有些會被證明是一個錯誤,有些在下一版中會被完善。總之,你不能依賴這些版本中的接口(這也是我不在本文檔中支援它們的原因, 它們更新的太快了)。在穩定版本中,我們可以期望接口保持一緻,除了那些修改代碼中錯誤的版本。
如果你要支援多版本的核心,你需要編寫為不同核心編譯的代碼樹。可以通過比較宏 LINUX_VERSION_CODE和宏KERNEL_VERSION在版本号為a.b.c 的核心中,該宏的值應該為 2^16×a+2^8×b+c
在上一個版本中該文檔還保留了詳細的如何向後相容老核心的介紹,現在我們決定打破這個傳統。 對為老核心編寫驅動感興趣的讀者應該參考對應版本的LKMPG,也就是說,2.4.x版本的LKMPG對應 2.4.x的核心,2.6.x版本的LKMPG對應2.6.x的核心。
分類: C/C++