操作系统第⼆次实验报告——Linux创建进程及可执⾏⽂件结构分析0 个⼈信息
张樱姿
201821121038
计算1812
1 实验⽬的
熟练Linux创建进程fork操作。
2 实验内容
在服务器上⽤VIM编写⼀个程序:⼀个进程创建两个⼦进程。
查看进程树
查看进程相关信息
3 实验报告
 3.1编写程序创建两个⼦进程
1 #include<sys/types.h>
2 #include<stdio.h>
3 #include<unistd.h>
4
5int main(){
6        pid_t cpid1 = fork();       //创建⼦进程1
7
8if(cpid1<0){
9                printf("fork cd1 failed\n");
10        }
11else if(cpid1==0){
12                printf("Child1:pid: %d, ppid: %d\n",getpid(),getppid());
13        }
14else{
15                pid_t cpid2 = fork();  //创建⼦进程2
16if(cpid2<0){
17                        printf("fork cd2 failed\n");
18                }
19else if(cpid2==0){
20                        printf("Child2:pid: %d, ppid: %d\n",getpid(),getppid());
21                }
22else{
23                        printf("Parent: pid :%d\n",getpid());
24                }
25        }
26 }
编译运⾏后的结果:
3.2打印进程树
 添加sleep函数以挂起进程,⽅便打印进程树:
1 #include<sys/types.h>
2 #include<stdio.h>
3 #include<unistd.h>
4
5int main(){
6        pid_t cpid1 = fork();
7
8if(cpid1<0){
9                printf("fork cd1 failed\n");
10        }
11else if(cpid1==0){
12                printf("Child1:pid: %d, ppid: %d\n",getpid(),getppid());
13                sleep(30);        //挂起30秒
14        }
15else{
16                pid_t cpid2 = fork();
17if(cpid2<0){
18                        printf("fork cd2 failed\n");
19                }
20else if(cpid2==0){
21                        printf("Child2:pid: %d, ppid: %d\n",getpid(),getppid());
22                        sleep(30);   //挂起30秒
23                }
24else{
25                        printf("Parent: pid :%d\n",getpid());
26                        sleep(60);   //挂起60秒
27                }
28        }
29 }
pstree -p pid  #打印进程树
  3.3 解读进程相关信息
    3.3.1 解释执⾏ps -ef后返回结果中每个字段的含义
   ps -ef输出格式 :
UID    PID    PPID    C    STIME    TTY    TIME  CMD
UID: User ID,⽤户ID。
PID: Process ID ,进程ID。
PPID: Parent Process Pid,⽗进程ID。
C: CPU使⽤的资源百分⽐。
STIME: Start Time,进程启动时间。
TTY: Controlling Tty,进程的控制终端。
TIME: 进程占⽤CPU的时间总和。如果运⾏时间达到 100 分钟,以 mm:ss 或 mmmm:ss 格式显⽰时间。
CMD: Command name/line,所下达的指令名。
    3.3.2 解释执⾏ps -aux后返回结果中每个字段的含义
  ps -au(x) 输出格式 :
USER    PID    %CPU    %MEM    VSZ    RSS    TTY    STAT    START    TIME    COMMAND
USER: User Name,⾏程拥有者。
PID: Process ID ,进程ID。
%CPU: CPU usage,该进程占⽤的 CPU 资源百分⽐。
%MEM: Memory usage (RES),该进程所占⽤的物理内存百分⽐。
VSZ: Virtual Memory Size,该进程占⽤的虚拟内存⼤⼩,表明了该进程可以访问的所有内存,包括被交换的内存和共享库内存。
RSS: Resident Set Size,常驻内存集合⼤⼩,表⽰相应进程在RAM中占⽤了多少内存,并不包含在SWAP中占⽤的虚拟内存。
TTY: Controlling Tty,进程的控制终端。
STAT: Status,该进程程的状态:
进程状态含义
D不可中断 Uninterruptible sleep (usually IO)
R正在运⾏,或在队列中的进程
S处于休眠状态
T停⽌或被追踪
Z僵⼫进程
W进⼊内存交换(从内核2.6开始⽆效)
X死掉的进程
<⾼优先级
N低优先级
L有些页被锁进内存
s包含⼦进程
+位于后台的进程组
l多线程,克隆线程
START: ⾏程开始时间
TIME: 执⾏的时间
COMMAND:所执⾏的指令
  3.4 分析Linux可执⾏⽂件构成
 ⾸先写⼀个hello.c,
  3.4.1 使⽤file查看⽂件类型
  ①使⽤以下命令⽣成可重定位⽬标⽂件。即⽂件中的代码段和数据的地址还没有最终确定。
