16.2. 塊設(shè)備操作

2018-02-24 15:50 更新

16.2.?塊設(shè)備操作

在前面一節(jié)中我們對 block_device_operations 有了簡短的介紹. 現(xiàn)在我們詳細(xì)些看看這些操作, 在進(jìn)入請求處理之前. 為此, 是時間提到 sbull 驅(qū)動的另一個特性: 它假裝是一個可移出的設(shè)備. 無論何時最后一個用戶關(guān)閉設(shè)備, 一個 30 秒的定時器被設(shè)置; 如果設(shè)備在這個時間內(nèi)不被打開, 設(shè)備的內(nèi)容被清除, 并且內(nèi)核被告知介質(zhì)已被改變. 30 秒延遲給了用戶時間, 例如, 來卸載一個 sbull 設(shè)備在創(chuàng)建一個文件系統(tǒng)之后.

16.2.1.?open 和 release 方法

為實(shí)現(xiàn)模擬的介質(zhì)移出, 當(dāng)最后一個用戶已關(guān)閉設(shè)備時 sbull 必須知道. 一個用戶計數(shù)被驅(qū)動維護(hù). 它是 open 和 close 方法的工作來保持這個計數(shù)最新.

open 方法看起來非常類似于它的字符驅(qū)動對等體; 它用相關(guān)的節(jié)點(diǎn)和文件結(jié)構(gòu)指針作為參數(shù). 當(dāng)一個節(jié)點(diǎn)引用一個塊設(shè)備, i_bdev->bd_disk 包含一個指向關(guān)聯(lián) gendisk 結(jié)構(gòu)的指針; 這個指針可用來獲得一個驅(qū)動的給設(shè)備的內(nèi)部數(shù)據(jù)結(jié)構(gòu). 即, 實(shí)際上, sbull open 方法做的第一件事:


static int sbull_open(struct inode *inode, struct file *filp)
{
        struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
        del_timer_sync(&dev->timer);
        filp->private_data = dev;
        spin_lock(&dev->lock)
        ;
        if (! dev->users)
                check_disk_change(inode->i_bdev);
        dev->users++;
        spin_unlock(&dev->lock)
        ;
        return 0;
}

一旦 sbull_open 有它的設(shè)備結(jié)構(gòu)指針, 它調(diào)用 del_timer_sync 來去掉"介質(zhì)移出"定時器, 如果有一個是活的. 注意我們不加鎖設(shè)備自旋鎖, 直到定時器被刪除后; 如果定時器函數(shù)在我們可刪除它之前運(yùn)行, 反過來做會有死鎖. 在設(shè)備加鎖下, 我們調(diào)用一個內(nèi)核函數(shù), 稱為 check_disk_change, 來檢查是否已發(fā)生一個介質(zhì)改變. 可能有人爭論說內(nèi)核應(yīng)當(dāng)做這個調(diào)用, 但是標(biāo)準(zhǔn)模式是為驅(qū)動來在打開時處理它.

最后一步是遞增用戶計數(shù)并且返回.

釋放方法的任務(wù)是, 相反, 來遞減用戶計數(shù), 以及, 如果被指示了, 啟動介質(zhì)移出定時器:


static int sbull_release(struct inode *inode, struct file *filp)
{
        struct sbull_dev *dev = inode->i_bdev->bd_disk->private_data;
        spin_lock(&dev->lock)
        ;
        dev->users--;
        if (!dev->users)
        {
                dev->timer.expires = jiffies + INVALIDATE_DELAY;
                add_timer(&dev->timer);
        }

        spin_unlock(&dev->lock)
        ;
        return 0;
}

在一個處理真實(shí)的硬件設(shè)備的驅(qū)動中, open 和 release 方法應(yīng)當(dāng)相應(yīng)地設(shè)置驅(qū)動和硬件的狀態(tài). 這個工作可能包括起停磁盤, 加鎖一個可移出設(shè)備的門, 分配 DMA 緩沖, 等等.

你可能奇怪誰實(shí)際上打開了一個塊設(shè)備. 有一些操作可導(dǎo)致一個塊設(shè)備從用戶空間直接打開; 這包括分區(qū)一個磁盤, 在一個分區(qū)上建立一個文件系統(tǒng), 或者運(yùn)行一個文件系統(tǒng)檢查器. 當(dāng)加載一個分區(qū)時, 塊驅(qū)動也可看到一個 open 調(diào)用. 在這個情況下, 沒有用戶空間進(jìn)程持有一個這個設(shè)備的打開的文件描述符; 相反, 打開的文件被內(nèi)核自身持有. 塊驅(qū)動無法知道一個加載操作(它從內(nèi)核打開設(shè)備)和調(diào)用如 mkfs 工具(從用戶空間打開它)之間的差別.

