转与:http://blog.csdn.net/jinzhuojun/article/details/43113195
在移动设备上,虚拟化的需求正在逐渐增加。其一,移动设备配置越来越高,一些高端配置已和桌面设备接近,这为虚拟化奠定了基础;其二,用户对于移动设备使用场景的多样性与日俱增。现在移动设备不仅用于娱乐日用,还用于工作;其三,安全与隐私问题日益凸显。移动设备上有更多的隐私信息,如各种账号,支付密码等,同时,各种病毒木马正在向移动设备迅速蔓延。这种背景下在一个隔离的环境中运行敏感软件是更加安全的做法;其四,多用户需求的出现。有时手机,尤其平板用户是多个,比如给小孩玩时就希望在一个特定的受限运行环境下。
桌面系统中的虚拟化技术已比较成熟,厂商也都提供了硬件支持,各种虚拟化解决方案也都使用广泛。而移动平台上,一方面由于计算能力相对有限,另一方面移动处理器对虚拟化的支持没有桌面系统中那么成熟完善。于是基于kernel namespace的container技术因其轻量级成为主要研究热点。而移动平台的一大特点就是各种硬件设备种类繁多,而kernel namespace之前主要用于服务器,服务器外设无论是种类还是数量都较少,显示器一般都可以省掉。而手机上各种sensor, camera, wifi, audio, display, radio, input, LED名目繁多。所以基于kernel namespace的话,一大难题就是解决设备的虚拟化。
在这样的背景下,device namespace在Columbia大学的研究性项目Cell(http://systems.cs.columbia.edu/projects/cells/)中提出,用于实现同一个手机上多个Android系统对设备的并发使用。它本质上是对kernel namespace的扩展。在该方案中,在kernel-level的虚拟化可分为三种方式:1) device driver wrapper, 如framebuffer. 2) namespace-aware subsystem, 如input. 3) namespace-aware device driver, 如binder。另外针对一些闭源的模块,比如RIL和wifi,需要用用户态的device namespace。简言之,就是整个系统启动时会起一个可信的最小初始化环境,称为root namespace。类似Xen里的dom0,它负责真正管理和切换其它虚拟系统,以及真正访问那些闭源模块。其它虚拟系统要访问时,通过IPC向root namespace申请。
随后,Cellrox以商业用途的license发布了device namespace的patch,作为Cellrox的Thinvisor技术一部分(https://github.com/Cellrox/devns-patches/wiki/DeviceNamespace)。Patch分三部分:1)framework主要包含了device namespace的框架,active device namespace的切换处理和对其它开放的API。2)traditional virtualization主要是为了隔离。使不同namespace的进程在操作设备时互不影响。比如binder, alarm, logger。3)context-aware virtualization主要是为了支持foreground-background模型。比如input, framebuffer, LED, backlight。在这个模型中,系统虽然存在多个虚拟系统,但只有一个是active的。所以一般情况下只有active namespace所在进程对设备的操作会生效,其它的不起作用。这儿又细分为statefull和stateless的device driver。对于stateless的好办,只要在处理时判断下是否active device namespace,不是的话忽略。而statefull的话就得为每个device namespace保存虚拟状态。
下面以input系统为例来看看device namespace的基本框架和工作原理。首先,需要在已有kernel namespace的基础上加device namespace。具体地,在nsproxy结构中加了dev_namespace结构:
- struct nsproxy {
- ...
- struct dev_namespace *dev_ns;
- };
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];
- };
- struct dev_ns_info {
- struct dev_namespace *dev_ns;
- struct list_head list;
- struct notifier_block nb;
- atomic_t count;
- };
dev_namespace的初始值为init_dev_ns,它是代表init进程的device namespace。全局变量active_dev_ns指示现在active的device namespace。默认当然是init的device namespace。dev_ns_desc是系统中的全局数组,每个元素表示一个需要用device namespace的设备。
- 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];
- #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)
- 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);
- }
- ret = DEV_NS_REGISTER(evdev, "event dev");
- if (ret < 0) {
- input_unregister_handler(&evdev_handler);
- return ret;
- }
- static struct dev_ns_ops evdev_ns_ops = {
- .create = evdev_devns_create,
- .release = evdev_devns_release,
- };
然后,有那么一天,系统中的某一个进程打开了evdev子系统中的一个设备,然后evdev_open()-> evdev_ns_track_client(client)被调用。
- 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);
- ...
- }
上面的get_evdev_ns_cur()依次调用get_dev_ns_info_task() -> get_dev_ns_info()。这个函数中会查打开设备进程所在device namespace中是否已注册该设备。有的话就直接返回,否则就调用new_dev_ns_info()新建。但这个dev_ns_info结构是被包在一个driver-specific的xxx_dev_ns结构中的。所以要调用之前注册的回调先初始化外面的结构,这里就是evdev_devns_create()。初始化完evdev_dev_ns后再把它里边的dev_ns_info返回,串到代表该设备的dev_ns_desc数组中去。看一下evdev_dev_ns_create(),其中创建driver-specific的device namespace结构evdev_dev_ns。然后注册notifier函数,它在切换active device namespace时会被回调。
- dev_ns_info->nb = evdev_ns_switch_notifier;
- dev_ns_register_notify(dev_ns, &dev_ns_info->ns);
考虑上面的数据结构,下面是一张总图说明它们之间的大体关系。这个例子中,其中有两个device namespace,考虑两个设备evdev和alarm。evdev在在两个device namespace都有使用,其中一个device namespace中有两个client。alarm只在一个device namespace中使用。
然后,当active device namespace切换时set_active_dev_ns()被调用。active device namespace的切换是通过/proc文件来通知kernel的。当然这仅是作demo之用,真正用时可以改为其它接口。在dev_namespace_init()中,创建/proc/dev_ns/active_ns_pid和/proc/dev_ns/ns_tag。它们的file_operations结构分别为active_ns_fileops和ns_tag_fileops。以active_ns_pid为例,当它被写时,触发proc_active_ns_write() -> dev_ns_proc_write() -> set_active_dev_ns(),接着它会调用先前注册的notifier函数。这里过程很直观,比如A namespace切到B namespace,先发DEV_NS_EVENT_DEACTIVATE事件到input driver通知A namespace切到后台,然后将active device namespace设为B,最后发DEV_NS_EVENT_ACTIVATE事件到input driver通知B 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);
- ...
- }
- struct evdev_dev_ns *evdev_ns;
- struct list_head list;
- bool grab;
作为移动平台容器方案,device namespace还有需要扩展的地方,比如支持多个active的device namespace,但它提供了一种轻量级设备虚拟化的可行方案。其它设备虚拟化方案比如还有systemd中的multi-session(https://dvdhrm.wordpress.com/2013/08/25/sane-session-switching/),它可以免除对kernel的改动。具体使用可以按需求和设备类型结合多种方案。