Linux namespace 系列:初識 namespace

2021-03-02 喵喵不知道

Linux namespace 是 Linux 提供的一種內核級別環境隔離的方法。

Linux namespace 將全局系統資源封裝在一個抽象中,從而使 namespace 內的進程認為自己具有獨立的資源實例。

分類系統調用參數相關內核版本UTS namespaceCLONE_NEWUTSLinux 2.6.19IPC namespaceCLONE_NEWIPCLinux 2.6.19PID namespaceCLONE_NEWPIDLinux 2.6.24Mount namespaceCLONE_NEWNSLinux 2.4.19Network namespaceCLONE_NEWNETLinux 2.6.29User namespaceCLONE_NEWUSERLinux 3.8proc

每個進程都有一個 /proc/pid/ns 目錄,其下面的文件依次表示每個 namespace。

查看當前進程所屬的 namespace 信息

ll /proc/$$/nstotal 0lrwxrwxrwx 1 root root 0 Nov  3 11:50 cgroup -> cgroup:[4026531835]lrwxrwxrwx 1 root root 0 Nov  3 11:50 ipc -> ipc:[4026531839]lrwxrwxrwx 1 root root 0 Nov  3 11:50 mnt -> mnt:[4026531840]lrwxrwxrwx 1 root root 0 Nov  3 11:50 net -> net:[4026531992]lrwxrwxrwx 1 root root 0 Nov  3 11:50 pid -> pid:[4026531836]lrwxrwxrwx 1 root root 0 Nov  3 11:50 pid_for_children -> pid:[4026531836]lrwxrwxrwx 1 root root 0 Nov  3 11:50 user -> user:[4026531837]lrwxrwxrwx 1 root root 0 Nov  3 11:50 uts -> uts:[4026531838]

從 3.8 版本的內核開始,該目錄下的每個文件都是一個特殊的符號連結,連結指向 $namespace:[$namespace-inode-number]。前半部份為 namespace 的名稱,後半部份的數字表示這個 namespace 的 inode number(句柄號),inode number 用來對進程所關聯的 namespace 執行某些操作。

這些符號連結的用途之一是用來確認兩個不同的進程是否處於同一 namespace 中。如果兩個進程指向的 namespace inode number 相同,就說明他們在同一個 namespace 下,否則就在不同的 namespace 下。這些符號連結指向的文件比較特殊,不能直接訪問,事實上指向的文件存放在被稱為 nsfs 的文件系統中,該文件系統用戶不可見。可以使用系統調用 stat() 在返回的結構體的 st_ino 欄位中獲取 inode number,shell 命令也可以查看。

stat -L /proc/$$/ns/net  File: 『/proc/1244/ns/net』  Size: 0           Blocks: 0          IO Block: 4096   regular empty fileDevice: 4h/4d  Inode: 4026531992  Links: 1Access: (0444/-r--r--r--)  Uid: (    0/    root)   Gid: (    0/    root)Access: 2020-11-03 12:22:39.789973532 +0800Modify: 2020-11-03 12:22:39.789973532 +0800Change: 2020-11-03 12:22:39.789973532 +0800 Birth: -

如果打開了其中一個文件,那麼只要與該文件相關聯的文件描述符處於打開狀態。即使該 namespace 中的所有進程都終止了,該 namespace 依然不會被刪除。

API

Linux 提供的 namespace 操作 API 有 clone()、setns()、unshare()。

clone()

創建一個新的進程。

int clone(int (*child_func)(void *), void *child_stack, int flags, void *arg);

child_func 傳入子進程運行的程序主函數。

有別於系統調用 fork(),雖然都相當於把當前進程複製了一份。但 clone() 可以更細粒度地控制與子進程共享的資源,包括虛擬內存、打開的文件描述符和信號量等等。一旦指定了標誌位 CLONE_NEW*,相對應類型的 namespace 就會被創建,新創建的進程也會成為該 namespace 中的一員。

setns()

將進程加入到一個已經存在的 namespace 中。

將調用的進程與特定類型 namespace 的一個實例分離,並將該進程與該類型 namespace 的另一個實例重新關聯。

int setns(int fd, int nstype);

fd 表示要加入的 namespace 的文件描述符,可以通過打開其中一個符號連結來獲取,也可以通過打開 bind mount 到其中一個連結的文件來獲取。nstype 讓調用者可以去檢查 fd 指向的 namespace 類型,值可以設置為前文提到的常量 CLONE_NEW*,填 0 表示不檢查。如果調用者已經明確知道自己要加入了 namespace 類型,或者不關心 namespace 類型,就可以使用該參數來自動校驗。

結合 setns() 和 execve() 可以實現一個簡單但非常有用的功能:將某個進程加入某個特定的 namespace,然後在該 namespace 中執行命令。util-linux 包裡提供了 nsenter 命令,其提供了一種方式將新創建的進程運行在指定的 namespace 裡面。通過命令行(-t 參數)指定要進入的 namespace 的符號連結,然後利用 setns() 將當前的進程放到指定的 namespace 裡面,再調用 clone() 運行指定的執行文件。

nsenter --target $pid --mount --uts --ipc --net --pid

unshare()

讓進程脫離當前 namespace,加入一個新的 namespace。

unshare() 與 clone() 類似,但它運行在原先的進程上,不需要創建一個新進程。先通過指定的 flags 參數 CLONE_NEW* 創建一個新的 namespace,然後將調用者加入該 namespace。

Linux 自帶 unshare 命令。

echo $$1244
cat /proc/1244/mounts | grep mqmqueue /dev/mqueue mqueue rw,relatime 0 0
readlink /proc/1244/ns/mntmnt:[4026531840]
unshare -m /bin/bash
readlink /proc/$$/ns/mntmnt:[4026532213]

對比兩個 readlink 命令的輸出,可以知道兩個 shell 處於不同的 mount namespace 中。

umount /dev/mqueue
cat /proc/$$/mounts | grep mq
cat /proc/1244/mounts | grep mqmqueue /dev/mqueue mqueue rw,relatime 0 0

改變新的 namespace 中的某個掛載點。可以看出,新的 namespace 中的掛載點 /dev/mqueue 已經消失了,但在原來的 namespace 中依然存在。

namespace 之外

在 Linux 內核中,有很多資源和對象是不能被 namespace 化的,最典型的例子就是:時間。

相關焦點

  • Linux namespace之:pid namespace
    理解pid namespacePID namespace表示隔離一個具有獨立PID的運行環境。在每一個pid namespace中,進程的pid都從1開始,且和其他pid namespace中的PID互不影響。這意味著,不同pid namespace中可以有相同的PID值。
  • php命名空間namespace詳解
    PHP的命名空間主要解決三種衝突的問題:常量,函數,類簡單來說:namespace就相當於是用來建一個目錄,將namespace下面的代碼放在該目錄裡面,與外面的區分開來。例如:/***namespace 之前不能有任何代碼,除了declare();多個文件可以使用同一個命名空間,但同一命名空間下定義的內容是不能衝突的。
  • C++命名空間namespace的理解
    namespace Name1{ namespace Name2 { /*...*/ } /*...*/}全局作用域也叫默認命名空間。2、舉例說明比如現在有一個小學,學校裡有六個年級,每個年級有三個班級。
  • 【乾貨】網絡虛擬化—— linux虛擬網絡基礎
    在linux裡面devic(設備)與傳統網絡概念裡的物理設備(如交換機、路由器)不同,Linux所說的設備,其背後指的是一個類似於數據結構、內核模塊或設備驅動這樣的含義。就是說device可能只是軟體系統裡的一個驅動,一個函數接口。Tap位於二層數據鏈路層,tun位於三層網絡層,兩者在linux裡的函數結構幾乎一致,除了一個flag值區分tap/tun。
  • 小白入門:初識C++
    初識C++,向大家介紹幾個我用來做練習編寫的幾道小程序,都是C++Primer上面的練習題,分享給大家。eg1:編寫程序,使用遞減運算符在循環中按遞減順序列印出10到1之間的整數。代碼如下:#include<iostream>#include<cstdlib>usingnamespacestd;intmain(){inti=10;while(i){cout<<i;--i;}system("pause");return0;}運行結果:10 9 8 7 6 5 4 3 2 1小貼士:由於在main函數前添加了
  • 容器網絡基礎之linux bridge
    本文帶你理論+實踐了解linux brdge。當你看到容器網絡問題的時候不用慌。
  • linux的mount流程
    最近都有碰到一些關於linux的mount的問題,需要去解決,先把整個mount流程理清楚後,查一個問題會比較清楚,同時也是為了鞏固自己的知識點
  • Oracle Linux 7.4 發布,新增多項功能
    Prevents container users from being able to gain the same privileges at the global level by allowing separation of the container namespace from the underlying operating system namespace.
  • C++系列2-1:C++快速入門之命名空間和輸入輸出
    舉個例子:要想理解,而不是以一種模板的方式看待C++輸出Hello World的這樣一段簡單程序,就需要很多概念#include <instream>using namespace std;int main(){ cout<<"Hello World!"
  • LeetCode刷題:Array系列之Remove Element
    2、第一種實現方式思路:遍歷vector的所有項,分別與val比較2.1 頭文件和準備工作#include <iostream>#include <vector>using namespace std;2.2 實現代碼class Solution{public:    int removeElement