2015年3月31日 星期二

LDD3 潤飾 3.9. quick reference

3.9. 快速參考

本章介紹了下面symbols和header files. struct file_operations 和struct file 中的成員的列表這裡不重複了.
#include <linux/types.h>
dev_t
dev_t 是用來在內核裡代表device number的類型.
int MAJOR(dev_t dev);
int MINOR(dev_t dev);
從device number中抽取major number,minor number的巨集.
dev_t MKDEV(unsigned int major, unsigned int minor);
從major & minor number來建立dev_t 數據項的巨集定義.
#include <linux/fs.h>
"filesystem"header是編寫device driver需要的header. 許多重要的函數和數據結構在此定義.
int register_chrdev_region(dev_t first, unsigned int count, char *name)
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name)
void unregister_chrdev_region(dev_t first, unsigned int count);
允許driver分配和釋放device number的範圍的函數. register_chrdev_region 應當用在事先知道需要的major number時; 對於動態分配, 使用alloc_chrdev_region 代替.
int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);
老的( 2.6 之前) 字符設備註冊函數. 它在2.6 內核中被模擬, 但是不應當給新代碼使用. 如果主編號不是0, 可以不變地用它; 否則一個動態編號被分配給這個設備.
int unregister_chrdev(unsigned int major, const char *name);
恢復一個由register_chrdev 所作的註冊的函數. major 和name 字符串必須包含之前用來註冊設備時同樣的值.
struct file_operations;
struct file;
struct inode;
大部分設備驅動使用的3 個重要數據結構. file_operations 結構持有一個char driver的method; struct file 代表一個打開的文件, struct inode 代表磁盤上的一個文件.
#include <linux/cdev.h>
struct cdev *cdev_alloc(void);
void cdev_init(struct cdev *dev, struct file_operations *fops);
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
void cdev_del(struct cdev *dev);
cdev 結構管理的函數, 它代表內核中的char device.
#include <linux/kernel.h>
container_of(pointer, type, field);
一個傳統巨集定義, 可用來獲取一個結構pointer, 從它裡面包含的某個其他結構的pointer.
#include <asm/uaccess.h>
這個包含文件聲明內核代碼使用的函數來移動數據到user space和從user space.
unsigned long copy_from_user (void *to, const void *from, unsigned long count);
unsigned long copy_to_user (void *to, const void *from, unsigned long count);
在user space和kernel space拷貝數據.

LDD3 潤飾 3.8 Using new device

3.8. 使用新設備

一旦你裝備好剛剛描述的4 個方法, driver可以編譯並測試了; 它保留了你寫給它的任何數據, 直到你用新數據覆蓋它. 這個設備表現如一個數據緩存器, 它的長度僅僅受限於可用的真實RAM 的數量. 你可試著使用cp, dd, 以及輸入/輸出重定向來測試這個driver.

free 命令可用來看空閒內存的數量如何縮短和擴張的, 依據有多少數據寫入scull.

為對一次讀寫一個quantum有更多信心, 你可增加一個printk 在driver的適當位置, 並且觀察當應用程序讀寫大塊數據中發生了什麼. 另一選擇是使用strace 工具來監視程序發出的system call以及它們的返回值. 跟蹤一個cp 或者一個ls -l > /dev/scull0 展示了量子化的讀和寫. 監視(以及調試)技術在第4 章詳細介紹.

LDD3 潤飾 3.7 Read and Write

3.7. 讀和寫

讀和寫方法都進行類似的任務, 就是, 從應用程序拷貝數據 或是拷貝數據到應用程序. 因此, 它們的原型相當相似, 可以同時介紹它們:

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 pointer, count 是請求的傳輸數據大小. buff 參數指向持有被寫入數據的緩存, 或者放入新數據的空緩存. 最後, offp 是一個pointer指向一個"long offset type"對象, 它指出用戶正在存取的文件位置. 返回值是一個"signed size type"; 它的使用在後面討論.

讓我們重複一下, read 和write 方法的buff 參數是user space pointer. 因此, 它不能被內核代碼直接 解引用. 這個限制有幾個理由:

