操作系统第二次试验:进程控制试验

操作系统第二次试验:进程控制试验

  • 前言
  • 实验目的
  • 试验环境
  • 实验内容及步骤
      • 1:练习编写以下实例,来分析和理解Linux中进程的状态转换:
      • 2.(选做)在Linux下,分析程序的功能与运行结果,熟悉Linux进程控制常用的系统调用。
      • 3.线程异步并发实验
  • 操作步骤清单
  • 实验结果(含分析)
  • 实验总结
    • Fork函数和子进程

前言

为了帮助同学们完成痛苦的实验课程设计,本作者将其作出的实验结果及代码贴至CSDN中,供同学们学习参考。如有不足或描述不完善之处,敬请各位指出,欢迎各位的斧正!

实验目的

  1. 理解进程的状态转换,了解进程控制函数:fork、wait/waitpid函数。
  2. 掌握系统调用的简单编程。
  3. 进一步掌握C语言程序的开发方法,阅读、调试C程序并编写简单的进程创建程序。
  4. 通过有关进程控制的应用实例,深刻理解进程的管理过程。

试验环境

实验机房软件Ubuntu和在VMware虚拟机上安装的Linux操作系统。

实验内容及步骤

  1. 在Linux下,top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器。(用来看查看进程耗费cpu和内存的情况)[按键Ctrl+c退出]
  2. 在Linux下,熟悉进程状态命令(ps)的使用。编写一些简单代码来观察各种情况下,Linux进程的状态,进一步理解进程的状态及转换机制。
    Linux中的进程状态转换
    注:Linux下的TASK_RUNING状态包含了课本上的“运行”及“就绪”两种状体
    通过ps命令,可以查看进程的状态:
    R(TASK_RUNNING):可执行状态或运行状态
    S(TASK_INTERRUPTIBLE):可中断阻塞状态,可响应中断、接收信号(如SIGKILL)
    D( TASK_ UNINTERRUPTIBLE):不可中断阻塞状态,只能通过wake_up()函数明确唤醒时才能转换到可运行的就绪状态。
    T( TASK_ STOPPED/ TASK_ TRACED):暂停状态或跟踪状态
    Z( TASK_ DEAD/EXIT_ZOMBIE):退出状态,进程成为僵尸进程,此时,进程结束,但其父进程没有回收其剩余PCB资源,进程处于僵尸状态。

1:练习编写以下实例,来分析和理解Linux中进程的状态转换:

  1. 创建一个C程序,如run_status.c,运行一段长循环,使用gcc-o run_status run_status.c编译链接后,运行该程序。使用./run_status & ps ax | grep run_status命令查看程序状态,写出结果。
    在这里插入图片描述
  2. 使用kill命令,向run_status进程发送SIGSTOP信号,并使用ps命令观察其状态(进入了T状态),如:kill –SIGSTOP 进程号
  3. 使用kill命令,向run_status进程发送SIGCONT信号,并使用ps命令观察其状态(恢复到R状态),如:kill –SIGCONT 进程号
  4. 创建一个C程序,如interruptiblie_status.c,让其睡眠30s。在这里插入图片描述
  5. 编译链接,后台运行该程序(后接&),并使用ps命令查看运行状态。在该状态下,使用kill –SIGKILL 进程号可强行终止该进程,写出运行结果。
  6. 创建一个C程序,如uninter_status.c,让其睡眠30s。使用kill -9 进程号命令后,用ps观察结果。(说明:vfork也是进程创建函数,和fork函数的区别是:子进程创建后,父进程只能等到子进程调用exit或exec之后才能被调度执行;子进程和父进程共享空间。)在这里插入图片描述
  7. 创建一个C程序,如zombie_status.c,在其中创建一个子进程,并让子进程迅速结束,而父进程陷入阻塞。
    在这里插入图片描述
  8. 编译链接,后台运行该程序,并使用ps命令查看运行状态(30s内)

写出上述进程的运行结果,并对每一个状态作分析。

2.(选做)在Linux下,分析程序的功能与运行结果,熟悉Linux进程控制常用的系统调用。

(1)分析下列程序的功能与程序的运行结果。

#include 
main()
{int p1,p2;while((p1=fork())== -1);       if (p1==0) putchar('b');       else{   while((p2=fork( ))= = -1);  if(p2==0)  putchar('c');  else  putchar('a');    }
}

(2)分析使用wait()和exit()控制进程的程序功能与运行结果。

#include 
main()
{ int pid;if(pid=fork()){ wait();printf("it is parent process\n");}else{ printf("it is child process\n");exit();}    
printf("it is end\n");
}

(3)写出下列程序的功能,并分析程序的运行结果。

#include 
main()
{   int p1,p2,i;while((p1=fork())== -1);         if (p1==0)for(i=0;i<10;i++)printf("daughter %d\n",i);else{while((p2=fork())== -1);  if(p2= =0)for(i=0;i<10;i++)printf("son %d\n",i);elsefor(i=0;i<10;i++)printf("parent  %d\n",i);}
}