gcc -c hello.c -o hello.o
  ②使⽤以下命令⽣成⼀个可执⾏共享对象⽂件。
gcc -o hello.out hello.c
  Linux下可执⾏⽂件的格式主要是ELF格式,即可链接格式(Executable and Linkable Format)。
  ELF⽂件由四个部分组成:ELF头(ELF header)、程序头表(Program header table)、节(Section)和节头表(Section header table)。包括三个索引表:
ELF头:ELF header,在⽂件开始处描述了整个⽂件的组织情况。ELF的⽂件头包含整个执⾏⽂件的控制结构。
程序头表:program header table,⽤来告知系统如何创建进程映像。
节头表:section header table,包含描述⽂件节区的信息,每个节区在表中都有⼀项,给出节区名称、⼤⼩等信息。
  上⾯⽣成的hello.o和hello.out有什么区别?
  ①.o⽂件通常没有程序头表(program header table),⽽是由⼀个或多个.o⽂件链接编译后⽣成程序头表。
  ②.o⽂件⽤section来概括它的各个部分,⽽.out⽂件,编译器会将它的各个section进⼀步整合成各⼤的部分,称为segment。
  ③因此,对于⽬标代码⽂件(.o⽂件)来说,program header table是可选的,⽽对于可执⾏⽂件(.out⽂件)来说,section header table是可选的。
  所以⽬标⽂件并不是可执⾏的,其链接后才⽣成可执⾏⽂件。这⾥讨论的是可执⾏⽂件的内部结构,即hello.out。可见,绿⾊也代表了它的可执⾏性。
  3.4.2 vim查看该可执⾏⽂件(hello.out):
   发现这是个⼆进制⽂件,因此(在vim中)先把它转换成⼗六进制⽂件以便查看。之后要记得转换回来。
:%!xxd    #将2进制格式⽂件转换为16进制格式
:%!xxd -r  #转换回2进制格式
   转换为16进制后(⼩端地址存储,采取两个两个从右往左读的⽅法):
  3.4.3 readelf/objdump解析该可执⾏⽂件(hello.out):
  readelf命令本⾝也是⼀个elf类型的可执⾏⽂件,⽤来解析⼆进制的elf⽂件;另外objdump命令可以⽤来解析exe或elf⽂件,也更具⼀般性。
  readelf命令的使⽤⽅法:
Usage: readelf <option(s)> elf-file(s)
Display information about the contents of ELF format files
Options are:
-a --all              Equivalent to: -h -l -S -s -r -d -V -A -I
-h --file-header      Display the ELF file header
-l --program-headers  Display the program headers
--segments          An alias for --program-headers
-S --section-headers  Display the sections' header
--sections          An alias for --section-headers
-g --section-groups    Display the section groups
-t --section-details  Display the section details
-e --headers          Equivalent to: -h -l -S
-s --syms              Display the symbol table
--symbols          An alias for --syms
--dyn-syms            Display the dynamic symbol table
-n --notes            Display the core notes (if present)
-r --relocs            Display the relocations (if present)
-u --unwind            Display the unwind info (if present)
-d --dynamic          Display the dynamic section (if present)
-
V --version-info      Display the version sections (if present)
-A --arch-specific    Display architecture specific information (if any)
-c --archive-index    Display the symbol/file index in an archive
-D --use-dynamic      Use the dynamic section info when displaying symbols
-x --hex-dump=<number|name>
Dump the contents of section <number|name> as bytes
-p --string-dump=<number|name>
Dump the contents of section <number|name> as strings
-R --relocated-dump=<number|name>
Dump the contents of section <number|name> as relocated bytes
-z --decompress        Decompress section before dumping it
-
w[lLiaprmfFsoRtUuTgAckK] or
--debug-dump[=rawline,=decodedline,=info,=abbrev,=pubnames,=aranges,=macro,=frames,
=frames-interp,=str,=loc,=Ranges,=pubtypes,
=gdb_index,=trace_info,=trace_abbrev,=trace_aranges,
=addr,=cu_index,=links,=follow-links]
Display the contents of DWARF debug sections
--dwarf-depth=N        Do not display DIEs at depth N or greater
--dwarf-start=N        Display DIEs starting with N, at the same depth
or deeper
-I --histogram        Display histogram of bucket list lengths
-W --wide              Allow output width to exceed 80 characters
@<file>                Read options from <file>
-H --help              Display this information
-v --version          Display the version number of readelf
  ①使⽤以下命令查看hello.out的ELF Header:
