- 袁祎琦
- 201821121033
- 计算1812
1. 编写程序
在服务器上用VIM编辑器编写一个程序:一个进程创建(fork)两个子进程。给出源代码:
以下是fork.c文件的代码:
1 #include<sys/types.h> //提供pid_t定义 2 #include<unistd.h> 3 #include<stdio.h> 4 int main(){ 5 pid_t fpid1,fpid2;//fpid表示fork函数返回的值 7 fpid1= fork(); 8 if(fpid1<0){ 9 printf("error in fork!\n"); 10 return 0; 11 } 12 else if(fpid1==0){//子进程1 13 printf("Child1 process,my process id is %d\n",getpid()); 14 } 15 else{ 16 fpid2=fork(); 17 if(fpid2<0){ 18 printf("error in fork!\n"); 19 return 0; 20 } 21 else if(fpid2==0){//子进程2 22 printf("Child2 process,my process id is %d\n",getpid()); 23 } 24 else{//父进程 25 printf("Parent process,my process id is %d\n",getpid()); 26 } 27 sleep(100);//使用sleep函数,便于打印进程树 28 } 29 }
2. 打印进程树
2.1、对与上述代码,使用gcc进行编译,操作命令: gcc -o fork fork.c
如果编译成功,则终端不会报错。
2.2、编译成功后,使用 ls 命令,会发现在目录下多了一个fork名称的可执行程序Hello:
使用 ./fork &运行可执行文件,&符号的意思是后台运行,这时候会给出一个pid。但是如果已经使用了sleep函数进行延时,也可以另外开一个终端,这时候跑出来的pid有三个(因为一个父进程,两个子进程)你需要找出父进程的pid,使用./fork命令。
2.3、 使用 pstree -p 995 命令,其中995是父进程的PID。
3. 解读进程相关信息
3.1 ps -ef 使用ps -ef
给出所创建进程的信息,并分析每个字段的含义。
将上面的输出整理为:
ps -ef|grep "fork" UID PID PPID C STIME TTY TIME CMD yuanyiqi 995 751 0 20:37 pts/1 00:00:00 ./fork yuanyiqi 996 995 0 20:37 pts/1 00:00:00 [fork] <defunct> yuanyiqi 997 995 0 20:37 pts/1 00:00:00 ./fork yuanyiqi 1214 1125 0 20:38 pts/5 00:00:00 grep --color=auto fork
- UID:进程所属用户;
- PID:进程号;
- PPID:父进程号;
- C:占用的CPU使用率;
- STIME:进程运行的时间;
- TTY:终端的次要装置号码;
- TIME:执行的时间;
- CMD:进程所执行的指令。
各字段的值释义,以第一行为例:
(1)yuanyiqi是用户ID;
(2)995是996(子进程),997(子进程)父进程ID;
(3)751是995的父进程ID;
(4)pts/1,虚拟终端号,打开一个终端,这个终端就是pts/0,再打开一个,就是pts/1;
(5)TIME:进程0时0分0秒就执行完了;
(6)CMD:第一行的指令为./fork。
3.2 ps -aux 使用ps -aux
给出所创建进程的信息,并分析每个字段的含义。
由于没来得及,还没输入该语句,程序已经执行完了,所以另外又执行了一次 ./fork 命令。
此时输入 ps -aux|grep "fork" 命令,得出:
将上面的输出整理得出以下:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND yuanyiqi 3861 0.0 0.0 4508 720 pts/1 S+ 20:56 0:00 ./fork yuanyiqi 3862 0.0 0.0 0 0 pts/1 Z+ 20:56 0:00 [fork] <defunct> yuanyiqi 3863 0.0 0.0 4508 72 pts/1 S+ 20:56 0:00 ./fork yuanyiqi 3869 0.0 0.0 13772 1116 pts/5 S+ 20:56 0:00 grep --color=auto fork
- USER:进程所属用户的用户名;
- PID:进程号;
- %CPU:占用的CPU使用率;
- %MEM:占用的内存使用率;
- VSZ:占用的虚拟机内存大小;
- RSS:占用的内存大小;
- TTY:终端的次要装置号码;
- START:进程开始时间;
- COMMAND:进程执行的指令。
各字段的值释义,以第一行为例:
(1)yuanyiqi为用户ID;
(2)3861为3862(子进程)、3863(子进程)的父进程;
(3)CPU使用率为0;
(4)内存使用率为0;
(5)占用虚拟机内存大小为4508K;
(6)占用内存大小为720K;
(7)pts/1为虚拟终端号;
(8)3861处于睡眠状态,“+”位于后台的进程组。(1、R:正在运行,可执行状态;
2、S:处于可中断的睡眠状态;
3、D:无法中断的休眠状态;
4、T:暂停状态或跟踪状态;
5、Z:退出状态,进程成为僵尸进程;
6、X:退出状态,进程即将被销毁;
<高优先级
n低优先级
s(小写)包含子进程
+位于后台的进程组)
(9)START:进程开始时间为20:56分;
(10)进程的持续时间:0分0秒;
(11)3861所执行的指令为 ./fork
4. 通过该实验产生新的疑问及解答
问题一:根据父子进程这个名称,是不是会根据名字,认为父进程一定比子进程先执行?
答:子进程和父进程执行的是同一个程序,绝大部分就是原进程(也就是该进程的父进程)的拷贝,使用fork()以后,子进程就相当于父进程的兄弟一样了,这个子进程和父进程不同的地方只有它的ID和其父进 程的ID。
所以fork()之后,除非采用了同步手段,否则不能确定谁先运行,也不确定谁先结束它们是处于竞争关系,在父进程执行过程中,子进程可以抢占资源。
问题二:sleep()函数在C语言中的意义?
答:这个函数具有不同含义,功能:执行挂起一段时间,用法:usigned sleep(unsigned seconds),注意,如果使用的头文件是#include<windows.h>,则Sleep(1000)表示休息1秒,当然,此次实验是在linux平台下进行的,使用的头文件是#include<unistd.h>,sleep(1)表示休息1秒。
问题三:既然可以创建进程,那怎样实现撤销进程呢?
答:进程终止的一般方式是调用exit()库函数。进程终止:
1、有exit_group()系统调用,它终止整个线程组;
2、exit()系统调用,它终止某一个线程。那么进程删除呢?wait()库函数,会检查子进程是否终止,如果子进程已经终止,那么它的终止代号将告诉父进程这个任务是否已成功地完成。
问题四:根据多方了解,fork()和vfork()都是创建一个进程函数,那么它们有什么区别呢?
答:(1)、fork():子进程拷贝父进程的数据段,代码段;vfork()子进程与父进程共享数据段。
(2)、fork()父子进程的执行次序不确定(如我的问题一所说);vfork()保证子进程先运行。
(3)、其他我还不了解 。
5. 加分项
结合实例(实例可以是写一个输出Hello World的简单程序)分析Linux可执行文件构成。
PS:在VIM下查看可执行文件
:%!xxd
将当前文本转换为16进制格式:%!xxd -r
将当前文件转换回文本格式
5.1、在linux下,程序是一个普通的可执行文件,以下列出了一个二进制可执行文件的基本情况:
由此可以看出,此可执行文件在存储时,分为代码区,数据区和未初始化数据区3个部分。
代码区(text):存放CPU执行的机器指令。代码区的指令包括操作码和操作对象(或对象地址引用)。
静态数据/全局初始化数据区(数据段data):包含了在程序中已经初始化的静态变量、明确被初始化的全局变量和常数变量。
未初始化数据区(BSS区):存入的是全局未初始化变量和未初始化静态变量。未初始化数据区的数据在程序开始执行之前被内核初始化为0或者空(NULL)。
进程是linux事务管理的基本单元,所有的进程均拥有自己独立的处理环境和系统资源。一个进程是一个运行着的代码段,一个进程主要包括在内存中申请的空间,代码(包括代码段,数据段,BSS),堆,栈,以及内核提供的内核进程信息结构体。
5.2、编写Hello程序,使用 vim Hello 查看可执行文件,使用 :%!xxd 将Hello转换为十六进制。 :%!xxd -r的作用是将图三转换回图二。
——Hello.c文件及其十六进制表示
——Hello(可执行文件及其十六进制表示)
——124、125行为输出的Hello World!
使用 objdump -h Hello 命令打印各个段及的基本信息
其中包含了刚才说的代码段、数据段、BSS段、只读段、注释信息段和堆栈提示段(照理说应该有,但是在图上没找到)。
上面通过一个实例了解了可执行文件的基本轮廓,大致得出可执行文件的总体结构:
Reference: