一、实验目的及要求
(1)加深对进程概念的理解,明确进程与程序的区别,并认识并发执行的实质。
(2)掌握linux中进程的创建及撤消,理解进程的生命周期。
(3)理解进程同步工作的原理,掌握linux中wait()、exit ()、sleep()实现进程的同步。
二、实验工具
计算机、VMware Workstation Pro
三、实验内容及步骤
一、安装编译器GCC
Linux下C语言编程常用的编辑器是vim,编译器一般用gcc,编译链接程序用make,跟踪调试一般使用gdb。
在Ubuntu上安装GCC:
在默认的Ubuntu资源库中,有一个名为build-essential的元包,其中包含GCC和其他各种编译器,如g++和make。我们也可以通过安装build-essentials包来安装GCC。一旦我们安装了build-essential包,GCC也就安装在我们的系统中了。
按照以下步骤进行:
(1)安装gcc: sudo apt-get install gcc
安装到最后出现一些错误:
(2)使用命令更新软件包列表。
sudo apt update
(3)使用命令安装 build-essential 包。
sudo apt install build-essential
它与我们系统中的其他编译器一起安装GCC。
(4)验证GCC安装的情况
我们用下面的命令来验证GCC是否安装成功。
gcc --version
如果GCC成功安装,我们会得到一个输出,包括版本和其他关于GCC的信息。输出:
二、
- 编程实现
- 进程的创建及终止
(1)编写三个程序实现进程的创建及撤消。要求分别调用fork()、vfork()实现进程的创建,调用exit()终止进程,调用exec()为进程指定新的运行程序。
(2)调试并分析结果
任务一:编写一段程序,使用系统调用fork()创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符,父进程显示字符“a”,子进程分别显示字符“b”和“c”。试观察记录屏幕上的显示结果,并分析原因。
(1)首先创建一个.c文件
(2)然后代码编辑
(3)接着使用gcc 1.c -o 1编辑
该命令是直接进行链接生成可执行程序, 链接之前的三步会自动执行。
(4)最后执行运行出结果
(5)分析:
分析:从进程执行并发来看,输出abc的排列都是有可能的。
原因:fork()创建进程所需的时间虽然可能多于输出一个字符的时间,但各个进程的时间片的获得却不是一定是顺序的,所以输出abc的排列都是有可能的。而从进程并发执行来看,各种情况都有可能。上面的三个进程没有同步措施,所以父进程与子进程的输出内容会叠加在一起。输出次序带有随机性。
任务二:调用fork()编写一段程序,在子进程中修改变量,并在父进程中观察其影响。
利用fork() 函数创建进程
用fork ()函数创建进程时,语句调用系列如下:
Pit_t fork(void); fork()函数是一个单调用双返回的函数,也就是说,该函数有父进程调用,执行时,在父进程中返回子进程标识,在子进程中返回0。fork()调用后,子进程是父进程的一个复制,都是从fork()调用语句开始执行。
(1)首先创建文件
(2)然后编辑代码
(3)接着使用gcc进行编辑
(4)最后执行出结果
fork( )返回值意义如下:
0:在子进程中,pid变量保存的fork( )返回值为0,表示当前进程是子进程。
>0:在父进程中,pid变量保存的fork( )返回值为子进程的id值(进程唯一标识符)。
-1:创建失败。
如果fork( )调用成功,它向父进程返回子进程的PID,并向子进程返回0,即fork( )被调用了一次,但返回了两次。此时OS在内存中建立一个新进程,所建的新进程是调用fork( )父进程(parent process)的副本,称为子进程(child process)。子进程继承了父进程的许多特性,并具有与父进程完全相同的用户级上下文。父进程与子进程并发执行。
任务三:调用vfork()编写一段程序,在子进程中修改变量,并在父进程中观察其影响。
vfork()系统调用:
用vfork()函数创建进程时,通常使用exec()函数紧跟其后,以便为新创建进程指派另外一个可执行程序。用vfork()创建的新进程并不完全复制父进程的数据区。vork()与fork()另一个不同点表现在父子进程的执行顺序上。fork()不对父子进程的执行次序进行任何限制,而vfork()调用后,子进程先运行,父进程挂起,直到子进程调用exec()或exit()之后,父子进程的执行顺序才不再有限制。否则,如果子进程在调用exec()或exit()之前,父进程被激活,就会造成死锁。
(1)首先创建文件
(2)然后编辑代码
头文件 #include<unistd.h>
定义函数pid_t vfork(void);
(3)接着使用gcc进行编辑
(4)最后执行出结果
如果vfork()成功则在父进程会返回新建立的子进程代码(PID),而在新建立的子进程中则返回0。如果vfork失败则直接返回-1,失败原因存于errno中。
vfork()用法与fork()相似.但是也有区别,具体区别归结为以下3点:
①fork():子进程拷贝父进程的数据段,代码段。
vfork():子进程与父进程共享数据段.
②fork():父子进程的执行次序不确定.
vfork():保证子进程先运行,在调用exec或_exit之前与父进程数据是共享的,在它调用exec或_exit之后父进程才可能被调度运行。
③vfork()保证子进程先运行,在她调用exec或_exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。
④当需要改变共享数据段中变量的值,则拷贝父进程。
任务四:可以打印出传递给运行进程的参数和所使用的环境变量的程序。
环境变量是指操作系统中用来指定操作系统运行环境的一下参数。
特征:
①字符串
②有统一的格式: 名=值[:值]
③值用来描述进程的环境信息
使用形式:与命令行参数类似
存储形式:与命令行参数类似,char* []数组,数组名 environ, 内部存储字符串, NULL做为哨兵
加载位置:与命令行参数类似,位于用户区, 高于stack区域 的起始位置。
引入环境变量表:必须声明 环境变量 extern char** environ;
(1)首先创建文件
(2)然后编辑代码
(3)接着使用gcc进行编辑
(4)结果:
主函数的参数(3个)
int main(int argc , char* argv[] , char* envp[])
int argc:参数个数(数组argv[]元素个数)
char* argv[]:参数内容(其中argv[0]=./main 运行程序名)
char* envp[]:环境变量
由于环境变量末尾为空指针,所以循环条件即为*environ!=NULL。
四、实验总结
通过这次实验使我掌握了linux中进程的创建及撤消,wait()、exit()、sleep()实现进程的同步,理解了进程的生命周期进程同步工作及进程通信的原理。对系统调用的一些函数的使用加深了理解,对linux下进程的创建有了进一步的理解,对父子进程的同步产生的结果有了一定的认识。在老师的指导下,顺利地完成了本次实验,即使在有些地方还是不太明白,但经过不断的练习对进程创建会有新的认识的。