一文搞懂 Linux 系統下進程

  1. 進程概念 =======

所謂進程 (Process),是指運行中的程序,它是程序的一個運行實例。對於同一個程序,當我們分別運行兩次時候,操作系統會創建兩個不同的進程。注意,程序不等同於進程,進程是正在執行中的程序以及相關資源 (如程序計數器 PC、打開的文件描述符、掛起的信號、處理器狀態、寄存器狀態、內核數據、臨時數據堆棧等) 的總稱,而程序只是存儲在某種介質上 (如磁盤) 的一組機器機器代碼指令和數據。

對於進程與程序之間的差異,其示意圖如下所示。

  1. 進程數據結構 =========

Linux 操作系統爲了便於管理進程,在linux/sched.h文件中定義了名爲task_struct的數據結構,該結構包含了每一個運行中的具體進程所需的所有信息,比如進程優先級、進程的狀態、進程 PID、文件系統信息等等。對於 task_struct 數據結構,又稱爲 “進程描述符 (Process Descriptor)” 。Linux 把每個進程 (task_struct) 都放在內核中的雙向循環鏈接中,該雙向循環鏈表又被稱爲 “任務隊列 (Task List)”。

Linux 0.01版本中,該結構體 (task_struct) 僅有幾十個成員,閱讀起來十分方便。

struct task_struct {
/* these are hardcoded - don't touch */
 long state; /* -1 unrunnable, 0 runnable, >0 stopped */
 long counter;
 long priority;
 long signal;
 fn_ptr sig_restorer;
 fn_ptr sig_fn[32];
/* various fields */
 int exit_code;
 unsigned long end_code,end_data,brk,start_stack;
 long pid,father,pgrp,session,leader;
 unsigned short uid,euid,suid;
 unsigned short gid,egid,sgid;
 long alarm;
 long utime,stime,cutime,cstime,start_time;
 unsigned short used_math;
/* file system info */
 int tty;  /* -1 if no tty, so it must be signed */
 unsigned short umask;
 struct m_inode * pwd;
 struct m_inode * root;
 unsigned long close_on_exec;
 struct file * filp[NR_OPEN];
/* ldt for this task 0 - zero 1 - cs 2 - ds&ss */
 struct desc_struct ldt[3];
/* tss for this task */
 struct tss_struct tss;
};

但是到了5.4.3版本及之後,該結構體大小已經到了KB級別了,由此可見從0.01版本到5.4.3版本間新增了許多的特性和功能。

2.1 進程描述符 PID

前面提到過,task_struct數據結構描述了每個進程的詳細完整信息。在該數據結構中,有一個最爲顯眼的成員變量,即pid。它是 Linux 內核用來識別各進程的唯一標識,稱爲 “進程標識值 (Process Identificaion Value, PID)”。在0.01內核版本中,它是long int類型,但是之後,其類型統一適配爲pid_t類型。pid_tunsigned int的一個別名。

typedef   signed int pid_t;
pid_t     pid;

從某種層面上來說,成員pid的數據類型取值範圍表明了系統中允許同時運行 (存在) 的進程的最大數量,因爲進程 PID 不能爲負數。儘管unsigned int類型的取值範圍達到了0~4294967295,但是實際情況中,基於硬件技術和資源限制等緣故,操作系統上不可能同時運行這麼多數量的進程。

可通過查看/proc/sys/kernel/pid_max文件以得知當前設備環境能夠支持的同時運行的最大進程數量限制。某些情況下,可能通過修改該文件配置值,以提高進程的並行數量。

2.1.1 進程資源限制

由於系統資源有限,因此,內核必須嚴格把控並記錄每一個運行中的進程的資源 (包括內存、CPU、文件句柄等) 詳細使用情況,並且給每一個進程一個默認限制閾值。這是保證每個進程得以在操作系統上完美運行的前提。在task_struct數據結構中,有一個數據成員signal,該成員是一個struct signal_struc類型。該數據類型中成員變量 (數組)rlim用來記錄當前進程的資源限制。

//記錄各進程中的16個屬性資源.
#define RLIM_NLIMITS  16
typedef unsigned long __kernel_ulong_t;

