W3Cschool
恭喜您成為首批注冊(cè)用戶
獲得88經(jīng)驗(yàn)值獎(jiǎng)勵(lì)
提供存取控制對(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 操作中.
提供存取控制的強(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ù)制.
單打開設(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).
當(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í).
管理存取控制的另一個(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;
}
Copyright©2021 w3cschool編程獅|閩ICP備15016281號(hào)-3|閩公網(wǎng)安備35020302033924號(hào)
違法和不良信息舉報(bào)電話:173-0602-2364|舉報(bào)郵箱:jubao@eeedong.com
掃描二維碼
下載編程獅App
編程獅公眾號(hào)
聯(lián)系方式:
更多建議: