侧边栏壁纸
  • 累计撰写 32 篇文章
  • 累计创建 55 个标签
  • 累计收到 2 条评论

目 录CONTENT

文章目录

Linux Namespace:UTS

Testerfans
2022-05-06 / 0 评论 / 23 点赞 / 2,393 阅读 / 8,617 字 / 正在检测是否收录...
温馨提示:
本文最后更新于 2022-06-29,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

本文演示 CentOS 7.6:Linux testerfans 3.10.0-1160.45.1.el7.x86_64

前言

上一篇Linux Namespaces概述我们对容器基于的Linux底层技术—namespace有了整体了解,本文我们将具体学习一下其中的UTS namespace,UTS是UNIX Time-sharing System的缩写,从Linux 2.6.19加入到内核中,用来隔离系统的主机名和 NIS(Network Information Service) 域名资源。

常用命令

我们可以通过uname和domainname查看主机的hostname和NIS名称,大家可以通过uname --help 和domainname --help来查看具体的使用方法。

查看hostname

使用uname(unix name)可以打印本机的主机名、系统版本等信息,或者使用hostname查看主机名。

[root@testerfans ~]# uname -n
testerfans
[root@testerfans ~]# hostname
testerfans

使用 uname --help可以查看详细信息,关于Linux内核版本命名规则大家可以参考Linux内核版本介绍与查询

命令参数 描述 示例 示例说明
-a, --all 按顺序打印全部信息,如果 -p 和 -i 的信息是未知,那么省略
-s, --kernel-name 打印内核名称 Linux 当前系统使用的Linux内核,Linux内核属于单内核
-n, --nodename 打印网络节点主机名称 VM-16-7-centos 当前主机名
-r, --kernel-release 打印内核release 3.10.0-1160.45.1.el7.x86_64 测试系统使用的是CentOS 7系统
3 主版本号
10 次版本号
0 修订版本号
1160.45.1 发行版本的补丁版本
el7 在Red Hat Linux中用来表示企业版Linux(Enterprise Linux)
x86_64 采用的是64位的CPU
-v, --kernel-version 打印内核版本 #1 SMP Wed Oct 13 17:20:51 UTC 2021 SMP: 对称多处理机,表示内核支持多核、多处理器
Wed Oct 13 17:20:51 UTC 2021 内核的编译时间 2021/10/13 17:20:51
-m, --machine 打印机器名称 x86_64 x86_64 : 64位系统
ix86 : 32位系统(x表示3、4、5、6)
-p, --processor 打印处理器名称 x86_64 该机器处理器的类型(CPU)
-i, --hardware-platform 打印硬件平台名称 x86_64 硬件平台告诉我们构建内核的架构
-o, --operating-system 打印操作系统名称。 GNU/Linux 表示当前运行的操作系统为GNU/Linux

查看NIS domain name

domainname命令作用是显示或设置当前网络信息服务(NIS)域的名称。如果不指定参数domainname命令则显示当前NIS域的名称,一个域通常包含同一管理器下的一组主机。hostname可以查看和设置主机名。

[root@testerfans ~]# hostname -y
testerfans.com
[root@testerfans ~]# domainname
testerfans.com
命令参数 描述 示例 示例说
hostname 显示主机名 testerfans 显示主机名为testerfans
hostname [-b] 设置主机名(或从文件) hostname testerfans 设置主机名为 testerfans
{yp,nis,}domainname 显示NIS域名 testerfans.com 显示NIS域名为 testerfans.com
{yp,nis,}domainname 设置NIS域名(或从文件) domainname testerfans.com 设置NIS域名为 testerfans.com

系统调用

clone() :新建 UTS namespace

我们在Linux系统上通过执行man clone后,可以翻到EXAMPLE位置可以查看样例代码。clone()方法是在创建进程的同时创建UTS namespace,并将进程加入到新建的namespace中。

注意:如果man clone搜索不到内容可能是man手册版本问题或者没有man手册,通过执行yum install -y man man-pages 之后再用man clone搜索EXAMPLE。如果还找不到可以直接查找man pages online在线man手册。

#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)

// 调用 clone 时执行的函数
static int childFunc(void *arg)
{
    struct utsname uts;
    char *shellname;
    // 在子进程的 UTS namespace 中设置 hostname
    if (sethostname(arg, strlen(arg)) == -1)
        errExit("sethostname");

    // 显示子进程的 hostname
    if (uname(&uts) == -1)
        errExit("uname");
    printf("uts.nodename in child:  %s\n", uts.nodename);
    printf("My PID is: %d\n", getpid());
    printf("My parent PID is: %d\n", getppid());
    // 获取系统的默认 shell
    shellname = getenv("SHELL");
    if(!shellname){
        shellname = (char *)"/bin/sh";
    }
    // 在子进程中执行 shell
    execlp(shellname, shellname, (char *)NULL);

    return 0;
}
// 设置子进程的堆栈大小为 1M
#define STACK_SIZE (1024 * 1024)

