Device namespace简介 - 基于Kernel namespace的设备虚拟化

在移动设备上,虚拟化的需求正在逐渐增加。其一,移动设备配置越来越高,一些高端配置已和桌面设备接近,这为虚拟化奠定了基础;其二,用户对于移动设备使用场景的多样性与日俱增。现在移动设备不仅用于娱乐日用,还用于工作;其三,安全与隐私问题日益凸显。移动设备上有更多的隐私信息,如各种账号,支付密码等,同时,各种病毒木马正在向移动设备迅速蔓延。这种背景下在一个隔离的环境中运行敏感软件是更加安全的做法;其四,多用户需求的出现。有时手机,尤其平板用户是多个,比如给小孩玩时就希望在一个特定的受限运行环境下。

桌面系统中的虚拟化技术已比较成熟,厂商也都提供了硬件支持,各种虚拟化解决方案也都使用广泛。而移动平台上,一方面由于计算能力相对有限,另一方面移动处理器对虚拟化的支持没有桌面系统中那么成熟完善。于是基于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;
};

而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上。

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];

其中的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中的链表为空。

然后,有那么一天,系统中的某一个进程打开了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);
   ...
}

这个函数中创建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中。

上面的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);

这个notifier chain的结构如下:

考虑上面的数据结构,下面是一张总图说明它们之间的大体关系。这个例子中,其中有两个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);
   ...
}

这里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状态。到这里,把上面的流程总结一下:

有了这些信息后,就可以在读写input event时做一些namespace-aware的逻辑了。如在广播event事件时会检查device namespace,如果不是active的namespace就忽略。对于设备的写处理函数evdev_write()也是类似的。另外,在evdev_do_ioctl()中。对于非active的device namespace,很多时候就不作处理。在EVIOCGRAB的ioctl处理中,对于inactive的device namespace中的会话,将client->grab中的状态设为应该要设的状态,但不真正做事,而是如上节所说等到active
device namespace切换时再做。

作为移动平台容器方案,device namespace还有需要扩展的地方,比如支持多个active的device namespace,但它提供了一种轻量级设备虚拟化的可行方案。其它设备虚拟化方案比如还有systemd中的multi-session(https://dvdhrm.wordpress.com/2013/08/25/sane-session-switching/),它可以免除对kernel的改动。具体使用可以按需求和设备类型结合多种方案。

时间: 2024-10-05 09:59:48

Device namespace简介 - 基于Kernel namespace的设备虚拟化的相关文章

Linux Namespace : 简介

在初步的了解 docker 后,笔者期望通过理解 docker 背后的技术原理来深入的学习和使用 docker,接下来的几篇文章简单的介绍下 linux namespace 的概念以及基本用法. namespace 的概念 namespace 是 Linux 内核用来隔离内核资源的方式.通过 namespace 可以让一些进程只能看到与自己相关的一部分资源,而另外一些进程也只能看到与它们自己相关的资源,这两拨进程根本就感觉不到对方的存在.具体的实现方式是把一个或多个进程的相关资源指定在同一个 n

C#:基于WMI查询USB设备

来源:http://blog.csdn.net/jhqin/article/details/6734673 /* ---------------------------------------------------------- 文件名称:WMIUsbQuery.cs 作者:秦建辉 MSN:[email protected] QQ:36748897 博客:http://blog.csdn.net/jhqin 开发环境: Visual Studio V2010 .NET Framework 4

Docker存储驱动之Device Mapper简介

Device Mapper是一个基于kernel的框架,它增强了很多Linux上的高级卷管理技术.Docker的devicemapper驱动在镜像和容器管理上,利用了该框架的超配和快照功能.为了区别,本文使用Device Mapper指驱动中的框架,而devicemapper指Docker的存储驱动. 注意:商业支持的Docker Engine(CS-Engine)建议在RHEL和CentOS上使用devicemapper存储驱动. AUFS之外的另一种选择 Docker最初运行在Ubuntu和

基于libUSB的USB设备固件更新程序(下载数据)(转)

源:基于libUSB的USB设备固件更新程序(下载数据) 本文紧接上一篇日志:基于libUSB-Win32的USB设备固件更新程序(前言),相关背景以及起因等,此处不再赘述,如感兴趣请移步. libUSB-Win32给出的example里面,有一个bulk.c文件,分析其关键代码,结合libusb官方文档,摘出其关键代码如下: int main(void) { usb_dev_handle *dev = NULL; /* the device handle */ usb_init(); /* i

基于IOS和Android设备MDM技术方案服务价格

导读:前段时间 www.mbaike.net 博客被恶意攻击,导致程序崩溃,目前已经替换了以前的Wordpress程序,现提供IOS和Android版本MDM的代码和相关文档咨询服务. 一.IOS版MDM服务内容及价格: 套餐一:IOS端MDM Server代码(提供MDM Server端的代码和部署文档,不含后期技术支持) 3000元套餐二:IOS端MDM开发技术顾问(提供MDM开发的顾问服务,协助理解MDM原理流程及搭建MDM Server工作的咨询) 1500元套餐三:IOS端MDM全部服

LambdaMART简介——基于Ranklib源码(一 lambda计算)

学习Machine Learning,阅读文献,看各种数学公式的推导,其实是一件很枯燥的事情.有的时候即使理解了数学推导过程,也仍然会一知半解,离自己写程序实现,似乎还有一道鸿沟.所幸的是,现在很多主流的Machine Learning方法,网上都有open source的实现,进一步的阅读这些源码,多做一些实验,有助于深入的理解方法. Ranklib就是一套优秀的Learning to Rank领域的开源实现,其主页在:http://people.cs.umass.edu/~vdang/ran

ServerSAN解析(一):vVNX专业存储设备虚拟化

vVNX专业存储设备虚拟化 EMC在2014 World大会上宣布了Project Liberty计划,主要内容是将专业存储设备实现软硬件解耦.虚拟化部署.目前EMC已经发布vVNX虚拟化社区版本并提供免费下载链接.vVNX是将一款基于业界领先的 VNX 阵列实现虚拟化(vVNX),用户可以享有VNX 提供丰富的数据服务.易管理能和丰富的用户体验,同时vVNX让开发和测试系统的部署变得灵活.简单.用户可以在使用VNX存储专业存储服务的同时,也降低采购和软硬件成本. Project Liberty

[转载] linux namespace简介

原文: http://blog.lucode.net/linux/intro-Linux-namespace-1.html 虽然对于linux container机制的功能有过初步的了解, 但是仍然感到非常神秘, 并且没有找到比较好的文档来介绍. 这篇文章虽然介绍的东西不是特别多, 但是却对linux container机制讲解的非常到位, 使得不了解linux container的人立刻对linux container技术不再陌生了, 特别是举了一个非常简单但是足够典型的例子, 说明通过clo

Linux Namespace 入门系列:Namespace API

Linux Namespace 是 Linux 提供的一种内核级别环境隔离的方法.用官方的话来说,Linux Namespace 将全局系统资源封装在一个抽象中,从而使 namespace 内的进程认为自己具有独立的资源实例.这项技术本来没有掀起多大的波澜,是容器技术的崛起让他重新引起了大家的注意. Linux Namespace 有如下 6 个种类: 分类 系统调用参数 相关内核版本 Mount namespaces CLONE_NEWNS Linux 2.4.19 UTS namespace