1、编写一段程序,使用系统调用fork( )创建两个子进程。当此程序运行时,在系统中有一个父进程和两个子进程并发执行,观察实验结果并分析原因。
2、用fork( )创建一个进程,再调用exec( ),用新的程序替换该子进程的内容,利用wait( )来控制进程执行顺序,掌握进程的睡眠、同步、撤消等进程控制方法,并根据实验结果分析原因。
3、编写一段多进程并发运行的程序,用lockf( )来给每一个进程加锁,以实现进程之间的互斥,观察并分析出现的现象及原因。
4、编写程序:用fork( )创建两个子进程,再用系统调用signal( )让父进程捕捉键盘上来的中断信号(即按^c键);捕捉到中断信号后,父进程用系统调用kill( )向两个子进程发出信号,子进程捕捉到信号后分别输出下列信息后终止:
Child process1 is killed by parent!
Child process2 is killed by parent!
父进程等待两个子进程终止后,输出如下的信息后终止:
Parent process is killed!
分析利用信号量机制中的软中断通信实现进程同步的机理。
5、编写程序实现进程的管道通信。用系统调用pipe( )建立一管道,二个子进程P1和P2分别向管道各写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
父进程从管道中读出二个来自子进程的信息并显示(要求先接收P1,后P2),分析管道通信机制及控制原理。
6、使用系统调用msgget( ),msgsnd( ),msgrev( ),及msgctl( )编制一长度为1k的消息发送和接收的程序,并分析消息的创建、发送和接收机制及控制原理。
7、编制一长度为1k的共享存储区发送和接收的程序,并设计对该共享存储区进行互斥访问及进程同步的措施,必须保证实现正确的通信。
思考题:
1、进程创建与进程并发执行
(1)系统是怎样创建进程的?
(2)当首次调用新创建进程时,其入口在哪里?
(3)利用strace 和ltrace -f -i -S ./executable-file-name查看程序执行过程,并分析原因,画出进程家族树。
2、进程的睡眠、同步、撤消等进程控制
(1)可执行文件加载时进行了哪些处理?
(2)什么是进程同步?wait( )是如何实现进程同步的?
(3)wait( )和exit()是如何控制实验结果的随机性的?
3、多进程通过加锁互斥并发运行
(1)进程加锁和未上锁的输出结果相同吗? 为什么?
4、进程间通过信号机制实现软中断通信
(1)为了得到实验内容要求的结果,需要用到哪些系统调用函数来实现及进程间的通信控制和同步?
(2)kill( )和signal( )函数在信号通信中的作用是什么?如果分别注释掉它们,结果会如何?
5、进程的管道通信
(1)父进程如何从管道中先读出P1,后读出P2两个子进程的信息的?程序中用到的系统调用函数是什么?起什么作用?
(2)子进程P1和P2为什么也能对管道进行操作?
6、消息的发送与接收
(1)为了便于操作和观察结果,需要编制几个程序分别用于消息的发送与接收?
(2)这些程序如何进行编辑、编译和执行?为什么?
(3)如何实现消息的发送与接收的同步?
7、进程的共享存储区通信
(1)为了便于操作和观察结果,需要如何合理设计程序来实现子进程间的共享存储区通信?
(2)比较消息通信和共享存储区通信这两种进程通信机制的性能和优缺点。
实验思路及程序逻辑框图:
1.实验思路:一个函数可以用fork()函数创建一个新的子进程,称为它的子进程,fork调用可能有三种返回值:若在父进程中,fork返回新创建的子进程的进程id,若在子进程中,则返回0,若出现错误,fork返回一个负值。因此可以通过返回值来判断位于子进程还是父进程,再让该进程输出不同的东西来观察进程的运行情况。
2.实验思路:用fork()创建一个子进程,然后在子进程中调用exec(),同时父进程调用wait()函数等待子进程运行结束,在子进程没有结束之前,使父进程一直处于睡眠状态,这样就实现了进程的同步。
3.实验思路:这里可以以ppt上面第一题的代码为基础进行修改,分别将a,b,c改为parent,son和daughter。子进程1输出5次daughter,并用lock(1,1,0)进行加锁,输出一次daughter后立刻用lockf(1,0,0)进行解锁,随后休眠几秒,让子进程2抢夺stdout资源,子进程2也采用加锁解锁的方式来进行输出,这样就实现了交替输出,也就是实现了进程间的互斥。
4.实验思路:首先父进程先创建两个子进程,在父进程中产生产生两个中断信号SIGUSR1和SIGUSR2,随后用kill()向两个子进程发送软中断信号。在子进程1中子进程1等到软中断信号后子进程1被父进程杀死,随后等待子进程2被杀死。在子进程2中,子进程2等到软中断信号后子进程2被父进程杀死。进程1和进程2被杀死后,父进程终止。
5.实验思路:首先创建一个管道,然后创建进程1向管道中写入“Child 1 is sending a message!”,并做好跟父进程的同步执行;随后创建子进程2并向管道中写入“Child 2 is sending a message!”,,并做好跟父进程的同步执行;最后父进程从管道中读出数据,并打印输出。
流程图:
6.实验思路:这里采用两个程序来实现,一个是客户端,一个是服务端,分别用于消息的发送与接收。server建立一个key为75的消息队列,等待其他进程发来消息。并将类型为1的消息作为结束信号,取消该队列,然后退出server。server收到一条信息后输出一句“(server)received!”。client使用Key为75的消息队列,先后发送类型为10到1的信息,然后退出。client每发送一条信息后便输出一句”(client)sent!”。
7.实验思路:首先利用fork()创建两个进程,父进程中是Server,子进程是Client,两者之间进行通信。Server端建立一个key为75的共享区,并将第一个字节置为-1,作为数据空的标志。等待其他进程发来的消息,当该字节的值发生变化的时候,表示收到了消息,然后进行处理,并再次把他的值设为-1,如果遇到值为0,则表示结束信号,取消该队列,并退出。Server接收到一个消息后便输出“(server)receive”。Client端使用Key为75的共享区,当共享取得第一个字节为-1的时候,表示Server端是空闲的状态,这时发送请求。Client随即填入9到0,期间等待Server端再次空闲,进行完这些操作后退出。最后一个消息为0,也是作为Server端需要结束的信号。Client每发送一条消息后会显示一句“(client)sent”。在server和client均退出后父进程结束。
实验数据及源代码
1.源代码:
test1-1.c #include<stdio.h> #include<unistd.h> void main(){ int pid1=fork(); //创建子进程1 if(pid1<0) //创建失败 {printf("error\n");} else if(pid1==0) //在子进程中 {printf("A");} else //在父进程中 {printf("B");} int pid2=fork(); //创建子进程2 if(pid2<0) //创建失败 {printf("error\n");} else if(pid2==0) //在子进程中 {printf("C");} else //在父进程中 {printf("D");} }
思考题源代码:
test1-2.c #include<stdio.h> #include<unistd.h> void main(){ int pid1=fork(); //创建进程1 if(pid1<0) //创建失败 { printf("error\n"); } else if(pid1==0) //在子进程中 { //输出该进程进程块号以及该进程父进程进程块号 printf("A pid=%d,parentpid=%d\n",getpid(),getppid()); } else //在父进程中 { printf("B pid=%d\n",getpid()); //输出该进程进程块号 } int pid2=fork(); //创建进程2 if(pid2<0) //创建失败 { printf("error\n"); } else if(pid2==0) //在子进程中 { //输出该进程进程块号以及该进程父进程进程块号 printf("C pid=%d,parentpid=%d\n",getpid(),getppid()); } else //在父进程中 { printf("D pid=%d\n",getpid()); //输出父进程进程块号 } }
2.源代码:
test2-1.c #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> void main( ) { int pid; pid=fork( ); //创建子进程 if(pid==-1){ //创建失败 printf("创建进程失败!\n"); exit(1); //异常终止 } else if(pid==0){ //在子进程中 execl("/bin/ls","ls","-1","-color",NULL); printf("exec fail!\n"); exit(1); //异常终止 } else{ //在父进程 wait(NULL); //同步机制 printf("ls completed !\n"); exit(-1); //正常终止 } } 取消wait(): test2-2.c #include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> void main( ) { int pid; pid=fork( ); //创建子进程 if(pid==-1){ //创建失败 printf("创建进程失败!\n"); exit(1); //异常终止 } else if(pid==0){ //在子进程中 execl("/bin/ls","ls","-1","-color",NULL); printf("exec fail!\n"); exit(1); //异常终止 } else{ //在父进程 //wait(NULL); //同步机制 printf("ls completed !\n"); exit(-1); //正常终止 } }
3.源代码:
test3-1.c #include<stdio.h> #include<unistd.h> int main(){ int p1,p2; p1=fork(); //创建进程 if(p1==0){ //在子进程1中 int i; for(i=0;i<5;i++){ lockf(1,1,0); //加锁 printf("daughter %d\n",i); lockf(1,0,0); //解锁 sleep(2); //睡眠2秒 } } else{ p2=fork(); if(p2==0){ //在子进程2中 int j; for(j=0;j<5;j++){ lockf(1,1,0); //加锁 printf("son %d\n",j); lockf(1,0,0); //解锁 sleep(2); //睡眠2秒 } } else printf("parent \n"); //在父进程中 } } 不加锁: test3-2.c #include<stdio.h> #include<unistd.h> int main(){ int p1,p2; p1=fork(); //创建进程 if(p1==0){ //在子进程1中 int i; for(i=0;i<5;i++){ //lockf(1,1,0); //加锁 printf("daughter %d\n",i); // lockf(1,0,0); //解锁 sleep(2); //睡眠2秒 } } else{ p2=fork(); if(p2==0){ //在子进程2中 int j; for(j=0;j<5;j++){ //lockf(1,1,0); //加锁 printf("son %d\n",j); //lockf(1,0,0); //解锁 sleep(2); //睡眠2秒 } } else printf("parent \n"); //在父进程中 } }
源代码:
test4-1.c #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int wait_mark; void waiting( ) { while(wait_mark!=0); //等待,直至wait_mark=0 } void stop( ) { wait_mark=0; } int main( ) { int p1,p2; if (p1=fork()) //创建子进程1 { if(p2=fork()) //创建子进程2 { //在父进程中 wait_mark=1; //等待信号量置为1 signal(SIGINT,stop); //接受中断信号 waiting(); //等待,直至wait_mark=0 signal(SIGALRM,stop); kill(p1,SIGUSR1); // 向进程1发软中断信号SIGUSR1 kill(p2,SIGUSR2); // 向进程2发软中断信号SIGUSR2 wait(0); // 父进程等待两个进程中止 wait(0); printf("Parent process is killed!\n"); exit(0); } else { wait_mark=1; signal(SIGUSR2,stop); // 接收到软中断信号SIGUSR2 signal(SIGINT,SIG_IGN); // 忽略软中断信号 waiting(); //等待,直至wait_mark=0 lockf(1,1,0); // 加锁 printf("Child process2 is killed by parent!\n"); lockf(1,0,0); // 解锁 exit(0); } } else { wait_mark=1; signal(SIGUSR1,stop); //接收到软中断信号 signal(SIGINT,SIG_IGN);// 忽略中断信号 waiting(); lockf(1,1,0); // 加锁 printf("Child process1 is killed by parent!\n"); lockf(1,0,0); // 解锁 exit(0); } } 注释掉kill(): test4-2.c #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int wait_mark; void waiting( ) { while(wait_mark!=0); //等待,直至wait_mark=0 } void stop( ) { wait_mark=0; } int main( ) { int p1,p2; if (p1=fork()) //创建子进程1 { if(p2=fork()) //创建子进程2 { wait_mark=1; signal(SIGINT,stop); //接受中断信号 waiting(); //等待,直至wait_mark=0 signal(SIGALRM,stop); //kill(p1,SIGUSR1); // 向进程1发软中断信号SIGUSR1 //kill(p2,SIGUSR2); // 向进程2发软中断信号SIGUSR2 wait(0); // 父进程等待两个进程中止 wait(0); printf("Parent process is killed!\n"); exit(0); } else { wait_mark=1; signal(SIGUSR2,stop); // 接收到软中断信号SIGUSR2 signal(SIGINT,SIG_IGN); // 忽略软中断信号 waiting(); //等待,直至wait_mark=0 lockf(1,1,0); // 加锁 printf("Child process2 is killed by parent!\n"); lockf(1,0,0); // 解锁 exit(0); } } else { wait_mark=1; signal(SIGUSR1,stop); //接收到软中断信号 signal(SIGINT,SIG_IGN);// 忽略中断信号 waiting(); lockf(1,1,0); // 加锁 printf("Child process1 is killed by parent!\n"); lockf(1,0,0); // 解锁 exit(0); } } 注释掉signal(): test4-3.c #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int wait_mark; void waiting( ) { while(wait_mark!=0); //等待,直至wait_mark=0 } void stop( ) { wait_mark=0; } int main( ) { int p1,p2; if (p1=fork()) //创建子进程1 { if(p2=fork()) //创建子进程2 { wait_mark=1; //signal(SIGINT,stop); //接受中断信号 waiting(); //等待,直至wait_mark=0 //signal(SIGALRM,stop); kill(p1,SIGUSR1); // 向进程1发软中断信号SIGUSR1 kill(p2,SIGUSR2); // 向进程2发软中断信号SIGUSR2 wait(0); // 父进程等待两个进程中止 wait(0); printf("Parent process is killed!\n"); exit(0); } else { wait_mark=1; //signal(SIGUSR2,stop); // 接收到软中断信号SIGUSR2 //signal(SIGINT,SIG_IGN); // 忽略软中断信号 waiting(); //等待,直至wait_mark=0 lockf(1,1,0); // 加锁 printf("Child process2 is killed by parent!\n"); lockf(1,0,0); // 解锁 exit(0); } } else { wait_mark=1; //signal(SIGUSR1,stop); //接收到软中断信号 //signal(SIGINT,SIG_IGN);// 忽略中断信号 waiting(); lockf(1,1,0); // 加锁 printf("Child process1 is killed by parent!\n"); lockf(1,0,0); // 解锁 exit(0); } } 不加锁: test4-4.c #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/wait.h> int wait_mark; void waiting( ) { while(wait_mark!=0); //等待,直至wait_mark=0 } void stop( ) { wait_mark=0; } int main( ) { int p1,p2; if (p1=fork()) //创建子进程1 { if(p2=fork()) //创建子进程2 { wait_mark=1; signal(SIGINT,stop); //接受中断信号 waiting(); //等待,直至wait_mark=0 signal(SIGALRM,stop); kill(p1,SIGUSR1); // 向进程1发软中断信号SIGUSR1 kill(p2,SIGUSR2); // 向进程2发软中断信号SIGUSR2 wait(0); // 父进程等待两个进程中止 wait(0); printf("Parent process is killed!\n"); exit(0); } else { wait_mark=1; signal(SIGUSR2,stop); // 接收到软中断信号SIGUSR2 signal(SIGINT,SIG_IGN); // 忽略软中断信号 waiting(); //等待,直至wait_mark=0 printf("Child process2 is killed by parent!\n"); exit(0); } } else { wait_mark=1; signal(SIGUSR1,stop); //接收到软中断信号 signal(SIGINT,SIG_IGN);// 忽略中断信号 waiting(); printf("Child process1 is killed by parent!\n"); exit(0); } }
源代码:
test5.c #include <unistd.h> #include <signal.h> #include <stdio.h> #include<stdlib.h> #include<sys/wait.h> int pid1,pid2; int main( ) { int fd[2]; //0是读,1是写 char outpipe[100],inpipe[100]; pipe(fd); //创建一个管道 while ((pid1=fork( ))==-1); //进程1抢到cpu使用权,则将写端锁定,禁止其他进程访问写端 if(pid1==0) { lockf(fd[1],1,0); //加锁 sprintf(outpipe,"child 1 process is sending message!"); //把串放入数组outpipe中 write(fd[1],outpipe,50); //将50字节的内容写到共享文件 sleep(5); //自我阻塞5秒 lockf(fd[1],0,0); //解锁 exit(0); } else { while((pid2=fork( ))==-1); //父进程抢到cpu资源,然后创建一个进程2,写入内容 if(pid2==0) //进程2抢到cpu使用权 { lockf(fd[1],1,0); //互斥 sprintf(outpipe,"child 2 process is sending message!"); write(fd[1],outpipe,50); sleep(5); lockf(fd[1],0,0); exit(0); } else { //父进程抢到cpu资源,开始读出两个进程写的内容 wait(0); //同步 read(fd[0],inpipe,50); //从管道中读长为50字节的串 printf("%s\n",inpipe); wait(0); read(fd[0],inpipe,50); printf("%s\n",inpipe); exit(0); } } }
源代码:
client.c: #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/msg.h> #include <sys/ipc.h> #define MSGKEY 75 struct msgform { long mtype; //定义消息类型为long char mtext[1000]; //消息的文本 }msg; int msgqid; void client() { msgqid = msgget(MSGKEY,0777);//打开 75#消息队列 for(int i = 10;i>=1;i--) { msg.mtype = i; printf("(client)sent\n"); msgsnd(msgqid,&msg,1024,0);//发送消息 } exit(0); } int main() { client(); } server.c: #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/msg.h> #include <sys/ipc.h> #define MSGKEY 75 struct msgform { long mtype; //定义消息类型为long char mtext[1000]; //消息文本 }msg; int msgqid; void server() { msgqid = msgget(MSGKEY,0777|IPC_CREAT);//创建 75#消息队列 do { msgrcv(msgqid,&msg,1030,0,0);//接收消息 printf("(server)received\n"); }while(msg.mtype!=1); msgctl(msgqid,IPC_RMID,0);//删除消息队列,归还资源 exit(0); } int main() { server(); } 思考题源代码: client加入阻塞: client-1.c #include <stdio.h> #include <signal.h> #include <unistd.h> #include <stdlib.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/types.h> #include <sys/msg.h> #include <sys/ipc.h> #define MSGKEY 75 struct msgform { long mtype; char mtext[1000]; }msg; int msgqid; void client() { msgqid = msgget(MSGKEY,0777);//打开 75#消息队列 for(int i = 10;i>=1;i--) { msg.mtype = i; printf("(client)sent\n"); msgsnd(msgqid,&msg,1024,0);//发送消息 sleep(1); //睡眠1秒,便于观察 } exit(0); } int main() { client(); } 7. 源代码: test7.c #include <sys/shm.h> #include <sys/ipc.h> #include <sys/wait.h> #include <unistd.h> #include <stdlib.h> #include <stdio.h> #define SHMKEY 75 //共享存储区名字 int shmid, i; int *addr; void client() { int i; shmid = shmget(SHMKEY, 1024, 0777| IPC_CREAT); /*创建一个共享存储区,名字为75,大小为1024字节,不重复创建*/ addr = shmat(shmid, 0, 0); //共享存储区所附接到的进程虚地址(首地址) for(i = 9; i>=0; i--) { while(*addr != -1); //确保服务端收到了一条信息,再发下一条 printf("(client)sent\n"); *addr = i; } exit(0); } void server() { shmid = shmget(SHMKEY, 1024, 0777 | IPC_CREAT); /*创建一个共享存储区,名字为75,大小为1024字节,不重复创建*/ addr = shmat(shmid, 0, 0);//共享存储区所附接到的进程虚地址(首地址) do { *addr = -1; while(*addr == -1);/*响应客户端,addr到0退出循环*/ printf("(server)received\n"); }while(*addr); shmctl(shmid, IPC_RMID, 0); /*撤消共享存储区,归还资源*/ exit(0); } void main() { while((i = fork()) == -1);/*创建子进程*/ if(!i) server();/*子进程运行server*/ system("ipcs -m");/*共享内存资源*/ while((i = fork()) == -1);/*创建子进程*/ if(!i) client();/*子进程运行client*/ wait(0);/*等待子进程结束*/ wait(0); }
七、实验结果分析(截屏的实验结果,与实验结果对应的实验分析)
1、
一开始我的程序输出结果都是相同的值,通过查询检查我认为是一开始就是父进程在占用CPU,因此即使子进程创建了也得等父进程运行完。后来我通过将虚拟机的cpu内核数量提高到4,就出现了上面的结果。通过观察可以看到每次输出的结果都不一样,而且存在随机性。因为在申请创建子进程之后是父进程先执行还是子进程先执行是不确定的,取决于内核的调度算法,相互之间没有任何时序上的关系,因此在在没有加入进程同步机制的情况下,父进程和子进程的输出内容会叠加在一起,也就会使每次运行输出不同的结果。
思考题:
1、进程创建与进程并发执行
(1)系统是怎样创建进程的?
系统创建进程的过程如下:
①.为新进程分配一个唯一的标识符
②.为进程分配空间
③.初始化进程控制块
④.新进程插入就绪队列
(2)当首次调用新创建进程时,其入口在哪里?
fork系统调用创建的子进程对父进程进行了继承,即父进程的副本,也就是说fork调用成功后,子进程与父进程并发执行相同的代码。但由于子进程也继承了父进程的程序指针,所以子进程是从fork()后的语句开始执行,也就是该进程的入口。在实验第一点的程序中,下面箭头所指方向即为新进程的入口,箭头1为进程1入口,箭头2为进程2入口。
(3)利用strace 和ltrace -f -i -S ./executable-file-name查看程序执行过程,并分析原因,画出进程家族树。
执行strace ./a.out
执行ltrace -S ./a.out
可以看到,两个命令都能输出程序用到的一些系统调用。
执行ltrace -i ./a.out用于跟踪当前的进程
执行ltrace -f ./a.out,可以看到除了跟踪了当前进程之外还对子进程进行了跟踪。
为了便于发现进程之间的家族关系,我在代码中加入了getpid()和getppid()函数用于获取进程识别码,修改的代码见实验源代码。
输出结果:
因此可以确定他们的家族关系如下:
2、
取消wait():
通过查询,得知在Linux中并不存在exec()函数,exec指的是一组函数,一共有6个,分别是execl()、execlp()、execle()、execle()、execv()、execvp()、execve()。其中只有execve()是真正意义上的系统调用,其他都是在此基础上经过包装的库函数。这里用到的是excel()函数,它采用罗列的方式,把参数一个一个列出来,然后以一个NULL表示结束。
在程序调用fork()创建一个子进程后,马上调用wait(),使父进程在子进程结束之前一直处于睡眠状态。子进程用exce()装入命令ls,exec()后子进程的代码被ls的代码所取代,子进程PC指向Is第一条语句,开始执行ls的命令代码,于是就输出了当前目录(桌面)下所有文件和子目录。
取消wait()后,可以看到顺序输出变错乱了,说明程序父进程和子进程实现了同步。
思考题:
2、进程的睡眠、同步、撤消等进程控制
(1)可执行文件加载时进行了哪些处理?
C源程序一>编译预处理一>编译一>优化程序一>汇编程序一>链接程序一>可执行文件
(2)什么是进程同步?wait( )是如何实现进程同步的?
进程同步:进程同步是指多个相关进程在执行次序上进行协调,以使并发执行的主进程之间有效的共享资源和相互合作,从而使程序的执行具有可再现性。
wait()函数实现进程同步是靠使父进程在子进程调用之前一直处于睡眠状态,这样使子进程先运行。子进程运行exec()装入命令后,然后调用wait(0),使子进程和父进程并发执行以实现同步。
(3)wait( )和exit()是如何控制实验结果的随机性的?
可以看出在使用了exec()函数后程序使用了ls的命令,列出/bin/目录下的文件信息,执行完execl()函数后,子进程调用exit()函数,退出当前进程,我们可以发现在使用wait()函数后,父进程永远将在其他的子进程完成之后才执行,所以在输出的结果中我们可以看到最后输出的将是父进程的信息,这样进而可以控制实验结果的随机性。
3、
不加锁:
这里和思考题一起进行分析
思考题3.(1)进程加锁和未上锁的输出结果相同吗? 为什么?
可以看到加锁之后进程1和进程2交替执行,没有连续输出daughter或者son,lock()的基本功能是实现资源的互斥访问,这里的资源是系统的标准输出stdout,互斥的结果为同一时刻只可以有一个进程进行输出,并且不可被打断。可以看到,当没有加锁是,输出的是连续的daughter和son,而加上锁以后,两者交替进行输出。daughter()进程先用lockf(1,1,0)锁定stdout,执行输出后,立即用lockf(1,0,0)释放stdout,在这段时间内没有进入下一次循环,而是休眠了2秒,这时候进程2可以争夺资源,于是进程2进行输出。这样就实现了交替输出,即实现了进程的互斥。
实验结果:
从结果中可以看出,当按下^c键后,父进程通过系统调用signal()来捕捉到这一中断信号,子进程1和子进程2都被父进程杀死。同时,父进程调用wait()函数来等待子进程结束,然后自己输出“Parent process is killed!”后被杀死。
一开始我并没有给两个子进程进行加锁,虽然没有加锁,但是大多数情况下程序运行结果都是正确的,也即子进程1先被杀死然后子进程2被杀死。但是后来我意识到这个问题后发现有没有可能出现子进程2先被杀死,经过几次执行,最终证实了我的观点。结果如下:
为何会出现这样的情况?我认为软中断信号是向子进程1和子进程2同时发出的,也就是说这两个进程会对stdout资源进行互斥访问,这时候就要通过加锁解锁来消除这个互斥。
思考题:
4、进程间通过信号机制实现软中断通信
(1)为了得到实验内容要求的结果,需要用到哪些系统调用函数来实现及进程间的通信控制和同步?
为了得到实验内容要求的结果,需要用到的系统函数调用有signal()、fork()、kill()、wait()、exit()和lockf()来实现进程间的通信控制和同步。
(2)kill( )和signal( )函数在信号通信中的作用是什么?如果分别注释掉它们,结果会如何?
kill()函数负责杀死另外的函数,在这道题中,kill函数负责给子进程1或子进程2发送软中断信号。
signal()函数预置对信号的处理方式,在这里负责处理收到的软中断信号。
如果注释掉kill()函数,如下图,可以看到即使发出了中断信号,父进程也不会杀死子进程,因为这时候子进程1或子进程2无法收到软中断信号,子进程没有结束,则父进程就会一直处于等待状态,这样程序就会处于僵死状态。
如果注释掉signal()函数,如下图,可以看到在给出中断信号后,程序没有输出便直接终止了。因为这时候子进程和父进程都提前中断了,都被中断信号杀死。
实验结果:
首先先理解什么是管道,所谓管道,是一种最基本的IPC机制,是指能够连接一个读进程的、并允许它们以生产者-消费者方式进行通信的一个共享文件,又称为pipe文件。由写进程的写入端(句柄1)将数据写入管道,而读进程则从管道的读出端(句柄0)读出数据。
可以看到,程序运行5秒后输出Child 1 is sending a message!,再经过5秒,程序输出Child 2 is sending a message!。
思考题:
5、进程的管道通信
(1)父进程如何从管道中先读出P1,后读出P2两个子进程的信息的?程序中用到的系统调用函数是什么?起什么作用?
子进程1和子进程2之间利用加锁解锁的方式来实现互斥,同时父进程利用wait()函数来实现其与子进程间的同步,在子进程被调用之前父进程一直处于睡眠状态。而在两个子进程之中,子进程1抢到stdout资源后立刻对该资源进行加锁,此时子进程2不能抢夺该资源,必须等到子进程1执行完毕释放资源子进程2才能抢夺该资源进行输出,由此实现了父进程先读出P1后读出P2。程序中用到的系统调用函数有:
pipe():作用为建立一条无名管道。
read():调用格式read(fd,buf,nbyte),其功能为从fd所指示的文件中读出nbyte字节的数据,并将它们送至由指针buf所指示的缓冲区。
write():调用格式write(fd,buf,nbyte),其功能为把nbyte个字节的数据从buf所指示的缓冲区写到由fd所指向的文件中。
lockf():其功能为给进程加锁和解锁。
wait():其功能为用于父进程对子进程的等待,用以实现同步功能。
(2)子进程P1和P2为什么也能对管道进行操作?
pipe()函数的作用为建立一无名管道,实验中所用到的无名管道实际上是一个没有路径的临时文件,进程通过该文件的文件描述符来识别它,而子进程会继承父进程的环境和上下文的大部分内容,包括文件描述符,从而子进程也能对父进程中创建的管道进行操作。
实验结果:
client:
server:
这里我打开了两个终端来分别运行客户端程序和服务端程序,先运行服务端程序用于准备接收消息,再运行客户端向服务端发送信息。在这里理想情况下应该是每当client发送一条消息之后server接收一条消息,然后client再继续发送下一条信息。但是实际中很难观察到这一现象,因为当我执行服务端,再执行客户端之后两者迅速就完成了全部消息的发送与接收。因此我在客户端程序上做了一些改动,每次发送完一次消息之后睡眠1秒,最终可以看到客户端发送一条消息服务端接收后经过一秒客户端再继续发送。修改的程序见源代码。
思考题:
(1)为了便于操作和观察结果,需要编制几个程序分别用于消息的发送与接收?
需要编制两个程序分别用于消息的发送与接收,一个是服务端,一个是客户端。
(2)这些程序如何进行编辑、编译和执行?为什么?
首先运行client.c和server.c程序分别编译出client.out和server.out;然后先执行server.out再执行client.out。因为这两个是不同的程序,需要分开运行编译,客户端用于发送请求,服务端用于接收信息。
(3)如何实现消息的发送与接收的同步?
先运行服务端再运行客户端以保证消息的的发送与接收的同步。
7.
实验结果:
可以看出Client和Server是交替进行发送和接收的。有人说client发送一次数据后,server要等0.1秒才有响应,同样,client又要等0.1秒才能发送下一条信息。虽然我看不出有这样的现象,毕竟0.1秒很难观察得出来。但我认为出现上述应答延迟的现象是程序设计的问题。当client端发送了数据后,并没有任何措施通知server端数据已经发出,需要由client的查询才能感知。此时,client端并没有放弃系统的控制权,仍然占用CPU的时间片。只有当系统进行调度时,切换到了server进程,再进行应答。这个问题,也同样存在于server端到client的应答过程中。
思考题:
(1)为了便于操作和观察结果,需要如何合理设计程序来实现子进程间的共享存储区通信?
可以用一个程序作为父进程创建两个子进程,子进程1用于client端,子进程2用于server端。在服务端先创建一段共享内存,然后客户端往共享内存写入东西。这样就可以确保在写入之前有共享分区,在每次读写之前先判断内存里面是否为空。这样就可以用一个程序就能观察到消息的发送与接收。
(2)比较消息通信和共享存储区通信这两种进程通信机制的性能和优缺点。
在传输少量数据时,消息通信会比共享存储区通信快,但是当进行大量的数据传输时,因共享区的数据传输受到了系统硬件的支持,不会消耗多余的资源;而进行消息传递,因为需要由软件进行控制和实现,需要消耗一定的CPU资源,因此共享区会更加适合于频繁和大量的数据传输。而在同步方面,因消息的传递机制本身就带有同步的控制,当等到消息的时候,进程进入睡眠状态,不会再消耗CPU资源;而共享区如果不借助于其他的机制进行同步,接受数据一方必须不断的进行查询,会浪费大量CPU资源。