依賴於你的driver運行的體系, 以及內核被如何配置的, user space pointer當運行於內核模式可能根本是無效的. 可能沒有那個地址的映射, 或者它可能指向一些其他的隨機數據.

就算這個pointer在內核空間是同樣的東西, user space內存是分頁的, 在做system call時這個內存可能沒有在RAM 中. 試圖直接引用user space內存可:能產生一個頁面錯, 這是內核代碼不允許做的事情. 結果可能是一個"oops", 導致進行system call的進程死亡.

置疑中的pointer由一個用戶程序提供, 它可能是錯誤的或者惡意的. 如果你的driver盲目地解引用一個用戶提供的pointer, 它提供了一個打開的門路使user space程序存取或覆蓋系統任何地方的內存. 如果你不想負責你的用戶的系統的安全危險, 你就不能直接解引用user space pointer.

顯然, 你的driver必須能夠存取user space緩存以完成它的工作. 但是, 為安全起見這個存取必須使用特殊的, 內核提供的函數. 我們介紹幾個這樣的函數(定義於<asm/uaccess .h>), 剩下的在第一章"使用ioctl 參數"一節中. 它們使用一些特殊的, 依賴體系的技巧來確保內核和user space的數據傳輸安全和正確.

scull 中的read, write需要拷貝一整段數據到或者從user address space. 這個能力由下列內核函數提供, 它們拷貝一個任意的字節數組, 並且位於大部分讀寫實現的核心中.

unsigned long copy_to_user(void __user *to,const void *from,unsigned long count);
unsigned long copy_from_user(void *to,const void __user *from,unsigned long count);
儘管這些函數表現象正常的memcpy 函數, 必須特別小心在從內核代碼中存取user space. 要存取的user pages可能當前不在memory裡, 在這個page被傳送到位時virtual memory子系統會使進程sleep.例如, 這發生在必須從swap space獲取page的時候. 對於driver編寫者來說, 最終結果是任何存取user space的函數必須是可重入的, 必須能夠和其他driver函數並行執行, 並且, 特別的, 必須在一個它能夠合法地sleep的位置. 我們在第5 章再回到這個主題.

這2 個函數的角色不限於拷貝數據到和從user space: 它們還檢查user space pointer是否有效. 如果pointer無效, 不進行拷貝; 如果在拷貝中遇到一個無效地址, 另一方面, 只拷貝部分數據. 在2 種情況下, 返回值是還要拷貝的數據量. scull 代碼查看這個錯誤返回, 並且如果它不是0 就返回-EFAULT 給用戶.

user space存取和無效user space pointer的主題有些進階, 在第6 章討論. 然而, 值得注意的是如果你不需要檢查user space pointer, 你可以調用__copy_to_user 和__copy_from_user 來代替. 這是有用處的, 例如, 如果你知道你已經檢查了這些參數. 但是, 要小心; 事實上, 如果你不檢查你傳遞給這些函數的user space pointer, 那麼你可能造成內核崩潰和/或安全漏洞.

至於實際的設備方法, read 方法的任務是從設備拷貝數據到user space(使用copy_to_user), 而write 方法必須從user space拷貝數據到設備(使用copy_from_user). 每個read 或write system call請求一個特定數目字節的傳送, 但是driver可自由傳送較少數據-- 對讀和寫這確切的規則稍微不同, 在本章後面描述.

不管這些方法傳送多少數據, 它們通常應當更新*offp 中的文件位置來表示在system call成功完成後當前的文件位置. 內核接著在適當時候傳播文件位置的改變到文件結構. pread 和pwrite system call有不同的語義; 它們從一個給定的文件偏移操作, 並且不改變其他的system call看到的文件位置. 這些調用傳遞一個指向用戶提供的位置的pointer, 並且放棄你的driver所做的改變.

圖給read的參數表示了一個典型讀實現是如何使用它的參數.
給read 的參數
圖 3.2. 給read 的參數