struct rlimit {
 __kernel_ulong_t rlim_cur;
 __kernel_ulong_t rlim_max;
};

struct signal_struct {
 struct rlimit rlim[RLIM_NLIMITS];
};
struct task_struct{
 /* Signal handlers: */
 struct signal_struct  *signal;
};

signal成員和rlim成員間的關聯如下圖:

signal_struct 類型聲明中的rlim成員可知,該成員是一個擁有 16 個元素大小的數組,這意味着操作系統對每個進程有 16 個資源限制。對於struct rlimit數據類型,共聲明瞭兩個成員,分別是:rlim_currlim_max。其中rlim_cur是軟限制 (Soft Limit),rlim_max是硬限制 (Hard Limit)。

軟限制硬限制

  1. 硬限制是指由超級用戶 / root 設置的對用戶的最大限制。該值在/etc/security/limits.conf配置文件中進行設置,將其視爲上限或是天花板。

  2. 軟限制是內核對相應資源強制執行的值。硬限制充當軟限制的上限;非特權進程可以將其軟限制設置爲從 0 到硬限制的範圍內的值,並且 (不可逆地) 降低其硬限制。

下面是這 16 個特定於進程的資源限制的說明。它們定義於include/uapi/asm-generic/resource.h文件中,其中各選項的具體含義如下:

#define RLIMIT_CPU  0  // 按毫秒計算的最大CPU時間
#define RLIMIT_FSIZE 1  // 允許的最大文件長度
#define RLIMIT_DATA  2  // 數據段的最大長度 
#define RLIMIT_STACK 3  // (用戶狀態)棧的最大長度
#define RLIMIT_CORE  4  // core轉存文件的最大長度
#define RLIMIT_RSS  5  // 常駐內存的最大尺寸(進程使用頁幀的最大數目)
#define RLIMIT_NPROC 6  // 進程UID用戶可以打開的進程的最大數量
#define RLIMIT_NOFILE 7  // 允許打開文件的最大數量
#define RLIMIT_MEMLOCK 8     // 不可換出頁的最大數量 
#define RLIMIT_AS   9 // 進程佔用的虛擬地址空間的最大尺寸
#define RLIMIT_LOCKS  10 // 文件鎖的最大數目
#define RLIMIT_SIGPENDING 11 // 待決信號的最大數量
#define RLIMIT_MSGQUEUE  12 // 信息隊列的最大數目
#define RLIMIT_NICE   13 // 非實時進程的優先級(和調度有關, 0-39 for nice level 19 .. -20)
#define RLIMIT_RTPRIO  14 // 最大的實時優先級
#define RLIMIT_RTTIME  15 /* 指定在不進行阻塞系統調用的情況下,根據實時調度策略調度的進程可能消耗的CPU時間的限制(以微秒爲單位) */

Linux 內核在/proc文件系統中,對每一個運行着的進程,都會創建一個相應的資源限制詳情的文件。因此通過查看/proc/self/limits文件可得知當前進程的資源限制。查看指定進程的資源限制,只需將self換爲對應的進程PID,即:/proc/PID/limits。比如查看 PID 爲 141(cat /proc/7/limits) 的進程的資源限制

Linux 系統提供了getrlimits()setrlimit()兩個系統調用函數,它們分別用來獲取、設置資源限制。

#include <sys/time.h>
#include <sys/resource.h>

int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);

int prlimit(pid_t pid, int resource, const struct rlimit *new_limit,
     struct rlimit *old_limit);

除了使用這兩個系統函數外,還可以通過修改上面提到的系統配置文件/etc/security/limits.conf來動態更改資源限制。在該配置文件中,每一行均以 “#<domain> <type> <item> <value>” 的組成形式,描述了用戶的限制。其中#<domain> 可以是用戶名或組名,也可以是通配符*<type> 表明是軟限制,還是硬限制。<item>表明要限制的條目 (即上面 16 種資源限制之一);<value>則是該限制條目的值。

////vim /etc/security/limits.conf
#<domain>      <type>  <item>         <value>
#*               soft    core            0
#*               hard    rss             10000
#@student        hard    nproc           20
#@faculty        soft    nproc           20
#@faculty        hard    nproc           50
#ftp             hard    nproc           0
#@student        -       maxlogins       4