(4)分析并完善下列程序。程序的功能是:输入两个整数并求和输出,然后创建一个子进程,当进程调度程序调度到父进程或子进程时将输出不同的信息。

#include 
main()
{int i,j,k,sum;scanf("%d%d",&j,&k);sum=j+k;printf("sum=%d \n",sum);while((i=fork())==-1){printf("i=%d\n",i);}if(i=getpid())printf("it is parent process!\n");elseprintf("it is child process!\n");
}

(5) 分析并完善下列程序。程序的功能是:如果父进程要通过建立子进程在同一显示器上分别循环显示“Parents”和“Children”,循环次数由n决定。

#include 
main( )
{int pid,n=1;if((fork())!=0)While(n<10){printf("%d",n++);printf("Parents"); }    elseWhile(n<10){                  printf("Children"); }      }

3.线程异步并发实验

step1:编写一个c语言程序:
初始化一个count变量为1;
使用pthread_create函数创建两个线程,每个线程对count加1之后,显示“I am son,count=?”或“I am daughter, count=?”,?使用count值代替;
父进程对count加之后,显示“I am father,count=?”,?使用count值替代;
最后,父进程使用pthread_join等待两个线程结束后退出。
step2:编译后,运行该程序,分析屏幕上显示结果的顺序性,直至出现不一样的情况为止,观察每行打印结果中count的值。注意,需要链接librt.so库,编译如:gcc -o tst_thread tst_thread.c -lrt
在这里插入图片描述

操作步骤清单

操作步骤如下:

  1. 在桌面新建文件夹exp2,打开后右键选择Open in Terminal(用终端方式打开)。
  2. 在终端输入“vi interruptiblie_status.c”等操作命令,按i进入输入模式,进行C语言程序的编写,编写结束后按ESC键退出输入模式,输入“:wq”以保存文件。
  3. 在终端输入gcc -o interruptiblie_status interruptiblie_status.c,即保存到interruptiblie_status文件中。
  4. 在终端输入./interruptiblie_status,即可看到输出。其余文件操作相同。

实验结果(含分析)

(1)编译run_status.c代码如下:

#include
#include
int main()
{int i,j,k;for(i=0;i<1000000;i++){for(j=0;j<1000000;j++){k++;k--;}}
}

输入ps aux | grep run status监察运行状态
在这里插入图片描述
分析:观察到进程4159处于R(运行)状态
(2)输入kill -SIGSTOP 4159停止进程运行
输入ps aux | grep run status监察运行状态
在这里插入图片描述
分析:观察到进程4159处于T(暂停)状态
(3)输入kill -SIGCONT 4159 继续进程运行
输入ps aux | grep run status监察运行状态
在这里插入图片描述
分析:观察到进程4159处于R(运行)状态
(4)编译interruptiblie_status.c代码如下:

#include
#include
int main()
{sleep(30);//睡眠30秒return 0;
}

在这里插入图片描述
分析:程序引导终端休眠30秒
(5)输入ps监察进程
输入kill -SIGKILL 3619强行终止该进程
输入ps监察进程
在这里插入图片描述
分析:观察到进程3619被杀死(Killed)
(6)编译uninter_status.c代码如下:

#include
#include
int main()
{//vfork:create a child processif(vfork()==0){sleep(30);//the child process sleepreturn 0;}
}

输入kill -9 3665杀死进程
输入ps监察进程
在这里插入图片描述
分析:进程被杀死
(7)编译zombie——status.c代码如下:

#include
#include
int main()
{if(fork()){sleep(30);}
}

输入ps监察进程
在这里插入图片描述
分析:僵尸程序3730、3731为defunct状态
3-1
编译代码如下:

#include
#include
int main()
{int p1,p2;while((p1=fork())==-1);if (p1==0)putchar('b');else{while((p2=fork())==-1);if(p2==0)putchar('c');elseputchar('a');}
return 0;
}

在这里插入图片描述
3-2
编译代码如下:

#include
#include
int main()
{ int pid;
if(pid=fork())
{ wait();
printf("it is parent process\n");}
else
{ printf("it is child process\n");
exit(1);}
printf("it is end\n");
return 0;
}

在这里插入图片描述
3-3
编译代码如下:

#include
#include
int main()
{int p1,p2,i;while((p1=fork())== -1);if (p1==0)for(i=0;i<10;i++)printf("daughter %d\n",i);else{while((p2=fork())== -1);if(p2==0)
for(i=0;i<10;i++)
printf("son %d\n",i);
else
for(i=0;i<10;i++)
printf("parent  %d\n",i);
}
return 0;
}

在这里插入图片描述
3-4
编译代码如下:

#include
#include
int main()
{int i,j,k,sum;scanf("%d%d",&j,&k);sum=j+k;printf("sum=%d\n",sum);while((i=fork())==-1){printf("i=%d\n",i);}if(i=getpid())printf("it is parent process!\n");elseprintf("it is child process!\n");return 0;
}

在这里插入图片描述
3-5(注:由于3-5样例代码会造成死循环,修改编译代码如下)