給read 的參數
read 和write 方法都在發生錯誤時返回一個負值. 相反, 大於或等於0 的返回值告知調用程序有多少字節已經成功傳送. 如果一些數據成功傳送接著發生錯誤, 返回值必須是成功傳送的字節數, error不會報告直到函數下一次調用. 當然, 為了實現這個慣例,要求你的driver記住錯誤已經發生, 以便它們可以在以後返回錯誤狀態.

儘管內核函數返回一個負數指示一個錯誤, 這個數的值指出所發生的錯誤類型( 如第2 章介紹), user space運行的程序常常看到-1 作為錯誤返回值. 它們需要存取errno 變量來找出發生了什麼. user space的行為由POSIX 標準來規定, 但是這個標準沒有規定內核內部如何操作.

3.7.1. read 方法

read 的返回值由調用的應用程序解釋:

如果這個值等於傳遞給read system call的count 參數, 代表請求的字節數已經被傳送. 這是最好的情況.

如果是正數, 但是小於count, 則只有部分數據被傳送. 這可能由於幾個原因, 與設備有關. 一般而言,應用程序重新試著讀取. 例如, 假如你使用fread 函數來讀取, library function重新發出system call直到請求的數據傳送完成.

如果值為0, 代表到達了文件末尾(沒有讀取數據).

一個負值表示有一個錯誤. , 根據<linux/errno.h>這個值指出了什麼錯誤. 出錯的典型返回值包括-EINTR( 被打斷的system call) 或者-EFAULT( 錯誤地址).

前面列表中漏掉的是這種情況"沒有數據, 但是可能之後會有". 在這種情況下, read system call應當blocking. 我們將在第6 章涉及blocking.

scull 代碼利用了這些規則. 特別是它利用了partial-read規則. 每個scull_read 調用只處理單個數據quantum, 不實現一個循環來收集所有的數據; 這使得代碼更短更易讀. 如果確實需要更多數據, 它重複重新調用. 如果標準I/O library(例如, fread)用來讀取設備, 應用程序甚至不會注意到數據傳送的量子化.

如果當前讀取位置大於設備大小, scull 的read 方法返回0 來表示沒有可用的數據(換句話說, 我們在文件尾). 這個情況發生在如果進程A 在讀取設備, 同時進程B 打開它來寫入, 這樣將設備截短為0. 進程A 突然發現自己過了文件尾, 下一個read call返回0.

這是read 的代碼( 忽略對down_interruptible 的調用並且現在為up; 我們在下一章中討論它們):