# End of file
* soft nofile 131072
* hard nofile 131072

此外,還可以通過命令行方式來進行資源限制的獲取與設置。該命令是ulimit,它的使用方式如下 (方括號的數字用以表明ulimit支持的選項參數)。

ulimit: usage: ulimit [-SHacdefilmnpqrstuvx] [limit]

比如查看當前系統的軟限制:ulimit -S -a;查看當前系統的硬限制:ulimit -H -a。爲一個變量設置指定的軟限制:ulimit -S [option] [number],其中[option]是各限制的縮寫,通過ulimit -a可以看到各限制條目的縮寫,即下面括號中的-c、-d、-e、-f等。

[root@center-controller-7f4b9fbffc-t5wnh CenterController]# ulimit -a
core file size          (blocks, -c) unlimited
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 255136
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1048576
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) unlimited
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

爲一個限制條目設置特定的硬限制:ulimit -H [option] [number]

2.2 進程運行狀態

操作系統上的進程並非總是能夠立刻執行,有時它需要等待內核調度的切換,以及資源的分配才能夠運行。對於被加載器加載到內存並開始運行的每一個進程,都將有一個對應的狀態標誌,且這些狀態標誌是互斥的,即同一個時刻,進程只能存在一個狀態 (State)。這些狀態可以是下圖中的之一。

該狀態由task_struct數據類型中的state成員表示。

struct task_struct {
 /* -1 unrunnable, 0 runnable, >0 stopped: */
 volatile long   state;
};

對於0.01版本中 Linux 的進程,其狀態共有以下 5 種,它們分別是:運行狀態 (TASK_RUNNING)、可中斷睡眠狀態 (TASK_INTERRUPTIBLE)、不可中斷睡眠狀態 (TASK_UNINTERRUPTIBLE)、殭屍狀態 (TASK_ZOMBIE) 和暫停狀態 (TASK_STOPPED)。這些狀態定義於linux/sched.h文件中。

//linux/sched.h   0.01 version
#define TASK_RUNNING   0
#define TASK_INTERRUPTIBLE  1
#define TASK_UNINTERRUPTIBLE 2
#define TASK_ZOMBIE    3
#define TASK_STOPPED   4

對於TASK_RUNNINGTASK_INTERRUPTIBLETASK_UNINTERRUPTIBLETASK_ZOMBIETASK_STOPPED狀態,它們的含義分別如下:

  1. 可運行狀態 (TASK_RUNNING)

進程是可執行的。該進程可能正在執行,或在運行隊列中等待被執行。該狀態是進程在用戶空間中執行的唯一可能狀態。

  1. 可中斷的等待狀態 (TASK_INTERRUPTIBLE)

進展正在睡眠 (被阻塞,或是被掛起),直到某個條件到達。喚醒進程的條件可以是:產生一個硬件中斷,釋放其正在等待的系統資源;亦或是傳遞一個信號。這時進程的狀態將會切換到TASK_RUNNING

  1. 不可中斷的等待狀態 (TASK_UNINTERRUPTIBLE)

與 “可中斷的等待狀態” 類似。但是有一個不同地方是即使把信號傳遞到一個處於TASK_UNINTERRUPTIBLE狀態中的進程,也不會改變該進程的狀態。相比於 “可中斷的等待狀態 (TASK_INTERRUPTIBLE)”,使用場景相對較少,但在某些驅動程序中應用較爲廣泛。

  1. 暫停狀態 (TASK_STOPPED)

進程的執行被暫停。比如當進程收到SIGSTOPSIGTSTPSIGTTIN或是SIGTTOU信號,或是在調試期間收到任何信息,都會使進程進入這種狀態。

  1. 殭屍狀態 (TASK_ZOMBIE)

進程的執行已經終止,但是其父進程還沒有回收 (通過wait()waitpid()) 該進程所佔用的某些系統資源。

這 5 個狀態之間在滿足某種條件之下,能夠相互地進行轉換。Linux 5.4.3版本的中,進程的狀態已經添加了很多,如下所示:

