標籤:
CORE採用LXC(Linux namespace Container)技術和Bridge技術實現虛擬機器主機和虛擬網路的模擬類比。LXC利用cgroups子系統中的進程組資源管理架構將虛擬機器主機實現為同一個組相對獨立的進程。LXC已加入到核心2.6.28版本,CORE對虛擬機器主機建立和管理,通過的C語言系統調用來的實現,具體代碼實現在core/daemon/src檔案夾下。
檔案清單
檔案名稱 |
功能說明 |
vnoded_main.c |
建立LXC容器啟動並執行守護進程vnoded(PID 1) |
vcmd_main.c |
tool, 指定LXC容器運行命令 |
netns_main.c |
tool, 建立LXC容器中運行指定程式 |
netns.h, netns.c |
提供LXC容器管理操作 |
netnsmodule.c |
提供LXC容器管理操作的Python庫 |
vnode_server.h, .c |
|
vnode_client.h, .c |
|
vnode_cmd.h, .c |
|
vnode_msg.h, .c |
|
vnode_io.h, .c |
提供了io基本操作 |
vnode_chnl.h, .c |
提供了針對vnoded進程的串連管理操作 |
LXC容器(netns.h, netns.c)
Linux下通過進程複製(syscall調用)來建立LXC容器。每個LXC容器PID、IPC、Network等系統資源不再是全域的,而是屬於特定的Namespace,每個Namespace中的資源相對其它Namespace是透明的。
pid = syscall(SYS_clone, flags | NSCLONEFLGS, NULL, NULL, NULL, NULL)
在建立新的LXC容器時需要指定相應的flag。
#define NSCLONEFLGS \ //netns.c
( \
SIGCHLD | \
CLONE_NEWNS | \
CLONE_NEWUTS | \
CLONE_NEWIPC | \
CLONE_NEWPID | \
CLONE_NEWNET \
)
l SIGCHLD表示建立進程退出後向父進程發送SIGCHLD訊號
l CLONE_NEWNS表示建立進程設定獨立的檔案層次視圖
l CLONE_NEWUTS表示建立進程設定獨立的主機名稱
l CLONE_NEWIPC表示建立進程設定獨立的IPC環境
l CLONE_NEWPID表示建立進程設定獨立的PID環境
l CLONE_NEWNET表示建立進程獨立的網路環境
以上flags可以組合使用,根據需要進行建立LXC容器。
vnoded進程(vnoded_main.c)
vnoded進程是虛擬機器主機建立後啟動並執行第一個進程(pid=1),它由pycore調用/usr/sbin/vnoded命令來建立,命令執行時需要提供的參數有newnetns, ctrlchnlname, logfilename, pidfilename等。vnoded進程是一個守護進程,執行時會進入訊息迴圈,直至退出。
ev_loop(vnodeserver->loop, 0);
vnoded進程相當於LXC容器內啟動並執行作業系統,因此它提供了簡易作業系統功能:檔案管理,使用者訪問,進程管理三個準系統。vnoded進程執行分為三個步驟:
Step1. 建立使用者訪問ctrlchnl。
CORE虛擬出來的節點需要通過ctrlchnl進行訪問,ctrlchnl其實是SOCK_SEQPACKET 類型的Socket進程間通訊機制。ctrlchnl建立位於進程複製之前,這樣複製出來的進程(子進程)自然可以訪問該Socket控制代碼。
fd = socket(AF_UNIX, SOCK_SEQPACKET, 0)
bind(fd, (struct sockaddr *)&addr, sizeof(addr))
Step2. 建立容器(namespace)。
pid = nsfork(0);
子進程:
關閉父進程開啟的所有檔案;設定輸入/輸出。
for (i = 3; i < openmax; i++)
if (i != ctrlfd) close(i);
…
DUPFILE("/dev/null", O_RDONLY, STDIN_FILENO);
setvbuf(stdout, NULL, _IOLBF, 0);
setvbuf(stderr, NULL, _IOLBF, 0);
父進程:
退出等待。
_exit(0); /* nothing else for the parent to do */
Step3. 建立監聽訊息迴圈。
vnoded進程(運行在容器中)建立兩個訊息迴圈,一個是Ctrlchnl訊息迴圈,接收使用者命令。另一個是子進程訊息,接收子進程資訊。這兩個訊息迴圈的實現採用了libev庫,封裝在vnode_server_t結構體中來完成。
server = vnode_newserver(ev_default_loop(0), ctrlfd, ctrlchnlname);
vcmd命令(vcmd_main.c)
vcmd命令用於指定容器裡運行程式,確切地說是向vnoded進程發送訊息,讓它clone出一個新的進程。關於網卡和鏈路的配置都需要通過vcmd命令進行。。為了指定LXC容器,vcmd執行時需要指定容器的ctrlchnl。
Linux下的程式需要標準輸入輸出和出錯輸出,可以是管道(Pipe),也可以是終端(Pty),統稱為I/O。用vcmd命令執行另一個程式,需要為該程式指定I/O,通過參數-I, -i, -q等。vmd定義了三種輸入輸出I/O類型:檔案、管道、終端,V4.5版本中僅僅實現終端類型,即VCMD_IO_PTY。
typedef enum {
VCMD_IO_NONE = 0,
VCMD_IO_FD,
VCMD_IO_PIPE,
VCMD_IO_PTY,
} vnode_client_cmdiotype_t;
Vcmd命令執行分為以下四個步驟。
Step1. 初始化輸入輸出。
根據命令參數,對終端、管道進行初始化。
vcmd.cmdio = vnode_open_clientcmdio(iotype);
Step2. 建立容器訪問用戶端。
容器訪問用戶端封裝在vnode_client_t結構體中。vcmd命令會建立vnode_client_t結構體,並通過它與伺服器處理序(vnoded)訪問。
vcmd.client = vnode_client(ev_default_loop(0), ctrlchnlname,
vcmd_ioerrorcb, &vcmd);
Step3. 建立命令回複響應訊息迴圈。
vcmd命令進程在執行時,接收使用者輸入,並將輸入傳遞給容器中的進程,同時也將容器中的進程執行回複顯示在使用者終端,因此vcmd需要建立兩個訊息迴圈:自身I/O訊息迴圈和容器中的進程的I/O訊息迴圈。
vcmd->stdin_fwdfd = vcmd->cmdio->stdiopty.masterfd;
vcmd->stdin_watcher.data = &vcmd->stdin_fwdfd;
ev_io_init(&vcmd->stdin_watcher, vcmd_rwcb,
STDIN_FILENO, EV_READ);
ev_io_start(loop, &vcmd->stdin_watcher);
vcmd->ptymaster_fwdfd = STDOUT_FILENO;
vcmd->ptymaster_watcher.data = &vcmd->ptymaster_fwdfd;
ev_io_init(&vcmd->ptymaster_watcher, vcmd_rwcb,
vcmd->cmdio->stdiopty.masterfd, EV_READ);
ev_io_start(loop, &vcmd->ptymaster_watcher);
Step4. 發送命令訊息。
vcmd通過argc, argv獲得使用者想要在容器中執行程式的名稱和參數,然後通過ctrlchnl發送給vnoded進程,然後由vnoded進程通過forkexec來執行。為了編程方便,vcmd作為如下層次抽象。
圖1 vcmd訊息傳遞機制
命令層cmd交給使用者用戶端vnode_client,使用者用戶端交給訊息層msg,然後通過Socket發送給Vnode進程的訊息層msg,上傳給服務用戶端,然後由命令層cmd解包獲得命令參數進行執行。此外,Vcmd還與子進程建立了標準輸入輸出。
vnode_server_t結構體(vnoded_server.h, .c)
vnoded進程啟動的系統服務功能為接收使用者請求串連,根據使用者命令,在容器中執行(forkexec)相應程式,並將程式結果返回給使用者。由於使用者可能有多個,執行的程式也會有多個,vnode_server_t結構體用隊列clientlist,cmdlist來儲存。serverfd用於儲存使用者串連綁定的contrlchnl建立的Socket。fdwatcher和childwatcher分別是建立兩個訊息觀察器,前者觀察使用者串連訊息,後都觀察子進程執行訊息。
typedef struct {
TAILQ_HEAD(clientlist, cliententry) clientlisthead;
TAILQ_HEAD(cmdlist, cmdentry) cmdlisthead;
struct ev_loop *loop;
char ctrlchnlname[PATH_MAX];
char pidfilename[PATH_MAX];
int serverfd;
ev_io fdwatcher;
ev_child childwatcher;
} vnode_server_t;
使用者串連訊息迴圈
使用者串連訊息由回呼函數vnode_server_cb處理。
static void vnode_server_cb(struct ev_loop *loop, ev_io *w, int revents)
該函數將產生vnode_client對象,加入到clientlist隊列中,並啟動vnode_client對象的msgio訊息迴圈,之後該client對象與server的io處理交給msgio訊息迴圈處理。
vnode_msgiostart(&client->msgio, server->loop, client->clientfd, client, client_ioerror, msghandler)
子進程訊息迴圈
子進程訊息由回呼函數vnode_child_cb處理。
static void vnode_child_cb(struct ev_loop *loop, ev_child *w, int revents)
該函數根據子進程執行返回的狀態,向使用者發送狀態資訊。
vnode_send_cmdstatus(client->clientfd, cmd->cmdid, w->rstatus)
之後,從cmdlist中將相應的cmd移除。
TAILQ_REMOVE(&server->cmdlisthead, cmd, entries);
vnode_client_t結構體(vnoded_client.h, .c)
vnode_client_t結構體用於建立使用者用戶端實體,對應於vnoded進程中一個vnode_cliententry_t表項。vnode_client_t接收使用者命令vcmd_t,並將使用者命令封裝在vnode_msgio結構體中,並vnode_msgiostart啟動該訊息迴圈。
typedef struct vnode_client {
TAILQ_HEAD(cmdlist, cmdentry) cmdlisthead;
struct ev_loop *loop;
int serverfd;
struct vnode_msgio msgio;
void *data;
vnode_clientcb_t ioerrorcb;
int32_t cmdid;
} vnode_client_t;
cmdlist用於儲存vcmd_t,但理解上一條命令對應一個使用者用戶端,不需要用列表來儲存,檢查代碼後發現沒有往該列表中插入任何對象,vcmd_t存在data中。serverfd用於儲存vnoded進程ctrlchnl。msgio儲存msg訊息層結構體vnode_msgio。Ioerrorcb用於儲存伺服器端io出錯回呼函數。cmdid用於儲存子進程id號。
使用者用戶端向服務用戶端發送訊息,應該向訊息層指定訊息響應回調。程式定義了四種訊息類型。
typedef enum {
VNODE_MSG_NONE = 0,
VNODE_MSG_CMDREQ,
VNODE_MSG_CMDREQACK,
VNODE_MSG_CMDSTATUS,
VNODE_MSG_CMDSIGNAL,
VNODE_MSG_MAX,
} vnode_msgtype_t;
但實際只指定了兩個類型的回調。
static const vnode_msghandler_t msghandler[VNODE_MSG_MAX] = {
[VNODE_MSG_CMDREQACK] = vnode_clientrecv_cmdreqack,
[VNODE_MSG_CMDSTATUS] = vnode_clientrecv_cmdstatus,
};
vnode_msgio_t結構體(vnoded_msg.h, .c)
vnode_msgio_t負責收發訊息,它維護的重點是fd的處理,但不負責fd的初始化,且msghandler也是由client為它指定。
typedef struct vnode_msgio {
struct ev_loop *loop;
int fd;
ev_io fdwatcher;
vnode_msgbuf_t msgbuf;
void *data;
vnode_msghandler_t ioerror;
vnode_msghandler_t msghandler[VNODE_MSG_MAX];
} vnode_msgio_t;
vnode_msgio_t最關鍵的兩個函數是收發訊息函數。
ssize_t vnode_sendmsg(int fd, vnode_msgbuf_t *msgbuf);
ssize_t vnode_recvmsg(vnode_msgio_t *msgio);
vnode_sendmsg可由外部調用,收訊息vnode_recvmsg由fdwatcher在訊息觸發時在vnode_msg_cb回呼函數中調用,讀取訊息後根據訊息類型,選擇不同的msghandler處理。
msghandlefn = msgio->msghandler[msgio->msgbuf.msg->hdr.type];
msghandlefn(msgio);
vnode_cmd.h, .c, vcmdmodule.c
定義了結構體vnode_cmdentry_t,但沒有引用(可見代碼是在其它代碼上修改而來)。定義了命令層次收發函數。主要是發送命令請求vnode_send_cmdreq,接收命令請求vnode_recv_cmdreq,發送命令狀態vnode_send_cmdstatus,發送命令訊號vnode_send_cmdsignal,接收命令訊號vnode_recv_cmdsignal。
void vnode_recv_cmdreq(vnode_msgio_t *msgio);
int vnode_send_cmdreq(int fd, int32_t cmdid, char *argv[], int infd, int outfd, int errfd);
int vnode_send_cmdstatus(int fd, int32_t cmdid, int32_t status);
int vnode_send_cmdsignal(int fd, int32_t cmdid, int32_t signum);
void vnode_recv_cmdsignal(vnode_msgio_t *msgio);
部分函數只有定義,並沒有調用,估計在完善中,例如vnode_send_cmdstatus和
vnode_recv_cmdsignal。
CORE網路模擬軟體分析