ssiz??e_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;
        ssiz??e_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;

        /* 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;
}
3.7.2. write 方法

write, 像read, 可以傳送少於要求的數據, 根據返回值的下列規則:

如果值等於count, 要求的字節數已被傳送.

如果正值, 但是小於count, 只有部分數據被傳送. 程序最可能會重試寫入剩下的數據.

如果值為0, 什麼都沒有寫入. 這個結果不是一個錯誤, 因此沒有理由返回一個錯誤碼. 再一次, 標準庫重試寫入調用. 我們將在第6 章查看這種情況的確切含義, 那裡介紹了blocking.

一個負值表示發生一個錯誤; 如同對於讀, 有效的錯誤值是定義於<linux/errno.h>中.

不幸的是, 仍然可能有些不當行為程序, 它在進行了部分傳送時,發出error message 並且終止程序. 這是因為一些程序員習慣看寫調用要么完全失敗要么完全成功, 這實際上是大部分時間的情況, 應當也被設備支持. scull 實現的這個限制可以修改, 但是我們不想使代碼不必要地複雜.

write 的scull 代碼一次處理單個quantum, 如read 方法做的:

ssiz??e_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;
        ssiz??e_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 *));
        }
        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;

}
3.7.3. readv 和writev

Unix 系統已經長時間支持名為readv 和writev 的2 個system call. 這些read 和write 的"vector"版本使用一個array structure, 每個包含一個緩存的pointer和一個長度值. 一個readv 調用被期望來輪流讀取指示的數量到每個緩存. 相反, writev 要收集每個緩存的內容到一起並且作為單個write操作送出它們.

如果你的driver不提供方法來處理vector操作, readv 和writev 由多次調用你的read 和write 方法來實現. 但是在許多情況, 直接實現readv 和writev 能獲得更大的效率.

vector操作的原型是:

ssize_t (*readv) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
ssize_t (*writev) (struct file *filp, const struct iovec *iov, unsigned long count, loff_t *ppos);
這裡, filp 和ppos 參數與read 和write 的相同. iovec 結構, 定義於<linux/uio.h>, 如同:

struct iovec
{
    void __user *iov_base; __kernel_size_t iov_len;
};
每個iovec 描述了一塊要傳送的數據; 它開始於iov_base (在user space)並且有iov_len 字節長. count 參數告訴有多少iovec 結構. 這些結構由應用程序創建, 但是內核在調用driver之前拷貝它們到內核空間.

矢量操作的最簡單實現是一個直接的循環, 只是傳遞出去每個iovec 的地址和長度給driver的read 和write 函數. 然而, 有效的和正確的行為常常需要driver更聰明. 例如, 一個磁帶driver上的writev 應當將全部iovec 結構中的內容作為磁帶上的單個記錄.

但是很多driver 沒有從自己實現這些方法中獲益. 因此, scull 省略它們. 內核使用read 和write 來模擬它們, 最終結果是相同的.

2015年3月30日 星期一

LDD3 潤飾 3.6 scull’s Memory Usage

3.6. scull 的memory使用

在介紹read, write operation前, 我們最好看看如何以及為什麼scull 進行memory分配. "如何"是需要全面理解代碼, "為什麼"演示了driver編寫者需要做的選擇, 儘管scull 明確地不是典型設備.

本節只處理scull 中的memory分配策略, 不展示給你編寫真正driver需要的硬件管理技能. 這些技能在第9 章和第10 章介紹. 因此, 你可跳過本章, 如果你不感興趣於理解memory-oriented的scull 驅動的內部工作.

scull 使用的memory區, 也稱為一個設備, 長度可變. 你寫的越多, 它增長越多; 通過使用一個短文件覆蓋設備來進行修整.

scull 驅動引入2 個核心函數來管理Linux 內核中的memory. 這些函數, 定義在<linux/slab.h>, 是:

void *kmalloc(size_t size, int flags);
void kfree(void *ptr);
對kmalloc 的調用試圖分配size 字節的memory; 返回值是指向那個memory的pointer或者如果分配失敗為NULL. flags 參數用來描述memory應當如何分配; 我們在第8 章詳細查看這些flags. 對於現在,我們一直使用GFP_KERNEL. 分配的memory應當用kfree 來釋放. 你不能傳遞任何不是從kmalloc 獲得的東西給kfree. 但是, 傳遞一個NULL pointer給kfree 是合法的.

kmalloc 不是分配大memory區最有效的方法(見第8 章), 所以挑選給scull 的實現不是一個特別巧妙的. 一個巧妙的源碼實現可能更難閱讀, 而本節的目標是展示讀和寫, 不是memory管理. 這是為什麼代碼只是使用kmalloc 和kfree 而不依靠整頁的分配, 儘管這個方法會更有效.

另一方面, 我們不想限制"設備"區的大小, 由於理論上的和實踐上的理由. 理論上, 在被管理的數據項目施加武斷的限制總是個壞想法. 實踐上, scull 可用來暫時地吃光你係統中的memory, 以便運行在低memory條件下的測試. 運行這樣的測試可能會幫助你理解系統的內部. 你可以使用命令cp /dev/zero /dev/scull0 來用scull 吃掉所有的真實RAM, 並且你可以使用dd 工具來選擇拷貝多少數據給scull 設備.

在scull,每個設備是一個pointer的linked list,每個都指向一個scull_dev結構.每個這樣的結構,預設地,指向最多4兆字節,通過一個中間pointer陣列.發行代碼使用一個1000個pointer的array指向每個4000字節的區域.我們稱每個memory區域為一個quantum,陣列(或者它的長度)為一個quantum set.一個scull設備和它的memory區如圖一個scull設備的佈局所示.
一個scull 設備的佈局
圖 3.1. 一個scull 設備的佈局

一個scull 設備的佈局
選定的數字是這樣, 在scull 中寫單個一個字節消耗8000 或12,000 KB memory: 4000 是quantum, 4000 或者8000 是quantum set(根據pointer在目標平台上是用32位還是64位表示). 相反, 如果你寫入大量數據, linked list的開銷不是太壞. 每4 MB 數據只有一個list元素, 設備的最大尺寸受限於計算機的memory大小.

為quantum和quantum set選擇合適的值是一個策略問題, 而不是機制, 並且優化的值依賴於設備如何使用. 因此, scull driver不應當強制給quantum和quantum set使用任何特別的值. 在scull 中,用戶有幾個途徑可以改變這些值:編譯時通過改變scull.h 中的巨集SCULL_QUANTUM 和SCULL_QSET, 在模塊加載時設定整數值scull_quantum 和scull_qset, 或者使用ioctl 在運行時改變當前值和預設值.

使用巨集定義和一個整數值來進行編譯時和加載時配置, 是讓人聯想起major number如何選擇的. 我們在driver中任何與策略相關或專斷的值上運用這個技術.

餘下的唯一問題是如果選擇預設值. 在這個特殊情況下, 問題是在下列兩種情況中找到最好的平衡, 由填充了一半的quantum和quantum set導致memory浪費, 以及quantum和quantum set很小的情況下allocation ,deallocation,和pointer連接 的開銷. 另外, kmalloc 的內部設計應當考慮進去. (現在我們不追求這點, 不過; kmalloc 的內部在第8 章探索.)預設值的選擇來自假設測試時可能有大量數據寫進scull, 儘管設備的正常使用最可能只傳送幾KB 數據.

我們已經見過內部代表我們設備的scull_dev 結構. 結構的quantum 和qset 分別代表設備的quantum和quantum set大小. 實際數據, 但是, 是由一個不同的結構跟蹤, 我們稱為struct scull_qset:

struct scull_qset {
 void **data;
 struct scull_qset *next;
};
下一個代碼片段展示了實際中struct scull_dev 和struct scull_qset 是如何被用來持有數據的. sucll_trim 函數負責釋放整個數據區, 由scull_open 在文件為寫而打開時調用. 它簡單地遍歷列表並且釋放它發現的任何quantum和quantum set.

int scull_trim(struct scull_dev *dev)
{
        struct scull_qset *next, *dptr;
        int qset = dev->qset; /* "dev" is not-null */
        int i;
        for (dptr = dev->data; dptr; dptr = next)
        { /* all the list items */
                if (dptr->data) {
                        for (i = 0; i < qset; i++)
                                kfree(dptr->data[i]);
                        kfree(dptr->data);
                        dptr->data = NULL;
                }

                next = dptr->next;
                kfree(dptr);
        }
        dev->size = 0;
        dev->quantum = scull_quantum;
        dev->qset = scull_qset;
        dev->data = NULL;
        return 0;
}
scull_trim 也用在模塊清理函數中, 來歸還scull 使用的memory給系統.