/* Used in tsk->state: */
#define TASK_RUNNING  0x0000
#define TASK_INTERRUPTIBLE 0x0001
#define TASK_UNINTERRUPTIBLE 0x0002
#define __TASK_STOPPED  0x0004
#define __TASK_TRACED  0x0008
/* Used in tsk->exit_state: */
#define EXIT_DEAD   0x0010
#define EXIT_ZOMBIE   0x0020
#define EXIT_TRACE   (EXIT_ZOMBIE | EXIT_DEAD)
/* Used in tsk->state again: */
#define TASK_PARKED   0x0040
#define TASK_DEAD   0x0080
#define TASK_WAKEKILL  0x0100
#define TASK_WAKING   0x0200
#define TASK_NOLOAD   0x0400
#define TASK_NEW   0x0800
#define TASK_STATE_MAX  0x1000

這裏對新增的__TASK_TRACED、和TASK_DEAD狀態進行簡要的補充說明。

  1. 跟蹤狀態 (__TASK_TRACED)

被其他進程跟蹤的進程。如通過ptrace 對調試程序進行跟蹤。__TASK_TRACED本身不是進程狀態,它主要用於區分常規的進程。

  1. 殭屍撤銷狀態 (TASK_DEAD)

對於TASK_DEAD狀態,《深入 Linux 內核》一書中有說道,“它是指wait()系統調用已發出,而進程完全從系統中移除之前的狀態。只有多個線程同時對同一個進程發出wait()調用時候,該狀態纔有意義。”

2.2.1 進程狀態的縮寫標誌

在 2.3 節裏詳細描述了 Linux 系統上進程的可呈現狀態和轉換。通常使用top來查看進程列表時候,其狀態列都僅顯示一個縮寫標誌,比如RSD等,下面將一一列出通常進程狀態的縮寫。

R是可執行狀態 (TASK_RUNNING) 的狀態標誌;S是可中斷的睡眠狀態 (TASK_INTERRUPTIBLE) 狀態標誌;D是不可中斷睡眠狀態 (TASK_UNINTERRUPTIBLE) 的狀態標誌;T是暫停狀態 (TASK_STOPPED) 或跟蹤狀態 (TASK_TRACED) 的標誌;Z是殭屍進程 (TASK_ZOMBIE) 的狀態標誌。

2.3 查找指定進程 PID

對於運行中的進程,可以使用以下幾種方式來快速查看其進程的 PID。

(1) ps假如當前系統上面運行着進程 A,那麼使用 ps 命令查看該進程的 PID 時,結合管道、grep 命令方式,即: ps -axu|grep A ,即可得到進程 A 的 PID。

可以看到該進程的 PID 是 4007。

(2) pidof除了使用 ps 來查看進程的 PID 外,還可以使用 “pidof 進程名” 的方式來查看。如下:

(3) pgrep pgrep命令是使用通過grep命令管道傳輸的ps命令的快捷方式。它使用名稱或定義的模式搜索特進程的所有匹配項。其命令語法爲:pgrep <options> <pattern>。常用的選項參數如下:

 -d, --delimiter <string>  specify output delimiter
 -l, --list-name           list PID and process name
 -a, --list-full           list PID and full command line
 . . . . . . //省略若干選項參數
 -v, --inverse             negates the matching
 -w, --lightweight         list all TID
 -c, --count               count of matching processes

比如查看kubelet進程的PID,並且列出該PID對應的進程名,則使用pgrep -l kubelet

附:可以使用命令 "pstree -p PID"、"ps -ejH"、"ps -aux --forest"或"ps axjf" 以樹 (tree) 的形式打印進程。

2.4 查找指定 PID 的線程

獲取操作系統上有關線程的信息,可以使用 “ps -eLf” 和 “ps axms” 命令。若想查看某個進程下的線程,則可使用 “ps -T -p PID”,其中參數-T可以開啓線程;或使用 “top -H -p PID” 方式。也可以直接使用pstree -p PID命令以樹形方式打印指定PID下的所有線程列表信息,不過該方式不會列出線程名。

2.5 kill 指定 PID 進程

