进程是如何启动的?
进程启动过程
示例代码
#include <stdio.h>
int main()
{
printf("hello,world\r\n");
return 0;
}
启动两个终端,使用strace命令跟踪进程启动过程strace -f -s 655000 -i -T -o output.txt -p 19510
各个选项参考strace的man手册
-f 跟踪由进程及其子进程发出的系统调用
- s 设置 strace 打印的字符串的最大长度
- i 在打印每条系统调用时,同时打印该调用的指令指针
- T 显示每次系统调用花费的时间
- o 将 strace 的输出重定向到文件中
-p 指定 strace 要跟踪的进程ID
其他选项参考man手册,截取出的有效记录如下
- 1 19510 它是bin/bash程序启动的 19510它是进程的标识
19510 [00007ffb18a075e2] clone(child_stack=NULL, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0x7ffb1934ca10) = 19889 <0.000188>
- 2 19510进程调用clone/fork 系统函数并执行得到了一个新的子进程,这个新的子进程是19889 ,此时19510进程调用wait4在此阻塞
19510 [00007ffb18a0725b] wait4(-1, <unfinished ...>
- 3 新进程19889此时调用相关系统函数函数 execve 加载ELF可执行文件 demo2,[“./demo2”] 是命令行参数【执行参数】
0x55c2a06d6690 /* 32 vars */ 是环境表 【环境参数】 是公共的,在Linux是供所有应用程序可以使用的公共数据 19889 [00007ffb18a086fb] setpgid(19889, 19889) = 0 <0.000004> 19889 [00007ffb18a0782b] execve("./demo2", ["./demo2"], 0x55c2a06d6690 /* 32 vars */) = 0 <0.000394>
- 4 加载所依赖的相关库文件 libc.so.6文件 ELF 文件 共享目标文件 共享库 【动态库】
19889 [00007f551f604af1] openat(AT_FDCWD, "/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3 <0.000009>
19889 [00007f551f604bb8] read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\2009\2\0\0\0\0\0@\0\0\0\0\0\0\0\200_[\0\0\0\0\0\0\0\0\0@\08\0\v\0@\0L\0K\0\6\0\0\0\4\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0@\0\0\0\0\0\0\0h\2\0\0\0\0\0\0h\2\0\0\0\0\0\0\10\0\0\0\0\0\0\0\3\0\0\0\4\0\0\0\260\347\30\0\0\0\0\0\260\347\30\0\0\0\0\0\260\347\30\0\0\0\0\0\34\0\0\0\0\0\0\0\34\0\0\0\0\0\0\0\20\0\0\0\0\0\0\0\1\0\0\0\5\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\334\217\33\0\0\0\0\0\334\217\33\0\0\0\0\0\0\0 \0\0\0\0\0\1\0\0\0\6\0\0\0(\226\33\0\0\0\0\0(\226;\0\0\0\0\0(\226;\0\0\0\0\0XR\0\0\0\0\0\0\370\221\0\0\0\0\0\0\0\0 .....
- 5 进程执行调用系统函数write向屏幕输出 字符串 “hello,world"的内容 上层的应用函数是printf 【打印函数,是标准库stdio.h头文件声明的】
write在libc.so.6库里 19889 [00007f551f312e18] write(1, "hello,world\r\n", 13) = 13 <0.000105>
- 6 此时子进程19889调用系统函数exit_group(0) 退出进程 这个0它是进程退出状态码
19889 [00007f551f2ee7f6] exit_group(0) = ? 19889 [????????????????] +++ exited with 0 +++
- 7 父进程19510调用wait4回收退出的子进程 [{WIFEXITED(s) && WEXITSTATUS(s) == 0}] WIFEXITED 这是一个宏函数 WEXITSTATUS 拿到退出状态码
0 退出状态码 19889 退出的子进程标识符 并且回收退出进程的内存资源 ,经过这些流程整个进程执行结束 19510 [00007ffb18a0725b] <... wait4 resumed>[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], WSTOPPED|WCONTINUED, NULL) = 19889 <0.002737>
总结
- 在bin/bash或者其他解释器脚本中执行可执行文件,解释器脚本会调用clone/fork生成一个子进程,父进程等待子进程退出,子进程调用execve加载ELF可执行文件(.txt、.data、.bss段以及环境表)覆盖原有的参数,随意调用_libc_start_main启动主函数,子进程执行完成后,父进程resumed等到子进程的退出状态码