SA6196 闫**
进程的创建与可执行程序的加载
1 Linux下的进程创建与执行
在传统的Linux环境下,有两个基本的操作用于创建和修改进程:函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;函数族exec( )用来启动另外的进程以取代当前运行的进程。
1.1 fork()函数的详解和执行的步骤:
fork()函数通过系统调用创建一个与原来进程几乎完全相同的进程,一个进程调用fork()函数后,系统先给新的进程分配资源,例如存储数据和代码的空间。然后把原来的进程的所有值都复制到新的新进程中,只有少数值与原来的进程的值不同。相当于克隆了一个自己。
我们可以分析一段关于fork()的程序:
fork.c
#include<unistd.h>
#include<stdio.h>
int main()
{
pid_t fpid;
int flag=0;
fpid=fork();
if(fpid<0){
printf("error");
}else if(fpid==0){
printf("我是子进程 我的id是: %d\n",getpid());
flag++;
}else{
printf("我是父进程 我的id是: %d\n",getpid());
flag++;
}
printf("标志数为: %d\n",flag);
}
执行结果如下:
在语句fpid=fork()之前,只有一个进程在执行这段代码,执行fork()后,就变成两个进程在执行了,这两个进程的几乎完全相同,将要执行的下一条语句都是if(fpid<0)…… 为什么两个进程的fpid不同呢,这与fork函数的特性有关。fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:
1)在父进程中,fork返回新创建子进程的进程ID;
2)在子进程中,fork返回0;
3)如果出现错误,fork返回一个负值;
在fork函数执行完毕后,如果创建新进程成功,则出现两个进程,一个是子进程,一个是父进程。在子进程中,fork函数返回0,在父进程中,fork返回新创建子进程的进程ID。我们可以通过fork返回的值来判断当前进程是子进程还是父进程。
执行步骤如下:
1.通过查找pidmap_array位图,为子进程分配新的PID;
2.检查父进程的ptrace字段(current->ptrace),如果它的值不是0,说明有另一个进程正在跟踪父进程;fork()检查编译器程序自己是否想跟踪子进程。在这种情况下,如果子进程不是内核线程那么fork()函数设置CLONE_PTRACE标志;
3.调用copy_process()复制进程描述符,若所有必需的资源都是可用的,该函数返回刚创建的task_struct描述符地址。
4.如果设置了CLONE_PTRACE标志获知必须跟踪子进程,那么子进程的状态被设置成TASK_STOPPED,并未子进程增加挂起的SIGSTOP信号。在另外一个进程把子进程的状态恢复成TASK_RUNNING之前的状态,子进程则保持TASK_STOPPED,等待被唤醒;
5.如果没有设置CLONE_STOPPED标志,则调用wake_up_new_task()函数执行以下操作:
5.1)调整父进程和子进程的调度参数
5.2)如果子进程和父进程运行在同一个CPU上,而父进程和子进程再能共享同一组页表,那么就把子进程插入父进程运行队列,插入时让子进程恰好在父进程之前,因而迫使子进程先于父进程运行。如果子进程刷新其地址空间,并在创建之后执行新程序,那么这种简单的处理会产生较好的性能。而如果我们让父进程先运行的话,那么写时复制机制将会执行一系列不必要的页面复制;
5.3)如果子进程和父进程运行在不同的CPU上,或者父进程和子进程共享同一组页表,就把子进程插入父进程的队尾。
6.如果CLONE_STOPPED标志被设置,则把子进程设置为TASK_STOPPED状态;
7.如果父进程被跟踪,则把子进程的PID存入current的ptrace——message字段并调用ptrace_notify。ptrace_notify()时当前进程停止运行,并向当前进程的父进程发送SIGCHLD信号。子进程的祖父进程是跟踪父进程的调试器进程。SIGCHLD信号通知编译器进程:current已经创建了一个子进程,可以通过查找current->ptrace_message字段获得子进程的PID;
8.如果设置了CLONE_VFOKE标志,则把父进程插入队列,并挂起父进程知道子进程释放自己的内存地址空进;
9.结束并返回子进程的PID。
1.2 exec()函数族的分析
使用 fork()创建子进程后,子程序通常会调用 exec 函数族来执行另外一个程序,这个 exec 函数族就提供了一个在进程中启动另一个程序执行的方法。它根据指定的文件名或目录名找到可执行文件,并用它来代替当前进程的执行映像。也就是说,exec调用并没有生成新进程,一个进程一旦调用 exec函数,它本身就“死亡”了,系统把代码段替换成新程序的代码,放弃原有的数据段和堆栈段,并为新程序分配新的数据段与堆栈段,惟一保留的就是进程的 ID。也就是说,对系统而言,还是同一个进程,不过执行的已经是另外一个程序了。
exec()函数族执行步骤:
系统中存在一个formats链表,其链表结构分别对应一种可执行文件的执行方法,execl()函数对应的系统调用sys_exece()函数会分配一个linux_binprm数据结构并将可执行文件的数据拷贝到其中,并依次扫描formats链表试图执行这个可执行文件,一旦找到了就执行链表结构中的load_binary方法,其主要步骤为:
1.将可执行文件的首部拷贝至内存;
2.根据动态链接程序路径名将共享库对应函数映射到内存;
3.释放原进程的内存描述符、线性区描述符、所有页框;
4.选择线性区的布局;
5.为可执行文件的代码段、数据段以及动态链接程序的代码段、数据段分别进行内存映射;
6.修改内核态堆栈中eip、esp寄存器的值,使其分别指向程序的入口点以及新的用户态堆栈顶并返回;
exec函数族的函数原型如下所示:
int execl(const char *path, const char *arg, ...)
int execv(const char *path, char *const argv[])
int execle(const char *path, const char *arg, ... , char *const envp[])
int execve(const char *path, char *const argv[], char *const envp[])
int execlp(const char *file, const char *arg, ...)
int execvp(const char *file, *const argv[])
我们知道进程调用exec函数族执行程序时,是将可执行程序(ELF)加载执行,下面我将分析ELF的文件格式并分析其加载执行过程;
2.task_struct进程控制块和 ELF可执行文件的格式
2.1 task_struct进程控制块的结构图
2.2 ELF可执行文件与进程空间的表现形式
当在LINUX系统下,用C编译器把C源代码编译成可执行文件时,C编译驱动器一般将调用C的预处理,编译器,汇编器和连接器。C编译驱动器首先把C源代码传到C的预处理器,它以处理过的宏和指示器形式输出纯C语言代码。 C编译器把处理过的C语言代码翻译为机器相关的汇编代码。 汇编器把结果的汇编语言代码翻译成目标的机器指令。结果这些机器指令就被存储成指定的二进制文件格式,就是我们的ELF格式。
其格式为:
利用readelf -a fork查看一下ELF的格式(下图列出了ELF的头部内存分布):
分析:
task_struct进程控制块中的mm字段所指向的mm_struct结构描述了进程地址空间的信息,包括代码段、数据段、堆段、栈段所在地址空间里的起始和结束地址等信息。ELF文件格式中的 ELF头部、段头部表对应进程地址空间中的代码段,在加载可执行文件时,会把它们映射到进程地址空间中的代码段区域。ELF文件格式中的 .data对应进程地址空间中的数据段,在加载可执行文件时,会把它们映射到进程地址空间的数据段区域。
3. ELF可执行文件的动态装载
动态装载是这样一个过程:把共享库放到执行时进程的地址空间,在库中查找函数的地址,然后调用那个函数,当不再需要的时候,卸载共享库。它的执行过程作为动态连接的服务接口。
动态链接,是指库函数的代码并不进入应用软件的目标映像,应用软件在编译/链接阶段并不完成跟库函数的链接,而是把函数库的映像也交给用户,到启动应用软件目标映像运行时才把程序库的映像也装入用户空间(并加以定位),再完成应用软件与库函数的连接。
链接装载时候的内存空间表现为:
ELF文件加载和链接的简要总结
用户通过shell执行程序,shell通过exceve进入系统调用。sys_execve经过一系列过程,并最终通过ELF文件的处理函数load_elf_binary将用户程序和ELF解释器加载进内存,并将控制权交给解释器。 ELF解释器进行相关库的加载,并最终把控制权交给用户程序。由解释器处理用户程序运行过程中符号的动态解析。
附录:
ELF可执行文件加载时进程的内存空间分布如下:
yanzl@yanzl:~$ readelf -a fork
ELF 头:
Magic: 7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
Class: ELF32
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: EXEC (可执行文件)
Machine: Intel 80386
Version: 0x1
入口点地址: 0x8048380
程序头起点: 52 (bytes into file)
Start of section headers: 4440 (bytes into file)
标志: 0x0
本头的大小: 52 (字节)
程序头大小: 32 (字节)
Number of program headers: 9
节头大小: 40 (字节)
节头数量: 30
字符串表索引节头: 27
节头:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 08048154 000154 000013 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 08048168 000168 000020 00 A 0 0 4
[ 3] .note.gnu.build-i NOTE 08048188 000188 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 080481ac 0001ac 000020 04 A 5 0 4
[ 5] .dynsym DYNSYM 080481cc 0001cc 000070 10 A 6 1 4
[ 6] .dynstr STRTAB 0804823c 00023c 000058 00 A 0 0 1
[ 7] .gnu.version VERSYM 08048294 000294 00000e 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 080482a4 0002a4 000020 00 A 6 1 4
[ 9] .rel.dyn REL 080482c4 0002c4 000008 08 A 5 0 4
[10] .rel.plt REL 080482cc 0002cc 000028 08 A 5 12 4
[11] .init PROGBITS 080482f4 0002f4 000023 00 AX 0 0 4
[12] .plt PROGBITS 08048320 000320 000060 04 AX 0 0 16
[13] .text PROGBITS 08048380 000380 0001f4 00 AX 0 0 16
[14] .fini PROGBITS 08048574 000574 000014 00 AX 0 0 4
[15] .rodata PROGBITS 08048588 000588 00006b 00 A 0 0 4
[16] .eh_frame_hdr PROGBITS 080485f4 0005f4 00002c 00 A 0 0 4
[17] .eh_frame PROGBITS 08048620 000620 0000b0 00 A 0 0 4
[18] .init_array INIT_ARRAY 08049f08 000f08 000004 00 WA 0 0 4
[19] .fini_array FINI_ARRAY 08049f0c 000f0c 000004 00 WA 0 0 4
[20] .jcr PROGBITS 08049f10 000f10 000004 00 WA 0 0 4
[21] .dynamic DYNAMIC 08049f14 000f14 0000e8 08 WA 6 0 4
[22] .got PROGBITS 08049ffc 000ffc 000004 04 WA 0 0 4
[23] .got.plt PROGBITS 0804a000 001000 000020 04 WA 0 0 4
[24] .data PROGBITS 0804a020 001020 000008 00 WA 0 0 4
[25] .bss NOBITS 0804a028 001028 000004 00 WA 0 0 4
[26] .comment PROGBITS 00000000 001028 00002a 01 MS 0 0 1
[27] .shstrtab STRTAB 00000000 001052 000106 00 0 0 1
[28] .symtab SYMTAB 00000000 001608 000450 10 29 45 4
[29] .strtab STRTAB 00000000 001a58 000273 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings)
I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
O (extra OS processing required) o (OS specific), p (processor specific)
There are no section groups in this file.
程序头:
Type Offset VirtAddr PhysAddr FileSiz MemSiz Flg Align
PHDR 0x000034 0x08048034 0x08048034 0x00120 0x00120 R E 0x4
INTERP 0x000154 0x08048154 0x08048154 0x00013 0x00013 R 0x1
[正在请求程序解释器:/lib/ld-linux.so.2]
LOAD 0x000000 0x08048000 0x08048000 0x006d0 0x006d0 R E 0x1000
LOAD 0x000f08 0x08049f08 0x08049f08 0x00120 0x00124 RW 0x1000
DYNAMIC 0x000f14 0x08049f14 0x08049f14 0x000e8 0x000e8 RW 0x4
NOTE 0x000168 0x08048168 0x08048168 0x00044 0x00044 R 0x4
GNU_EH_FRAME 0x0005f4 0x080485f4 0x080485f4 0x0002c 0x0002c R 0x4
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
GNU_RELRO 0x000f08 0x08049f08 0x08049f08 0x000f8 0x000f8 R 0x1
Section to Segment mapping:
段节...
00
01 .interp
02 .interp .note.ABI-tag .note.gnu.build-id .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .eh_frame_hdr .eh_frame
03 .init_array .fini_array .jcr .dynamic .got .got.plt .data .bss
04 .dynamic
05 .note.ABI-tag .note.gnu.build-id
06 .eh_frame_hdr
07
08 .init_array .fini_array .jcr .dynamic .got
Dynamic section at offset 0xf14 contains 24 entries:
标记 类型 名称/值
0x00000001 (NEEDED) 共享库:[libc.so.6]
0x0000000c (INIT) 0x80482f4
0x0000000d (FINI) 0x8048574
0x00000019 (INIT_ARRAY) 0x8049f08
0x0000001b (INIT_ARRAYSZ) 4 (bytes)
0x0000001a (FINI_ARRAY) 0x8049f0c
0x0000001c (FINI_ARRAYSZ) 4 (bytes)
0x6ffffef5 (GNU_HASH) 0x80481ac
0x00000005 (STRTAB) 0x804823c
0x00000006 (SYMTAB) 0x80481cc
0x0000000a (STRSZ) 88 (bytes)
0x0000000b (SYMENT) 16 (bytes)
0x00000015 (DEBUG) 0x0
0x00000003 (PLTGOT) 0x804a000
0x00000002 (PLTRELSZ) 40 (bytes)
0x00000014 (PLTREL) REL
0x00000017 (JMPREL) 0x80482cc
0x00000011 (REL) 0x80482c4
0x00000012 (RELSZ) 8 (bytes)
0x00000013 (RELENT) 8 (bytes)
0x6ffffffe (VERNEED) 0x80482a4
0x6fffffff (VERNEEDNUM) 1
0x6ffffff0 (VERSYM) 0x8048294
0x00000000 (NULL) 0x0
重定位节 '.rel.dyn' 位于偏移量 0x2c4 含有 1 个条目:
Offset Info Type Sym.Value Sym. Name
08049ffc 00000306 R_386_GLOB_DAT 00000000 __gmon_start__
重定位节 '.rel.plt' 位于偏移量 0x2cc 含有 5 个条目:
Offset Info Type Sym.Value Sym. Name
0804a00c 00000107 R_386_JUMP_SLOT 00000000 printf
0804a010 00000207 R_386_JUMP_SLOT 00000000 getpid
0804a014 00000307 R_386_JUMP_SLOT 00000000 __gmon_start__
0804a018 00000407 R_386_JUMP_SLOT 00000000 __libc_start_main
0804a01c 00000507 R_386_JUMP_SLOT 00000000 fork
The decoding of unwind sections for machine type Intel 80386 is not currently supported.
Symbol table '.dynsym' contains 7 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND printf@GLIBC_2.0 (2)
2: 00000000 0 FUNC GLOBAL DEFAULT UND getpid@GLIBC_2.0 (2)
3: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
4: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@GLIBC_2.0 (2)
5: 00000000 0 FUNC GLOBAL DEFAULT UND fork@GLIBC_2.0 (2)
6: 0804858c 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
Symbol table '.symtab' contains 69 entries:
Num: Value Size Type Bind Vis Ndx Name
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 08048154 0 SECTION LOCAL DEFAULT 1
2: 08048168 0 SECTION LOCAL DEFAULT 2
3: 08048188 0 SECTION LOCAL DEFAULT 3
4: 080481ac 0 SECTION LOCAL DEFAULT 4
5: 080481cc 0 SECTION LOCAL DEFAULT 5
6: 0804823c 0 SECTION LOCAL DEFAULT 6
7: 08048294 0 SECTION LOCAL DEFAULT 7
8: 080482a4 0 SECTION LOCAL DEFAULT 8
9: 080482c4 0 SECTION LOCAL DEFAULT 9
10: 080482cc 0 SECTION LOCAL DEFAULT 10
11: 080482f4 0 SECTION LOCAL DEFAULT 11
12: 08048320 0 SECTION LOCAL DEFAULT 12
13: 08048380 0 SECTION LOCAL DEFAULT 13
14: 08048574 0 SECTION LOCAL DEFAULT 14
15: 08048588 0 SECTION LOCAL DEFAULT 15
16: 080485f4 0 SECTION LOCAL DEFAULT 16
17: 08048620 0 SECTION LOCAL DEFAULT 17
18: 08049f08 0 SECTION LOCAL DEFAULT 18
19: 08049f0c 0 SECTION LOCAL DEFAULT 19
20: 08049f10 0 SECTION LOCAL DEFAULT 20
21: 08049f14 0 SECTION LOCAL DEFAULT 21
22: 08049ffc 0 SECTION LOCAL DEFAULT 22
23: 0804a000 0 SECTION LOCAL DEFAULT 23
24: 0804a020 0 SECTION LOCAL DEFAULT 24
25: 0804a028 0 SECTION LOCAL DEFAULT 25
26: 00000000 0 SECTION LOCAL DEFAULT 26
27: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
28: 08049f10 0 OBJECT LOCAL DEFAULT 20 __JCR_LIST__
29: 080483c0 0 FUNC LOCAL DEFAULT 13 deregister_tm_clones
30: 080483f0 0 FUNC LOCAL DEFAULT 13 register_tm_clones
31: 08048430 0 FUNC LOCAL DEFAULT 13 __do_global_dtors_aux
32: 0804a028 1 OBJECT LOCAL DEFAULT 25 completed.6339
33: 08049f0c 0 OBJECT LOCAL DEFAULT 19 __do_global_dtors_aux_fin
34: 08048450 0 FUNC LOCAL DEFAULT 13 frame_dummy
35: 08049f08 0 OBJECT LOCAL DEFAULT 18 __frame_dummy_init_array_
36: 00000000 0 FILE LOCAL DEFAULT ABS fork.c
37: 00000000 0 FILE LOCAL DEFAULT ABS crtstuff.c
38: 080486cc 0 OBJECT LOCAL DEFAULT 17 __FRAME_END__
39: 08049f10 0 OBJECT LOCAL DEFAULT 20 __JCR_END__
40: 00000000 0 FILE LOCAL DEFAULT ABS
41: 08049f0c 0 NOTYPE LOCAL DEFAULT 18 __init_array_end
42: 08049f14 0 OBJECT LOCAL DEFAULT 21 _DYNAMIC
43: 08049f08 0 NOTYPE LOCAL DEFAULT 18 __init_array_start
44: 0804a000 0 OBJECT LOCAL DEFAULT 23 _GLOBAL_OFFSET_TABLE_
45: 08048570 2 FUNC GLOBAL DEFAULT 13 __libc_csu_fini
46: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_deregisterTMCloneTab
47: 080483b0 4 FUNC GLOBAL HIDDEN 13 __x86.get_pc_thunk.bx
48: 0804a020 0 NOTYPE WEAK DEFAULT 24 data_start
49: 00000000 0 FUNC GLOBAL DEFAULT UND printf@@GLIBC_2.0
50: 0804a028 0 NOTYPE GLOBAL DEFAULT 24 _edata
51: 08048574 0 FUNC GLOBAL DEFAULT 14 _fini
52: 00000000 0 FUNC GLOBAL DEFAULT UND getpid@@GLIBC_2.0
53: 0804a020 0 NOTYPE GLOBAL DEFAULT 24 __data_start
54: 00000000 0 NOTYPE WEAK DEFAULT UND __gmon_start__
55: 0804a024 0 OBJECT GLOBAL HIDDEN 24 __dso_handle
56: 0804858c 4 OBJECT GLOBAL DEFAULT 15 _IO_stdin_used
57: 00000000 0 FUNC GLOBAL DEFAULT UND __libc_start_main@@GLIBC_
58: 08048500 97 FUNC GLOBAL DEFAULT 13 __libc_csu_init
59: 0804a02c 0 NOTYPE GLOBAL DEFAULT 25 _end
60: 08048380 0 FUNC GLOBAL DEFAULT 13 _start
61: 08048588 4 OBJECT GLOBAL DEFAULT 15 _fp_hw
62: 0804a028 0 NOTYPE GLOBAL DEFAULT 25 __bss_start
63: 0804847c 130 FUNC GLOBAL DEFAULT 13 main
64: 00000000 0 FUNC GLOBAL DEFAULT UND fork@@GLIBC_2.0
65: 00000000 0 NOTYPE WEAK DEFAULT UND _Jv_RegisterClasses
66: 0804a028 0 OBJECT GLOBAL HIDDEN 24 __TMC_END__
67: 00000000 0 NOTYPE WEAK DEFAULT UND _ITM_registerTMCloneTable
68: 080482f4 0 FUNC GLOBAL DEFAULT 11 _init
Histogram for `.gnu.hash' bucket list length (total of 2 buckets):
Length Number % of total Coverage
0 1 ( 50.0%)
1 1 ( 50.0%) 100.0%
版本符号节“.gnu.version”含有 7 个条目:
地址: 0000000008048294 Offset: 0x000294 Link: 5 (.dynsym)
000: 0 (*本地*) 2 (GLIBC_2.0) 2 (GLIBC_2.0) 0 (*本地*)
004: 2 (GLIBC_2.0) 2 (GLIBC_2.0) 1 (*全局*)
Version needs section '.gnu.version_r' contains 1 entries:
地址:0x00000000080482a4 Offset: 0x0002a4 Link: 6 (.dynstr)
000000: Version: 1 文件:libc.so.6 计数:1
0x0010: Name: GLIBC_2.0 标志:无 版本:2
注释位于偏移量 0x00000168 长度为 0x00000020:
Owner Data size Description
GNU 0x00000010 NT_GNU_ABI_TAG (ABI version tag)
OS: Linux, ABI: 2.6.24
注释位于偏移量 0x00000188 长度为 0x00000024:
Owner Data size Description
GNU 0x00000014 NT_GNU_BUILD_ID (unique build ID bitstring)
Build ID: 45c2b571da6920c0b36358efe2dce4b2fff5ff2e
yanzl@yanzl:~$