前面的过程就不赘述了,看一下lxc-start 一些流程中重要的过程,有些无关紧要的函数还是依旧跳过。
OK。
1、首先就是第一个lxc_check_inherited函数
dir = opendir("/proc/self/fd");
if (!dir) {
WARN("failed to opendirectory: %m");
return -1;
}
此函数是根据配置将/proc/self/fd下,关闭fd。
然后就跳到__lxc_start中
2、看下lxc-init
在init中 设置一些关于LXC_XXX的环境变量,猜测用于后面的使用。
可以再lxc启动的时候加一些脚本。
会在hook中先执行pre-start的前缀的脚本
if (run_lxc_hooks(name, "pre-start", conf,handler->lxcpath, NULL)) {
ERROR("failed to runpre-start hooks for container '%s'.", name);
goto out_aborting;
}
继续,后面有调用lxc_create_tty,细致研究发现,这个函数是根据conf中设置tty的个数,通过opentty函数来创建pts给容器使用。
ret = openpty(&pty_info->master, &pty_info->slave,pty_info->name,NULL, NULL);
这个可以再config文件中设置tty的个数
tty的作用是,如果容器配置了根文件系统和inittab文件设置启动gettty,同时在inittab中gettty的个数不能超过设置的tty的个数,否则会出问题
同理 lxc_console_create 也是一样
如果容器配置了根文件系统和inittab文件设置使用控制台,您可能希望指定该控制台的输出。可以在config中设置lxc.console.logfile来指定输出的位置,lxc.console指定console的个数
然后通过ttys_shift_ids来设置tty的owner。
这样init的初始化过程就结束了。
3、然后到must_drop_cap_sys_boot(handler->conf)这个步骤中。
这个函数会读系统中/proc/sys/kernel/ctrl-alt-del这个文件,判断确定cmd的命令,cmd = v ?LINUX_REBOOT_CMD_CAD_ON : LINUX_REBOOT_CMD_CAD_OFF;
然后会系统调用clone,其中函数指针为container_reboot_supported,最终会调用reboot这个函数,
通过man reboot可以看到细节
LINUX_REBOOT_CMD_CAD_OFF
(RB_DISABLE_CAD, 0). CAD is disabled. This means that the CAD keystroke will cause a SIGINT signalto be sent to init
(process 1),whereupon this process may decide upon a proper action (maybe: kill allprocesses, sync, reboot).
LINUX_REBOOT_CMD_CAD_ON
(RB_ENABLE_CAD,0x89abcdef). CAD is enabled. This means that the CAD keystroke willimmediately cause the action associated
withLINUX_REBOOT_CMD_RESTART.
那么,问题来了,到底reboot什么东西,系统?还是container?一个已经启动,一个正在start过程。
暂时还没搞懂,是不是NEWPID|NEWUSER 启动的新的namespace的空间中的东西,可能发SIGINT信号给主机的init的进程。将以前启动的container剩余的部分重新启动?先mark一下。
4、然后判断if (geteuid() == 0&& !lxc_list_empty(&conf->id_map)),id_map是空的,因为目前所有的的流程,都是以privilegecontainer说的,所有非root的用户就不分析了。
检查rootfs_is_blockdev(conf) 感觉函数是在判断rootfs的路径是否为blockdev,然后remount_all_slave打开/proc/self/mountinfo然后将shared enties 改变到slave中,就看当前的系统有没有share entries了。
然后调用do_rootfs_setup(conf, name,lxcpath) 将container rootfs 挂载上去。同时也通过pre-mount的脚本将自定义的一些mount 加进去,因此,这个地方也可以自己自定义,复用一些东西
然后调用setup_rootfs,先是调用mount("","/", NULL, MS_SLAVE|MS_REC, 0),mount /,调用bdev_init,初始化rootfs。
5、然后进去lxc-spawn这个函数中,在别的地方很多次见到spawn这个函数,只知道spawn的英文意思是产卵的意思。这个函数上次分析,里面有很多事在做。
首先将以前的cloneflag 保存,记得start的刚开始初始化的时候如果没设置,ns_info中都设置默认的-1,然后就是同步handler,没什么好说的。
然后就是讲handler的clone_flags设置CLONE_NEWXXX,获取物理网络,等等设置一堆东西, 然后就要想办法将cgroup与namespace联系到一块了,到cgroup_init里面看看是什么流程。
首先,前面一直迷惑的ops怎么被初始化的问题,
__attribute__((constructor))
void cgroup_ops_init(void)
这个结构,在函数未调用之前就被执行了,这个回头会在杂篇中讲到,首先程序会根据系统中是否有cgmanager 来使用不同的初始化函数,本文就默认没有cgmanager,调用通用的cgfs_ops_init;返回一个引用值,返回静态变量cgfs_ops;将一些指针赋值,ok,看cgroup_init初始化过程,init指向cgfs_init,因此到cgfs_init这个函数中看一下
首先初始化cgfs_data的数据结构,然后设置cgroup_pattern为全局变量中lxc.cgroup.pattern即在编译中的DEFAULT_CGROUP_PATTERN,默认的是/lxc/%n,这个暂时不知道含义。继续看
然后调用lxc_cgroup_load_meta加载metadata,函数中会判断cgroup的使用情况,然后会调用lxc_cgroup_load_meta2的函数,会查找子系统的白名单,或者指定的hierarchies。
最终返回给handler->cgroup_data。
然后调用cgroup_create(handler)来创建cgroup,调用ops的create,create的指针指向cgfs_create,是个内联函数,最终调用lxc_cgroupfs_create,lxc_cgroupfs_create(d->name,d->cgroup_pattern, md, NULL)用来创建new cgroup
/* we will modify the result of this operation directly,
* so we don't have to copythe data structure
*/
base_info = (path_pattern[0]== '/') ?
lxc_cgroup_process_info_get_init(meta_data) : //pattern为/lxc/%n
lxc_cgroup_process_info_get_self(meta_data);
if (!base_info)
return NULL;
其中get_init为returnlxc_cgroup_process_info_get(1, meta);pid 为1号进程get数据,根据/proc/1/cgroup中的信息添加到cgroup_process_info的链表中。
new_cgroup_paths = calloc(meta_data->maximum_hierarchy + 1,sizeof(char *));
if (!new_cgroup_paths)
goto out_initial_error;
new_cgroup_paths_sub =calloc(meta_data->maximum_hierarchy + 1, sizeof(char *));
if (!new_cgroup_paths_sub)
goto out_initial_error;
分配空间
/* find mount points we can use */
for (info_ptr = base_info;info_ptr; info_ptr = info_ptr->next) {
h =info_ptr->hierarchy;
mp =lxc_cgroup_find_mount_point(h, info_ptr->cgroup_path, true);
if (!mp) {
ERROR("Could notfind writable mount point for cgroup hierarchy %d while trying to createcgroup.", h->index);
gotoout_initial_error;
}
info_ptr->designated_mount_point= mp;
if(lxc_string_in_array("ns", (const char **)h->subsystems))
continue;
if(handle_cgroup_settings(mp, info_ptr->cgroup_path) < 0) {
ERROR("Could notset clone_children to 1 for cpuset hierarchy in parent cgroup.");
gotoout_initial_error;
}
}
/* normalize the path */
cgroup_path_components =lxc_normalize_path(path_pattern);
if (!cgroup_path_components)
goto out_initial_error;
来看主要的find_name_on_this_level程序块
/* determine name of the path component we should create */
if (contains_name&& suffix > 0) {
char *buf =calloc(strlen(name) + 32, 1);
if (!buf)
gotoout_initial_error;
snprintf(buf, strlen(name)+ 32, "%s-%u", name, suffix);
current_component =lxc_string_replace("%n", buf, p_eff);
free(buf);
} else {
current_component =contains_name ? lxc_string_replace("%n", name, p_eff) : p_eff;
}
parts[0] = path_so_far;
parts[1] =current_component;
parts[2] = NULL;
current_subpath =path_so_far ? lxc_string_join("/", (const char **)parts, false) :current_component;
/* Now go through each hierarchy and try to create the
* corresponding cgroup
*/
其中最主要的是
r = create_cgroup(info_ptr->designated_mount_point,current_entire_path);来创建cgroup的目录层级。
理一下头绪,cgroup通过cgroup.patternd 的模式,然后读取/proc/1/cgroup下去创建相应的cgroup层级,最后创建cgroup的目录。
6、回到lxc-spawn中,然后到通过一些网络的netpipepair设置,这些都不是我们关心的。
最后调用lxc_clone函数调用do_start来对container进行一系列的初始化操作,首先是lxc_setup 前面也介绍了,通过初始化,mount rootfs,网络,autodev,自动挂载/proc,/sys等文件,然后设置tty,console等设置标准输入输出的位置,等等。
然后可以设置if(run_lxc_hooks(handler->name, "start", handler->conf,handler->lxcpath, NULL)) start脚本来辅助工作,这个也是可以自定义的内容
最后在do_start函数中调用handler->ops->start(handler,handler->data);
ops为lxc的operation中的内容,来看看想干嘛。execvp(arg->argv[0],arg->argv);执行start container了,这里面,我们用到的是/init不是默认的/sbin/init,因为我们的容器不是标准的容器,所以这点是不同的。
里面注释也谈到了,当我们执行这个/init的时候,函数就不会返回来了,那么后面的程序怎么办?
所以在do_start中子进程一直等到父进程完成工作和配置。
/* Tell the parent task it can begin to configure the
* container and wait for itto finish
*/
if(lxc_sync_barrier_parent(handler, LXC_SYNC_CONFIGURE))
return -1;
然后父进程进行一系列的配置,其中最主要的就是cgroup的配置,如果容器没有cgroup的话,资源划分就成问题了,
cgroup_setup_limits 资源限制,cgroup_enter将pid进程加入task任务中,等等设置cgroup
然后还是配置网络,将container加入到veth当中,这当年还是要看自己config网络相关的配置,so,网络配置有很多,就忽略网络的问题了。
然后又告诉子进程继续初始化过程
/* Tell the child to continue its initialization. we'll get
* LXC_SYNC_CGROUP when it isready for us to setup cgroups
*/
if(lxc_sync_barrier_child(handler, LXC_SYNC_POST_CONFIGURE))
goto out_delete_net;
然后当子进程setup过程完成之后,让父进程设置cgroup,同时父进程设置完cgroup时,也通知子进程完成,此时子进程就真正进入到container的init的进程了。
一直没发现这个LXC_SYNC_POST_CGROUPwait 子进程的信号谁发给他,这个比较疑惑?
最后发现是do_stat这个函数if判断失败后goto的,则表示中间会error,最后还有个post_cgroup,注释是这样说道。
/* Tell the child to complete its initialization and wait for
* it to exec or return anerror. (the child will never
* returnLXC_SYNC_POST_CGROUP+1. It will eitherclose the
* sync pipe, causinglxc_sync_barrier_child to return
* success, or return adifferent value, causing us to error
* out).
*/
if(lxc_sync_barrier_child(handler, LXC_SYNC_POST_CGROUP))
return -1;
然后就是调用post-start,NOTICE 运行的pid,最后设置container的状态为RUNNING,至此spawn就结束了。
回到__lxc_start中,get_netns_fd获得network的状态,然后进入lxc_poll中.后面没什么好说的,现在主要考虑lxc 在exec container的init的进程过后,lxc是如何继续接管程序的。