int main(int argc, char *argv[])
{
    char *stack;
    char *stackTop;        
    pid_t pid;

    if (argc < 2) {
        fprintf(stderr, "Usage: %s <child-hostname>\n", argv[0]);
        exit(EXIT_SUCCESS);
    }

    // 为子进程分配堆栈空间,大小为 1M
    stack = malloc(STACK_SIZE);
    if (stack == NULL)
        errExit("malloc");
    stackTop = stack + STACK_SIZE;  /* Assume stack grows downward */

    // 通过 clone 函数创建子进程
    // CLONE_NEWUTS 标识指明为新进程创建新的 UTS namespace
    pid = clone(childFunc, stackTop, CLONE_NEWUTS | SIGCHLD, argv[1]);
    if (pid == -1)
        errExit("clone");

    // 等待子进程退出
    if (waitpid(pid, NULL, 0) == -1)
        errExit("waitpid");
    printf("child has terminated\n");

    exit(EXIT_SUCCESS);
}
  • main 函数负责调用 clone 函数创建一个子进程。在调用 clone 函数时通过设置 CLONE_NEWUTS 标识让子进程拥有自己的 UTS namespace。
  • 子进程执行 childFunc 函数,它先设置新的 hostname,然后打印hostname、当前进程的 PID 和 父进程的 PID,并在最后执行系统的默认 shell。
  • 父进程等待子进程的退出,并最终退出程序。

保存并编译

[root@testerfans ~]# mkdir namespace
[root@testerfans ~]# cd namespace/
[root@testerfans namespace]# vim uts_clone_test.c  #将如下代码拷贝进去,保存
[root@testerfans namespace]# gcc uts_clone_test.c -o uts_clone_test  #编译文件,输出到uts_clone_test内                

执行
执行程序输入child作为子进程的主机名,可以看到如下输出并执行hostname查看主机名。

[root@testerfans namespace]# ./uts_clone_test child      #执行程序
uts.nodename in child:  child	#打印子进程的 主机名
My PID is: 20051	#打印进程ID
My parent PID is: 20050	#打印父进程ID
[root@child namespace]# hostname	#查看主机名变成了child
child

查看uts链接文件
保持上面程序运行(保持执行程序的终端不关闭即可),打开一个新的终端,对子进程和父进程的uts进行检查

[root@testerfans ~]# sudo readlink /proc/$$/ns/uts   #查看当前进程的uts
uts:[4026531838]
[root@testerfans ~]# sudo readlink /proc/20050/ns/uts	#查看父进程的uts
uts:[4026531838]
[root@testerfans ~]# sudo readlink /proc/20051/ns/uts	#查看子进程的uts
uts:[4026532291]
  • 当前进程$$和父进程因为在系统默认的namespace下,namespace-inode-number相同,为4026531838。
  • child进程因为创建分配了不同的uts namespace,namespace-inode-number不同,为4026532291。

退出程序
在执行了./uts_clone_test的终端输入exit退出执行,我们可以看到退出后主机名恢复为testerfans。

[root@child namespace]# exit
exit
child has terminated
[root@testerfans namespace]# 

setns() :加入到已存在UTS namespace

我们在Linux系统上通过执行man 2 setns后,可以翻到EXAMPLE位置可以查看样例代码。通过调用setns()方法可以将进程加入到一个已经存在的namespace内。

       #define _GNU_SOURCE
       #include <fcntl.h>
       #include <sched.h>
       #include <unistd.h>
       #include <stdlib.h>
       #include <stdio.h>

       #define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                               } while (0)
       int
       main(int argc, char *argv[])
       {
           int fd;

           if (argc < 3) {
               fprintf(stderr, "%s /proc/PID/ns/FILE cmd args...\n", argv[0]);
               exit(EXIT_FAILURE);
           }
	   /* 打开一个已经存在的UTS namespace 文件*/
           fd = open(argv[1], O_RDONLY);  
           if (fd == -1)
               errExit("open");
	   /* 把当前进程的 UTS namespace 设置为命令行参数传入的 namespace */
           if (setns(fd, 0) == -1)         
               errExit("setns");
           /* 在新的 UTS namespace 中运行用户指定的程序 */
           execvp(argv[2], &argv[2]);     
           errExit("execvp");
       }
  • 通过 open 函数打开用户传入的 UTS namespace 文件。
  • 把得到的文件描述符传递给 setns 函数。
  • 最后执行用户指定的程序。

保存并编译

[root@child namespace]# vim uts_setns_test.c
[root@child namespace]# gcc uts_setns_test.c -o uts_setns_test

执行
因为我们要将一个进程加入到一个已经存在的UTS namespace中,所以这里面我们传入的参数有两个:

  • argv[1] 一个已经存在的 UTS namespace 文件
  • argv[2] 指定要运行的程序