#include
#include
int main()
{int pid,n;n=1;if((fork())!=0)while(n<10){printf("%d",n++);printf("Parents\n");}//新加换行,便于查看运行结果,下同elsewhile(n<10){printf("%d",n++);//此行代码为新加行printf("children\n");}return 0;
}

在这里插入图片描述
4*(注:运行时代码末尾应加 -L../boost/stage/lib -pthread
编译代码如下:

#include
#include
#include
void *daughter(void *num)
{int* a=(int *)num;*a +=1;printf("I am daughter,count=%d\n",*a);
}
void *son(void *num)
{int* a=(int *)num;*a +=1;printf("I am son,count=%d\n",*a);
}
int main()
{pthread_t son_tid,daughter_tid;int count=1;pthread_create(&son_tid,NULL,son,&count);pthread_create(&daughter_tid,NULL,daughter,&count);count++;printf("I am parent,count:=%d\n",count);pthread_join(son_tid,NULL);pthread_join(daughter_tid,NULL);return 0;
}

输入gcc -o tst_thread tst_thread.c -lrt -L../boost/stage/lib -pthread

实验总结

输入ps aux | grep run status监察运行状态
输入kill -SIGSTOP 4159停止进程运行
输入kill -SIGCONT 4159 继续进程运行
输入kill -SIGKILL 3619强行终止该进程
程序4的输出不会改变,持续保持形式如下:
在这里插入图片描述

Fork函数和子进程

函数pid_t fork(void)
正确返回:在父进程中返回子进程的进程号,在子进程中返回0
错误返回:-1
子进程是父进程的一个拷贝。即,子进程从父进程得到了数据段和堆栈段的拷贝,这些需要分配新的内存;而对于只读的代码段,通常使用共享内存的方式访问。fork返回后,子进程和父进程都从调用fork函数的下一条语句开始执行。父进程与子进程的不同之处在于:fork的返回值不同——父进程中的返回值为子进程的进程号,而子进程为0。
以下是fork的两个示例程序:

//fork.c
#include 
#include 
void main ()
{int pid;//printf("Process [%d] begin",getpid());		//print twiceprintf("Process [%d] begin\n",getpid());		//print once
//由于fork时pc等值的拷贝,子进程只会从fork处开始执行
pid = fork();
if (pid < 0)printf("error in fork!"); 
else if (pid == 0) 
printf("I'm child process, my pid is %d\n", getpid());
else 
printf("I'm parent process, my pid is %d\n", getpid());printf("Process [%d] end\n",getpid());
return;
}

输出结果:

//使用printf("Process [%d] begin\n",getpid())时
Process [11155] begin
I'm parent process, my pid is 11155
Process [11155] end
I'm child process, my pid is 11156
Process [11156] end
//使用printf("Process [%d] begin",getpid())时
Process [11054] beginI'm parent process, my pid is 11054
Process [11054] end
Process [11054] beginI'm child process, my pid is 11055
Process [11055] end

不同的输出结果是因为Printf的缓冲机制。printf某些内容时,操作系统仅仅是把该内容放到stdout的缓冲队列里,并没有实际写到屏幕上。但是,只要看到有"\n"则会立即刷新stdout,因此就马上能够打印了。运行了printf(“string”) 后,string 仅仅被放到了缓冲里,再运行到fork时,缓冲里的 string 被子进程继承了,因此在子进程度stdout缓冲里面就也有了 string。而运行 printf(“string\n”)后,string 被立即打印到了屏幕上,之后fork到的子进程里的stdout缓冲里不会有 string 内容。实际上,fork语句之前的指令在子进程中不会再执行。

//son_2_father.c
#include 
#include 
int main()
{int i;printf("Process\t[%d] begin\n",getpid());
for( i = 0; i < 3; i++)
{int pid = fork();
if(pid == 0)printf("son\t[%d] create [%d]\n", getpid(), pid);elseprintf("father\t[%d] create [%d]\n", getpid(), pid);
}
printf("Process\t[%d] finish\n",getpid());
return 0;
}

输出结果如下:

Process	[10026] begin
father	[10026] create [10027]
son	[10027] create [0]
father	[10026] create [10028]
father	[10027] create [10029]
father	[10026] create [10030]
Process	[10026] finish
father	[10027] create [10031]
Process	[10027] finish
son	[10028] create [0]
father	[10028] create [10032]
Process	[10028] finish
son	[10032] create [0]
Process	[10032] finish
son	[10030] create [0]
Process	[10030] finish
son	[10031] create [0]
son	[10029] create [0]
Process	[10031] finish
father	[10029] create [10033]
Process	[10029] finish
son	[10033] create [0]
Process	[10033] finish

在这里插入图片描述
我们可以将此进程的执行过程表示为上图,子进程fork返回0,但是fork之后便成为了父进程,再次fork就可以得到子进程。最后,需要注意,派生子进程的进程,即父进程,其pid不变,fork之后父子进程除非采用了同步手段,否则不能确定谁先运行,也不能确定谁先结束。

作者:Knight Schumacher
首次编辑日期:2020.12.2
诚挚的希望能帮到各位同学,祝各位学业有成!


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部