标签:
在移动设备上,虚拟化的需求正在逐渐增加。其一,移动设备配置越来越高,一些高端配置已和桌面设备接近,这为虚拟化奠定了基础;其二,用户对于移动设备使用场景的多样性与日俱增。现在移动设备不仅用于娱乐日用,还用于工作;其三,安全与隐私问题日益凸显。移动设备上有更多的隐私信息,如各种账号,支付密码等,同时,各种病毒木马正在向移动设备迅速蔓延。这种背景下在一个隔离的环境中运行敏感软件是更加安全的做法;其四,多用户需求的出现。有时手机,尤其平板用户是多个,比如给小孩玩时就希望在一个特定的受限运行环境下。下面以input系统为例来看看device namespace的基本框架和工作原理。首先,需要在已有kernel namespace的基础上加device namespace。具体地,在nsproxy结构中加了dev_namespace结构:
struct nsproxy {
...
struct dev_namespace *dev_ns;
};而nsproxy结构作为表示进程的结构task_struct的成员。也就是说,这样,对于一个进程,其nsproxy下的dev_ns代表了它所在的device namespace。nsproxy结构在同个namespace的进程间可以共享,但当这个nsproxy中的其中一个namespace被拷贝或取消共享,nsproxy就会被拷贝,变成所在进程私有。有点COW的意思。
dev_namespace这个核心结构如下:
struct dev_namespace {
bool active;
...
pid_t init_pid;
...
struct blocking_notifier_head notifiers;
...
struct dev_ns_info *info[DEV_NS_DESC_MAX];
};其中active代表它是否是active的device namespace。init_pid是该device namespace下的init进程的pid。info是一个dev_ns_info结构的数组,每个元素代表该device namespace下的一个设备或子系统(为了简便下面统称设备)。notifiers是一个notifier_block链表,它把dev_ns_info结构里的成员nb串起来。它通过Linux notifier chains机制来让active device
namespace切换时可以让每个注册设备调用处理函数。dev_ns_info代表在单个device namespace中的单个设备,它在device namespace中使用设备时创建(如果还没创建)。
struct dev_ns_info {
struct dev_namespace *dev_ns;
struct list_head list;
struct notifier_block nb;
atomic_t count;
};其中的list元素用于把不同device namespace的同一设备串在表示该设备的结构dev_ns_desc上。struct dev_ns_desc {
char *name;
struct dev_ns_ops *ops;
struct list_head head;
};
static struct dev_ns_desc dev_ns_desc[DEV_NS_DESC_MAX];其中的dev_ns_ops定义了device namespace framework调用具体device driver的接口。它的实现在device driver中。每个特定设备在初始化时会在dev_ns_desc里面注册一项。在driver-specific的xxx_dev_ns(如evdev_dev_ns)结构中要包含dev_ns_info结构。这样就把common的dev_ns_desc和具体device driver联系起来了。另外,device namespace的framework还通过DEFINE_DEV_NS_INFO定义了一系列的helper函数供driver使用。#define DEFINE_DEV_NS_INFO(X) _dev_ns_id(X) _dev_ns_find(X) _dev_ns_get(X) _dev_ns_get_cur(X) _dev_ns_put(X)在每个需要device namespce的内核模块中需要定义该宏,如DEFINE_DEV_NS_INFO(alarm)。以evdev模块为例,DEFINE_DEV_NS_INFO(evdev)会生成以下内容:
static int evdev_ns_id;
static inline struct evdev_dev_ns *get_evdev_ns(struct dev_namespace *dev_ns)
{
struct dev_ns_info *info;
info = get_dev_ns_info(evdev_ns_id, dev_ns, 1, 1);
return info ? container_of(info, struct evdev_dev_ns, dev_ns_info) : NULL;
}
static inline struct evdev_dev_ns *find_evdev_ns(struct dev_namespace *dev_ns)
{
struct dev_ns_info *info;
info = get_dev_ns_info(evdev_ns_id, dev_ns, 0, 0);
return info ? container_of(info, struct evdev_dev_ns, dev_ns_info) : NULL;
}
static inline struct evdev_dev_ns *get_evdev_ns_cur(void)
{
struct dev_ns_info *info;
info = get_dev_ns_info_task(evdev_ns_id, current);
return info ? container_of(info, struct evdev_ns, dev_ns_info) : NULL;
}
static inline void put_evdev_ns(struct evdev_dev_ns *evdev_ns)
{
put_dev_ns_info(evdev_ns_id, &evdev_ns->dev_ns_info, 1);
}其中evdev_ns_id为dev_ns_desc数组和dev_namespace的info数组中对应该设备元素的index。它是在evdev初始化时evdev_init()函数中赋值的。 ret = DEV_NS_REGISTER(evdev, "event dev");
if (ret < 0) {
input_unregister_handler(&evdev_handler);
return ret;
}本质上调用register_dev_ns_ops()在dev_ns_desc中注册了一个新设备,然后初始化。初始化时比较重要的一步是把模块相关的结构evdev_ns_ops注册到dev_ns_desc中去。这个接口的实现在evdev中,用于日后让device namespace的framework回调evdev子系统。static struct dev_ns_ops evdev_ns_ops = {
.create = evdev_devns_create,
.release = evdev_devns_release,
};前面提到过,dev_ns_desc中的一个元素代表一个设备。相当于设备的全局注册表。这里的注册过程是线性搜索第一个空的位置,返回这个位置的index作为evdev_ns_id。这里,设备还没有被真正使用,所以相应的dev_ns_info结构也没有创建,因此元素head中的链表为空。static int evdev_ns_track_client(struct evdev_client *client)
{
struct evdev_dev_ns *evdev_ns;
evdev_ns = get_evdev_ns_cur();
...
client->evdev_ns = evdev_ns;
...
list_add(&client->list, &evdev_ns->clients);
...
}这个函数中创建evdev_dev_ns结构。前面提到过,每个需要使用device namespace的设备都要定义这个xxx_dev_ns结构。它是deivce driver与device namespace framework的桥梁。evdev_dev_ns中包含了dev_ns_info结构。每次打开evdev设备会创建一个evdev_client对象。所有同一个device namespace下的evdev_client被串到代表该device namespace中evdev设备的结构evdev_dev_ns的成员clients中。dev_ns_info->nb = evdev_ns_switch_notifier; dev_ns_register_notify(dev_ns, &dev_ns_info->ns);这个notifier chain的结构如下:
考虑上面的数据结构,下面是一张总图说明它们之间的大体关系。这个例子中,其中有两个device namespace,考虑两个设备evdev和alarm。evdev在在两个device namespace都有使用,其中一个device namespace中有两个client。alarm只在一个device namespace中使用。
void set_active_dev_ns(struct dev_namespace *next_ns)
{
...
(void) blocking_notifier_call_chain(&prev_ns->notifiers,
DEV_NS_EVENT_DEACTIVATE, prev_ns);
(void) blocking_notifier_call_chain(&dev_ns_notifiers,
DEV_NS_EVENT_DEACTIVATE, prev_ns);
...
next_ns->active = true;
...
active_dev_ns = next_ns;
...
(void) blocking_notifier_call_chain(&next_ns->notifiers,
DEV_NS_EVENT_ACTIVATE, next_ns);
(void) blocking_notifier_call_chain(&dev_ns_notifiers,
DEV_NS_EVENT_ACTIVATE, next_ns);
...
}这里blocking_notifier_call_chain()本质上是调用之前注册的evdev_ns_swtich_callback(),该函数中先根据当前device namespace找到相应的evdev_dev_ns结构。这个evdev_dev_ns中的clients成员指示的链表串了该device namespace的所有设备使用会话,每个用evdev_client表示。前面图中有描述,evdev_client中为device namespace服务的成员有:struct evdev_dev_ns *evdev_ns; struct list_head list; bool grab;注意这里的grab是一个虚拟状态,记录着该会话如果在没有多个虚拟系统时是否独占设备,它只代表请求,不代表真正的grab状态,因为还要经过device namespace的逻辑。找到evdev_dev_ns结构后,通过clients成员遍历该device namespace中evdev所有打开的会话,如果处理的消息是DEV_NS_EVENT_ACTIVATE,说明该device namespace切到前台,则如果该会话之前设为独占,就调用evdev_grab()让其真正独占。如果处理的是DEV_NS_EVENT_DEACTIVATE,即该device namespace切到后台,如果当前会话是真正的独占会话,则调用evdev_ungrab()取消它的grab状态。到这里,把上面的流程总结一下:
Device namespace简介 - 基于Kernel namespace的设备虚拟化
标签:
原文地址:http://blog.csdn.net/jinzhuojun/article/details/43113195