16.2.2.?支持可移出的介質(zhì)

block_device_operations 結(jié)構(gòu)包含 2 個方法來支持可移出介質(zhì). 如果你為一個非可移出設(shè)備編寫一個驅(qū)動, 你可安全地忽略這些方法. 它們的實(shí)現(xiàn)是相對直接的.

media_changed 方法被調(diào)用( 從 check_disk_change ) 來看是否介質(zhì)已經(jīng)被改變; 它應(yīng)當(dāng)返回一個非零值, 如果已經(jīng)發(fā)生. sbull 實(shí)現(xiàn)是簡單的; 它查詢一個已被設(shè)置的標(biāo)志, 如果介質(zhì)移出定時器已超時:


int sbull_media_changed(struct gendisk *gd)
{
        struct sbull_dev *dev = gd->private_data;
        return dev->media_change;
}

revalidate 方法在介質(zhì)改變后被調(diào)用; 它的工作是做任何需要的事情來準(zhǔn)備驅(qū)動對新介質(zhì)的操作, 如果有. 在調(diào)用 revalidate 之后, 內(nèi)核試圖重新讀分區(qū)表并且啟動這個設(shè)備. sbull 的實(shí)現(xiàn)僅僅復(fù)位 media_change 標(biāo)志并且清零設(shè)備內(nèi)存來模擬一個空盤插入.


int sbull_revalidate(struct gendisk *gd)
{
        struct sbull_dev *dev = gd->private_data;

        if (dev->media_change)
        {
                dev->media_change = 0;
                memset (dev->data, 0, dev->size);
        }
        return 0;
}

16.2.3.?ioctl 方法

塊設(shè)備可提供一個 ioctl 方法來進(jìn)行設(shè)備控制函數(shù). 高層的塊子系統(tǒng)代碼在你的驅(qū)動能見到它們之前解釋許多的 ioctl 命令, 但是( 全部內(nèi)容見 drivers/block/ioctl.c , 在內(nèi)核源碼中). 實(shí)際上, 一個現(xiàn)代的塊驅(qū)動根本不必實(shí)現(xiàn)許多的 ioctl 命令.

sbull ioctl 方法只處理一個命令 -- 一個對設(shè)備的結(jié)構(gòu)的請求:


int sbull_ioctl (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)
{
        long size;
        struct hd_geometry geo;
        struct sbull_dev *dev = filp->private_data;

        switch(cmd)
        {
        case HDIO_GETGEO:
                /*
                * Get geometry: since we are a virtual device, we have to make
                * up something plausible. So we claim 16 sectors, four heads,
                * and calculate the corresponding number of cylinders. We set the
                * start of data at sector four.
                */ 
                size = dev->size*(hardsect_size/KERNEL_SECTOR_SIZE);
                geo.cylinders = (size & ~0x3f) >> 6;
                geo.heads = 4;
                geo.sectors = 16;
                geo.start = 4;
                if (copy_to_user((void __user *) arg, &geo, sizeof(geo)))
                        return -EFAULT;
                return 0;
        }

        return -ENOTTY; /* unknown command */
}

提供排列信息可能看來象一個奇怪的任務(wù), 因?yàn)槲覀兊脑O(shè)備是純粹虛擬的并且和磁道和柱面沒任何關(guān)系. 甚至大部分真正的塊硬件都已很多年不再有很多更復(fù)雜的結(jié)構(gòu). 內(nèi)核不關(guān)心一個塊設(shè)備的排列; 只把它看作一個扇區(qū)的線性數(shù)組. 但是, 有某些用戶工具仍然想能夠查詢一個磁盤的排列. 特別的, fdisk 工具, 它編輯分區(qū)表, 依靠柱面信息并且如果這個信息沒有則不能正確工作.

我們希望 sbull 設(shè)備是可分區(qū)的, 即便使用老的, 簡單的工具. 因此, 我們已提供了一個 ioctl 方法, 這個方法提供了一個可靠的能夠匹配我們設(shè)備容量的排列的假象. 大部分磁盤驅(qū)動做類似的事情. 注意, 象通常, 扇區(qū)計數(shù)被轉(zhuǎn)換, 如果需要, 來匹配內(nèi)核使用的 512-字節(jié) 的慣例.

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號