系统相关
首页 > 系统相关> > 2021-9-24 Linux操作系统实验二:进程通信

2021-9-24 Linux操作系统实验二:进程通信

作者:互联网

Linux操作系统实验二:进程通信

题目:

【实验目的】

进一步提高 Linux 环境下 C 编程能力,了解和熟悉 Linux 支持的多种IPC 机制。Linux 作为一个多任务多进程的操作系统,各个进程间信息交互不可避免,进程间通信可分为本地进程间通信和远程进程间通信。本地进程间通信主要包括信号,管道,消息队列,信号量,共享内存等通信方式。

【实验预备内容】

(1)阅读 Linux 进程间通信章节的相关参考资料。
(2)重点学习信号量、共享内存机制。

【实验内容】

阅读参考资料,分析示例程序代码,用 C 语言编程实现以下要求。

学号:021900208 姓名:高旭 专业:计算机科学与技术 班级:02

一、实验环境:

Oracle VM VirtualBox、Ubuntu(64-bit)

二、实验内容:

代码部分:

my.h

// 本次实验实现了3种进程通信,1.命名管道FIFO 2.消息队列 3.共享内存&信号量

// my.h 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/msg.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <sys/shm.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <error.h>
#include <errno.h>
#include <unistd.h>
#include <linux/stat.h>

#define BUFES 80  

// 消息的组成
struct msgbuf  
{
	long int my_type;  // 消息的类型域
	char text[BUFES];  // 消息传递的数据域
};

union semun
{
    int val;
    struct semid_ds *buf;
    unsigned short *array;
    struct seminfo *buf_info;
    void *pad;
};

mode_t mode = 00666;  // IPC设置为默认模式
char pathname1[] = "/tmp/myfifo";  // FIFO路径
char pathname2[] = "/tmp/my_shm";  // 共享内存路径
char pathname3[] = "/tmp/my_sem";  // 信号量路径
char proj_id = 'a';  // 用于生成key

// p操作, 获取信号量
int sem_p(int semid, int index)
{
    struct sembuf sbuf = {0, -1, IPC_NOWAIT};  // 每个sembuf结构描述了一个对信号量的操作
    if(index < 0)
    {
        perror("index指定的信号量不存在!\n");
        return -1;
    }
    sbuf.sem_num = index;
    if(semop(semid, &sbuf, 1) == -1)
    {
        perror("在对信号量进行 p 操作时出现了一个错误!\n");
        return -1;
    }
    return 0;
}

// V操作, 释放信号量
int sem_v(int semid, int index)
{
    struct sembuf sbuf = {0, 1, IPC_NOWAIT};  // 每个sembuf结构描述了一个对信号量的操作
    if(index < 0)
    {
        perror("index指定的信号量不存在!\n");
        return -1;
    }
    sbuf.sem_num = index;
    if(semop(semid, &sbuf, 1) == -1)
    {
        perror("在对信号量进行 v 操作时出现了一个错误!\n");
        return -1;
    }
    return 0;
}

// 等待信号量
int wait_sem(int semid, int index)
{
    while(semctl(semid, index, GETVAL, 0) == 0) ;
    return 1;
}

客户端程序代码myclient.c:

// myclient.c

#include "my.h"

int running = 1;

void  stop()
{
	running = 0;
}

