一、实验目的
1、掌握Linux中进程的创建方法及执行情况;
2、加深对进程、进程树等概念的理解;
3、掌握Linux中如何加载子进程自己的程序;
4、掌握父进程通过创建子进程完成某项任务的方法;
5.、掌握系统调用exit()和_exit()调用的使用。
6、分析进程竞争资源的现象,学习解决进程互斥的方法;进一步认识并发执行的实质
二、实验内容
(一)进程的创建
1、编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程活动。让每一个进程在屏幕上显示一个字符。
#include main() { int p,x; p=fork(); if(p>0) x=fork(); if(x>0) printf("father\\n"); else printf("child2"); } else printf("child1"); } 输出结果: child1 child2 father 2、运行以下程序,分析程序执行过程中产生的进程情况。 #include main() { int p,x; p=fork(); if (p>0) fork(); else{ fork(); fork(); } sleep(15); } 实验步骤: 编译连接 gcc –o forktree forktree.c 后台运行 ./forktree & 使用 pstree –h 查看进程树 运行结果: ├─gnome-terminal─┬─bash─┬─forktree─┬─forktree─┬─forktree───forktree │ │ │ │ └─forktree │ │ │ └─forktree │ │ └─pstree 分析:程序运行,系统首先创建一个进程forktree,执行到p=fork()创建一个子进程forktree,子进程获得处理机优先执行,父进程等待;执行else,当执行到第一个fork()函数时,子进程创建了一个进程forktree,称之为孙进程,孙进程获得处理机往下执行,子进程等待;执行到第二个fork()函数时,孙进程又创建一个进程forktree,称之为重孙进程,重孙进程很快执行完,将处理机还给孙进程,孙进程很快执行完,将处理机还给子进程;子进程继续往下执行,执行到第二个fork()函数,又创建一个进程forktree,称之为第二孙进程,并获得处理机执行,此进程很快执行完,将处理机还给子进程,子进程也很快执行完,将处理机还给父进程,父进程P>0执行if语句,运行fork()函数,又创建一个进程forktree,称之为第二子进程,此进程获得处理机执行很快运行完,将处理机还给父进程,父进程运行sleep(15)语句,休眠15秒,用pstree命令查询进程树。 3、运行程序,分析运行结果。 #include main() { int p,x,ppid,pid; x=0; p=fork(); if(p>0) { printf("parent output x=%d\\n",++x); ppid=getpid(); printf("Thi id number of parent is:ppid=%d\\n",ppid); } else { printf("child output x=%d\\n",++x); pid=getpid(); printf("Thi id number of child is:pid=%d\\n",pid); } } 运行结果: Parent output x=1 This id number of parent is:ppid=3110 Child output x =1 This is number of child is:pid=3111 分析:fork创建进程的时候子进程与父进程共享代码区,子进程复制父进程的数据区,所以,两个进程中的数据互不影响都是1。 4、loop.c #include main() { while(1){ }; { 实验步骤: 编译 gcc loop.c –o loop 后台运行 ./loop &(可多次使用该命令) 多次使用ps命令查看进程状态 结果: F S UID PID PPID C PRI NI ADDR SZ WCHAN TTY TIME CMD 0 S 1000 25 23 0 80 0 - 1444 wait pts/0 00:00:00 bash 0 R 1000 3622 25 78 80 0 - 403 - pts/0 00:00:18 loop 0 R 1000 3627 25 39 80 0 - 403 - pts/0 00:00:02 loop 0 R 1000 3628 25 39 80 0 - 403 - pts/0 00:00:01 loop 0 R 1000 3630 25 0 80 0 - 625 - pts/0 00:00:00 ps (二)进程控制 1、设计一程序,父进程创建一子进程,子进程的功能是输出“hello world!”,使用execl()加载子进程的程序。 程序代码如下: execl.c程序: #include #include #include main() { \\n"); \\n"); hellohello",0); printf("child is running!\\n"); } hello.c程序: #include main() { } 输出结果: child is running! hello world! father is running! 2、运行以下程序,分析程序执行结果。 #include #include main() { int p; p=fork(); if (p>0) printf("this is parent "); else{ printf("this is child first\\n”); printf("this is child second ”); _exit(0); } } 执行结果: this is child first this is parent[chun@RedFlagLinux jjz]$ 换为exit(0)的执行结果: this is child first this is child second this is parent[chun@RedFlagLinux jjz]$ 分析:_exit()只是返回进程状态SZOMB,不清除缓冲区内容; 表示进程正常终止; (三)、进程的互斥 1、利用cat to_be_locked.txt 查看下面程序的输出结果。 #include #include main() { fp=fopen("to_be_locked.txt 创建子进程p1*/ 加锁*/ 解锁*/ 创建子进程p2*/ 加锁*/ 解锁*/ 加锁*/ 解锁*/ } 执行结果: daughter0 daughter1 daughter2 daughter3 daughter4 daughter5 daughter6 daughter7 daughter8 daughter9 son0 son1 son2 son3 son4 son5 son6 son7 son8 son9 parent0 parent1 parent2 parent3 parent4 parent5 parent6 parent7 parent8 parent9 分析:程序开始定义了文件读写指针用于打开指定的文件,当文件不存在时则自动创建。然后有创建了一个进程p1,p1获得处理机执行,给文件读写指针加锁,这样,即使p1失去处理机,其他获得处理机的进程也无法访问文件指针指向的文件,当p1再次获得处理机后继续执行直至进程p1结束并解锁;p1结束后父进程获得处理机执行又创建了进程p2,p2获得处理机执行,也给文件指针加锁,同理直至p2运行完解锁;p2结束后父进程获得处理机,父进程也给文件指针加锁,直至父进程执行完毕解锁,程序结束。 2、按以下步骤分析下面的程序: (1)查看程序执行的结果并估计程序执行所需要时间。 (2)将程序中所有的lockf函数加上注释,再观察程序执行的结果和估算程序执行所需的时间。 (3)分析这两次执行的结果与时间的区别。 #include #include main( ) { int p1,p2,i; p1=fork( ); /*创建子进程p1*/ if (p1==0) { lockf(1,1,0); /*加锁,这里第一个参数为stdout(标准输出设备的描述符)*/ for(i=0;i<10;i++) { printf("child1=%d\\n",i); sleep(1); } lockf(1,0,0); /*解锁*/ } else { p2=fork(); /*创建子进程p2*/ if (p2==0) { lockf(1,1,0); /*加锁*/ for(i=0;i<10;i++) { printf("child2= %d\\n",i); sleep(1); } lockf(1,0,0); /*解锁*/ } else { lockf(1,1,0); /*加锁*/ for(i=0;i<10;i++) { printf(" parent %d\\n",i); sleep(1); } lockf(1,0,0); /*解锁*/ } } } 未加注释的执行结果: child1=0 child1=1 child1=2 child1=3 child1=4 child1=5 child1=6 child1=7 child1=8 child1=9 child2=0 child2=1 child2=2 child2=3 child2=4 child2=5 child2=6 child2=7 child2=8 child2=9 parent0 parent1 parent2 parent3 parent4 parent5 parent6 parent7 parent8 parent9 加上注释的执行结果: child1=0 child2=0 parent0 child1=1 child2=1 parent1 child1=2 child2=2 parent2 child1=3 child2=3 parent3 child1=4 child2=4 parent4 child1=5 child2=5 parent5 child1=6 child2=6 parent6 child1=7 child2=7 parent7 child1=8 child2=8 parent8 child1=9 child2=0 parent9 分析:未加注释时程序运行时间大约是30秒,注释后的程序运行时间大约只是10秒钟。在注释掉之前,由于给后面的循环输出语句块加了锁,所以该语句只能被当前进程访问,即使其他进程获得处理机也无法访问,只有当前进程获得处理机并接着中断的地方继续执行完毕将锁释放其他的进程才能访问。所以输出结果按照顺序依次输出。由于sleep(1)语句在输出语句块中,每输出一次的要休眠1秒钟,所以大约需要30秒钟。 注释掉所有lockf()函数后,循环输出语句块没有被加锁,当当前进程失去处理机后,获得处理机的其它进程也可以继续输出。另外,当进程child1输出一句后进入1秒休眠,此间进程child2获得处理机并输出一句也进入1秒休眠,此时进程parent获得处理机输出一句后也进入1秒休眠,由于三进程是依次进入休眠,因此也会依次醒来,依次获得处理机,依次输出,再依次休眠,直至循环输出完毕。由于一个进程的输出是在其他两个进程休眠期间运行的,而这两个进程之一也在这个进程运行完一次输出进入休眠期间醒来,因此,只相当于休眠了10次,故大约需要10秒钟。 三、实验总结 1、fork()函数用于创建进程,返回值是整数。当为0时,表示当前进程是子进程;>0时,表示父进程。-1时,表示创建进程失败。子进程与父进程共享代码区,子进程复制父进程的数据区。 2、fork()创建进程的时候,子进程复制父进程的代码,要想改变子进程的代码就可以调用execl()来实现,调用execl()即可用于新程序的运行;注意exit()和_exit()的不同区别。 3、lockf(files,function,size) 用作锁定文件的某些段或者整个文件,从而实现互斥。头文件为#include "unistd.h" 参数定义: int lockf(files,function,size) int files,function; long size; 其中:files是文件描述符;function是锁定和解锁:1表示锁定,0表示解锁。size是锁定或解锁的字节数,为0,表示从文件的当前位置到文件尾。下载本文