readelf -h hello.out
  该可执⾏⽂件hello.out⽂件的程序头⼤⼩为56字节。
  64位系统的ELF头⽂件(位于/usr/include/elf.h中)定义:
typedef struct
{
unsigned char e_ident[EI_NIDENT];    /* Magic number and other info */
Elf64_Half    e_type;                /* Object file type */
Elf64_Half    e_machine;              /* Architecture */
Elf64_Word    e_version;              /* Object file version */
Elf64_Addr    e_entry;                /* Entry point virtual address */
Elf64_Off    e_phoff;                /* Program header table file offset */
Elf64_Off    e_shoff;                /* Section header table file offset */
Elf64_Word    e_flags;                /* Processor-specific flags */
Elf64_Half    e_ehsize;              /* ELF header size in bytes */
Elf64_Half    e_phentsize;            /* Program header table entry size */
Elf64_Half    e_phnum;                /* Program header table entry count */
Elf64_Half    e_shentsize;            /* Section header table entry size */
Elf64_Half    e_shnum;                /* Section header table entry count */
Elf64_Half    e_shstrndx;            /* Section header string table index */
} Elf64_Ehdr;
  第⼀⾏:
e_ident⽤16个字节表⽰为“7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00”,其中“7f45 4c46”表⽰“ELF”的ASCII码表。“0102”中前⼀个“02”表⽰是64位的机器,后⼀个“01”表⽰使⽤的是⼩端法。“0100”中的“01”表⽰的是版本号是01。剩下的0为默认填充。
  第⼆⾏:
e_type⽤2个字节表⽰,为“0003”,表⽰是⼀个动态共享⽬标⽂件。
e_machine⽤2个字节表⽰,为“003e”,表⽰Inter 80386的处理器体系结构(64位)。
e_version⽤4个字节表⽰,为“0000 0001”,表⽰的是当前版本。
e_entry⽤4个字节表⽰,为“0000 0530 ”表⽰没有⼊⼝地址为0x530。 
  第三⾏:
e_phoff⽤8个字节表⽰,为“0000 0000 0000 0040”表⽰程序头表(Program header table)在⽂件中的偏移量为64字节。
e_shoff⽤8个字节表⽰,为“0000 0000 0000 1930”表⽰段表(Section header table)在⽂件中的偏移量为6448字节。
  第四⾏:
e_flags⽤4个字节表⽰,为“0000 0000”表⽰未知处理器特定标志。
e_ehsize⽤2个字节表⽰,为“0040”表⽰elf⽂件头⼤⼩为64字节。
e_phentsize⽤2个字节表⽰,为“0038”表⽰程序头表中每⼀个条⽬的⼤⼩为56字节。
e_phnum⽤2个字节表⽰,为“0009”表⽰Program header table中有9个条⽬。
e_shentsize⽤2个字节表⽰,为“0040”,表⽰段头⼤⼩为64个字节(由此知道section header table⾥⾯每⼀个table的⼤⼩为64个字节)。
e_shnum⽤2个字节表⽰,为“001d”,表⽰段表⼊⼝有29个,即段有29个。
e_shstrndx⽤2个字节表⽰,为“001c”,表⽰段名串表在段表中的索引,(符号表的信息在段表的索引号是28)。
  ②使⽤以下命令查看hello.out的段表信息:
readelf -S hello.out
  段表信息:其中.text section是可执⾏指令的集合,位偏移0x0000 0530,size=0x0000 01a2(即418字节),.data section是初始化后数据的集合,位偏移  0x0000 1000,size=0x0000 0010(即16字节),
