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