天天看點

LXC1.0.7-- lxc-start 源碼分析 03

前面的過程就不贅述了,看一下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

   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;

配置設定空間

    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;

        }

}

    cgroup_path_components =lxc_normalize_path(path_pattern);

    if (!cgroup_path_components)

        goto out_initial_error;

來看主要的find_name_on_this_level程式塊

        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;

其中最主要的是

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中子程序一直等到父程序完成工作和配置。

    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,網絡配置有很多,就忽略網絡的問題了。

然後又告訴子程序繼續初始化過程

    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,注釋是這樣說道。

    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是如何繼續接管程式的。

繼續閱讀