操作系统实验2:进程管理与进程通信
预习Linux进程间通讯的主要方式有哪些?管道(有名管道和无名管道)、信号量、信号、消息队列、共享内存、套接字等。用PV操作实现进程间的互斥与同步的步骤?1、确定进程间的关系2、设置信号量,说明含义、初值3、用伪代码描述在这里插入代码片什么是管道?如何定义管道?如何读写管道?*管道是能够连接一个写进程和一个读进程,并允许它们以生产者-消费者方式进行通信的一个共享文件,又称pipe文件。定义管道:p
预习
- Linux进程间通讯的主要方式有哪些?
管道(有名管道和无名管道)、信号量、信号、消息队列、共享内存、套接字等。 - 用PV操作实现进程间的互斥与同步的步骤?
1、确定进程间的关系
2、设置信号量,说明含义、初值
3、用伪代码描述在这里插入代码片 - 什么是管道?如何定义管道?如何读写管道?
*管道是能够连接一个写进程和一个读进程,并允许它们以生产者-消费者方式进行通信的一个共享文件,又称pipe文件。
定义管道:
pipe(fd);
intfd[2];
访问管道:
read(fd[0],buf,size);
实验原理
1.进程的同步与互斥
进程的同步:所谓同步是指完成同一任务的进程间,由于需要在某些位置上协调它们的工作而相互等待,相互交换信息所产生的制约。
进程的互斥:进程之间因相互竞争(互斥、死锁、饥饿)使用独占型资源而产生的制约。
2.临界区问题(可以解决并发环境中不可再现性、不确定性——控制访问共享变量的代码)
一次仅允许一个进程使用的资源称为临界资源,而各进程必须互斥执行的那种程序段称为临界区。对临界区的调度原则为:不能假设各并发进程的相对执行速度,当没有进程在临界区时,允许一进程立即进入;若已经存在,其它需进入的进程必须等待,且进入临界区的必须在有限的时间内得到满足。
3.互斥的加锁实现与信号量和P、V操作
用锁操作原语实现互斥
信号量这一特殊变量,且其只能被P、V操作使用。P、V操作都是原语,所谓原语是指由若干条机器指令构成的一段程序,用以完成特定功能。原语在执行期间是不可分割的,执行中也不允许中断。
P、V操作定义如下:
P(S):
S:=S-l
若S≥0,则该进程继续执行;否则,该迸程进入S信号量的队列中等待,直到有其他进程在S上执行V操作为止。
V(S):
S:=S+l
若S>0,则进程继续执行;否则,释放S信号量队列上的一个等待进程,使之进入就绪队列,然后继续执行。
每执行一次P操作意味着要求分配一个资源,每执行一次V操作意味着要释放一个资源。
4.用P、V操作实现进程之间的互斥和同步
经典的同步互斥问题:读者、写者;生产者、消费者(有限缓冲、无限缓冲、环形缓冲);哲学家吃通心面等问题
实现互斥,令S初值为1,进程A、B竞争进入临界区的程序可以写成:
进程A 进程B
P(S); P(S);
临界区 临界区
V(S); V(S);
实现同步,设两个信号量并赋值为0,full表示缓冲区中装满信息;avail表示空信息。
进程A 进程B
P(full); P(avail);
把信息从缓冲区取走 把信息送人缓冲区
V(avail); V(full);
5.进程通信
进程之间的信息交换称为进程通讯:消息及共享存储区等等。
实验内容
- 消息的创建,发送和接收
<任务>
使用系统调用msgget( ), megsnd( ), msgrev( )及msgctl()编制一长度为1K的消息发送和接收的程序 。
<程序设计>
(1)为了便于操作和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和CLIENT,进行通信。
(2)SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。
(3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,即是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。
(4)父进程在 SERVER和 CLIENT均退出后结束。 - 共享存储区的创建,附接和断接
<任务>
使用系统调用shmget(),sgmat(),smgdt(),shmctl()编制一个与上述功能相同的程序.
<程序设计>
(1)为了便于操作 和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和 CLIENT,进行通信。
(2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1.作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER每接收到一次数据后显示”(server)received”.
(3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送 请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT退出. CLIENT每发送一次数据后显示”(client)sent”.
(4)父进程在SERVER和CLIENT均退出后结束.
3.同步互斥
(1)互斥
互斥锁用来保证同一时间内只有一个线程在执行某段代码(临界区)。多线程编程最容易出问题的地方,就是临界区的界定和访问控制。下面是一个生产者,消费者的简单例子。生产者、消费者公用一个缓冲区,这里假定缓冲区只能存放一条消息。
(2)编写经典的“生产者-消费者”问题的实验,进一步熟悉Linux中的多线程编程,并且掌握用信号量处理线程间的同步和互斥问题。
有一个有限缓冲区(这里用有名管道实现FIFO式缓冲区)和两个线程:生产者和消费者,它们不停地把产品放入缓冲区和从缓冲区拿走产品。一个生产者在缓冲区满的时候必须等待,一个消费者在缓冲区空的时候也必须等待。另外,因为缓冲区是临界资源,所以生产者和消费者之间必须互斥执行。它们之间的关系如下图所示
这里要求使用有名管道来模拟有限缓冲区,并且使用信号量来解决“生产者—消费者”问题中的同步和互斥问题。
信号量的考虑。这里使用3个信号量,其中两个信号量avail和full分别用于解决生产者和消费者之间的同步问题,mutex用于解决这两个线程之间的互斥问题。其中,avail表示有界缓冲区中的空单元数,初始值为N;full表示有界缓冲区中的非空单元数,初始值为0;mutex是互斥信号量,初始值为1。流程图如下:
本实验采用的有界缓冲区拥有3个单元,每个单元为5字节。为了尽量体现每个信号量的意义,在程序中生产过程和消费过程是随机(采取0~5s的随机时间间隔)进行的,而且生产者的速度比比消费者的速度平均快两倍左右(这种关系可以相反)。生产者一次生产一个单元的产品(放入“hello”字符串),消费者一次消费一个单元的产品。
实验步骤
- 消息的创建,发送和接收
<任务>
使用系统调用msgget( ), megsnd( ), msgrev( )及msgctl()编制一长度为1K的消息发送和接收的程序 。
<程序设计>
(1)为了便于操作和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和CLIENT,进行通信。
(2)SERVER端建立一个Key为75的消息队列,等待其他进程发来的消息。当遇到类型为1的消息,则作为结束信号,取消该队列,并退出SERVER 。SERVER每接收到一个消息后显示一句“(server)received”。
(3)CLIENT端使用Key为75的消息队列,先后发送类型从10到1的消息,然后退出。最后的一个消息,即是 SERVER端需要的结束信号。CLIENT每发送一条消息后显示一句“(client)sent”。
(4)父进程在 SERVER和 CLIENT均退出后结束。
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <string.h>
#define MSG_SIZE 1024
struct msgt
{
long msgtype;
char msgtext[MSG_SIZE];
};
int main()
{
pid_t pid_A;
pid_t pid_B; /*父进程中创建两个子进程*/
key_t key;
int msgid;
int msg_type;
char str[256];
struct msgt send_msg;
struct msgt rcv_msg;
char i = 10;
/*1.新建键值*/
key = ftok("/home", 4);
/*2.创建消息队列*/
msgid = msgget(key, IPC_CREAT);
/*2.创建子进程A*/
pid_A = fork();
if( pid_A > 0 )
{
/*父进程操作*/
pid_B = fork();
if( pid_B > 0 )
{
/*父进程操作*/
/*等待A进程退出*/
wait(NULL);
/*等待B进程退出*/
wait(NULL);
}else if( pid_B == 0 ){
/*子进程B操作,CLIENT*/
for( i=10; i > 0; i-- )
{
printf("(client)sent! msg_type is:%d\n",i);
/*填充消息数据*/
send_msg.msgtype = i;
strcpy( send_msg.msgtext, "(server)received!" );
sleep(1);
/*发送消息*/
msgsnd( msgid, &send_msg, sizeof(struct msgt), 0);
}
exit(0);
}
}else if( pid_A == 0 ){
/*子进程A操作,SERVER*/
while(1){
/*接收消息队列*/
msgrcv( msgid, &rcv_msg, sizeof(struct msgt), 0, 0);
/*打印消息队列数据*/
printf("Message text:%s,msg_type is:%d\n", rcv_msg.msgtext ,rcv_msg.msgtype);
if( rcv_msg.msgtype == 1 ){
printf("rcv_msgtype is 1.\n");
break;
}
}
exit(0);
}
printf("over!\n");
/*删除消息队列*/
msgctl( msgid, IPC_RMID, 0);
exit(0);
}
- 共享存储区的创建,附接和断接
<任务>
使用系统调用shmget(),sgmat(),smgdt(),shmctl()编制一个与上述功能相同的程序.
<程序设计>
(1)为了便于操作 和观察结果,用一个程序为“引子”,先后fork( )两个子进程,SERVER和 CLIENT,进行通信。
(2)SERVER端建立一个KEY为75的共享区,并将第一个字节置为-1.作为数据空的标志.等待其他进程发来的消息.当该字节的值发生变化时,表示收到了该消息,进行处理.然后再次把它的值设为-1.如果遇到的值为0,则视为结束信号,取消该队列,并退出SERVER.SERVER每接收到一次数据后显示”(server)received”.
(3)CLIENT端建立一个为75的共享区,当共享取得第一个字节为-1时, Server端空闲,可发送 请求. CLIENT 随即填入9到0.期间等待Server端再次空闲.进行完这些操作后, CLIENT退出. CLIENT每发送一次数据后显示”(client)sent”.
(4)父进程在SERVER和CLIENT均退出后结束.
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
#include <sys/ipc.h>
#include <string.h>
#include <sys/shm.h>
#include <stdlib.h> /*EXIT_FAILURE*/
#define TEXT_SIZE 1024
struct shared_use_st
{
int written_by_you;
char some_text[TEXT_SIZE];
};
int main()
{
pid_t pid_A;
pid_t pid_B;
key_t key;
int shmid;
struct shared_use_st *shared_server_stuff;
struct shared_use_st *shared_client_stuff;
int running = 1; /*1时,循环正常运行*/
char i;
key = ftok("/home",1);
/*创建/获取共享内存*/
shmid = shmget(key, sizeof(struct shared_use_st), IPC_CREAT);
if( shmid == -1)
{
printf("Creat share memory fail!\n");
exit(EXIT_FAILURE); /*EXIT_FAILURE*/
}
pid_A = fork();
if( pid_A > 0 ){
/*父进程操作*/
pid_B = fork();
if( pid_B > 0 )
{
/*父进程操作*/
/*等待A进程退出*/
wait(NULL);
/*等待B进程退出*/
wait(NULL);
}else if( pid_B == 0 ){
/*子进程B操作,CLIENT*/
//1.映射共享内存*/
shared_client_stuff = (struct shared_use_st *)shmat(shmid, NULL, 0);
for( i=10; i >= 0; i-- )
{
while( shared_client_stuff->written_by_you != -1 )
{
sleep(1);
printf("wait read process!\n");
}
//3.2将用户输入的字符串放入共享内存/
strncpy(shared_client_stuff->some_text, "(server)received!", TEXT_SIZE);
shared_client_stuff->written_by_you = i;
printf("(client)sent!\n");
}
//分离共享内存*/
shmdt((const void *)shared_client_stuff);
exit(0);
}
}else if( pid_A == 0 ){
/*子进程A操作,SERVER*/
/*映射共享内存*/
shared_server_stuff = (struct shared_use_st *)shmat(shmid, NULL, 0);
shared_server_stuff->written_by_you = -1; /*先运行read,所以首先初始值给0*/
/*循环*/
while( running )
{
if( (shared_server_stuff->written_by_you != 0) && (shared_server_stuff->written_by_you != -1))
{
printf("Server rec: %s\n", shared_server_stuff->some_text);
shared_server_stuff->written_by_you = -1;
}else if(shared_server_stuff->written_by_you == 0){
printf("Server rec: %d,ending...\n",shared_server_stuff->written_by_you);
running = 0;
}
sleep(0.1);
}
/*分离共享内存*/
shmdt((const void *)shared_server_stuff);
exit(0);
}
printf("over!\n");
/*5.删除共享内存*/
shmctl(shmid, IPC_RMID, 0);
exit(0);
}
- 用管道实现进程间的消息通讯
管道在逻辑上被看作管道文件,在物理上则由文件系统的高速缓冲区构成,而很少启动外设。发送进程利用文件系统的系统调用write(fd[1],buf,size),把buf中的长度为size字符的消息送入管道入口fd[1],接收进程则使用系统调用read(fd[0],buf,size)从管道出口fd[0] 读出size字符的消息置入buf 中。这里,管道按FIFO(先进先出)方式传送消息,且只能单向传送消息(图)。
利用UNIX提供的系统调用pipe,可建立一条同步通信管道。其格式为:
pipe(fd)
int fd[2];
这里,fd[1] 为写入端,fd[0]为读出端。
1)、简单管道通讯问题
用C语言编写一个程序,建立一个pipe,同时父进程生成一个子进程,子进程向pipe中写入一字符串,父进程从pipe中读出该字符串。
#include(stdio.h)
main()
{
int x,fd[2];
char buf[30],s[30];
pipe(fd);/*创建管道*/
x=fork();
//while((x=fork())=-1);/*创建子进程失败时,循环*/
if(x>0)//父进程
{
printf("This is father process\n");
read(fd[0],s,30);//父进程读取管道中字符
wait(NULL);
printf("%s\n",s);
close(fd[0]);
}
else
{
printf("Now in child process\n");
sprintf(buf,"This is an exanplen");
write(fd[1],buf,30);//把buf中字符写入管道
close(fd[1]);
exit(0);
}
exit(0)
2)、实现多进程的管理通信。
使用系统调用pipe()建立一条管道线;两个子进程P1和P2分别向管道中写一句话:
Child 1 is sending a message!
Child 2 is sending a message!
而父进程则从管道中读出来自于两个子进程的信息,显示在屏幕上。
要求父进程先接收子进程P1发来的消息,然后再接收子进程P2发来的消息。如何让两个子进程P1和P2 互斥使用管道?(提示,调用lockf()来给管道进行加锁,可以实现进程之间的互斥)
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <stdio.h>
#include <signal.h>
int main()
{
pid_t pid_A;
pid_t pid_B;
int pipefd[2];
char a_buffer[10];
char b_buffer[10];
/*1.创建管道*/
pipe(pipefd);
/*2.创建子进程A*/
pid_A = fork();
if( pid_A > 0 )
{
/*父进程操作*/
pid_B = fork();
if( pid_B > 0 )
{
/*父进程操作*/
printf("This is father process\n");
/*父进程读A数据*/
read(pipefd[0], a_buffer, 8);
/*等待A进程退出*/
wait(NULL);
printf("father read:%s\n",a_buffer);
/*父进程读B数据*/
read(pipefd[0], b_buffer, 8);
/*等待B进程退出*/
wait(NULL);
printf("father read:%s\n",b_buffer);
close(pipefd[0]);
}else{
/*子进程B操作*/
/*锁定写入端*/
lockf(pipefd[1],1,0);
/*写入数据*/
write(pipefd[1], "Hello_B",8);
sleep(5);
lockf(pipefd[1],0,0);
exit(0);
}
}else{
/*锁定写入端*/
lockf(pipefd[1],1,0);
/*写入数据*/
write(pipefd[1], "Hello_A",8);
sleep(5);
lockf(pipefd[1],0,0);
close(pipefd[1]);
exit(0);
}
exit(0);
}
更多推荐
所有评论(0)