操作系统实验3---进程间通信(更完)

[实验内容] 1 消息的创建,发送和接收

使用系统调用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

#include

#include

1. msgget( )

创建一个消息,获得一个消息的描述符。

系统调用格式:int msgqid=msgget(key,flag)

参数定义

key_t  key;

int  flag;

key是用户指定的消息队列的名字;

flag是用户设置的标志和访问方式。如

    IPC_CREAT |0400     是否该队列已被创建。无则创建,是则打开;

    IPC_EXCL |0400        是否该队列的创建应是互斥的。

msgqid 是该系统调用返回的描述符,失败则返回-1。

2. msgsnd()

发送一消息。向指定的消息队列发送一个消息,并将该消息链接到该消息队列的尾部。

系统调用格式:int msgsnd(msgqid,msgp,size,flag)

参数定义:

int msgqid,size,flag;

struct msgbuf  * msgp;

其中msgqid是返回消息队列的描述符;msgp是指向用户消息缓冲区的一个结构体指针,包括消息类型和消息正文,即

{

    long  mtype;             /*消息类型*/

    char  mtext[ ];           /*消息的文本*/

 }

size指示由msgp指向的字符数组的长度;即消息的长度。这个数组的最大值由MSG-MAX( )系统可调用参数来确定。

flag 规定当核心用尽内部缓冲空间时应执行的动作: 进程是等待,还是立即返回。若在标志flag中未设置IPC_NOWAIT位,则当该消息队列中的字节数超过最大值时,或系统范围的消息数超过某一最大值时,调用msgsnd进程睡眠。若是设置IPC_NOWAIT,则在此情况下,msgsnd立即返回。

对于msgsnd( ),核心须完成以下工作:

(1)对消息队列的描述符和许可权及消息长度等进行检查。若合法才继续执行,否则返回;

(2)核心为消息分配消息数据区。将用户消息缓冲区中的消息正文,拷贝到消息数据区;

(3)分配消息首部,并将它链入消息队列的末尾。在消息首部中须填写消息类型、消息大小和指向消息数据区的指针等数据;

(4)修改消息队列头中的数据,如队列中的消息数、字节总数等。最后,唤醒等待消息的进程。

3. msgrcv( )

接受一消息。从指定的消息队列中接收指定类型的消息。

系统调用格式:int  msgrcv(msgqid,msgp,size,type,flag)

参数定义:

int  msgqid,size,flag;

struct  msgbuf  *msgp;

long  type;

其中,msgqid,msgp,size,flag与msgsnd中的对应参数相似,type是规定要读的消息类型,

flag规定倘若该队列无消息,核心应做的操作。如此时设置了IPC_NOWAIT标志,则立即返回,若在flag中设置了MS_NOERROR,且所接收的消息大于size,则核心截断所接收的消息。

对于msgrcv系统调用,核心须完成下述工作:

(1)对消息队列的描述符和许可权等进行检查。若合法,就往下执行;否则返回;

(2)根据type的不同分成三种情况处理:

type=0,接收该队列的第一个消息,并将它返回给调用者;

type为正整数,接收类型type的第一个消息;

type为负整数,接收小于等于type绝对值的最低类型的第一个消息。

(3)当所返回消息大小等于或小于用户的请求时,核心便将消息正文拷贝到用户区,并从消息队列中删除此消息,然后唤醒睡眠的发送进程。但如果消息长度比用户要求的大时,则做出错返回。

4. msgctl( )

消息队列的操纵。读取消息队列的状态信息并进行修改,如查询消息队列描述符、修改它的许可权及删除该队列等。

系统调用格式: int msgctl(msgqid,cmd,buf);

参数定义:

 int  msgqid,cmd;

struct  msgqid_ds  *buf;

其中,函数调用成功时返回0,不成功则返回-1。buf是用户缓冲区地址,供用户存放控制参数和查询结果;cmd是规定的命令。命令可分三类:

(1)IPC_STAT。查询命令。如查询队列中的消息数目、队列中的最大字节数、最后一个发送消息的进程标识符、发送时间等;

(2)IPC_SET。按buf指向的结构中的值,设置和改变有关消息队列属性的命令。如改变消息队列的用户标识符、消息队列的许可权等;

(3)IPC_RMID。消除消息队列的标识符。

原文链接:https://blog.csdn.net/gnosed/article/details/80664052

#include  
#include 
#include 
#include 
#include 
#include 
#include 
#define MSGKEY 75          /*定义关键词MEGKEY*/
struct msgform                /*消息结构*/
{
    long mtype;
    char mtexe[1030];         /*文本长度*/
}msg;
int msgqid,i;

void CLIENT()
{
    int i;
    msgqid=msgget(MSGKEY,0777);  //创建消息队列
    for(i=10;i>=1;i--)
    {
         msg.mtype=i;
         printf("(client)sent\n");
         msgsnd(msgqid,&msg,1024,0);       //发送消息msg入msgid消息队列
    }
    exit(0);
}