kill掉一個正在運行的進程,最常規的方法是先ps找出該進程的PID,然後再使用kill()命令向該進程PID發送信號以達到終止進程的目的。其實,有一個命令可以幫我們省去ps查找進程PID的過程。這個命令是pkill(此外,還有killall),該命令也是與kill命令一起使用ps命令的快捷方式。pkill命令用於根據進程PID名稱向指定進程發送信號。

pkill命令的語法格式是:pkill [options] <pattern>。該命令常見的選項參數有:

-<sig>, --signal <sig>    signal to send (either number or name)
 -e, --echo                display what is killed
 -c, --count               count of matching processes
 . . . . . . //省略
 -f, --full                use full process name to match
 -g, --pgroup <PGID,...>   match listed process group IDs
 -P, --parent <PPID,...>   match only child processes of the given parent

比如有一個進程A,現想要使用STGSTOP信號停止該進程,則可以:pkill -19 A。使用kill -l命令可以查看所有信號及對應的序號。

[root@node1 ~]# kill -l
 1) SIGHUP       2) SIGINT       3) SIGQUIT      4) SIGILL       5) SIGTRAP
 6) SIGABRT      7) SIGBUS       8) SIGFPE       9) SIGKILL     10) SIGUSR1
11) SIGSEGV     12) SIGUSR2     13) SIGPIPE     14) SIGALRM     15) SIGTERM
16) SIGSTKFLT   17) SIGCHLD     18) SIGCONT     19) SIGSTOP     20) SIGTSTP
21) SIGTTIN     22) SIGTTOU     23) SIGURG      24) SIGXCPU     25) SIGXFSZ
26) SIGVTALRM   27) SIGPROF     28) SIGWINCH    29) SIGIO       30) SIGPWR
31) SIGSYS      34) SIGRTMIN    35) SIGRTMIN+1  36) SIGRTMIN+2  37) SIGRTMIN+3
38) SIGRTMIN+4  39) SIGRTMIN+5  40) SIGRTMIN+6  41) SIGRTMIN+7  42) SIGRTMIN+8
43) SIGRTMIN+9  44) SIGRTMIN+10 45) SIGRTMIN+11 46) SIGRTMIN+12 47) SIGRTMIN+13
48) SIGRTMIN+14 49) SIGRTMIN+15 50) SIGRTMAX-14 51) SIGRTMAX-13 52) SIGRTMAX-12
53) SIGRTMAX-11 54) SIGRTMAX-10 55) SIGRTMAX-9  56) SIGRTMAX-8  57) SIGRTMAX-7
58) SIGRTMAX-6  59) SIGRTMAX-5  60) SIGRTMAX-4  61) SIGRTMAX-3  62) SIGRTMAX-2
63) SIGRTMAX-1  64) SIGRTMAX
  1. 殭屍進程 =======

對於殭屍進程,維基上面是這樣描述的:

在 Unix 和類 Unix 計算機操作系統上,殭屍進程或失效進程是已完成執行 (通過exit()系統調用) 但在進程表 (Process Table)中仍有條目的進程:它是處於 “終止狀態” 的進程。這發生在子進程中,其中仍然需要該條目以允許父進程讀取其子進程的退出狀態;一旦通過wait()waitpid()系統調用讀取退出狀態,則殭屍進程的條目將從進程表中刪除,並被收割。子進程從資源表中刪除之前總數首先變成殭屍進程。大多數情況下,在正常的系統操作中,殭屍進程會立即被它們的父進程wait,然後被系統收割。長時間處於殭屍狀態 (TASK_ZOMBIE) 的進程通常是一個錯誤,且會導致資源泄露。

進程終止後,有些信息對於父進程和內核是很有用的,比如進程PID、進程退出狀態、進程運行的CPU時間等。不允許類UNIX內核在進程一終止後就丟棄包含在進程描述符字段中的數據。只有父進程發出來wait()waitpid()等系統調用之後纔可以。

注:進程使用exit()系統調用終止時,分配給該進程的所有內存和資源都將被釋放。但是進程表中的條目仍然可用,比如PID、進程表項等。

3.1 殭屍進程產生

