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子系統本身只需要管理好可以訪問的設備列表就行了。