我们按照如下1、2、3步骤来进行验证:

  1. argv[1]参数我们通过uts_clone_test来创建一个新的UTS namespace,得到一个新的namespace—/proc/31686/ns/uts
[root@child namespace]# ./uts_clone_test child
uts.nodename in child:  child
My PID is: 31686
My parent PID is: 31685
[root@child namespace]# sudo readlink /proc/31686/ns/uts 
uts:[4026532292]
  1. argv[2] 传入要执行的程序:/bin/bash
  2. 新打开一个终端,执行uts_setns_test程序并传入argv[1]和argv[2]
[root@testerfans namespace]# ./uts_setns_test /proc/31686/ns/uts /bin/bash

查看uts链接文件
通过hostname和查看uts文件id,我们发现通过hostname命令获取到的名字均为child,查看/proc/1018/ns/uts、readlink /proc/31686/ns/uts 得到的id均为4026532292。说明/bin/bash加入到了我们创建的、已经存在的UTS namespace内。

[root@child namespace]# hostname
child
[root@child namespace]# echo $$
1018
[root@child namespace]# sudo readlink /proc/1018/ns/uts 
uts:[4026532292]

退出程序
shell 终端内exit退出演示程序。

unshare() :加入到新建UTS namespace

我们在Linux系统上通过执行man 2 unshare后,可以翻到EXAMPLE位置查看样例代码。unshare方法通过将当前进程加入到一个新建的namespace,当前进程不变。

#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>

#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); } while (0)

static void usage(char *pname)
{
    fprintf(stderr, "Usage: %s [options] program [arg...]\n", pname);
    fprintf(stderr, "Options can be:\n");
    fprintf(stderr, "    -i   unshare IPC namespace\n");
    fprintf(stderr, "    -m   unshare mount namespace\n");
    fprintf(stderr, "    -n   unshare network namespace\n");
    fprintf(stderr, "    -p   unshare PID namespace\n");
    fprintf(stderr, "    -u   unshare UTS namespace\n");
    fprintf(stderr, "    -U   unshare user namespace\n");
    exit(EXIT_FAILURE);
}

int main(int argc, char *argv[])
{
    int flags, opt;
    flags = 0;

    while ((opt = getopt(argc, argv, "imnpuU")) != -1) {
        switch (opt) {
        case 'i': flags |= CLONE_NEWIPC;        break;
        case 'm': flags |= CLONE_NEWNS;         break;
        case 'n': flags |= CLONE_NEWNET;        break;
        case 'p': flags |= CLONE_NEWPID;        break;
        case 'u': flags |= CLONE_NEWUTS;        break;
        case 'U': flags |= CLONE_NEWUSER;       break;
        default:  usage(argv[0]);
        }
    }

    if (optind >= argc)
        usage(argv[0]);

    if (unshare(flags) == -1)
        errExit("unshare");
	
    execvp(argv[optind], &argv[optind]);
    errExit("execvp");
}

直接执行程序我们可以看到程序的使用说明。

[root@testerfans namespace]# ./uts_unshare_test
Usage: ./uts_unshare_test [options] program [arg...]
Options can be:
    -i   unshare IPC namespace
    -m   unshare mount namespace
    -n   unshare network namespace
    -p   unshare PID namespace
    -u   unshare UTS namespace
    -U   unshare user namespace

保存并编译

[root@testerfans namespace]# gcc uts_unshare_test.c -o uts_unshare_test

执行
在执行前我们首先查看终端进程的uts namespace id是uts:[4026531838],然后运行程序。这里我们要模拟的是uts namespace,所以输入-u指令,含义是将当前shell进程加入到新建的UTS namespace中。

[root@testerfans namespace]# readlink /proc/$$/ns/uts
uts:[4026531838]
[root@testerfans namespace]# ./uts_unshare_test -u /bin/bash

查看uts链接文件
运行程序后查看ust namespace id uts:[4026532293],与[4026531838]不同,说明当前shell进程加入到了一个新建的UTS namespace中。

[root@testerfans namespace]# readlink /proc/$$/ns/uts
uts:[4026532293]

退出程序
shell 终端内exit退出演示程序。

clone和unshare的区别

clone和unshare的功能都是创建并加入新的namespace, 他们的区别是:

  • unshare是使当前进程加入新的namespace
  • clone是创建一个新的子进程,然后让子进程加入新的namespace,而当前进程保持不变

总结

本章我们通过实践clone()、setns()、unshare()三个系统调用对uts namespace进行了实际操作,理解了clone()、setns()、unshare()的使用和uts namespace的进程间隔离,接下来我们将继续介绍其他的namespace。


本文参考:
Linux Namespace:UTS
man 2 clone
man 2 setns
man 2 unshare
Network Information Service
Linux内核版本介绍与查询

23

评论区