從上面對殭屍進程的定義可知它發生在子進程中。其具體的產生過程是:父進程調用fork()創建子進程後,子進程一直運行直到其終止。此時,它將立即從內存中移除,但是其PID仍然保留在內存中 (儘管PID佔用的空間並不大)。這時子進程的狀態成爲EXIT_ZOMBIE,並向其父進程發送SIGCHLD信號。正常情況下,該進程的父進程此時會調用waitwaitpid系統函數來獲取子進程的退出狀態及相關信息,並於wait()/waitpid()之後,殭屍進程將完全徹底地從內存中移除。但如果因爲代碼編寫缺陷或是其他的一些因素影響,導致父進程未調用wait()/waitpid()系統函數,則該進程將一直成爲殭屍進程。

殭屍進程存在於其終止一直到父進程調用wait()/waitpid()系統函數這個時間段期間。

殭屍進程的創建與終止過程可參考下圖。

爲了更深一步理解殭屍進程的產生、終止過程,現編寫一個demo來進行演示說明。在該示例中,子進程打印PID之後便結束,而父進程也沒有調用wait()或是waitpid()來回收子進程的系統資源,直到睡眠2min之後,整個進程結束,此時進程的所有資源將交接給init進程來進行回收。

// file: zombie.c, gcc zombie.c -o zombie
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main()
{
     pid_t pid = 0;
     pid = fork();
     if(0 == pid){
         //打印進程PID之後,調用exit()系統函數結束.
        printf("Child PID is %ld\n"(long)getpid());
        exit(0);
     }else if(0 < pid){
        puts("Parent process . . .");
        sleep(120);
        //不回收子進程的資源.
        //wait(NULL);
     }else{
        perror("fork");
        exit(-1);
     }

       return 0;
}

可看到 (在2min時間段內)zombie進程處於殭屍狀態 (EXIT_ZOMBIE)。而該殭屍進程 (PID466910)的父進程 (PPID466909) 則處於 “可中斷的睡眠狀態”(左下窗口 2)。

3.2 殭屍進程危害

儘管殭屍進程不佔用任何資源,但是它們會保留其進程 PID,即進程描述符 (PID) 將永久佔據着 RAM。若有大量的殭屍進程存在,那麼所有可用的進程 ID 都將被獨佔,會導致其他進程沒有可用的 ID。此外,在較重負載下,會給系統帶來重大的問題。

3.3 殺死殭屍進程

殭屍進程可以通過使用kill命令向其父進程發送SIGCHLD信號來終止。該信號將通知父進程調用wait()系統函數來回收該殭屍進程。即:kill -s SIGCHLD PID,這裏PID是父進程的ID

若要找殭屍進程的父進程,可以使用命令:ps -o ppid = -p PID,這裏的PID是子進程的進程ID。當然也可以使用ps -aux|grep 進程名的方式。

  1. 進程與線程 ========

4.1 線程概念

進程是具有一定獨立功能的程序在數據集上的動態執行過程,它是操作系統中資源分配和調度的獨立單元,是應用程序的載體,每個進程都有自己獨立的內存空間,每個進程內存地址彼此隔離。進程一般由程序 (Program)、數據集指令 (Data Collection) 和進程控制塊 (Process Control Blok, PCB) 組成。其中程序用來描述要完成的功能,是控制進程執行的指令集;而數據集是程序執行過程中所需要的數據和工作區域;進程控制塊則包含進程的描述信息,並且控制信息是進程存在的唯一標誌。

4.2 線程表示形式

線程是進程執行操作的最小單位,也是處理器調度和分派的基本單位,它是進程的一個實體。一個進程可以有一個或多個線程,各線程間共享其所在進程的內存空間。一個標準線程是由線程 ID、程序計算機 PC、寄存器和堆棧組成。

4.3 兩者區別

一個線程只能屬於一個進程,但是一個進程可以有多個線程,且至少有一個線程。線程是進程中最小的執行單位,是調度的最小單位。而進程是資源的基本單位。進程和線程都可以並非,但是創建、銷燬一個進程比創建、銷燬一個線程的開銷要大很多,並且進程是擁有資源的獨立單位,而一個線程卻僅擁有很少的系統資源。最後,進程和線程在內核中都是由task_struct數據類型表示。

===============================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================================

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/-_17jsUixlcAXr4L512R-g