6.6. 在一個(gè)設(shè)備文件上的存取控制

2018-02-24 15:49 更新

6.6.?在一個(gè)設(shè)備文件上的存取控制

提供存取控制對(duì)于一個(gè)設(shè)備節(jié)點(diǎn)來(lái)說(shuō)有時(shí)是至關(guān)重要的. 不僅是非授權(quán)用戶不能使用設(shè)備(由文件系統(tǒng)許可位所強(qiáng)加的限制), 而且有時(shí)只有授權(quán)用戶才應(yīng)當(dāng)被允許來(lái)打開設(shè)備一次.

這個(gè)問(wèn)題類似于使用 ttys 的問(wèn)題. 在那個(gè)情況下, login 進(jìn)程改變?cè)O(shè)備節(jié)點(diǎn)的所有權(quán), 無(wú)論何時(shí)一個(gè)用戶登錄到系統(tǒng), 為了阻止其他的用戶打擾或者偷聽這個(gè) tty 的數(shù)據(jù)流. 但是, 僅僅為了保證對(duì)它的唯一讀寫而使用一個(gè)特權(quán)程序在每次打開它時(shí)改變一個(gè)設(shè)備的擁有權(quán)是不實(shí)際的.

迄今所顯示的代碼沒(méi)有實(shí)現(xiàn)任何的存取控制, 除了文件系統(tǒng)許可位. 如果系統(tǒng)調(diào)用 open 將請(qǐng)求遞交給驅(qū)動(dòng), open 就成功了. 我們現(xiàn)在介紹幾個(gè)新技術(shù)來(lái)實(shí)現(xiàn)一些額外的檢查.

每個(gè)在本節(jié)中展示的設(shè)備有和空的 scull 設(shè)備有相同的行為(即, 它實(shí)現(xiàn)一個(gè)持久的內(nèi)存區(qū))但是在存取控制方面和 scull 不同, 這個(gè)實(shí)現(xiàn)在 open 和 release 操作中.

6.6.1.?單 open 設(shè)備