void SERVER()
{ 
  msgqid=msgget(MSGKEY,0777|IPC_CREAT); //创建一个所有用户都可以读、写、执行的队列,因为读操作的数字代号是4,写是2,执行是1,7=4+2+1,四位数字分别代表特殊权限位,拥有者位,同组用内户位,其余用户位
  do
  {
      msgrcv(msgqid,&msg,1030,0,0);  //从队列msgid接受消息msg
      printf("(server)receive\n");
  }while(msg.mtype!=1);             //消息类型为1时,释放队列
      msgctl(msgqid, IPC_RMID,0);   //消除消息队列的标识符
      exit(0);
}

int main()
{
  if(fork())      //父进程
	  SERVER();
    else          //子进程
	  CLIENT();
    wait(0);
    wait(0);
}

操作系统实验3---进程间通信(更完)_第1张图片

操作系统实验3---进程间通信(更完)_第2张图片

<结果>

从理想的结果来说,应当是每当Client发送一个消息后,server接收该消息,Client再发送下一条。也就是说“(Client)sent”和“(server)received”的字样应该在屏幕上交替出现。实际的结果大多是,先由 Client 发送两条消息,然后Server接收一条消息。此后Client Server交替发送和接收消息.最后一次接收两条消息. Client 和Server 分别发送和接收了10条消息,与预期设想一致

<分析>

message的传送和控制并不保证完全同步,当一个程序不再激活状态的时候,它完全可能继续睡眠,造成上面现象,在多次send message 后才 receive message.这一点有助于理解消息转送的实现机理.

2.共享存储区的创建,附接和断接

<任务>

使用系统调用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
#include
#include
#include
#include
#include
#include
#include 
#define SHMKEY  75                   /*定义共享区关键词*/
int shmid,i;
int *addr;

void CLIENT()
{    
    int i;    
    shmid=shmget(SHMKEY,1024,0777);    /*获取共享区,长度1024,关键词SHMKEY*/    
    addr=shmat(shmid,0,0);                /*共享区起始地址为addr*/    
    for(i=9;i>=0;i--)     
    {        
        while(*addr!= -1);                          
        printf("(client)sent\n");                 /*打印(client)sent*/        
        *addr=i;                             /*把i赋给addr*/    
    }    
    exit(0);
}

void SERVER()
{     
    shmid=shmget(SHMKEY,1024,0777|IPC_CREAT);  /*创建共享区*/    
    addr=shmat(shmid,0,0);                         /*共享区起始地址为addr*/    
    do    
    {    
        *addr=-1;    
        while(*addr==-1);    
        printf("(server)received\n");               /*服务进程使用共享区*/    
    }
    while(*addr);    
    shmctl(shmid,IPC_RMID,0);    
    exit(0);
    }

 int main()
{    
    if(fork())
		SERVER();    
    if(fork())
		CLIENT();    
    wait(0);    
    wait(0);
}

操作系统实验3---进程间通信(更完)_第3张图片

 运行的结果和预想的完全一样。但在运行的过程中,发现每当client发送一次数据后,server要等大约0.1秒才有响应。同样,之后client又需要等待大约0.1秒才发送下一个数据。出现上述的应答延迟的现象是程序设计的问题。当client端发送了数据后,并没有任何措施通知server端数据已经发出,需要由client的查询才能感知。此时,client端并没有放弃系统的控制权,仍然占用CPU的时间片。只有当系统进行调度时,切换到了server进程,再进行应答。这个问题,也同样存在于server端到client的应答过程之中。

3.进程的管道通信 〈任务〉 编制一段程序,实现进程的管道通信。使用系统调用pipe()建立一条管道线。两个子进程p1和p2分别向通道个写一句话: child1 process is sending message! child2 process is sending message! 而父进程则从管道中读出来自两个进程的信息,显示在屏幕上。

#include 
#include 
#include 
#include 
#include 
int pid1, pid2;

int main()
{
	int fd[2];
	char outpipe[100], inpipe[100];
	pipe(fd);                              //创建一个管道
	while ((pid1 = fork()) == -1);         //fork函数是在当前进程中新建立一个子进程,如果这个创建子进程失败,那么返回-1,这个实际是把创建进程的返回值和百-1比较看看是否创建失败。因为是写在while语句里,那么当创建失败之后,如果在while里面没有break或者跳出,当while执行体执行结束后又会执行(p1 = fork()) == -1,等于不断重复创建子进程一度直到创建成功为止。返回两次是父进程返回的是子进程的ID,子进程返回的是0
	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);   //创建进程2
		if (pid2 == 0)
		{//进入进程2的子进程
			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
		{//进入进程2的父进程
			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);                  //程序并发执行,所以从管道读出哪一个顺序不确定
		}
	}
}

1、程序中的sleep(5)起什么作用?

如果运行结果没有发生变化,可在进程中使用sleep(5)来增加运
行时间来查看结果的变化。

2、子进程1和2为什么也能对管道进行操作?

实验中所用到的无名管道实际上是一个没有路径的临时文件,进程通过该文件的文件描述符来识别它,而子进程会继承父进程的环境和上下文中的大部分内容,包括文件描述符,从而子进程也能对父进程中创建的管道进行操作。

你可能感兴趣的:(操作系统)