2015年3月29日 星期日

LDD3 潤飾 3.5 open and release

3.5. open 和release

到此我們已經快速瀏覽了這些成員, 我們開始在真實的scull 函數中使用它們.

3.5.1. open 方法

open 方法提供給 driver來做任何的初始化來準備後續的操作. 在大部分 driver中, open 應當進行下面的工作:

檢查設備特定的錯誤(例如設備沒準備好, 或者類似的硬件錯誤

如果它第一次打開, 初始化設備

如果需要的話, 更新f_op pointer.

分配並填充要放進filp->private_data 的任何資料結構

但是, 事情的第一步常常是確定打開哪個設備. 記住open 方法的原型是:

int (*open)(struct inode *inode, struct file *filp);
inode 參數有我們需要的信息,以它的i_cdev 成員的形式, 裡面包含我們之前建立的cdev 結構. 唯一的問題是通常我們不想要cdev 結構本身, 我們需要的是包含cdev 結構的scull_dev 結構. C 語言使程序員玩弄各種技巧來做這種轉換; 但是, 這種技巧編程是易出錯的, 並且導致別人難於閱讀和理解代碼. 幸運的是, 在這種情況下, kernal hacker 已經為我們實現了這個技巧, 以container_of 巨集的形式, 在<linux/kernel.h> 中定義:

container_of(pointer, container_type, container_field);
這個巨集使用一個指向container_field 類型的成員的pointer, 它在一個container_type 類型的結構中, 並且return一個pointer指向包含結構. 在scull_open, 這個巨集用來找到適當的設備結構:

struct scull_dev *dev; /* device information */
dev = container_of(inode->i_cdev, struct scull_dev, cdev);
filp->private_data = dev; /* for other methods */
一旦它找到scull_dev 結構, scull 在file structure的private_data 成員中存儲一個它的pointer, 為以後更易存取.

識別打開的設備的另外的方法是查看存儲在inode 結構的minor number. 如果你使用register_chrdev 註冊你的設備, 你必須使用這個技術. 確認使用iminor 從inode 結構中獲取次編號, 並且確定它對應一個你的 driver真正準備好處理的設備.

scull_open 的代碼(稍微簡化過)是:

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 */
}
代碼看來相當稀疏, 因為在調用open 時它沒有做任何特別的設備處理. 它不需要, 因為scull 設備設計為全局的和永久的. 特別地, 沒有如"在第一次打開時初始化設備"等動作, 因為我們不為scull 保持打開 counter.

