1. 程式人生 > >關於file_operations結構體【轉】

關於file_operations結構體【轉】

結構體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[1]裝置文 件訪問。主裝置號告訴你哪些驅動模組是用來操縱哪些硬體裝置的。從裝置號是驅動模 塊自己使用來區別它操縱的不同裝置,當此驅動模組操縱不只一個裝置時。

將核心驅動模組載入入核心意味著要向核心註冊自己。這個工作是和驅動模組獲 得主裝置號時初始化一同進行的。你可以使用標頭檔案 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統呼叫建立裝置檔案並且呼叫 rm 刪除該裝置 檔案在驅動模組呼叫函式cleanup_module前。

登出一個裝置
即使時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);

 return 0;
}

/*
 * 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的核心。