提供存取控制的強(qiáng)力方式是只允許一個(gè)設(shè)備一次被一個(gè)進(jìn)程打開(單次打開). 這個(gè)技術(shù)最好是避免因?yàn)樗拗屏擞脩舻撵`活性. 一個(gè)用戶可能想運(yùn)行不同的進(jìn)程在一個(gè)設(shè)備上, 一個(gè)讀狀態(tài)信息而另一個(gè)寫數(shù)據(jù). 在某些情況下, 用戶通過(guò)一個(gè)外殼腳本運(yùn)行幾個(gè)簡(jiǎn)單的程序可做很多事情, 只要它們可并發(fā)存取設(shè)備. 換句話說(shuō), 實(shí)現(xiàn)一個(gè)單 open 行為實(shí)際是在創(chuàng)建策略, 這樣可能會(huì)介入你的用戶要做的范圍.

只允許單個(gè)進(jìn)程打開設(shè)備有不期望的特性, 但是它也是一個(gè)設(shè)備驅(qū)動(dòng)最簡(jiǎn)單實(shí)現(xiàn)的存取控制, 因此它在這里被展示. 這個(gè)源碼是從一個(gè)稱為 scullsingle 的設(shè)備中提取的.

scullsingle 設(shè)備維護(hù)一個(gè) atiomic_t 變量, 稱為 scull_s_available; 這個(gè)變量被初始化為值 1, 表示設(shè)備確實(shí)可用. open 調(diào)用遞減并測(cè)試 scull_s_available 并拒絕存取如果其他人已經(jīng)使設(shè)備打開.


static atomic_t scull_s_available = ATOMIC_INIT(1);
static int scull_s_open(struct inode *inode, struct file *filp)
{

        struct scull_dev *dev = &scull_s_device; /* device information */
        if (! atomic_dec_and_test (&scull_s_available))
        {
                atomic_inc(&scull_s_available);
                return -EBUSY; /* already open */
        }

        /* then, everything else is copied from the bare scull device */
        if ( (filp->f_flags & O_ACCMODE) == O_WRONLY)

                scull_trim(dev);
        filp->private_data = dev;
        return 0; /* success */
}

release 調(diào)用, 另一方面, 標(biāo)識(shí)設(shè)備為不再忙:


static int scull_s_release(struct inode *inode, struct file *filp)
{
        atomic_inc(&scull_s_available); /* release the device */
        return 0;
}

正常地, 我們建議你將 open 標(biāo)志 scul_s_available 放在設(shè)備結(jié)構(gòu)中( scull_dev 這里), 因?yàn)? 從概念上, 它屬于這個(gè)設(shè)備. scull 驅(qū)動(dòng), 但是, 使用獨(dú)立的變量來(lái)保持這個(gè)標(biāo)志, 因此它可使用和空 scull 設(shè)備同樣的設(shè)備結(jié)構(gòu)和方法, 并且最少的代碼復(fù)制.

6.6.2.?一次對(duì)一個(gè)用戶限制存取

單打開設(shè)備之外的下一步是使一個(gè)用戶在多個(gè)進(jìn)程中打開一個(gè)設(shè)備, 但是一次只允許一個(gè)用戶打開設(shè)備. 這個(gè)解決方案使得容易測(cè)試設(shè)備, 因?yàn)橛脩粢淮慰蓮膸讉€(gè)進(jìn)程讀寫, 但是假定這個(gè)用戶負(fù)責(zé)維護(hù)在多次存取中的數(shù)據(jù)完整性. 這通過(guò)在 open 方法中添加檢查來(lái)實(shí)現(xiàn); 這樣的檢查在通常的許可檢查后進(jìn)行, 并且只能使存取更加嚴(yán)格, 比由擁有者和組許可位所指定的限制. 這是和 ttys 所用的存取策略是相同的, 但是它不依賴于外部的特權(quán)程序.

這些存取策略實(shí)現(xiàn)地有些比單打開策略要奇怪. 在這個(gè)情況下, 需要 2 項(xiàng): 一個(gè)打開計(jì)數(shù)和設(shè)備擁有者 uid. 再一次, 給這個(gè)項(xiàng)的最好的地方是在設(shè)備結(jié)構(gòu)中; 我們的例子使用全局變量代替, 是因?yàn)橹盀?scullsingle 所解釋的的原因. 這個(gè)設(shè)備的名子是 sculluid.

open 調(diào)用在第一次打開時(shí)同意了存取但是記住了設(shè)備擁有者. 這意味著一個(gè)用戶可打開設(shè)備多次, 因此允許協(xié)調(diào)多個(gè)進(jìn)程對(duì)設(shè)備并發(fā)操作. 同時(shí), 沒(méi)有其他用戶可打開它, 這樣避免了外部干擾. 因?yàn)檫@個(gè)函數(shù)版本幾乎和之前的一致, 這樣相關(guān)的部分在這里被復(fù)制:


spin_lock(&scull_u_lock);
if (scull_u_count &&
                (scull_u_owner != current->uid) && /* allow user */
                (scull_u_owner != current->euid) && /* allow whoever did su */
                !capable(CAP_DAC_OVERRIDE))
{ /* still allow root */
        spin_unlock(&scull_u_lock);
        return -EBUSY; /* -EPERM would confuse the user */
}

if (scull_u_count == 0)
        scull_u_owner = current->uid; /* grab it */

scull_u_count++;
spin_unlock(&scull_u_lock);

注意 sculluid 代碼有 2 個(gè)變量 ( scull_u_owner 和 scull_u_count)來(lái)控制對(duì)設(shè)備的存取, 并且這樣可被多個(gè)進(jìn)程并發(fā)地存取. 為使這些變量安全, 我們使用一個(gè)自旋鎖控制對(duì)它們的存取( scull_u_lock ). 沒(méi)有這個(gè)鎖, 2 個(gè)(或多個(gè))進(jìn)程可同時(shí)測(cè)試 scull_u_count , 并且都可能認(rèn)為它們擁有設(shè)備的擁有權(quán). 這里使用一個(gè)自旋鎖, 是因?yàn)檫@個(gè)鎖被持有極短的時(shí)間, 并且驅(qū)動(dòng)在持有這個(gè)鎖時(shí)不做任何可睡眠的事情.

我們選擇返回 -EBUSY 而不是 -EPERM, 即便這個(gè)代碼在進(jìn)行許可檢測(cè), 為了給一個(gè)被拒絕存取的用戶指出正確的方向. 對(duì)于"許可拒絕"的反應(yīng)常常是檢查 /dev 文件的模式和擁有者, 而"設(shè)備忙"正確地建議用戶應(yīng)當(dāng)尋找一個(gè)已經(jīng)在使用設(shè)備的進(jìn)程.

這個(gè)代碼也檢查來(lái)看是否正在試圖打開的進(jìn)程有能力來(lái)覆蓋文件存取許可; 如果是這樣, open 被允許即便打開進(jìn)程不是設(shè)備的擁有者. CAP_DAC_OVERRIDE 能力在這個(gè)情況中適合這個(gè)任務(wù).

release 方法看來(lái)如下:


static int scull_u_release(struct inode *inode, struct file *filp)
{
        spin_lock(&scull_u_lock);
        scull_u_count--; /* nothing else */
        spin_unlock(&scull_u_lock);
        return 0;
}

再次, 我們?cè)谛薷挠?jì)數(shù)之前必須獲得鎖, 來(lái)確保我們沒(méi)有和另一個(gè)進(jìn)程競(jìng)爭(zhēng).

6.6.3.?阻塞 open 作為對(duì) EBUSY 的替代

當(dāng)設(shè)備不可存取, 返回一個(gè)錯(cuò)誤常常是最合理的方法, 但是有些情況用戶可能更愿意等待設(shè)備.

例如, 如果一個(gè)數(shù)據(jù)通訊通道既用于規(guī)律地預(yù)期地傳送報(bào)告(使用 crontab), 也用于根據(jù)用戶的需要偶爾地使用, 對(duì)于被安排的操作最好是稍微延遲, 而不是只是因?yàn)橥ǖ喇?dāng)前忙而失敗.

這是程序員在設(shè)計(jì)一個(gè)設(shè)備驅(qū)動(dòng)時(shí)必須做的一個(gè)選擇之一, 并且正確的答案依賴正被解決的實(shí)際問(wèn)題.

對(duì) EBUSY 的替代, 如同你可能已經(jīng)想到的, 是實(shí)現(xiàn)阻塞 open. scullwuid 設(shè)備是一個(gè)在打開時(shí)等待設(shè)備而不是返回 -EBUSY 的 sculluid 版本. 它不同于 sculluid 只在下面的打開操作部分:


spin_lock(&scull_w_lock);
while (! scull_w_available())
{
        spin_unlock(&scull_w_lock);
        if (filp->f_flags & O_NONBLOCK)
                return -EAGAIN;
        if (wait_event_interruptible (scull_w_wait, scull_w_available()))
                return -ERESTARTSYS; /* tell the fs layer to handle it */
        spin_lock(&scull_w_lock);
}
if (scull_w_count == 0)
        scull_w_owner = current->uid; /* grab it */
scull_w_count++;
spin_unlock(&scull_w_lock);

這個(gè)實(shí)現(xiàn)再次基于一個(gè)等待隊(duì)列. 如果設(shè)備當(dāng)前不可用, 試圖打開它的進(jìn)程被放置到等待隊(duì)列直到擁有進(jìn)程關(guān)閉設(shè)備.

release 方法, 接著, 負(fù)責(zé)喚醒任何掛起的進(jìn)程:


static int scull_w_release(struct inode *inode, struct file *filp)
{

 int temp;
 spin_lock(&scull_w_lock);
 scull_w_count--;
 temp = scull_w_count;
 spin_unlock(&scull_w_lock); 
    if (temp == 0)
 wake_up_interruptible_sync(&scull_w_wait); /* awake other uid's */
 return 0;
}

這是一個(gè)例子, 這里調(diào)用 wake_up_interruptible_sync 是有意義的. 當(dāng)我們做這個(gè)喚醒, 我們只是要返回到用戶空間, 這對(duì)于系統(tǒng)是一個(gè)自然的調(diào)度點(diǎn). 當(dāng)我們做這個(gè)喚醒時(shí)不是潛在地重新調(diào)度, 最好只是調(diào)用 "sync" 版本并且完成我們的工作.

阻塞式打開實(shí)現(xiàn)的問(wèn)題是對(duì)于交互式用戶真的不好, 他們不得不猜想哪里出錯(cuò)了. 交互式用戶常常調(diào)用標(biāo)準(zhǔn)命令, 例如 cp 和 tar, 并且不能增加 O_NONBLOCK 到 open 調(diào)用. 有些使用磁帶驅(qū)動(dòng)器做備份的人可能喜歡有一個(gè)簡(jiǎn)單的"設(shè)備或者資源忙"消息, 來(lái)替代被扔在一邊猜為什么今天的硬盤驅(qū)動(dòng)器這么安靜, 此時(shí) tar 應(yīng)當(dāng)在掃描它.

這類的問(wèn)題(需要一個(gè)不同的, 不兼容的策略對(duì)于同一個(gè)設(shè)備)最好通過(guò)為每個(gè)存取策略實(shí)現(xiàn)一個(gè)設(shè)備節(jié)點(diǎn)來(lái)實(shí)現(xiàn). 這個(gè)做法的一個(gè)例子可在 linux 磁帶驅(qū)動(dòng)中找到, 它提供了多個(gè)設(shè)備文件給同一個(gè)設(shè)備. 例如, 不同的設(shè)備文件將使驅(qū)動(dòng)器使用或者不用壓縮記錄, 或者自動(dòng)回繞磁帶當(dāng)設(shè)備被關(guān)閉時(shí).

6.6.4.?在 open 時(shí)復(fù)制設(shè)備

管理存取控制的另一個(gè)技術(shù)是創(chuàng)建設(shè)備的不同的私有拷貝, 根據(jù)打開它的進(jìn)程.

明顯地, 這只當(dāng)設(shè)備沒(méi)有綁定到一個(gè)硬件實(shí)體時(shí)有可能; scull 是一個(gè)這樣的"軟件"設(shè)備的例子. /dev/tty 的內(nèi)部使用類似的技術(shù)來(lái)給它的進(jìn)程一個(gè)不同的 /dev 入口點(diǎn)呈現(xiàn)的視圖. 當(dāng)設(shè)備的拷貝被軟件驅(qū)動(dòng)創(chuàng)建, 我們稱它們?yōu)樘摂M設(shè)備--就象虛擬控制臺(tái)使用一個(gè)物理 tty 設(shè)備.

結(jié)構(gòu)這類的存取控制很少需要, 這個(gè)實(shí)現(xiàn)可說(shuō)明內(nèi)核代碼是多么容易改變應(yīng)用程序的對(duì)周圍世界的看法(即, 計(jì)算機(jī)).

/dev/scullpriv 設(shè)備節(jié)點(diǎn)在 scull 軟件包只實(shí)現(xiàn)虛擬設(shè)備. scullpriv 實(shí)現(xiàn)使用了進(jìn)程的控制 tty 的設(shè)備號(hào)作為對(duì)存取虛擬設(shè)備的鑰匙. 但是, 你可以輕易地改變代碼來(lái)使用任何整數(shù)值作為鑰匙; 每個(gè)選擇都導(dǎo)致一個(gè)不同的策略. 例如, 使用 uid 導(dǎo)致一個(gè)不同地虛擬設(shè)備給每個(gè)用戶, 而使用一個(gè) pid 鑰匙創(chuàng)建一個(gè)新設(shè)備為每個(gè)存取它的進(jìn)程.

使用控制終端的決定打算用在易于使用 I/O 重定向測(cè)試設(shè)備: 設(shè)備被所有的在同一個(gè)虛擬終端運(yùn)行的命令所共享, 并且保持獨(dú)立于在另一個(gè)終端上運(yùn)行的命令所見到的.

open 方法看來(lái)象下面的代碼. 它必須尋找正確的虛擬設(shè)備并且可能創(chuàng)建一個(gè). 這個(gè)函數(shù)的最后部分沒(méi)有展示, 因?yàn)樗截愖钥盏?scull, 我們已經(jīng)見到過(guò).


/* The clone-specific data structure includes a key field */
struct scull_listitem
{
        struct scull_dev device;
        dev_t key;
        struct list_head list;

};
/* The list of devices, and a lock to protect it */
static LIST_HEAD(scull_c_list);
static spinlock_t scull_c_lock = SPIN_LOCK_UNLOCKED;

/* Look for a device or create one if missing */
static struct scull_dev *scull_c_lookfor_device(dev_t key)
{

        struct scull_listitem *lptr;
        list_for_each_entry(lptr, &scull_c_list, list)
        {
                if (lptr->key == key)
                        return &(lptr->device);
        }

        /* not found */
        lptr = kmalloc(sizeof(struct scull_listitem), GFP_KERNEL);
        if (!lptr)
                return NULL;
        /* initialize the device */
        memset(lptr, 0, sizeof(struct scull_listitem));
        lptr->key = key;
        scull_trim(&(lptr->device)); /* initialize it */
        init_MUTEX(&(lptr->device.sem));

        /* place it in the list */
        list_add(&lptr->list, &scull_c_list);

        return &(lptr->device);
}

static int scull_c_open(struct inode *inode, struct file *filp)
{
        struct scull_dev *dev;

        dev_t key;
        if (!current->signal->tty)
        {
                PDEBUG("Process \"%s\" has no ctl tty\n", current->comm);
                return -EINVAL;

        }
        key = tty_devnum(current->signal->tty);

        /* look for a scullc device in the list */
        spin_lock(&scull_c_lock);
        dev = scull_c_lookfor_device(key);
        spin_unlock(&scull_c_lock);

        if (!dev)
                return -ENOMEM;

        /* then, everything else is copied from the bare scull device */

這個(gè) release 方法沒(méi)有做特殊的事情. 它將在最后的關(guān)閉時(shí)正常地釋放設(shè)備, 但是我們不選擇來(lái)維護(hù)一個(gè) open 計(jì)數(shù)而來(lái)簡(jiǎn)化對(duì)驅(qū)動(dòng)的測(cè)試. 如果設(shè)備在最后的關(guān)閉被釋放, 你將不能讀相同的數(shù)據(jù)在寫入設(shè)備之后, 除非一個(gè)后臺(tái)進(jìn)程將保持它打開. 例子驅(qū)動(dòng)采用了簡(jiǎn)單的方法來(lái)保持?jǐn)?shù)據(jù), 以便在下一次打開時(shí), 你會(huì)發(fā)現(xiàn)它在那里. 設(shè)備在 scull_cleanup 被調(diào)用時(shí)釋放.

這個(gè)代碼使用通用的 linux 鏈表機(jī)制, 而不是從頭開始實(shí)現(xiàn)相同的功能. linux 鏈表在第 11 章中討論.

這里是 /dev/scullpriv 的 release 實(shí)現(xiàn), 它結(jié)束了對(duì)設(shè)備方法的討論.


static int scull_c_release(struct inode *inode, struct file *filp)
{
        /*
        *Nothing to do, because the device is persistent.
        *A `real' cloned device should be freed on last close */

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

掃描二維碼

下載編程獅App

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

編程獅公眾號(hào)