唯一在設備上的真實操作是當設備為寫而打開時將它截取為長度為0. 這樣做是因為, 在設計上, 用一個短的文件覆蓋一個scull 設備導致一個短的設備數據區. 這類似於為寫而打開一個常規文件, 將其截短為0. 如果設備為讀而打開, 這個操作什麼都不做.

在我們查看其他scull 特性的代碼時將看到一個真實的初始化如何起作用的.

3.5.2. release 方法

release 方法的角色是open 的反面. 有時你會發現方法的實現稱為device_close, 而不是device_release. 任一方式, 設備方法應當進行下面的任務:

釋放open 分配在filp->private_data 中的任何東西

在最後的close 關閉設備

scull的基本形式沒有硬件去關閉,因此需要的代碼是最少的: [ 12 ]

int scull_release(struct inode *inode, struct file *filp)
{
 return 0;
}
你可能想知道當一個設備文件關閉次數超過它被打開的次數會發生什麼. 畢竟, dup 和fork system calls不調用open 來創建打開文件的拷貝; 每個拷貝接著在程序終止時被關閉. 例如,大部分程序不打開它們的stdin 文件(或設備), 但是它們都以關閉它結束.  driver如何知道一個打開的設備文件已經真正被關閉?

答案簡單: 不是每個close system call引起調用release 方法. 只有真正釋放設備資料結構的system call會調用這個方法-- 因此得名. 內核維持一個file structure被使用多少次的 counter. fork 和dup 都不創建新文件(只有open 這樣); 它們只遞增正存在的結構中的 counter. closesystem call僅在file structure counter掉到0 時執行release 方法, 這在結構被銷毀時發生. release 方法和close system call之間的這種關係保證了你的 driver一次open 只看到一次release.

注意, flush 方法在每次應用程序調用close 時都被調用. 但是, 很少 driver實現flush, 因為常常在close 時沒有什麼要做, 除非調用release.

如你會想到的, 前面的討論即便是應用程序沒有明顯地關閉它打開的文件也適用: 內核在進程exit 時自動關閉了任何文件, 通過在內部使用close system call.


[ 12 ]其他風味的設備由不同的函數關閉,因為scull_open為每個設備替換了不同的filp->f_op.我們在介紹每種風味時再討論它們.