clone() - Unix、Linux系統呼叫
廣告
名稱
clone, __clone2 - 建立子程序
概要
#include <sched.h>
int clone(int (*fn)(void *), void *child_stack,
int flags, void *arg, ...
/* pid_t *pid, struct user_desc *tls
", pid_t *" ctid " */ );"
int __clone2(int (*fn)(void *), void *child_stack_base,
size_t stack_size, int flags, void *arg, ...
/* pid_t *pid, struct user_desc *tls
", pid_t *" ctid " */ );"
|
描述
clone() 建立一個新程序,方式類似於 fork(2)。它實際上是一個構建在底層 clone() 系統呼叫(下文稱為 sys_clone)之上的庫函式。sys_clone 的描述在頁面末尾給出。
與 fork(2) 不同,這些呼叫允許子程序與其呼叫程序共享其執行上下文的部分內容,例如記憶體空間、檔案描述符表和訊號處理程式表。(請注意,在本手冊頁中,“呼叫程序”通常對應於“父程序”。但請參見下面 CLONE_PARENT 的描述。)
clone() 的主要用途是實現執行緒:程式中多個控制執行緒在共享記憶體空間中併發執行。
當使用 clone() 建立子程序時,它將執行函式應用 fn(arg)。(這與 fork(2) 不同,在 fork(2) 呼叫之後,子程序從呼叫點繼續執行。)fn 引數是指向子程序在其執行開始時呼叫的函式的指標。arg 引數傳遞給 fn 函式。
當 fn(arg) 函式應用返回時,子程序終止。fn 返回的整數是子程序的退出程式碼。子程序也可以透過呼叫 exit(2) 或在接收到致命訊號後顯式終止。
child_stack 引數指定子程序使用的堆疊的位置。由於子程序和呼叫程序可能共享記憶體,因此子程序不可能在與呼叫程序相同的堆疊中執行。因此,呼叫程序必須為子程序堆疊設定記憶體空間,並將指向此空間的指標傳遞給 clone()。
flags 的低位元組包含子程序死亡時傳送到父程序的終止訊號的編號。如果此訊號指定為除 SIGCHLD 之外的任何其他訊號,則父程序必須在使用 wait(2) 等待子程序時指定 __WALL 或 __WCLONE 選項。如果沒有指定訊號,則子程序終止時不會向父程序傳送訊號。 .
flags 也可以與零個或多個以下常量按位或運算,以指定呼叫程序和子程序之間共享的內容
標籤 | 描述 |
CLONE_PARENT (自 Linux 2.3.12 起) | 如果設定了 CLONE_PARENT,則新子程序的父程序(由 getppid(2) 返回)將與呼叫程序的父程序相同。 如果沒有設定 CLONE_PARENT,則(與 fork(2) 一樣)子程序的父程序是呼叫程序。 請注意,當子程序終止時,由 getppid(2) 返回的父程序會收到訊號,因此,如果設定了 CLONE_PARENT,則呼叫程序的父程序(而不是呼叫程序本身)將收到訊號。 |
CLONE_FS | 如果設定了 CLONE_FS,則呼叫程序和子程序共享相同的系統資訊。這包括檔案系統的根目錄、當前工作目錄和 umask。呼叫程序或子程序執行的任何 chroot(2)、chdir(2) 或 umask(2) 呼叫也會影響另一個程序。 如果沒有設定 CLONE_FS,則子程序在 clone() 呼叫時使用呼叫程序的檔案系統資訊的副本工作。後來由其中一個程序執行的 chroot(2)、chdir(2)、umask(2) 呼叫不會影響另一個程序。 |
CLONE_FILES | 如果設定了 CLONE_FILES,則呼叫程序和子程序共享相同的檔案描述符表。呼叫程序或子程序建立的任何檔案描述符在另一個程序中也是有效的。類似地,如果其中一個程序關閉檔案描述符或更改其關聯標誌(使用 fcntl(2) F_SETFD 操作),則另一個程序也會受到影響。 如果沒有設定 CLONE_FILES,則子程序繼承在 clone() 時在呼叫程序中開啟的所有檔案描述符的副本。(子程序中複製的檔案描述符引用與呼叫程序中相應檔案描述符相同的開啟檔案描述符(參見 open(2))。)呼叫程序或子程序執行的後續開啟或關閉檔案描述符或更改檔案描述符標誌的操作不會影響另一個程序。 |
CLONE_NEWNS (自 Linux 2.4.19 起) | 在新名稱空間中啟動子程序。 每個程序都存在於一個名稱空間中。程序的名稱空間是描述該程序所看到的層級結構的資料(掛載集)。在沒有設定 CLONE_NEWNS 標誌的 fork(2) 或 clone(2) 之後,子程序與父程序位於相同的名稱空間中。mount(2) 和 umount(2) 系統呼叫會更改呼叫程序的名稱空間,因此會影響位於同一名稱空間中的所有程序,但不會影響不同名稱空間中的程序。 在設定了 CLONE_NEWNS 標誌的 clone(2) 之後,克隆的子程序將在一個新的名稱空間中啟動,該名稱空間使用父程序的名稱空間的副本進行初始化。 只有特權程序(擁有 CAP_SYS_ADMIN 功能的程序)才能指定 CLONE_NEWNS 標誌。不允許在同一個 clone() 呼叫中同時指定 CLONE_NEWNS 和 CLONE_FS。 |
CLONE_SIGHAND | 如果設定了 CLONE_SIGHAND,則呼叫程序和子程序共享相同的訊號處理程式表。如果呼叫程序或子程序呼叫 sigaction(2) 來更改與訊號關聯的行為,則該行為也會在另一個程序中更改。但是,呼叫程序和子程序仍然具有不同的訊號掩碼和掛起訊號集。因此,其中一個程序可以使用 sigprocmask(2) 來阻塞或解除阻塞某些訊號,而不會影響另一個程序。 如果沒有設定 CLONE_SIGHAND,則子程序在呼叫 clone() 時繼承呼叫程序的訊號處理程式的副本。後來由其中一個程序執行的 sigaction(2) 呼叫不會影響另一個程序。 自 Linux 2.6.0-test6 起,如果指定了 CLONE_SIGHAND,則 flags 還必須包含 CLONE_VM |
CLONE_PTRACE | 如果指定了 CLONE_PTRACE,並且正在跟蹤呼叫程序,則也跟蹤子程序(參見 ptrace(2))。 |
CLONE_UNTRACED (自 Linux 2.5.46 起) | 如果指定了 CLONE_UNTRACED,則跟蹤程序無法對此子程序強制執行 CLONE_PTRACE。 |
CLONE_STOPPED (自 Linux 2.6.0-test2 起) | 如果設定了 CLONE_STOPPED,則子程序最初處於停止狀態(好像它收到了 SIGSTOP 訊號),並且必須透過向其傳送 SIGCONT 訊號來恢復。 |
CLONE_VFORK | 如果設定了 CLONE_VFORK,則呼叫程序的執行將暫停,直到子程序透過呼叫 execve(2) 或 _exit(2) 釋放其虛擬記憶體資源(與 vfork(2) 一樣)。 如果沒有設定 CLONE_VFORK,則呼叫後呼叫程序和子程序都是可排程的,並且應用程式不應依賴於以任何特定順序發生的執行。 |
CLONE_VM | 如果設定了 CLONE_VM,則呼叫程序和子程序在相同的記憶體空間中執行。特別是,呼叫程序或子程序執行的記憶體寫入在另一個程序中也是可見的。此外,子程序或呼叫程序使用 mmap(2) 或 munmap(2) 執行的任何記憶體對映或取消對映也會影響另一個程序。 如果沒有設定 CLONE_VM,則子程序在 clone() 時在呼叫程序的記憶體空間的單獨副本中執行。與 fork(2) 一樣,由其中一個程序執行的記憶體寫入或檔案對映/取消對映不會影響另一個程序。 |
CLONE_PID (已過時) | 如果設定了 CLONE_PID,則子程序將使用與呼叫程序相同的程序 ID 建立。這對於入侵系統很有用,但在其他情況下沒什麼用。自 2.3.21 起,只有系統引導程序 (PID 0) 才能指定此標誌。它在 Linux 2.5.16 中消失了。 |
CLONE_THREAD (自 Linux 2.4.0-test8 起) | 如果設定了CLONE_THREAD,則子程序將與呼叫程序位於同一個執行緒組中。為了使後續關於CLONE_THREAD的討論更易於理解,本文使用“執行緒”來指代執行緒組內的程序。 執行緒組是 Linux 2.4 中新增的一項功能,用於支援 POSIX 執行緒的概念,即共享單個 PID 的一組執行緒。在內部,這個共享的 PID 是執行緒組的所謂的執行緒組識別符號 (TGID)。從 Linux 2.4 開始,對getpid(2) 的呼叫返回呼叫者的 TGID。 組內的執行緒可以透過其(系統範圍內的)唯一執行緒 ID (TID) 來區分。新執行緒的 TID 可作為clone() 呼叫者返回的函式結果獲得,執行緒可以使用gettid(2) 獲取自身的 TID。 如果呼叫clone() 時未指定CLONE_THREAD,則生成的執行緒將放入一個新的執行緒組中,其 TGID 與執行緒的 TID 相同。此執行緒是新執行緒組的領導者。 使用CLONE_THREAD建立的新執行緒與clone() 呼叫者的父程序相同(即,類似於CLONE_PARENT),因此對getppid(2) 的呼叫將為執行緒組中的所有執行緒返回相同的值。當CLONE_THREAD執行緒終止時,使用clone() 建立它的執行緒不會收到SIGCHLD(或其他終止)訊號;也不能使用wait(2) 獲取此執行緒的狀態。(據說該執行緒是分離的。) 執行緒組中的所有執行緒終止後,執行緒組的父程序將收到SIGCHLD(或其他終止)訊號。 如果執行緒組中的任何執行緒執行execve(2),則除執行緒組領導者之外的所有執行緒都將終止,並且新程式將線上程組領導者中執行。 如果執行緒組中的一個執行緒使用fork(2) 建立子程序,則組中的任何執行緒都可以使用wait(2) 等待該子程序。 從 Linux 2.5.35 開始,如果指定了CLONE_THREAD,則flags 也必須包含CLONE_SIGHAND。 可以使用kill(2) 將訊號傳送到整個執行緒組(即 TGID),或者使用tgkill(2) 傳送到特定執行緒(即 TID)。 訊號處理和操作是程序範圍的:如果未處理的訊號傳遞到一個執行緒,則它將影響(終止、停止、繼續、忽略)執行緒組的所有成員。 每個執行緒都有自己的訊號掩碼,由sigprocmask(2) 設定,但訊號可以掛起:對於整個程序(即,可傳遞到執行緒組的任何成員),當使用kill(2) 傳送時;或者對於單個執行緒,當使用tgkill(2) 傳送時。對sigpending(2) 的呼叫返回一個訊號集,該訊號集是整個程序的掛起訊號和呼叫執行緒的掛起訊號的並集。 如果使用kill(2) 將訊號傳送到執行緒組,並且執行緒組已為該訊號安裝了處理程式,則處理程式將恰好在一個任意選擇的執行緒組成員中被呼叫,該成員未阻塞該訊號。如果組中的多個執行緒正在等待使用sigwaitinfo(2) 接收相同的訊號,則核心將任意選擇其中一個執行緒來接收使用kill(2) 傳送的訊號。 |
CLONE_SYSVSEM(從 Linux 2.5.10 開始) | 如果設定了CLONE_SYSVSEM,則子程序和呼叫程序共享單個 System V 訊號量撤銷值列表(參見semop(2))。如果沒有設定此標誌,則子程序將擁有一個單獨的撤銷列表,該列表最初為空。 |
CLONE_SETTLS(從 Linux 2.5.32 開始) | newtls 引數是新的 TLS(執行緒區域性儲存)描述符。(參見set_thread_area(2)。) |
CLONE_PARENT_SETTID(從 Linux 2.5.49 開始) | 在父程序和子程序記憶體中的位置parent_tidptr處儲存子執行緒ID。(在 Linux 2.5.32-2.5.48 中,有一個標誌 CLONE_SETTID 執行此操作。) |
CLONE_CHILD_SETTID(從 Linux 2.5.49 開始) | 在子程序記憶體中的位置child_tidptr處儲存子執行緒ID。 |
CLONE_CHILD_CLEARTID(從 Linux 2.5.49 開始) | 當子程序退出時,擦除子程序記憶體中位置child_tidptr處的子執行緒ID,並在該地址的 futex 上執行喚醒操作。所涉及的地址可以透過set_tid_address(2) 系統呼叫更改。這被執行緒庫使用。 |
sys_clonesys_clone 系統呼叫與fork(2) 更接近,因為子程序中的執行從呼叫的位置繼續。因此,sys_clone 只需要flags 和child_stack 引數,它們與clone() 的含義相同。(請注意,這些引數的順序與clone() 不同。)sys_clone 的另一個區別是child_stack 引數可以為零,在這種情況下,寫時複製語義確保當任一程序修改堆疊時,子程序獲得堆疊頁面的單獨副本。在這種情況下,為了正確操作,不應指定CLONE_VM 選項。 從 Linux 2.5.49 開始,系統呼叫有五個引數。兩個新引數是parent_tidptr,它指向在指定 CLONE_PARENT_SETTID 的情況下將寫入子執行緒 ID 的位置(在父程序和子程序記憶體中),以及child_tidptr,它指向在指定 CLONE_CHILD_SETTID 的情況下將寫入子執行緒 ID 的位置(在子程序記憶體中)。 返回值成功時,子程序的執行緒 ID 將返回到呼叫者的執行緒執行中。失敗時,將返回 -1 到呼叫者的上下文,不會建立子程序,並且errno 將被相應地設定。錯誤
標籤 | 描述 |
EAGAIN | 已經有太多程序正在執行。 |
EINVAL |
指定了CLONE_SIGHAND,但未指定CLONE_VM。(從 Linux 2.6.0-test6 開始。) |
EINVAL |
指定了CLONE_THREAD,但未指定CLONE_SIGHAND。(從 Linux 2.5.35 開始。) |
EINVAL | 在flags中同時指定了CLONE_FS和CLONE_NEWNS。 |
EINVAL | 當為child_stack指定零值時,由clone() 返回。 |
ENOMEM | 無法分配足夠的記憶體來為子程序分配任務結構,或複製需要複製的呼叫者上下文的部分。 |
EPERM |
非 root 程序(沒有 CAP_SYS_ADMIN 許可權的程序)指定了CLONE_NEWNS。 |
EPERM |
非 0 程序指定了CLONE_PID。 |
版本
libc5 中沒有clone() 的條目。glibc2 提供瞭如本手冊頁中所述的clone()。
符合標準
clone() 和sys_clone 呼叫是 Linux 特定的,不應在旨在可移植的程式中使用。
備註
在核心 2.4.x 系列中,CLONE_THREAD 通常不會使新執行緒的父程序與呼叫程序的父程序相同。但是,對於核心版本 2.4.7 到 2.4.18,CLONE_THREAD 標誌暗示CLONE_PARENT 標誌(如核心 2.6 中那樣)。
曾經有過CLONE_DETACHED(在 2.5.32 中引入):父程序不需要子程序退出訊號。在 2.6.2 中,需要與CLONE_THREAD一起使用的情況消失了。此標誌仍然已定義,但沒有效果。在 x86 上,不應透過 vsyscall 呼叫clone(),而應直接透過int $0x80 呼叫。在 IA-64 上,使用不同的系統呼叫。
int __clone2(int (*fn)(void *), void *child_stack_base,
size_t stack_size, int flags, void *arg, ...
/* pid_t *pid, struct user_desc *tls
", pid_t *" ctid " */ );"
|
__clone2() 系統呼叫的操作方式與clone() 相同,只是child_stack_base 指向子程序堆疊區域的最低地址,而stack_size 指定child_stack_base指向的堆疊的大小。
錯誤
包含 NPTL 執行緒庫的 GNU C 庫版本包含一個用於getpid(2) 的包裝函式,該函式執行 PID 的快取。在針對此類庫連結的程式中,即使未使用CLONE_THREAD 建立執行緒(因此不在同一個執行緒組中),對getpid(2) 的呼叫也可能返回相同的值。要獲得真實結果,可能需要使用如下程式碼:
#include <syscall.h>
pid_t mypid;
mypid = syscall(SYS_getpid);
|
參見
廣告
|