16.2. 塊設(shè)備操作

2018-02-24 15:50 更新

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

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

16.2.1.?open 和 release 方法

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

open 方法看起來非常類似于它的字符驅(qū)動(dòng)對(duì)等體; 它用相關(guān)的節(jié)點(diǎn)和文件結(jié)構(gòu)指針作為參數(shù). 當(dāng)一個(gè)節(jié)點(diǎn)引用一個(gè)塊設(shè)備, i_bdev->bd_disk 包含一個(gè)指向關(guān)聯(lián) gendisk 結(jié)構(gòu)的指針; 這個(gè)指針可用來獲得一個(gè)驅(qū)動(dòng)的給設(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í)器, 如果有一個(gè)是活的. 注意我們不加鎖設(shè)備自旋鎖, 直到定時(shí)器被刪除后; 如果定時(shí)器函數(shù)在我們可刪除它之前運(yùn)行, 反過來做會(huì)有死鎖. 在設(shè)備加鎖下, 我們調(diào)用一個(gè)內(nèi)核函數(shù), 稱為 check_disk_change, 來檢查是否已發(fā)生一個(gè)介質(zhì)改變. 可能有人爭(zhēng)論說內(nèi)核應(yīng)當(dāng)做這個(gè)調(diào)用, 但是標(biāo)準(zhǔn)模式是為驅(qū)動(dòng)來在打開時(shí)處理它.

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

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


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;
}

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

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

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

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

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


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ū)動(dòng)對(duì)新介質(zhì)的操作, 如果有. 在調(diào)用 revalidate 之后, 內(nèi)核試圖重新讀分區(qū)表并且啟動(dòng)這個(gè)設(shè)備. sbull 的實(shí)現(xiàn)僅僅復(fù)位 media_change 標(biāo)志并且清零設(shè)備內(nèi)存來模擬一個(gè)空盤插入.


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

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


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 */
}

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

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

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

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)