.symtab section存放所有section中定义的符号名字,.strtab section位偏移0x0000 1628,size=0x0000 0203(即515字节),.shstrtab section与.symtab section之间存储的是段表。
Section Headers:
[Nr] Name              Type            Address          Offset
Size              EntSize          Flags  Link  Info  Align
[ 0]                  NULL            000000000000000000000000
00000000000000000000000000000000000
[ 1] .interp          PROGBITS        000000000000023800000238
000000000000001c  0000000000000000  A      001
[ 2] .note.ABI-tag    NOTE            000000000000025400000254
00000000000000200000000000000000  A      004
[ 3] .u.build-i NOTE            000000000000027400000274
00000000000000240000000000000000  A      004
[ 4] .gnu.hash        GNU_HASH        000000000000029800000298
000000000000001c  0000000000000000  A      508
[ 5] .dynsym          DYNSYM          00000000000002b8  000002b8
00000000000000a8  0000000000000018  A      618
[ 6] .dynstr          STRTAB          000000000000036000000360
00000000000000820000000000000000  A      001
[ 7] .gnu.version      VERSYM          00000000000003e2  000003e2
000000000000000e  0000000000000002  A      502
[ 8] .gnu.version_r    VERNEED          00000000000003f0  000003f0
00000000000000200000000000000000  A      618
[ 9] .rela.dyn        RELA            000000000000041000000410
00000000000000c0  0000000000000018  A      508
[10] .rela.plt        RELA            00000000000004d0  000004d0
00000000000000180000000000000018  AI      5228
[11] .init            PROGBITS        00000000000004e8  000004e8
00000000000000170000000000000000  AX      004
[12] .plt              PROGBITS        000000000000050000000500
00000000000000200000000000000010  AX      0016
[13] .          PROGBITS        000000000000052000000520
00000000000000080000000000000008  AX      008
[14] .text            PROGBITS        000000000000053000000530
00000000000001a2  0000000000000000  AX      0016
[15] .fini            PROGBITS        00000000000006d4  000006d4
printf输出格式linux00000000000000090000000000000000  AX      004
[16] .rodata          PROGBITS        00000000000006e0  000006e0
00000000000000100000000000000000  A      004
[17] .eh_frame_hdr    PROGBITS        00000000000006f0  000006f0
000000000000003c  0000000000000000  A      004
[18] .eh_frame        PROGBITS        000000000000073000000730
00000000000001080000000000000000  A      008
[19] .init_array      INIT_ARRAY      0000000000200db8  00000db8
00000000000000080000000000000008  WA      008
[20] .fini_array      FINI_ARRAY      0000000000200dc0  00000dc0
00000000000000080000000000000008  WA      008
[21] .dynamic          DYNAMIC          0000000000200dc8  00000dc8
00000000000001f0  0000000000000010  WA      608
[22] .got              PROGBITS        0000000000200fb8  00000fb8
00000000000000480000000000000008  WA      008
[23] .data            PROGBITS        000000000020100000001000
00000000000000100000000000000000  WA      008
[24] .bss              NOBITS          000000000020101000001010
00000000000000080000000000000000  WA      001
[25] ment          PROGBITS        000000000000000000001010
000000000000002b  0000000000000001  MS      001
[26] .symtab          SYMTAB          000000000000000000001040
00000000000005e8  000000000000001827438
[27] .strtab          STRTAB          000000000000000000001628
00000000000002030000000000000000001
[28] .shstrtab        STRTAB          0000000000000000  0000182b
00000000000000fe  0000000000000000001
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
  根据刚才在ELF头⽂件中读到的段表偏移为1930H字节,即6448字节。且段表存储在0x0000 0000 0000 1930~~~0x0000 0000 0000 2070,共
40H*29=740H字节。以第⼆个段表(0x0000 0000 0000 1970~~~0x0000 0000 0000 19B0)作为例⼦分析,每个段表占40H字节(64字节)。
64位系统的段表(位于/usr/include/elf.h中)定义:
typedef struct
{
Elf64_Word    sh_name;                /* Section name (string tbl index) */