操作系统实验:Linux下的进程控制实验
进程控制实验
⼀、实验⽬的:
加深对进程概念的理解,明确进程和程序的区别;掌握Linux操作系统的进程创建和终⽌操作,体会⽗进程和⼦进程的关系及进程状态的变化;进⼀步认识并发执⾏的实质,编写并发程序。
⼆、实验平台:
虚拟机:VMWare15.5.1
操作系统:CentOS7 64位
编辑器:Vim
编译器:Gcc
三、实验内容:
1.进程的创建与销毁
编写⼀段程序,使⽤系统调⽤fork()创建两个⼦进程,当此程序运⾏时,在系统中有⼀个⽗进程和两个⼦进程活动。让每⼀个进程在屏幕上显⽰“⾝份信息”:⽗进程显⽰“Parent process! PID=xxx1 PPID=xxx2”;⼦进程显⽰“Childx process! PID=xxx PPID=xxx”。多运⾏⼏次,观察记录屏幕上的显⽰结果,并分析原因。
说明:
xxx1为进程号,⽤getpid()函数可获取进程号;
xxx2为⽗进程号,⽤getppid()函数可获取⽗进程号;
Childx中x为1和2,⽤来区别两个⼦进程;
wait()函数⽤来避免⽗进程在⼦进程终⽌之前终⽌。
实验主要代码:
//print.c
#include<stdio.h>
int main(int argc,char*argv[])
{
for(int i=0;i<argc;i++){
printf("argv[%d]=%s\n",i,argv[i]);
}
printf("i am print.c,i am executed\n");
return0;
}
//example1.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(void)
{
pid_t pid=fork();
if(pid==0){
printf("Child1 process! PID=%d PPID=%d \n",getpid(),getppid());
if(execl("./print","print","hello",NULL)==-1)
printf("execl failed!");
printf("where am I?");
}else if(pid>0){
pid_t pid2=fork();
if(pid2==0){
printf("Child2 process! PID=%d PPID=%d \n",getpid(),getppid());
}else if(pid2>0){
wait();
printf("Parent process! PID=%d PPID=%d \n",getpid(),getppid());
exit(0);
}else{
printf("fork faild\n");
exit(0);
}
}else{
printf("fork faild\n");
exit(0);
}
}
实验过程:编辑print.c和example1.c两个⽂件,输⼊命令⾏对.c⽂件进⾏编译输出。编译过程:#gcc print.c –o print
#gcc example1.c –o example1
运⾏截图如下:
进程控制相关函数
1.fork()
⽤于创建⼀个新进程(⼦进程)。
函数原型:
#include <unistd.h>
pid_t fork(void);
返回:⼦进程中为0,⽗进程中为⼦进程ID,出错为-1。
⼦进程被创建后就进⼊就绪队列和⽗进程分别独⽴地等待调度。⼦进程继承⽗进程的程序段代码,⼦进程被调度执⾏时,也会和⽗进程⼀样从fork()返回。
从共享程序段代码的⾓度来看,⽗进程和⼦进程所执⾏的程序代码是同⼀个,在内存中只有⼀个程序副本;
但是从编程的⾓度来看,为了使⼦进程和⽗进程做不同的事,需要在程序中区分⽗进程和⼦进程的代码段。这需要借助于fork()的返回值来标志当前进程的⾝份。从fork()返回后,都会执⾏语句:pid=frok();
得到返回值pid有三种情况,分别对应出错(⼩于0)、在⽗进程中(⼤于0)、在⼦进程中(等于0)。
由于⽗进程和⼦进程各⾃独⽴地进⼊就绪队列等待调度,所以谁会先得到调度是不确定的,这与系统的调度策略和系统当前的资源状态有关。因此谁会先从fork()返回并继续执⾏后⾯的语句也是不确定的。
2.wait()
查询⼦进程的退出状态(即正常退出的退出码或导致异常终⽌的信号),并回收⼦进程的内核资源。
函数原型:
#include <sys/wait.h>
pid_t wait(int * status);
返回值:若成功则返回进程ID,若出错则返回-1。
Wait()函数常⽤来控制⽗进程和⼦进程的同步。在⽗进程中调⽤wait()函数,则⽗进程被阻塞,进⼊等
待队列,等待⼦进程结束。当⼦进程结束时,会产⽣⼀个终⽌状态字,系统会向⽗进程发送SIGCHLD信号。当接到信号后,⽗进程提取⼦进程的终⽌状态字,从wait()函数返回继续执⾏原程序。
3. exit()
exit()是进程结束最常调⽤的函数,在main()函数中调⽤return,最终也是调⽤exit()函数。这些都是进程的正常终⽌。在正常终⽌时,exit()函数返回进程结束状态。
函数原型:
#include<stdio.h>
void exit(int status)
从OS⾓度来看,进程终⽌会释放进程⽤户空间的所有资源,⽽进程描述符并不释放,它将来由其⽗进程回收。若⽗进程先于⼦进程终⽌,⼦进程成为“孤⼉进程”,“孤⼉进程”会被init进程(进程号为1)领养;若⼦进程先于⽗进程终⽌,⼦进程成为“僵⼫进程”,⽗进程尚未对其进⾏善后处理(获取终⽌⼦进程的有关信息,释放它仍占⽤的资源)。
程序可以执⾏到main()的最后⼀个“}“结束,也可以运⾏exit();结束,也可以运⾏到return 0;结束。
系统调⽤execl()可以将新程序加载到某⼀进程的内存空间,该进程会从新程序的main()函数开始执⾏。
函数原型:
#include <unistd.h>
int execl (const char * pathname, const char * arg0,…/*(char*)0*/)
pathname是要加载程序的全路径名,arg0及以后为参数列表。
进程的⽤户内存空间被替换,⽽进程的其他属性基本不改变。进程完整的内存空间:正⽂区、堆区、栈区、数据区都被替换,原内容不存在了。代码替换完后,在execl()后⾯的代码毫⽆意义。
实验结果分析
先后四次运⾏,根据输出分析实验结果:
①每次运⾏创建的⼦进程Childx process的PPID均为⽗进程Parent process的PID,说明两个⼦进程均
为同⼀⽗进程创建。
②每次运⾏的⽗进程Parent process的PPID均为3105,说明⽗进程也以⼦进程的形式存在,其⽗进程为同⼀系统进程。
③我们注意第⼀个if代码段,调⽤了execl()函数,将当前进程child1替换掉,所以之后的"where am I?”字符串并没有被输出,child1进程从print程序的main()开始执⾏,输出两个传参以及⼀段字符串,最终在main()的return 0;后结束。
④进程创建后由于⽗进程和⼦进程各⾃独⽴地进⼊就绪队列等待调度,所以谁会先得到调度是不确定,实验数据可以看出child1进程先运⾏和child2进程先运⾏各出现两次。
⑤⽗进程代码段因为调⽤了wait()函数,等待⼦进程结束后函数返回继续执⾏原程序,输出相应信息,所以⽗进程字符串最后才输出。
2.多进程并发执⾏
fork()和exec()系列函数能同时运⾏多个程序,利⽤上述函数将下⾯单进程顺序执⾏的程序single.c改造成可并发执⾏3个进程的程序multi_process.c;并⽤time命令获取程序的执⾏时间,⽐较单进程和多进程运⾏时间,并分析原因。
//single.c
#include<stdio.h>
#define NUM 5
int main(void)
{
void print_msg(char*m);
print_msg("Good ");
print_msg("Morning ");
print_msg("007\n");//将007替换为本⼈学号
return0;
}
void print_msg(char*m)
{
int i;
for(i =0; i<NUM; i++){
printf("%s",m);
fflush(stdout);
sleep(1);
}
}
编译过程:#gcc single.c –o single
运⾏截图如下:
结果分析:单进程执⾏按次序依次打印五次三个字符串。
创建多个进程代码:
//print1.c
#include<stdio.h>
#define NUM 5
int main(int argc,char*argv[])
{
for(int i=0;i<NUM;i++){
printf("%s\n",argv[1]);
fflush(stdout);
sleep(1);
}
return0;
}
//multi_process.c
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
int main(void){
pid_t pid[3];
int i;
for(int i=0;i<3;i++){
pid[i]=fork();
if(pid[i]==0){
break;//防⽌⼦进程再次创建⼦进程
}
}
if(pid[0]==0){
execl("print1","print","Good",NULL);
}else{
if(pid[1]==0){
execl("print1","print","Hello",NULL);
}else{
if(pid[2]==0)execl("print1","print","201919044217",NULL);
wait();
wait();
wait();//调⽤⼀次,只能回收⼀个⼦进程
exit(0);
}
}
return0;
}
编译过程:#gcc print1.c –o print1
#gcc multi_process.c –o multi
运⾏截图如下:
linux使用vim编辑文件time命令
time命令
linux下time命令可以获取到⼀个程序的执⾏时间,包括程序的实际运⾏时间(real time),以及程序运⾏
在⽤户态的时间(user time)和内核态的时间(sys time)。命令⾏执⾏结束时在标准输出中打印执⾏该命令⾏的时间统计结果,其统计结果包含以下数据:
1)实际时间(real time): 从command命令⾏开始执⾏到运⾏终⽌的消逝时间;
2)⽤户CPU时间(user CPU time): 命令执⾏完成花费的⽤户CPU时间,即命令在⽤户态中执⾏时间总和;
3)系统CPU时间(system CPU time): 命令执⾏完成花费的系统CPU时间,即命令在核⼼态中执⾏时间总和。
其中,⽤户CPU时间和系统CPU时间之和为CPU时间,即命令占⽤CPU执⾏的时间总和。实际时间要⼤于CPU时间,因为Linux是多任务操作系统,往往在执⾏⼀条命令时,系统还要处理其它任务。
⽤法:#time ./example
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论