이전 글
https://braindisk.tistory.com/140
UTS namespace란
system의 hostname을 namespace 별로 격리시켜 주는 기능입니다. 자세히 말하자면, Linux system call 중 하나인 uname에서 utsname이라는 struct에 정의된 식별자 중 nodename을 isolate하는 것입니다.
일단 이해하기 어려운데요,,
Linux system call 중 하나인 uname에서 utsname이라는 struct에 정의된 식별자 중 nodename을 isolate하는 것입니다.
위 문장은 uname이 어떤 도구인지 파악하면 이해하기 쉽습니다. 일단 uname은 현재 커널에 대한 이름 및 정보를 가져오는 도구입니다. 그리고 사실 uname은 utsname이라는 구조체에서 정보를 가져오는 함수입니다.
utsname 구조체는 아래와 같이 생겼습니다.
struct utsname {
char sysname[]; /* Operating system name (e.g., "Linux") */
char nodename[]; /* Name within "some implementation-defined
network" */
char release[]; /* Operating system release
(e.g., "2.6.28") */
char version[]; /* Operating system version */
char machine[]; /* Hardware identifier */
#ifdef _GNU_SOURCE
char domainname[]; /* NIS or YP domain name */
#endif
};
그리고 문장에서 "utsname 구조체에 정의된 식별자 중 nodename을 isolate하는 것"이라고 마저 설명하고 있습니다. utsname 구조체를 보시면 nodename이라는 필드가 있는 것을 보실 수 있습니다. 즉 UTS namespace는 utsname 구조체의 필드인 nodename을 격리시키는 기능이라고 이해할 수 있습니다.
그럼 nodename은 뭘까요? 사실 nodename은 네트워크를 통해 통신할 때 시스템이 사용하는 이름입니다. 네트워크 상에서 호스트 네임이라고 볼 수 있는 것이죠. uname -n 명령어를 입력하면 호스트 네임을 확인할 수 있습니다.
ubuntu@ip-192-168-0-26:~$ uname -n
ip-192-168-0-26
(프라이빗 ipv4 주소가 보이네요)
clone()을 이용한 namespace 생성
사실 clone()은 새로운 프로세스를 생성하는 시스템 콜이며 다양한 매개 변수를 통해서 여러 설정이 가능합니다. 따라서 스레드 또는 독립적인 프로세스를 생성하는데 유용하게 사용됩니다.
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *ptid, struct user_desc *tls, pid_t *ctid */ );
- int (*fn)(void *) --> 함수 포인터: 자식 프로세스 또는 스레드가 수행할 동작 정의
- void *child_stack --> 자식 프로세스가 사용할 스택의 위치: 리눅스의 모든 프로세서(HPPA 프로세서 제외)에서 스택은 아래 방향으로 커지기 때문에(?) 할당한 메모리 공간의 최상위 주소를 가리킨다고 합니다.
- int flags --> namespace 글에서 다뤘던 6가지 flag: 저희는 새로운 UTS namespace에서 프로세스를 생성할 것이기 때문에 CLONE_NEWUTS 플래그를 넣어주면 됩니다. 그리고 예제에서는 SIGCHLD을 함께 사용하는데, 이건 자식 프로세스의 종료 신호로 사용되는 시그널입니다.
- void *arg --> 자식 프로세스 함수 호출 시 넘겨 줄 매개변수
이제 clone()을 이용해서 자식 프로세스 또는 스레드를 생성할 때 sethostname()을 사용하여 기존 프로세스의 hostname과 다른 hostname을 가지는 UTS namespace를 생성하는 예제를 작성해보겠습니다.
#define _GNU_SOURCE
#include <sys/wait.h>
#include <sys/utsname.h>
#include <sched.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define errExit(msg) do { perror(msg); exit(EXIT_FAILURE); \
} while (0)
static int /* Start function for cloned child */
childFunc(void *arg)
{
struct utsname uts;
/* Change hostname in UTS namespace of child */
if (sethostname(arg, strlen(arg)) == -1)
errExit("sethostname");
/* Retrieve and display hostname */
if (uname(&uts) == -1)
errExit("uname");
printf("uts.nodename in child: %s\n", uts.nodename);
/* Keep the namespace open for a while, by sleeping.
This allows some experimentation--for example, another
process might join the namespace. */
sleep(200);
return 0; /* Child terminates now */
}
#define STACK_SIZE (1024 * 1024) /* Stack size for cloned child */
int
main(int argc, char *argv[])
{
char *stack; /* Start of stack buffer */
char *stackTop; /* End of stack buffer */
pid_t pid;
struct utsname uts;
if (argc < 2) {
fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
exit(EXIT_SUCCESS);
}
/* Allocate stack for child */
stack = malloc(STACK_SIZE);
if (stack == NULL)
errExit("malloc");
stackTop = stack + STACK_SIZE; /* Assume stack grows downward */
/* Create child that has its own UTS namespace;
child commences execution in childFunc() */
pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);
if (pid == -1)
errExit("clone");
printf("clone() returned %ld\n", (long) pid);
/* Parent falls through to here */
sleep(1); /* Give child time to change its hostname */
/* Display hostname in parent's UTS namespace. This will be
different from hostname in child's UTS namespace. */
if (uname(&uts) == -1)
errExit("uname");
printf("uts.nodename in parent: %s\n", uts.nodename);
if (waitpid(pid, NULL, 0) == -1) /* Wait for child */
errExit("waitpid");
printf("child has terminated\n");
exit(EXIT_SUCCESS);
}
// code from https://linux.die.net/man/2/clone
함수 흐름을 따라가시면 main 함수 호출 시 첫번째 인자로 넘어온 이름(argv[1])을 childFunc() 함수로 넘기고 childFunc() 내부에서 sethostname()에 다시 매개변수로 넘기는 것을 볼 수 있습니다.
즉 UTS namespace가 기능하여 부모 프로세스와 hostname이 다른 자식 프로세스를 생성할 수 있었습니다.
최근에는 메모리 안정성 때문에 C나 C++이 Rust로 넘어가는 추세라던데 저도 Rust를 배워볼까 생각중입니다. 메모리 문제만 잡아도 C, C++로 인한 버그의 70%가 해결된다고 하니... 확실히 Rust가 각광받는 이유가 있습니다. Rust는 C나 C++과 다르게 소유권이라는 개념이 등장해서 메모리 안정성이 보장된다고 합니다
Ref
https://bluese05.tistory.com/12
'리눅스' 카테고리의 다른 글
리눅스 IPC namespace (0) | 2022.10.02 |
---|---|
리눅스 namespace (1) | 2022.09.30 |
리눅스 Cgroup (0) | 2022.09.30 |
리눅스 chroot (0) | 2022.09.29 |
리눅스 하드웨어 용량 확인 및 swap 메모리 확보 (0) | 2022.08.19 |