devices子系統
使用devices 子系統可以允許或者拒絕cgroup中的程序通路裝置。devices子系統有三個控制檔案:devices.allow,devices.deny,devices.list。devices.allow用于指定cgroup中的程序可以通路的裝置,devices.deny用于指定cgroup中的程序不能通路的裝置,devices.list用于報告cgroup中的程序通路的裝置。devices.allow檔案中包含若幹條目,每個條目有四個字段:type、major、minor 和 access。type、major 和 minor 字段中使用的值對應 Linux 配置設定的裝置。
type指定裝置類型:
a - 應用所有裝置,可以是字元裝置,也可以是塊裝置
b- 指定塊裝置
c - 指定字元裝置
major和minor指定裝置的主次裝置号。
access 則指定相應的權限:
r - 允許任務從指定裝置中讀取
w - 允許任務寫入指定裝置
m - 允許任務生成還不存在的裝置檔案
devices子系統是通過提供device whilelist 來實作的。與其他子系統一樣,devices子系統也有一個内嵌了cgroup_subsystem_state的結構來管理資源。在devices子系統中,這個結構是:
struct dev_cgroup {
struct cgroup_subsys_state css;
struct list_head whitelist;
};
這個結構體除了通用的cgroup_subsystem_state之外,就隻有一個連結清單指針,而這個連結清單指針指向了該cgroup中的程序可以通路的devices whilelist。
下面我們來看一下devices子系統如何管理whilelist。在devices子系統中,定義了一個叫dev_whitelist_item的結構來管理可以通路的device,對應于devices.allow中的一個條目。這個結構體的定義如下:
struct dev_whitelist_item {
u32 major, minor;
short type;
short access;
struct list_head list;
struct rcu_head rcu;
};
major,minor用于指定裝置的主次裝置号,type用于指定裝置類型,type取值可以是:#define DEV_BLOCK 1
#define DEV_CHAR 2
#define DEV_ALL 4
對應于之前devices.allow檔案中三種情況。
access用于相應的通路權限,access取值可以是:
#define ACC_MKNOD 1
#define ACC_READ 2
#define ACC_WRITE 4
也和之前devices.allow檔案中的情況對應。
List字段用于将該結構體連到相應的dev_cgroup中whitelist指向的連結清單。
通過以上資料結構,devices子系統就能管理一個cgroup的程序可以通路的devices了。 光有資料結構還不行,還要有具體實作才行。devices子系統通過實作兩個函數供核心調用來實作控制cgroup中的程序能夠通路的devices。首先我們來第一個函數:
int devcgroup_inode_permission(struct inode *inode, int mask)
{
struct dev_cgroup *dev_cgroup;
struct dev_whitelist_item *wh;
dev_t device = inode->i_rdev;
if (!device)
return 0;
if (!S_ISBLK(inode->i_mode) && !S_ISCHR(inode->i_mode))
return 0;
rcu_read_lock();
dev_cgroup = task_devcgroup(current);
list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) {
if (wh->type & DEV_ALL)
goto found;
if ((wh->type & DEV_BLOCK) && !S_ISBLK(inode->i_mode))
continue;
if ((wh->type & DEV_CHAR) && !S_ISCHR(inode->i_mode))
continue;
if (wh->major != ~0 && wh->major != imajor(inode))
continue;
if (wh->minor != ~0 && wh->minor != iminor(inode))
continue;
if ((mask & MAY_WRITE) && !(wh->access & ACC_WRITE))
continue;
if ((mask & MAY_READ) && !(wh->access & ACC_READ))
continue;
found:
rcu_read_unlock();
return 0;
}
rcu_read_unlock();
return -EPERM;
}
我們來簡單分析一下這個函數,首先如果該inode對應的不是devices,直接傳回0,如果既不是塊裝置也不是字元裝置,也傳回0,因為devices隻控制塊裝置和字元裝置的通路,其他情況不管。接着獲得目前程序的dev_cgroup,然後在dev_cgroup中whitelist指針的連結清單中查找,如果找到對應裝置而且mask指定的權限和裝置的權限一緻就傳回0,如果沒有找到就傳回錯誤。
這個函數是針對inode節點存在的情況,通過對比權限來控制cgroup中的程序能夠通路的devices。還有一個情況是inode不存在,在這種情況下,一個程序要通路一個裝置就必須通過mknod建立相應的裝置檔案。為了達到對這種情況的控制,devices子系統導出了第二個函數:
int devcgroup_inode_mknod(int mode, dev_t dev)
{
struct dev_cgroup *dev_cgroup;
struct dev_whitelist_item *wh;
if (!S_ISBLK(mode) && !S_ISCHR(mode))
return 0;
rcu_read_lock();
dev_cgroup = task_devcgroup(current);
list_for_each_entry_rcu(wh, &dev_cgroup->whitelist, list) {
if (wh->type & DEV_ALL)
goto found;
if ((wh->type & DEV_BLOCK) && !S_ISBLK(mode))
continue;
if ((wh->type & DEV_CHAR) && !S_ISCHR(mode))
continue;
if (wh->major != ~0 && wh->major != MAJOR(dev))
continue;
if (wh->minor != ~0 && wh->minor != MINOR(dev))
continue;
if (!(wh->access & ACC_MKNOD))
continue;
found:
rcu_read_unlock();
return 0;
}
rcu_read_unlock();
return -EPERM;
}
這個函數的實作跟第一個函數類似,這裡就不贅述了。
下面我們再來看一下devices子系統本身的一些東西。跟其他子系統一樣,devices同樣實作了一個cgroup_subsys:
struct cgroup_subsys devices_subsys = {
.name = "devices",
.can_attach = devcgroup_can_attach,
.create = devcgroup_create,
.destroy = devcgroup_destroy,
.populate = devcgroup_populate,
.subsys_id = devices_subsys_id,
};
devices相應的三個控制檔案:
static struct cftype dev_cgroup_files[] = {
{
.name = "allow",
.write_string = devcgroup_access_write,
.private = DEVCG_ALLOW,
},
{
.name = "deny",
.write_string = devcgroup_access_write,
.private = DEVCG_DENY,
},
{
.name = "list",
.read_seq_string = devcgroup_seq_read,
.private = DEVCG_LIST,
},
};
其中allow和deny都是通過devcgroup_access_write實作的,隻是通過private字段區分,因為二者的實作邏輯有相同的地方。devcgroup_access_write最終通過調用devcgroup_update_access來實作。在devcgroup_update_access根據寫入的内容構造一個dev_whitelist_item ,然後根據檔案類型做不同的處理:
switch (filetype) {
case DEVCG_ALLOW:
if (!parent_has_perm(devcgroup, &wh))
return -EPERM;
return dev_whitelist_add(devcgroup, &wh);
case DEVCG_DENY:
dev_whitelist_rm(devcgroup, &wh);
break;
default:
return -EINVAL;
}
allow的話,就将item加入whitelist,deny的話,就将item從whitelist中删去。
作者曰:devices子系統的實作相對比較簡單,通過在核心對裝置通路的時候加入額外的檢查來實作。而devices子系統本身隻需要管理好可以通路的裝置清單就行了。