揭開(kāi) Strace 命令捕獲系統(tǒng)調(diào)用的神秘面紗
在性能觀測(cè)領(lǐng)域,strace 命令是一個(gè)雖然很古老,但很常用的命令。使用它我們可以非常方便地觀察某個(gè)進(jìn)程正在執(zhí)行什么系統(tǒng)調(diào)用。
這個(gè)命令的使用方式也很簡(jiǎn)單,想觀察哪個(gè)進(jìn)程,直接將其 pid 作為參數(shù)傳給 strace 命令即可。
# strace -p {pid}
read(3, "\177ELF\2\1\1\0\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0@k\0\0\0\0\0\0"..., 832) = 832
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260A\2\0\0\0\0\0"..., 832) = 832
write(1, "anycast6 dev_snmp6\t if_inet6\ti"..., 137anycast6 dev_snmp6 if_inet6 ip6_mr_vif ip_mr_vif mcfilter nf_conntrack ptype rt6_stats sockstat tcp6 unix
) = 137
......
然而我們都知道,正常來(lái)講操作系統(tǒng)中的各個(gè)進(jìn)程之間是互相隔離的。那么 strace 命令是如何做到能獲取其他進(jìn)程執(zhí)行的系統(tǒng)調(diào)用信息的呢,我們今天就來(lái)揭開(kāi)這個(gè)謎底。
一、手工實(shí)現(xiàn)一個(gè) strace
要想理解清楚 strace 命令原理,我想最有效的辦法是我們自己親手寫(xiě)一個(gè)簡(jiǎn)單程序來(lái)模擬 strace 的工作過(guò)程。
為了方便大家理解,我這里只把這個(gè)程序的核心邏輯列出來(lái)。完整的程序源碼請(qǐng)大家查看strace配套源碼 https://github.com/yanfeizhang/coder-kung-fu/blob/main/tests/cpu/test11/main.c
int main(int argc, char *argv[]) {
// 1.attach 到 pid 指定的目標(biāo)進(jìn)程上
ptrace(PTRACE_ATTACH, pid, NULL, NULL)
while (1) {
// 2.等待目標(biāo)進(jìn)程的 PTRACE_SYSCALL
// 2.1 指定要捕獲目標(biāo)進(jìn)程的 PTRACE_SYSCALL
ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
// 2.2 當(dāng)目標(biāo)進(jìn)程有 SYSCALL 發(fā)生時(shí)醒來(lái)處理
waitpid(pid, &status, 0)
// 3.讀取并解析系統(tǒng)調(diào)用
// 3.1 讀取目標(biāo)進(jìn)程正在執(zhí)行的系統(tǒng)調(diào)用號(hào)
syscall_number = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL); 、
// 3.2 將系統(tǒng)調(diào)用號(hào)轉(zhuǎn)為系統(tǒng)調(diào)用名稱(chēng)
switch (syscall_number) {
case 5: syscall_name = "read"; break;
case 6: syscall_name = "write"; break;
case 10: syscall_name = "open"; break;
case 11: syscall_name = "close"; break;
......
default: syscall_name = "unknown"; break;
}
// 3.3 打印系統(tǒng)調(diào)用名稱(chēng)
printf("Syscall: %s (number: %ld)\n", syscall_name, syscall_number);
}
}
通過(guò)上面二十多行核心代碼,我們就實(shí)現(xiàn)了一個(gè)模擬 strace 命令跟蹤系統(tǒng)調(diào)用功能的簡(jiǎn)易程序。在這個(gè)程序中,主要是通過(guò)三塊邏輯來(lái)實(shí)現(xiàn):
第一,attach 到目標(biāo)進(jìn)程。在 C 標(biāo)準(zhǔn)庫(kù) 中有一個(gè) ptrace 函數(shù) , 該函數(shù)是一個(gè)系統(tǒng)調(diào)用方法。通過(guò)指定它的第一個(gè)參數(shù)為 PTRACE_ATTACH pid,這樣就可以建立當(dāng)前程序和目標(biāo)進(jìn)程的跟蹤關(guān)系了。要注意的是,這一步必須得有 root 權(quán)限才可以。
第二,將自己注冊(cè)為目標(biāo)進(jìn)程的 syscall 調(diào)試器。這次還是使用 ptrace 函數(shù)。但第一個(gè)參數(shù)設(shè)為 PTRACE_SYSCALL,這樣就在告訴內(nèi)核要將自己注冊(cè)為目標(biāo)進(jìn)程的 syscall 調(diào)試器。每當(dāng)目標(biāo)進(jìn)程發(fā)生系統(tǒng)調(diào)用的時(shí)候,都會(huì)通知當(dāng)前程序。
第三,讀取目標(biāo)進(jìn)程系統(tǒng)調(diào)用名。這里涉及到一個(gè)基礎(chǔ)知識(shí),Linux 內(nèi)核在幫用戶進(jìn)程執(zhí)行系統(tǒng)調(diào)用的時(shí)候,會(huì)將系統(tǒng)調(diào)用號(hào)保存到 ORIG_RAX 寄存器中。
ptrace 函數(shù)第一個(gè)參數(shù)設(shè)為 PTRACE_PEEKUSER,這是在告訴內(nèi)核幫忙讀取目標(biāo)進(jìn)程的用戶區(qū)域的數(shù)據(jù)。第三個(gè)參數(shù)指定要讀取目標(biāo)進(jìn)程的 ORIG_RAX 寄存器中保存的系統(tǒng)調(diào)用號(hào)。在 /usr/include/x86_64-linux-gnu/asm/unistd_64.h 中可以查看到所有的系統(tǒng)調(diào)用號(hào)信息。將其轉(zhuǎn)為系統(tǒng)調(diào)用名后輸出即可。
整個(gè)程序是一個(gè)循環(huán),每當(dāng)目標(biāo)程序有系統(tǒng)調(diào)用發(fā)生的時(shí)候,都會(huì)通知到當(dāng)前程序。當(dāng)前程序再將其正在執(zhí)行的系統(tǒng)調(diào)用信息輸出出來(lái)。這樣就實(shí)現(xiàn)了對(duì)目標(biāo)進(jìn)程實(shí)時(shí)行為的動(dòng)態(tài)跟蹤。
接下來(lái)我們分三個(gè)部分,從內(nèi)核視角深入地探究一下底層工作原理。
二、attach 到目標(biāo)進(jìn)程
要想實(shí)現(xiàn)對(duì)目標(biāo)程序的跟蹤,首先第一步準(zhǔn)備工作便是調(diào)用 ptrace 把自己 attach 到目標(biāo)進(jìn)程上。
int main(int argc, char *argv[]) {
// 1.attach 到 pid 指定的目標(biāo)進(jìn)程上
ptrace(PTRACE_ATTACH, pid, NULL, NULL)
...
}
我們來(lái)看下這個(gè)所謂的 attach ,在 Linux 內(nèi)部究竟是干了點(diǎn)啥。找來(lái) ptrace 系統(tǒng)調(diào)用的源碼。
//file:kernel/ptrace.c
SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, ...)
{
// 1. 根據(jù) pid 查找目標(biāo)進(jìn)程內(nèi)核對(duì)象
struct task_struct *child;
child = find_get_task_by_vpid(pid);
......
// 2. 執(zhí)行 ptrace_attach
if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) {
ret = ptrace_attach(child, request, addr, data);
...
}
......
}
在 ptrace 系統(tǒng)調(diào)用源碼中,第一步比較簡(jiǎn)單,根據(jù)參數(shù)中的 pid 查找目標(biāo)進(jìn)程在內(nèi)核中的 task_struct 內(nèi)核對(duì)象。第二步操作中的 ptrace_attach,是 attach 到目標(biāo)進(jìn)程的核心函數(shù)。
//file:kernel/ptrace.c
static int ptrace_attach(struct task_struct *task, long request, ...)
{
...
// 1.權(quán)限檢查
if (unlikely(task->flags & PF_KTHREAD))
goto out;
...
// 2.狀態(tài)設(shè)置
ptrace_link(task, current);
...
}
在 ptrace_attach 中先要進(jìn)行一些權(quán)限檢查,例如內(nèi)核線程是不允許被 attach 的。接著調(diào)用 ptrace_link 來(lái)修改當(dāng)前進(jìn)程,以及要跟蹤的目標(biāo)進(jìn)程的內(nèi)核對(duì)象相關(guān)字段。ptrace_link 的主要實(shí)現(xiàn)是 __ptrace_link。
//file:kernel/ptrace.c
void __ptrace_link(struct task_struct *child, struct task_struct *new_parent, ...)
{
list_add(&child->ptrace_entry, &new_parent->ptraced);
child->parent = new_parent;
...
}
在 ptrace_link 函數(shù)中,先是通過(guò) list_add 函數(shù),將目標(biāo)進(jìn)程(child)的 ptrace_entry 被插入到當(dāng)前進(jìn)程(new_parent)的 ptraced 鏈表的頭部。這樣當(dāng)前進(jìn)程(new_parent) 就可以通過(guò) ptraced 鏈表來(lái)跟蹤和管理所有在跟蹤的進(jìn)程。
接著調(diào)用 child->parent = new_parent 把當(dāng)前進(jìn)程設(shè)置成了目標(biāo)進(jìn)程(child)的 parent。目的是使當(dāng)前進(jìn)程能夠通過(guò)調(diào)用 waitpid 獲取到目標(biāo)進(jìn)程的 SIGTRAP 信號(hào)。
這樣就完成了到目標(biāo)進(jìn)程的 attach,當(dāng)前進(jìn)程通過(guò) ptraced 來(lái)管理目標(biāo)進(jìn)程,目標(biāo)進(jìn)程也可以發(fā)出 SIGTRAP 信號(hào)來(lái)和當(dāng)前進(jìn)程進(jìn)行消息傳遞。
三、捕獲目標(biāo)進(jìn)程 SYSCALL
3.1 設(shè)置等待目標(biāo)進(jìn)程 SYSCALL
在完成當(dāng)前進(jìn)程和目標(biāo)進(jìn)程的 attach 關(guān)聯(lián)后。接著下一步操作是告訴 Linux 內(nèi)核,要等待和捕獲目標(biāo)進(jìn)程的系統(tǒng)調(diào)用。
int main(int argc, char *argv[]) {
// 1.attach 到 pid 指定的目標(biāo)進(jìn)程上
...
while (1) {
// 2.等待目標(biāo)進(jìn)程的 PTRACE_SYSCALL
// 2.1 指定要捕獲目標(biāo)進(jìn)程的 PTRACE_SYSCALL
ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
// 2.2 當(dāng)目標(biāo)進(jìn)程有 SYSCALL 發(fā)生時(shí)醒來(lái)處理
waitpid(pid, &status, 0)
...
}
}
在 ptrace 系統(tǒng)調(diào)用中,由于這次傳入的第一個(gè)參數(shù)是 PTRACE_SYSCALL。所以其執(zhí)行的核心函數(shù)提煉后如下所示:
//file:kernel/ptrace.c
SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr,
unsigned long, data)
{
struct task_struct *child;
child = find_get_task_by_vpid(pid);
......
ret = arch_ptrace(child, request, addr, data)
......
}
首先還是先根據(jù) pid 獲取目標(biāo)進(jìn)程的 task_struct 內(nèi)核對(duì)象。接著執(zhí)行 arch_ptrace 來(lái)為目標(biāo)進(jìn)程內(nèi)核對(duì)象添加一個(gè)標(biāo)記 SYSCALL_TRACE。具體設(shè)置是在 ptrace_resume 函數(shù)中執(zhí)行的( arch_ptrace -> ptrace_request -> ptrace_resume )。我們直接來(lái)看 ptrace_resume 源碼。
//file:kernel/ptrace.c
static int ptrace_resume(struct task_struct *child, long request,
unsigned long data)
{
if (request == PTRACE_SYSCALL)
set_task_syscall_work(child, SYSCALL_TRACE);
...
}
set_task_syscall_work 函數(shù)就是在給目標(biāo)進(jìn)程的設(shè)置了一個(gè) SYSCALL_TRACE 標(biāo)記位。
//file:include/linux/thread_info.h
#define set_task_syscall_work(t, fl) \
set_bit(SYSCALL_WORK_BIT_##fl, &task_thread_info(t)->syscall_work)
這樣后面當(dāng)該進(jìn)程再執(zhí)行系統(tǒng)調(diào)用的時(shí)候,通過(guò)判斷該標(biāo)記就能發(fā)現(xiàn)有進(jìn)程在跟蹤它了。
3.2 等待目標(biāo)進(jìn)程信號(hào)發(fā)生
當(dāng)前進(jìn)程在設(shè)置完要對(duì)目標(biāo)進(jìn)程的 SYSCALL 進(jìn)行觀察后,接著就調(diào)用 waitpid 進(jìn)入了睡眠狀態(tài)。
int main(int argc, char *argv[]) {
// 1.attach 到 pid 指定的目標(biāo)進(jìn)程上
...
while (1) {
// 2.等待目標(biāo)進(jìn)程的 PTRACE_SYSCALL
// 2.1 指定要捕獲目標(biāo)進(jìn)程的 PTRACE_SYSCALL
ptrace(PTRACE_SYSCALL, pid, NULL, NULL)
// 2.2 當(dāng)目標(biāo)進(jìn)程有 SYSCALL 發(fā)生時(shí)醒來(lái)處理
waitpid(pid, &status, 0)
...
}
}
waitpid 也是一個(gè)系統(tǒng)調(diào)用。
//file:kernel/exit.c
SYSCALL_DEFINE3(waitpid, pid_t, pid, int __user *, stat_addr, int, options)
{
return kernel_wait4(pid, stat_addr, options, NULL);
}
在這個(gè)系統(tǒng)調(diào)用中,依次調(diào)用 kernel_wait4、do_wait 等內(nèi)核函數(shù),最后在 add_wait_queue 函數(shù)中,將當(dāng)前進(jìn)程加入到等待隊(duì)列中,更新進(jìn)程狀態(tài)為 TASK_INTERRUPTIBLE(可中斷睡眠狀態(tài)),等待子進(jìn)程信號(hào)。
// file:kernel/exit.c
static long do_wait(struct wait_opts *wo)
{
...
init_waitqueue_func_entry(&wo->child_wait, child_wait_callback);
wo->child_wait.private = current;
add_wait_queue(¤t->signal->wait_chldexit, &wo->child_wait);
...
}
當(dāng)子進(jìn)程退出時(shí),內(nèi)核會(huì)向父進(jìn)程發(fā)送一個(gè)信號(hào),父進(jìn)程的信號(hào)處理程序會(huì)喚醒等待隊(duì)列中的進(jìn)程,使它們重新進(jìn)入可運(yùn)行狀態(tài),等待被調(diào)度器調(diào)度執(zhí)行。
四、等待并讀取目標(biāo)進(jìn)程系統(tǒng)調(diào)用
4.1 目標(biāo)進(jìn)程系統(tǒng)調(diào)用發(fā)生
當(dāng)目標(biāo)進(jìn)程系統(tǒng)調(diào)用發(fā)生的時(shí)候,會(huì)檢查是否有被設(shè)置 SYSCALL_TRACE 標(biāo)記位。如果有,那就說(shuō)明有進(jìn)程正在跟蹤它。具體的檢測(cè)是在 syscall_trace_enter 內(nèi)核函數(shù)中做的
//file:arch/m68k/kernel/entry.S
ENTRY(system_call)
...
jbsr syscall_trace_enter
//file:arch/m68k/kernel/ptrace.c
asmlinkage int syscall_trace_enter(void)
{
int ret = 0;
if (test_thread_flag(TIF_SYSCALL_TRACE))
ret = ptrace_report_syscall_entry(task_pt_regs(current));
return ret;
}
如果有 SYSCALL_TRACE 標(biāo)志,那就會(huì)設(shè)置一下退出碼,發(fā)出 SIGTRAP 信號(hào),喚醒正在追蹤它的進(jìn)程,最后暫停當(dāng)前程序的運(yùn)行。具體的內(nèi)核函數(shù)調(diào)用過(guò)程是經(jīng)過(guò) ptrace_report_syscall_entry -> ptrace_report_syscall -> ptrace_notify -> ptrace_do_notify 這么一條長(zhǎng)的調(diào)用鏈后,最終在 ptrace_stop 內(nèi)核函數(shù)中執(zhí)行的。我們直接來(lái)看這個(gè)最關(guān)鍵的 ptrace_stop 函數(shù)。
//file:kernel/signal.c
static int ptrace_stop(int exit_code, int why, unsigned long message,
kernel_siginfo_t *info)
__releases(¤t->sighand->siglock)
__acquires(¤t->sighand->siglock)
{
......
// 1.
set_special_state(TASK_TRACED);
// 2.設(shè)置當(dāng)前進(jìn)程的 exit_code
current->ptrace_message = message;
current->last_siginfo = info;
current->exit_code = exit_code;
// 3.
if (current->ptrace)
do_notify_parent_cldstop(current, true, why);
if (gstop_done && (!current->ptrace || ptrace_reparented(current)))
do_notify_parent_cldstop(current, false, why);
cgroup_enter_frozen();
schedule();
......
}
在這個(gè)函數(shù)中做了這么幾件事情。
第一,調(diào)用 set_special_state(TASK_TRACED)
將當(dāng)前進(jìn)程(被跟蹤進(jìn)程)的狀態(tài)設(shè)置為 TASK_TRACED,表示進(jìn)程已被 ptrace 停止。該狀態(tài)意味著進(jìn)程將不會(huì)在下次調(diào)度時(shí)被調(diào)度執(zhí)行,因?yàn)樗F(xiàn)在處于被跟蹤狀態(tài)。
第二,設(shè)置自己的退出碼,到struct task_struct 的成員 exit_code 上。
第三,調(diào)用 do_notify_parent_cldstop()-->__wake_up_parent()喚醒跟蹤進(jìn)程(strace)
第四,調(diào)用 schedule 掛起自己讓出 CPU。
4.2 跟蹤進(jìn)程 waitpid 返回
接下來(lái)跟蹤進(jìn)程收到信號(hào),會(huì)被內(nèi)核喚醒,并中 waitpid 函數(shù)中返回。
//file:kernel/exit.c
SYSCALL_DEFINE3(waitpid, pid_t, pid, int __user *, stat_addr, int, options)
{
return kernel_wait4(pid, stat_addr, options, NULL);
}
long kernel_wait4(pid_t upid, int __user *stat_addr, int options,
struct rusage *ru)
{
...
ret = do_wait(&wo);
put_pid(pid);
//將進(jìn)程狀態(tài)保存到用戶傳入的地址中
if (ret > 0 && stat_addr && put_user(wo.wo_stat, stat_addr))
ret = -EFAULT;
return ret;
}
這時(shí),跟蹤進(jìn)程就知道目標(biāo)進(jìn)程有系統(tǒng)調(diào)用發(fā)生了。下一步就可以讀取目標(biāo)進(jìn)程正在執(zhí)行的系統(tǒng)調(diào)用信息了。
4.3 讀取目標(biāo)進(jìn)程系統(tǒng)調(diào)用號(hào)
在 Linux 內(nèi)核中,ORIG_RAX 寄存器用于保存進(jìn)程在執(zhí)行系統(tǒng)調(diào)用是的調(diào)用號(hào)。跟蹤進(jìn)程只需要訪問(wèn)下目標(biāo)進(jìn)程的 ORIG_RAX 寄存器就可以知道目標(biāo)進(jìn)程正在執(zhí)行哪個(gè)系統(tǒng)調(diào)用了。
圖片
具體執(zhí)行方式是調(diào)用 ptrace 系統(tǒng)調(diào)用。第一個(gè)參數(shù)設(shè)置為 PTRACE_PEEKUSER 表示要讀取目標(biāo)進(jìn)程的一些數(shù)據(jù)。第三個(gè)參數(shù)指定為 8*ORIG_RAX 表示要讀取 ORIG_RAX 寄存器。8*ORIG_RAX 計(jì)算出 ORIG_RAX 在用戶空間的偏移地址。
int main(int argc, char *argv[]) {
// 1.attach 到 pid 指定的目標(biāo)進(jìn)程上
...
while (1) {
// 2.等待目標(biāo)進(jìn)程的 PTRACE_SYSCALL
...
// 3.讀取并解析系統(tǒng)調(diào)用
// 3.1 讀取目標(biāo)進(jìn)程正在執(zhí)行的系統(tǒng)調(diào)用號(hào)
syscall_number = ptrace(PTRACE_PEEKUSER, pid, 8 * ORIG_RAX, NULL); 、
// 3.2 將系統(tǒng)調(diào)用號(hào)轉(zhuǎn)為系統(tǒng)調(diào)用名稱(chēng)
switch (syscall_number) {
case 5: syscall_name = "read"; break;
case 6: syscall_name = "write"; break;
case 10: syscall_name = "open"; break;
case 11: syscall_name = "close"; break;
......
default: syscall_name = "unknown"; break;
}
// 3.3 打印系統(tǒng)調(diào)用名稱(chēng)
printf("Syscall: %s (number: %ld)\n", syscall_name, syscall_number);
}
}
內(nèi)核執(zhí)行 ptrace 系統(tǒng)調(diào)用的時(shí)候,會(huì)執(zhí)行到 arch_ptrace 函數(shù)。
//file:arch/x86/kernel/ptrace.c
long arch_ptrace(struct task_struct *child, long request,
unsigned long addr, unsigned long data)
{
unsigned long __user *datap = (unsigned long __user *)data;
...
switch (request) {
...
case PTRACE_PEEKUSR: {
tmp = 0; /* Default return condition */
if (addr < sizeof(struct user_regs_struct))
tmp = getreg(child, addr);
else if (addr >= offsetof(struct user, u_debugreg[0]) &&
addr <= offsetof(struct user, u_debugreg[7])) {
addr -= offsetof(struct user, u_debugreg[0]);
tmp = ptrace_get_debugreg(child, addr / sizeof(data));
}
ret = put_user(tmp, datap);
break;
}
}
}
在 arch_ptrace 判斷是 PTRACE_PEEKUSER 參數(shù),會(huì)在計(jì)算一下目標(biāo)進(jìn)程數(shù)據(jù)地址 addr,然后將其讀取出來(lái)設(shè)置到跟蹤進(jìn)程的用戶空間中。這樣,就讀取到系統(tǒng)調(diào)用號(hào)了。
接下來(lái)在 /usr/include/x86_64-linux-gnu/asm/unistd_64.h 中可以查看到所有的系統(tǒng)調(diào)用號(hào)信息。將其轉(zhuǎn)為系統(tǒng)調(diào)用名后輸出即可。
五、總結(jié)
strace 命令跟蹤其它進(jìn)程的系統(tǒng)調(diào)用的整個(gè)過(guò)程可以同下面一張圖來(lái)總結(jié)。
圖片
首先是 strace 進(jìn)程執(zhí)行下面三步操作:
- 調(diào)用 ptrace(ATTACH, ...) 設(shè)置關(guān)聯(lián)跟蹤進(jìn)程和目標(biāo)進(jìn)程
- 再調(diào)用 ptrace(SYSCALL, ...) 設(shè)置要要跟蹤目標(biāo)進(jìn)程的系統(tǒng)調(diào)用
- 接著就調(diào)用 waitpid 去等待子進(jìn)程的信號(hào)了,而先暫停執(zhí)行了
再接下來(lái)當(dāng)目標(biāo)進(jìn)程有系統(tǒng)調(diào)用發(fā)生時(shí),
- 檢查當(dāng)前進(jìn)程是否被設(shè)置了 SYSCALL_TRACE 標(biāo)記
- 如果有,那么設(shè)置一下當(dāng)前進(jìn)程的狀態(tài),也暫停執(zhí)行了
- 通過(guò)信號(hào)機(jī)制喚醒跟蹤進(jìn)程
跟蹤進(jìn)程收到信號(hào)后會(huì)繼續(xù)執(zhí)行:
- 讀取目標(biāo)進(jìn)程 ORIG_RAX 寄存器,其中保存著目標(biāo)進(jìn)程的系統(tǒng)調(diào)用號(hào)
- 將系統(tǒng)調(diào)用號(hào)轉(zhuǎn)換成系統(tǒng)調(diào)用名輸出
再接下來(lái)再調(diào)用 wait_pid,讓目標(biāo)進(jìn)程繼續(xù)運(yùn)行。整體進(jìn)入一個(gè)不斷獲取,不斷打印的循環(huán)中。
從以上的執(zhí)行過(guò)程可以看出。strace 命令執(zhí)行的過(guò)程中,會(huì)讓目標(biāo)進(jìn)程執(zhí)行到系統(tǒng)調(diào)用時(shí)暫停運(yùn)行,從而導(dǎo)致比較頻繁的上下文切換,會(huì)增加目標(biāo)進(jìn)程 的運(yùn)行時(shí)間。所以,如果是在生產(chǎn)環(huán)境中,使用 strace 命令的時(shí)候還是要小心一點(diǎn)。更萬(wàn)萬(wàn)不可當(dāng)成一個(gè)長(zhǎng)期的線上監(jiān)控工具來(lái)使用。
不過(guò),strace 命令還是非常簡(jiǎn)單好用的,你說(shuō)呢?。?/p>