int main(int argc, char *argv[])
{
    FILE *fp;  //命名管道文件指针
    const int flag1 = IPC_CREAT | IPC_EXCL | mode;  // 创建对应IPC,若已存在则返回-1
    const int flag2 = IPC_CREAT | mode;  // 打开对应IPC,若不存在则创建
    struct msgbuf msg_sbuf;  // 消息队列所传递的消息
    struct msqid_ds msg_info;  // 用来设置或返回消息队列信息
    union semun arg;  // 函数semid的最后一个参数
    key_t semkey, shmkey;  // 信号量、共享内存键值
    int semid, shmid,msgid;  // 信号量id,共享内存id,消息队列的ID
    int sem_members=4;  // 信号量数量为4,0号信号为是否有人占用client共享内存,1号信号为是否有人占用server共享内存,2号信号为client共享内存是否可读,3号信号为server共享内存是否可读
    int reval;  // 接受消息队列操纵函数msgctl的返回值
    char writebuf[BUFES];  // 命名管道的写缓冲区  
    char write_str[BUFES];  // 共享内存的缓冲区
    char *shmaddr;  // 存放共享内存接口
    signal(SIGINT, stop);  // 注册SIGINT信号
    while (running)
    {
        if((fp=fopen(pathname1, "w")) == NULL)
	    {
		    printf("打开命名管道失败 \n");
		    exit(1);
	    }
        printf("请输入: ");
        fgets(writebuf, BUFES, stdin);
        if(strncmp(writebuf, "end", 3) == 0)
        {
            printf("接受到结束信号,命名管道通信结束 \n");
            running = 0;
        }
        if(fputs(writebuf, fp) == EOF)
		{
			printf("写入命名管道失败 \n");
			exit(1);
		}
        fclose(fp);
    }
    running = 1;
    while(running);
    running = 1;
    printf("接受到SIGINT信号,接下来开始进行消息队列通信 \n");   
    printf("-------------------------------------------------------------------------\n");
    if((msgid=msgget((key_t)1111, flag2)) == -1)  // 打开key值为1234的消息队列,如不存在则创建之
    {
        printf("创建/打开消息队列失败! \n");
        exit(1);    
    }
    else printf("消息队列创建成功 \n");
    while (running)
    {
		printf("请输入: ");
		fgets(msg_sbuf.text, BUFES, stdin);  // 读入键盘输入的消息
		msg_sbuf.my_type = 1;	
		if(msgsnd(msgid, (void *) &msg_sbuf, BUFES, 0) == -1)  // 发送消息
		{
			printf("发送信息失败!\n");
			exit(1);
		}
		if(strncmp(msg_sbuf.text, "end", 3) == 0)  // 输入end表示程序结束
		{
            printf("接受到结束信号,消息队列通信结束 \n");
            running = 0;
        }
    }
    if((reval=msgctl(msgid, IPC_STAT, &msg_info)) == -1)
    {
        printf("获取消息队列信息失败! \n");
        exit(1);
    }
    printf("消息队列信息: \n");
    printf("消息队列的容量: %ld \n", msg_info.msg_qbytes);
    printf("最近一个执行 msgsnd 函数的进程ID是:%d \n", msg_info.msg_lspid);
    printf("最近一个执行 msgrcv 函数的进程ID是:%d \n", msg_info.msg_lrpid);
	if(msgctl(msgid, IPC_RMID, 0)==-1)  //  删除消息队列
	{
		printf("删除消息队列失败! \n");
		exit(1);
	}
    else printf("删除消息队列成功\n");
    printf("接下来开始共享内存&信号量通信 \n"); 
    printf("-------------------------------------------------------------------------\n");
    if((shmkey = ftok(pathname2, proj_id)) == -1)
    {
        perror("返回共享内存键值失败!\n");
        exit(1);
    }
    else printf("获取共享内存键值成功!\n");
    if((shmid = shmget(shmkey, BUFES, flag2)) == -1){
        perror("创建/打开共享内存失败!\n");
        exit(1);
    }
    else printf("打开共享内存成功!\n");
    if((shmaddr=shmat(shmid, (char*)0, 0)) == (char *)-1)
    {
        perror("附加共享内存失败!\n");
        exit(1);
    }
    else printf("附加共享内存成功!\n");
    if((semkey = ftok(pathname3, proj_id)) == -1)
    {
        perror("返回信号量键值失败!\n");
        exit(1);
    }
    else printf("获取信号量键值成功!\n");
    if((semid = semget(semkey, sem_members, flag2)) == -1)  // 打开键值为semkey的信号量集,如不存在则创建之,返回信号量集标识符。members为信号量集中含信号量的数目。
    {  
        perror("打开信号量失败!\n");
        exit(1);
    }
    else printf("打开信号量成功!\n");
    arg.val = 1;
    for(int index = 0; index < sem_members; index++)
    {
        semctl(semid, index, SETVAL, arg);
        if(index==0) arg.val = 0;
    }
    printf("初始化信号量成功!\n");
    while(1)
    {
        wait_sem(semid, 0);  // 等待client共享内存可写信号量可以被获取
        sem_p(semid, 0);  // 获取client可写信号量
        printf("发送:");
        fgets(write_str, BUFES, stdin);
        int len = strlen(write_str);
        write_str[len] = '\0';
        strcpy(shmaddr, write_str);
        sem_v(semid, 1);  // 向server传递共享内存可读信号量
        if(strncmp(write_str, "end", 3) == 0)  // 输入end表示程序结束
		{
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
        wait_sem(semid, 3);  // 等待client共享内存可读信号量可以被获取
        sem_p(semid, 3);  // 获取client共享内存可读信号量
        printf("接收:%s", shmaddr);
        sem_v(semid, 0);  // 向client传递共享内存可写信号量
        if(strncmp(shmaddr, "end", 3) == 0)  // 输入end表示程序结束
		{
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
    }
    sleep(3);
    if((semctl(semid, 0, IPC_RMID)) == 0)  printf("删除信号量成功 \n");
    else printf("删除信号量失败");
    if((shmctl(shmid, IPC_RMID, NULL)) == 0) printf("删除共享内存成功 \n");
    else printf("删除共享内存失败");
    return 0;
}

服务器程序代码myserver.c

// myserver.c 

#include "my.h"

int running = 1;

void  stop()
{
	running = 0;
}

int main(int argc, char *argv[])
{       
    FILE *fp;  //命名管道文件指针
    const int flag1 = IPC_CREAT | IPC_EXCL | mode;  // 创建对应IPC,若已存在则返回-1
    const int flag2 = IPC_CREAT | mode;  // 打开对应IPC,若不存在则创建
    struct msgbuf msg_rbuf;  // 消息队列所传递的消息
    union semun arg;  // 函数semid的最后一个参数
    key_t semkey, shmkey;  // 信号量、共享内存键值
    long int msg_to_receive = 0;  // 接受消息队列的第一条消息
    int msgid,semid, shmid;  // 信号量id,共享内存id,消息队列的ID
    int sem_members = 4;  // 信号量数量为4,0号信号为是否有人占用client共享内存,1号信号为是否有人占用server共享内存,2号信号为client共享内存是否可读,3号信号为server共享内存是否可读  
	char readbuf[BUFES];  // 命名管道的读缓冲区 
    char write_str[BUFES];  // 共享内存的缓冲区
    char *shmaddr;  // 存放共享内存接口
    signal(SIGINT, stop);  // 注册SIGINT信号
    if((mkfifo(pathname1, mode)) < 0)
    {
        perror("创建命名管道失败!\n");
        exit(1);
    }
    else printf("你成功创建了一个命名管道 \n");
    while(running)
    {
        if((fp = fopen(pathname1, "r")) < 0)
        {
            printf("打开命名管道失败 \n");
		    exit(1);
        }
        if(fgets(readbuf, BUFES, fp) != NULL)
        {
			printf("读取字符串: %s", readbuf);
			fclose(fp);
        }
        else
		{
			if(ferror(fp))
			{
				printf("读取命名管道失败 \n");
				exit(1);
			}
		}
        if(strncmp(readbuf, "end", 3) == 0)
        {
            printf("接受到结束信号,命名管道通信结束 \n");
            running = 0;
        }
    }
    running = 1;
    while(running);
    running = 1;
    printf("接受到SIGINT信号,接下来开始进行消息队列通信 \n"); 
    printf("-------------------------------------------------------------------------\n");
    if((msgid=msgget((key_t) 1111, flag2)) == -1)  // 打开key值为1234的消息队列,如不存在则创建之
    {
        printf("创建/打开消息队列失败! \n");
        exit(1);    
    }
	while(running)
	{
		if(msgrcv(msgid, (void *) &msg_rbuf, BUFES, msg_to_receive, 0) == -1)  //接收消息,最后一个参数为设置默认模式
		{
			printf("获取消息失败!\n");
			exit(1);
		}
		printf("接受到消息 : %s", msg_rbuf.text);
		if(strncmp(msg_rbuf.text, "end", 3) == 0)  // 输入end表示程序结束
		{
            printf("接受到结束信号,消息队列通信结束 \n");
            running = 0;
        }
	}
    printf("接下来开始共享内存&信号量通信 \n"); 
    printf("-------------------------------------------------------------------------\n");
    if((shmkey = ftok(pathname2, proj_id)) == -1)
    {
        perror("返回共享内存键值失败!\n");
        exit(1);
    }
    else printf("获取共享内存键值成功!\n");
    if((shmid = shmget(shmkey, BUFES, flag2)) == -1){
        perror("创建/打开共享内存失败!\n");
        exit(1);
    }
    else printf("打开共享内存成功!\n");
    if((shmaddr=shmat(shmid, (char*)0, 0)) == (char *)-1)
    {
        perror("附加共享内存失败!\n");
        exit(1);
    }
    else printf("附加共享内存成功!\n");
    sleep(2);
    if((semkey = ftok(pathname3, proj_id)) == -1)
    {
        perror("返回信号量键值失败!\n");
        exit(1);
    }
    else printf("获取信号量键值成功!\n");
    if((semid = semget(semkey, sem_members, flag2)) == -1)  // 打开键值为semkey的信号量集,如不存在则创建之,返回信号量集标识符。members为信号量集中含信号量的数目。
    {  
        perror("打开信号量失败!\n");
        exit(1);
    }
    else printf("打开信号量成功!\n");
    while(1)
    {
        wait_sem(semid, 1);  // 等待server共享内存可读信号量可以被获取
        sem_p(semid, 1);  // 获取server共享内存可读信号量
        printf("接收:%s", shmaddr);
        sem_v(semid, 2);  // 向server传递共享内存可写信号量
        if(strncmp(shmaddr, "end", 3) == 0)  // 输入end表示程序结束
		{
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
        wait_sem(semid, 2);  // 等待server共享内存可写信号量可以被获取
        sem_p(semid, 2);  // 获取server共享内存可写信号量
        printf("发送:");
        fgets(write_str, BUFES, stdin);
        int len = strlen(write_str);
        write_str[len] = '\0';
        strcpy(shmaddr, write_str);
        sem_v(semid, 3);  // 向client传递共享内存可读信号量
        if(strncmp(write_str, "end", 3) == 0)  // 输入end表示程序结束
		{
            printf("接受到结束信号,共享内存&信号量通信结束 \n");
            break;
        }
    }
    return 0;
}

实验结果截图:

在这里插入图片描述
在这里插入图片描述

三、实验总结:

1.所编写程序思想:

本次实验编写了:client客户端程序与server服务器程序
主要目的是实现:
①server调用mkfifo创建命名管道
②client通过命名管道单向向server发送信息
③在接收到SIGINT信号后两个进程关闭命名管道进入消息队列通信阶段
④client单向通过消息队列发送信息给server
⑤在结束消息队列通信前调用msgctl函数查看消息队列部分信息
⑥client与server进入共享内存&信号量通信阶段,打开共享内存与信号量,信号量数量为4由client完成初始化操作
⑦调用封装在my.h的函数,实现clinet发送->server接收->server发送->client接收的顺序执行
⑧删除共享内存与信号量,进程结束。

2.本次实验自学哪些知识:

①管道

②命名管道

③ftok函数

④open函数

⑤消息队列

⑥共享内存

⑦信号量

3.本次实验遇到的困难与解决:

①第二次使用mkfifo报错

mkfifo不能存在同名文件,所以在第二次执行时应该删去前一次执行生成的fifo文件。

②flag=IPC_CREAT | IPC_EXCL | mode不能在my.h里定义

解决:放弃在my.h里定义,而是在客户端与服务器程序中都定义。

③删除消息队列与共享内存以及信号量时,另一方没有退出循环

解决:使用了上一次实验的方法,调用sleep函数。

④共享内存与信号量的路径报错

解决:共享内存与信号量的shmget已经semget的参数pathname必须是现在存在的路径,可以创建对应文件名的文件夹。

⑤由于同时实现了3种机制导致本次实验代码行数过多

解决:暂无

4.附上本次实验程序执行流程图:

在这里插入图片描述

标签:24,semid,int,Linux,信号量,队列,2021,printf,共享内存
来源: https://blog.csdn.net/ShakingSH/article/details/120448243