进程间通信

发布于:2025-02-11 ⋅ 阅读:(136) ⋅ 点赞:(0)

目录

一、管道

1、无名管道

1.1 定义

1.2 特点

1.3通信流程

1.4 相关函数

pipe

2、命名管道

2.1 定义

2.2 特点

2.3 通信流程

2.4 相关函数

mkfifo

remove

2.5 综合实例

2.6 补充说明

无名管道与命名管道的区别

文件系统与内存之间的交互

3、标准流管道

3.1 定义

3.2 相关函数

popen

pclose

二、信号

1、信号的本质

2、信号的来源

3、信号的分类

4、信号列表

5、通信流程

6、相关函数

发送信号

kill

alarm

raise

等待接收信号

pause

处理信号

signal

综合实例

三、共享内存

1、定义

2、特性

3、通信流程

4、相关函数

ftok

shmget

 shmat

shmdt

shmctl 

综合实例

四、消息队列

1、特性

2、通信流程

3、相关函数

ftok

msgget

msgsnd

msgrcv

 msgctl

综合实例 

五、信号量

1、定义

2、PV操作

3、通信流程

4、相关函数

semget

semctl

semop


IPC(Inter-Process Communication,进程间通信)是指至少两个进程或线程间传送数据或信号的一些技术或方法。在操作系统中,进程是资源分配的基本单位,每个进程都有自己的一部分独立的系统资源,彼此是隔离的。有时,进程间需要进行数据交换,这时就需要用到IPC机制。LINUX通信方式主要包括:管道、信号、共享内存、消息队列以及信号量

一、管道

1、无名管道

1.1 定义

无名管道只能在具有亲缘关系的进程间(如父子进程或兄弟进程)使用。管道也有两个端口,分别为读端口和写端口。管道的进水端可以理解为写入端口出水端就读取数据的端口

第一种:单进程的读写过程

第二种:父子进程读写过程

         通过上面可以发现,管道里面的数据,只能朝着一个方向传递,一个进程发送数据的时候,另外一个进程只能接收数据。

1.2 特点

只能在亲缘关系的进程间(父子或兄弟)进行通信

半双工(固定的读端口和固定的写端口)

特殊文件,可以通过read/write对管道进行读写,管道只能存在内存中。

无名管道没有文件名,也没有对应的文件节点。

说明:文件节点是文件系统中的一个数据结构,用于存储文件的数据(如权限、所有者、 大小等)以及指向文件数据的指针。对于无名管道来说,它们并不存在于文件系统 中,而是直接在内核的内存中创建的。这意味着它们没有与文件系统相关联的文件 名或文件节点。相反,无名管道是通过文件描述符来访问的,这些文件描述符是由 pipe系统调用返回的,并且只能在创建它们的进程及其子进程(或具有某种亲缘关 系的进程)之间共享。由于无名管道没有文件名和文件节点,因此它们不能通过常 规的文件操作(如open、read、write等,除非是通过文件描述符)来访问。相反, 它们必须使用专门的系统调用(如pipe、fork、exec等)来创建和使用。

管道具有阻塞的特性当缓冲区满时,写操作会阻塞,直到有空间可用;当缓冲区为空时,读操作会阻塞,直到有数据可读。

管道具有队列结构,特性先进先出FIFO(类似:DMA),放入管道的数据被接收,那么管道里面的数据被移除。

1.3通信流程

无名管道操作步骤:

创建管道-->创建父子进程-->利用无名管道进行父进程与子进程之间的数据交换-->摧毁无名管道

1.4 相关函数
pipe

功能:
    用于在进程间创建一个无名管道,并且这个管道是一个单向的、固定大小的缓冲区,在具有亲缘关系(通常指父子进程)的进程间传递数据。
头文件:
    #include <unistd.h>
函数原型:
    int pipe(int pipefd[2]);
函数参数:
    int pipefd[2]:pipe函数创建一个无名管道,将对应管道的fd—是两个端口,放到数组里
    pipefd[0]--->表示的是管道的读端口   pipefd[1]--->表示的是管道的写端口
函数返回值:
    成功 返回0,并且pipefd数组被填充为两个有效的文件描述符
    失败 -1

1:利用一下pipe函数,创建一个无名管道

现象:

代码:

#include <unistd.h>

int main(int argc,char *argv[])
{

	int pipefd[2];
	//创建管道
	int ret = pipe(pipefd);
	if(ret == 0)
	{
		printf("pipefd[0]=%d\r\n",pipefd[0]);
		printf("pipefd[1]=%d\r\n",pipefd[1]);
	}

	return 0;
}

示例2:利用单进程操作无名管道,实现数据写入和读取

现象:

代码:

#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])
{

	int pipefd[2];
	char buff_w[] = "hello";
	char buff_r[8];
	//创建管道
	int ret = pipe(pipefd);
	if(ret == 0)
	{
		printf("pipefd[0]=%d\r\n",pipefd[0]);
		printf("pipefd[1]=%d\r\n",pipefd[1]);
	}
	
	//写入数据
	int res1 = write(pipefd[1],buff_w,sizeof(buff_w));
	printf("res1=%d\r\n",res1);
	
	//读取数据
	int res2 = read(pipefd[0],buff_r,sizeof(buff_r));
	printf("res2=%d\r\n",res2);

    //关闭管道
	close(pipefd[0]);
	close(pipefd[1]);
	
	return 0;
}

示例3:利用父子进程,操作对应的无名管道,实现一次通信

现象:

代码:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc,char argv[])
{
	int ret;
	int pipefd[2];
	//创建管道
	ret = pipe(pipefd);
	if(ret == 0)
	{
		printf("pipe success\r\n");
		printf("pipefd[0]:%d\r\n",pipefd[0]);
		printf("pipefd[1]:%d\r\n",pipefd[1]);
	}
	
	//创建父子进程
	ret=fork();
	if(ret == 0)//子进程
	{
		char buf[10];
		//接收数据
		read(pipefd[0],buf,sizeof(buf));
		printf("buf:%s\r\n",buf);
		
	}
	else//父进程
	{	
		char str[10];
		
		scanf("%[^\n]",str);
		getchar();
		//发送数据
		write(pipefd[1],str,sizeof(str));
		printf("send success\r\n");
	}
	return 0;
}

示例4:利用父子进程,操作无名管道实现,交替通信 

现象:

代码:

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string.h>
int main(int argc,char argv[])
{
	int ret;
	int pipefd[2];
	char str[10];
	char buf[10];
	//创建管道
	ret = pipe(pipefd);
	if(ret == 0)
	{
		printf("pipe success\r\n");
		printf("pipefd[0]:%d\r\n",pipefd[0]);
		printf("pipefd[1]:%d\r\n",pipefd[1]);
	}
	
	//创建父子进程
	ret=fork();
	if(ret == 0)//子进程
	{
		
		while(1)
		{
			//接收数据
			read(pipefd[0],buf,sizeof(buf));
			printf("child_buf:%s\r\n",buf);
			
			//获取需要发送的数据
			scanf("%[^\n]",str);
			getchar();
			//发送数据
			write(pipefd[1],str,sizeof(str));
			printf("child send success\r\n");
			
			//阻塞
			sleep(1);
		}
		
	}
	else//父进程
	{	
		
		while(1)
		{
			//获取需要发送的数据
			scanf("%[^\n]",str);
			getchar();
			//发送数据
			write(pipefd[1],str,sizeof(str));
			printf("parent send success\r\n");
			
			//阻塞
			sleep(1);
			
			//接收数据
			read(pipefd[0],buf,sizeof(buf));
			printf("parent_buf:%s\r\n",buf);
		}
	}
	return 0;
}

2、命名管道

2.1 定义

        命名管道,也被称为FIFO,是一种特殊的文件类型,它允许在文件系统中通过路径名访问,从而实现非亲缘进程之间的通信。命名管道在文件系统中有一个对应的路径名,因此不同进程可以通过这个路径名来访问同一个管道。它提供了单向的数据流,即数据只能在一个方向上流动。如果需要双向通信,则需要在两端各建立一个命名管道。

2.2 特点

适用于任意两个进程之间的通信。

它是特殊文件(.fifo),可以使用read\write函数进行读写,并只能在内存中运行。

命名管道具有文件名和文件节点。--其他进程可以使用管道的名字进程操作

管道具有阻塞的特性当缓冲区满时,写操作会阻塞,直到有空间可用;当缓冲区为空时,读操作会阻塞,直到有数据可读。

管道具有队列结构,先进先出功能。

 打开命名管道,以O_RDONLY打开的方式,默认情况是读端口。

     打开命名管道,以O_WRONLY打开的方式,默认情况是写端口。

     一般情况下,打开命名管道,以O_RDWR方式打开-->自适应读端口或写端口

2.3 通信流程

命名管道操作步骤:

创建管道(获得对应的fd)-->打开管道文件-->利用命名管道进行父进程与子进程之间的数据交换-->摧毁命名管道

2.4 相关函数
mkfifo

函数功能:
    用于在进程间创建一个管道文件,允许无亲缘关系进程间通信。
头文件:
    #include <sys/types.h>
    #include <sys/stat.h>
函数原型:
    int mkfifo(const char *pathname,mode_t mode);
函数参数:
    const char *pathname:文件的路径名
    mode_t mode:期望创建管道文件的权限
函数返回值:
    成功 返回0
    失败 -1


示例1:利用mkfifo函数创建命名管道

现象:

代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
int main(int argc,char *argv[])
{
    //创建管道
	int ret = mkfifo("c.fifo",0666);
	if(ret == 0)
	{
		printf("pipe success\r\n");
	}
    
    return 0;
}

示例2:利用命名管道实现单进程的通信过程

现象:

代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
	int fd;
	char buf[10];
	int ret = mkfifo("c.fifo",0666);
	if(ret == 0)
	{
		printf("pipe success\r\n");
	}
	
	//打开管道文件
	fd = open("c.fifo",O_RDWR);
	printf("fd=%d\r\n",fd);
	
	//往对应的命名管道写入数据
	write(fd,"hello",sizeof("hello"));
	
	//从出口处获取数据
	read(fd,buf,sizeof(buf));
	printf("buf:%s\r\n",buf);
	
	//关闭对应的文件描述符
	close(fd);
	
	return 0;
}

函数功能:
    删除对应的管道文件
头文件:
    #include <unistd.h>
函数原型:
    int unlink(const char *pathname);
函数参数:
    const char *pathname:表示待删除命名管道文件的路径
函数返回值:
    成功 返回0
    失败 -1 


示例:利用一下unlink函数,将对应的命名管道删除掉,观察对应的现象

现象:

代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
	int fd;
	char buf[10];
	int ret = mkfifo("c.fifo",0666);
	if(ret == 0)
	{
		printf("pipe success\r\n");
	}
	
	//打开管道文件
	fd = open("c.fifo",O_RDWR);
	printf("fd=%d\r\n",fd);
	
	//往对应的命名管道写入数据
	write(fd,"hello",sizeof("hello"));
	
	//从出口处获取数据
	read(fd,buf,sizeof(buf));
	printf("buf:%s\r\n",buf);
	
	//删除对应的管道
	ret = unlink("c.fifo");
	if(ret == 0)
	{
		printf("unlink success\r\n");
	}
	
	//关闭对应的文件描述符
	close(fd);
	
	return 0;
}

remove

函数功能:
    删除对应的文件(包括管道文件、文件夹)
头文件:
    #include <stdio.h>
函数原型:
    int remove(const char *pathname)
函数参数:
    const char *pathname:表示待删除文件的路径
函数返回值:
    成功 返回0
    失败 -1

示例:调用一下remove函数,观察对应的现象

现象:

代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
	int fd;
	char buf[10];
	int ret = mkfifo("c.fifo",0666);
	if(ret == 0)
	{
		printf("pipe success\r\n");
	}
	
	//打开管道文件
	fd = open("c.fifo",O_RDWR);
	printf("fd=%d\r\n",fd);
	
	//往对应的命名管道写入数据
	write(fd,"hello",sizeof("hello"));
	
	//从出口处获取数据
	read(fd,buf,sizeof(buf));
	printf("buf:%s\r\n",buf);
	
	//删除对应的管道
	ret = remove("c.fifo");
	if(ret == 0)
	{
		printf("remove success\r\n");
	}
	
	//关闭对应的文件描述符
	close(fd);
	
	return 0;
}
2.5 综合实例

综合1:利用父子进程,操作命名管道,进行通信的过程。

现象:

代码:

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
	int ret,fd;
	char str[10],buf[10];
    //创建管道
	ret = mkfifo("c.fifo",0666);
	if(ret == 0)
	{
		printf("mkfifo success\r\n");
	}
	
	//打开管道文件
	fd = open("c.fifo",O_RDWR);
	
	//创建父子进程
	ret=fork();
	if(ret == 0)//子进程
	{
		
		while(1)
		{
			//接收数据
			read(fd,buf,sizeof(buf));
			printf("child_buf:%s\r\n",buf);
			
			//获取需要发送的数据
			scanf("%[^\n]",str);
			getchar();
			//发送数据
			write(fd,str,sizeof(str));
			printf("child send success\r\n");
			
			//阻塞
			sleep(1);
		}
		
	}
	else//父进程
	{	
		
		while(1)
		{
			//获取需要发送的数据
			scanf("%[^\n]",str);
			getchar();
			//发送数据
			write(fd,str,sizeof(str));
			printf("parent send success\r\n");
			
			//阻塞
			sleep(1);
			
			//接收数据
			read(fd,buf,sizeof(buf));
			printf("parent_buf:%s\r\n",buf);
		}
	}
    //等待子进程运行结束
    wait(NULL);
	
	//删除对应的管道
	ret = remove("c.fifo");
	if(ret == 0)
	{
		printf("remove success\r\n");
	}
	
	//关闭对应的文件描述符
	close(fd);
    return 0;
}

综合2:利用命名管道,非亲缘关系进程实现—自由通信

现象:

代码:

进程A
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
	int ret,fd1,fd2;
	char str[10],buf[10];
    //创建管道1
	ret = mkfifo("a.fifo",0666);
	if(ret == 0)
	{
		printf("mkfifo1 success\r\n");
	}
	 //创建管道2
	ret = mkfifo("b.fifo",0666);
	if(ret == 0)
	{
		printf("mkfifo2 success\r\n");
	}
	
	//打开管道文件
	fd1 = open("a.fifo",O_RDWR);
	fd2 = open("b.fifo",O_RDWR);
	//创建父子进程
	ret=fork();
	if(ret == 0)//子进程
	{
		
		while(1)
		{
			//接收数据
			read(fd2,buf,sizeof(buf));
			printf("child_buf:%s\r\n",buf);
			
		}
		
	}
	else//父进程
	{	
		
		while(1)
		{
			//获取需要发送的数据
			scanf("%[^\n]",str);
			getchar();
			//发送数据
			write(fd1,str,sizeof(str));
			printf("parent send success\r\n");
			
		}
	}
    //等待子进程运行结束
    wait(NULL);
	
	//删除对应的管道
	ret = remove("a.fifo");
	if(ret == 0)
	{
		printf("remove success\r\n");
	}
	
	//关闭对应的文件描述符
	close(fd1);
	close(fd2);
    return 0;
}


进程B
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
	int ret,fd1,fd2;
	char str[10],buf[10];

	//打开管道文件
	fd1 = open("a.fifo",O_RDWR);
	fd2 = open("b.fifo",O_RDWR);
	//创建父子进程
	ret=fork();
	if(ret == 0)//子进程
	{
		
		while(1)
		{
			//接收数据
			read(fd1,buf,sizeof(buf));
			printf("child_buf:%s\r\n",buf);
			
		}
		
	}
	else//父进程
	{	
		
		while(1)
		{
			//获取需要发送的数据
			scanf("%[^\n]",str);
			getchar();
			//发送数据
			write(fd2,str,sizeof(str));
			printf("parent send success\r\n");
			
		}
	}
    //等待子进程运行结束
    wait(NULL);
	
	//删除对应的管道
	ret = remove("b.fifo");
	if(ret == 0)
	{
		printf("remove success\r\n");
	}
	//关闭对应的文件描述符
	close(fd1);
	close(fd2);
    return 0;
}

 注意:非亲缘关系的进程,操作命名管道的时候。某个时刻,进程A和进程B都没有操作命名管道,管道里面的数据将会消失

2.6 补充说明
无名管道与命名管道的区别

①创建方式与存在位置
无名管道:
由操作系统内核自动创建,无需显式地在文件系统中创建文件;不存在于任何文件系统中,只存在于内存中
命名管道:
需要在文件系统中创建一个特殊类型的文件(FIFO文件),以提供一个命名管道的路径;存在于文件系统中,并有一个与之关联的文件路径名

②使用范围与进程关系
无名管道:
只能用于具有亲缘关系的进程之间的通信,如父子进程或兄弟进程;由于无名管道不存在于文件系统中,因此无法被不相关的进程访问。
命名管道:
可以被多个进程同时使用,即多个读取或写入进程可以通过指定相同的路径来进行通信;适用于任意多个进程之间的通信,无论这些进程是否具有亲缘关系。

③通信模式与方向
无名管道:
是单工的通信模式,数据只能单向发送。即数据只能从管道的一端写入,从另一端读出,有固定的读端和写端。
命名管道:
可以支持双向通信(两个进程进行自由通信->两个管道),两个进程也可以通过同一管道进行交互(半双工)

文件系统与内存之间的交互

文件读取:

当进程需要读取文件中的数据时,操作系统会将文件的部分或全部内容加载到内存中,形成一个内存缓冲区。

进程通过访问内存缓冲区来读取文件数据,而不是直接访问存储设备。

文件写入:

当进程需要向文件中写入数据时,操作系统通常会将数据先写入内存中的一个缓冲区。

当缓冲区满或进程显式地要求将数据写入存储设备时,操作系统会将缓冲区中的数据写入文件系统对应的存储设备中。

3、标准流管道

3.1 定义

   标准流管道---标准IO有关的,文件流指针---FILE *fp  --头文件:#include <stdio.h>,Linux文件中的标准IO操作方法,都是管道流文件操作一种方式。

3.2 相关函数
popen

函数功能:

        用于创建一个管道连接到另一个进程,通常用于执行一个命令,并且能够从该命令读取输出或者向该命令写入输入

头文件:

        #include <stdio.h>

函数原型:

        FILE *popen(const char *command, const char *type);

函数参数:

        const char *command:表示需要在子进程中执行的命令

        const char *type :r 可以用于返回文件的指针,读取命令的输出w" 可以用于返回文件的指针,向命令写入输入

函数返回值:

        成功 返回文件指针--->第二个参数决定的 

        失败 返回-1

注意:

使用 popen() 函数可以简化管道流操作,它结合了管道创建、进程创建、文件描述符管理和命令执行等多个步骤。具体执行步骤

创建一个管道。

fork()函数创建子进程。

在父子进程中关闭不需要使用的文件描述符。

执行execl()函数

执行函数中指定的命令—command。

特点:调用的函数大大减少,减少了代码的数量,不需要自己进行创建,并注意popen()必须使用标准IO进行操作—返回对应的文件流指针fp。例如:“fread”“fwrite”,不能使用文件IO里面的read、write,必须使用pclose()函数关闭标准流管道。

pclose

函数功能:

        关闭管道文件

头文件:

        #include <stdio.h>

函数原型:

        int pclose(FILE *stream);

函数参数:

        FILE *stream:文件流指针

函数返回值:

        返回子进程结束的状态

示例1:利用popen函数,使用命令ls,观察对应的现象

现象:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
   //直接调用对应的popen  
   FILE *fp;
   char buf[40]={0};
   fp=popen("ls", "r");
   if(fp != NULL)
   {
      printf("popen success\r\n");
   }
   
   //读取对应fp里面的内容
   fread(buf,sizeof(char),sizeof(buf),fp);

   //输出一下buf里面的内容
   printf("buf=%s",buf);
   
   //调用对应的pclose函数
   pclose(fp);
   
   return 0;
}

执行流程:

创建管道和子进程:

在 main() 函数中,调用 popen("ls", "r")。

popen() 函数内部首先创建一个管道,用于父子进程之间的通信。

接着,popen() 调用 fork() 创建一个子进程。

子进程执行命令:

在子进程中,popen() 函数重定向子进程的标准输出(stdout)到管道的写端

子进程执行 ls 命令,并将输出写入到管道中。

子进程执行完毕后(在输出完所有内容后),它会关闭管道的写端并退出。

父进程读取输出:

在父进程中,popen() 返回一个指向 FILE 结构体的指针 fp,该指针与管道的读端相关联。父进程使用 fread(buf, 1, sizeof(buf) , fp) 从管道中读取数据到缓冲区 buf 中。fread() 会持续读取数据,直到达到缓冲区大小限制、遇到文件结束符(EOF),或发生错误。

处理读取的数据:

父进程在读取完数据后,手动在缓冲区末尾添加 null 终止符 '\0',以确保字符串正确终止。然后,父进程使用 printf() 函数将缓冲区中的内容打印到标准输出上。

关闭管道和清理资源:

父进程调用 pclose(fp) 关闭与管道相关联的文件指针,并等待子进程结束(如果子进程还没有结束的话)。pclose() 函数内部会关闭管道的读端,并处理任何必要的清理工作。

如果子进程已经结束,pclose() 会返回子进程的退出状态。

示例2:利用popen函数,写操作---关键:找到一个可以写的命令 cat > 1.txt (可以利用键盘写到1.txt文件里面的)

现象:

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
	 FILE *fp;
	char buf[10];
	//创建管道
	fp = popen("cat > 1.txt", "w");
	if(fp != NULL)
    {
      printf("popen success\r\n");
    }
	
	scanf("%s",buf);
	//往对应的fp里面写入数据
	fwrite(buf,sizeof(char),sizeof(buf),fp);

	//调用对应的pclose函数
	pclose(fp);
}

示例3:利用popen函数—调用对应“r”,调用一个命令:cat a.c –功能:将对应a.c这个源文件里面的代码,显示到屏幕上(输出内容)

现象:

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
	FILE *fp;
	int res;
	char buff[2];
	//创建管道
	fp = popen("cat a.c", "w");
	if(fp != NULL)
    {
      printf("popen success\r\n");
    }
	
	while(1)
	{
		//读取数据
		res = fread(buff,sizeof(char),1,fp);
		if(res == 0)
		{
			break;
		}
		//显示到屏幕
		fwrite(buff,sizeof(char),1,stdout);
		
		//清数组
		memset(buff,0,sizeof(buff));
	}
	//调用对应的pclose函数
	pclose(fp);
}

二、信号

1、信号的本质

         Linux系统中信号相当于软中断(本质)用来通知异步事件的发生,软件层上对中断的一种模拟。在原理上,一个进程接收到一个信号,和处理器接受到一个中断请求(硬中断)可以说是一样的。信号是进程通信的一种异步通信机制,一个进程不必通过任何操作等待信号到来,实际上,进程也不知道什么时候信号会达到。

         一旦进程A,接收到了信号,中断当前进程的执行,进入到对应的信号处理函数里面,执行对应的代码;在软中断处理完毕后,系统会检查之前保存的现场信息,并根据这些信息恢复到被中断的进程的执行点。

2、信号的来源

信号的来源可以分为多种,按照产生条件的不同可以分为两种--->硬件和软件

第一种:硬件方式

按键ctrl+c-->发送SIGINT信号(2)给前台进程组中的所有的进程,这些正在运行的进程接收到信号,终止运行。会终止当前进程的执行。也可以改变对应信号的处理方式

按键ctrl+z-->发送SIGSTOP信号(19)给其他进程组中的所有进程,挂起进程

按键ctrl+\-->发送SIGTTOU信号(22)给前台进程组中的所有进程,终止进程并生成core文件

第二种:软件方式

使用命令:kill 发送信号(内部实现就是通过kill函数)-->把某个进程杀死(可以在终端发送信号)

使用函数:kill() ,sigqueue()发送信号

3、信号的分类

从不同的角度信号进行分类:

             可靠性方面:可靠信号非可靠信号

             与时间的关系:实时信号与非实时信号

=========================================================================

可靠信号与不可靠信号的来源

UNIX系统早期系统比较简单,信号可能丢失,对这一类信号统称为非可靠信号(早期是1~31这个范围)。目前Linux继承这类信号(1~31信号编号表示非可靠信号)。

可靠信号:在原来的基础上,增加了另外一批可靠信号,增加这批(34~64范围)信号支持队列(当处理一个信号的时候接受到另外一个信号,其他信号需要进行排队,等当前正在执行的信号处理完毕,依次处理其他的信号),不会丢失,因此,称为信号为可靠信号(从SIGRTMIN~SIGRTMAX之间的信号)。

=========================================================================

实时信号与非实时信号的来源

因为早期的不可靠信号每个信号都有固定的含义,不支持排队---非实时信号(1~31早期的信号还容易丢失)

后期增加可靠信号每个信号可以用户自定义,并且支持排队功能--实时信号(34~64后期的信号实时信号)

4、信号列表

可以使用kill -l函数,观察当前linux系统里面的信号列表:

每个信号的含义如下:

1) SIGHUP:当用户退出 Shell 时,由该 Shell 启动所有进程都退接收到这个信号,默认动作为终止进程。 

2) SIGINT:用户按下组合 Ctrl +c 键时,用户端时向正在运行中的由该终端启动的程序发出此信号。默认动作为终止进程 

3) SIGQUIT:当用户按下组合键时产生该信号,用户终端向正在运行中的由该终端启动的程序发出此信 号。默认动作为终止进程并产生 core 文件。 

4) SIGILL CPU 检测到某进程执行了非法指令。默认动作为终止进程并产生 core 文件。 

5) SIGTRAP:该信号由断点指令或其他 trap 指令产生。默认动作为终止进程并产生 core 文件。 

6) SIGABRT:调用 abort 函数时产生该信号。默认动作为终止进程并产生 core 文件。 

7) SIGBUS:非法访问内存地址,包括内存地址对齐(alignment)出错,默认动作为终止进程并产生 core 文件。 

8) SIGFPE:在发生致命的算术错误时产生。不仅包括浮点运行错误,还包括溢出及除数为 0 等所有的算 术错误。默认动作为终止进程并产生 core 文件。

9) SIGKILL:无条件终止进程。本信号不能被忽略、不可变更处理方式和阻塞。默认动作为终止进程。它 向系统管理员提供了一种可以杀死任何进程的方法。 

10) SIGUSR1:用户定义的信号,即程序可以在程序中定义并使用该信号。默认动作为终止进程。 

11) SIGSEGV:指示进程进行无效的内存访问。默认动作为终止进程并使用该信号。默认动作为终止进程。 

12) SIGUSR2:这是另外一个用户定义信号,程序员可以在程序中定义并使用该信号。默认动作为终止进 程。 13) SIGPIPEBroken pipe:向一个没有读端的管道写数据。默认动作为终止进程。 

14) SIGALRM:定时器超时,超时的时间由系统调用 alarm 设置。默认动作为终止进程。 

15) SIGTERM:程序结束(terminate)信号,与 SIGKILL 不同的是,该信号可以被阻塞和处理。通常用来要求 程序正常退出。执行 Shell 命令 kill 时,缺少产生这个信号。默认动作为终止进程。 

17) SIGCHLD:子程序结束时,父进程会收到这个信号。默认动作为忽略该信号。 

18) SIGCONT:让一个暂停的进程继续执行。 

19) SIGSTOP:停止(stopped)进程的执行。注意它和 SIGTERM 以及 SIGINT 的区别:该进程还未结束, 只是暂停执行。本信号不能被忽略、处理和阻塞。默认作为暂停进程。 

20) SIGTSTP:停止进程的动作,但该信号可以被处理和忽略。按下组合键时发出该信号。默认动作为暂停进程。 

21) SIGTTIN:当后台进程要从用户终端读数据时,该终端中的所有进程会收到 SIGTTIN 信号。默认动作 为暂停进程。  

22) SIGTTOU:该信号类似于 SIGTIN,在后台进程要向终端输出数据时产生。默认动作为暂停进程。( 果不行需要设置 stty tostop)

23) SIGURG:套接字(socket)上有紧急数据时,向当前正在运行的进程发出此信号,报告有紧急数据到达。 默认动作为忽略该信号。 

24) SIGXCPU:进程执行时间超过了分配给该进程的 CPU 时间,系统产生该信号并发送给该进程。默认动 作为终止进程。

25) SIGXFSZ:超过文件最大长度的限制。默认动作为 yl 终止进程并产生 core 文件。 

26) SIGVTALRM:虚拟时钟超时时产生该信号。类似于 SIGALRM,但是它只计算该进程占有用的 CPU  间。默认动作为终止进程。 

27) SIGPROF:类似于 SIGVTALRM,它不仅包括该进程占用的 CPU 时间还抱括执行系统调用的时间。默 认动作为终止进程。 

28) SIGWINCH:窗口大小改变时发出。默认动作为忽略该信号。 

29) SIGIO:此信号向进程指示发出一个异步 IO 事件。默认动作为忽略。 

30) SIGPWR:关机。默认动作为终止进程。 

31) SIGSYS:非法系统调用 

34) SIGRTMIN~SIGRTMAXLinux 的实时信号,它没有固定的含义(或者说可以由用户自由使用)—可以自己设定处理方式。注意, Linux 线程机制使用了前 3 个实时信号。所有的实时信号的默认动作都是终止进程

注意:标准的写法应该是使用-前缀来明确指定信号编号如kill -9 PID号

5、通信流程

一个完整的信号生命周期可以分为3个阶段:3个阶段里面有4个重要的事情组成信号产生(linux内核)、信号在进程中注册,信号的注销执行信号的处理函数。

补充:一个用户进程不能直接给另外一个用户进程直接发送信号,必须通过内核来给另外一个用户进程发送信号用户空间不具备发送信号的能力,进程把信号传递给内核中,所以必须通过内核来发送信号。

6、相关函数

发送信号
kill

函数功能:

        给任一进程/进程组(除了当前进程),发送任一信号

头文件:

        #include <sys/types.h>

        #include <signal.h>

函数原型:

        int kill(pid_t pid, int sig);

函数参数:

        pid_t pid:对应进程的PID号

        int sig:信号的编号—>可以使用kill -l查看

                        pid > 0 发送信号给进程的PID

                        pid = 0 发送信号当前进程组下的所有进程

                        pid = -1尽可能给进程组下所有的进程发送信号

                        pid <-1 发送信号给进程组-pid的进程发送相应的信号

函数返回值:

        成功 0  

        失败 -1

示例1:使用一下kill命令,给当前进程发送一个9号信号

现象:

代码:

#include <sys/types.h>
#include <unistd.h>
#include <stdio.h>

int main(int argc,char *argv[])
{
   //获取当前进程的PID
    pid_t pid;
	pid=getpid();

    //输出一下当前进程的PID
	printf("pid=%d\r\n",pid);
    
	while(1);

   return 0;
}

示例2:进程B使用一下kill函数,给进程A发送一个信号

现象:

现象:

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
   //获取进程A的PID号
   int A = atoi(argv[1]);
   //输出一下当前进程的PID
   printf("A=%d\r\n",A);
   
   //利用内核向进程A发送信号---杀死进程编号9
   int ret = kill(A,9);
   sleep(5);
   if(ret == 0)
   {
		printf("kill success\r\n");
   }
   
   return 0;
}

alarm

函数功能:

        可以定时对应的时间,时间到了可以发送闹钟信号给当前进程;如果设置秒数为0,取消对应的闹钟事件

头文件:

        #include <unistd.h>

函数原型:

        unsigned int alarm(unsigned int seconds);

函数参数:

        unsigned int seconds:就是定时时间的长度

函数返回值:

        返回上一次,闹钟事件的剩余时间

示例1:调用对应的alarm函数,观察对应的现象

现象:

代码:

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
   printf("hello\r\n");
   
   //调用alarm函数
   int ret = alarm(5);//无阻塞作用,默认为终止进程
   printf("ret=%d\r\n",ret);
   
   printf("world\r\n");
   
   while(1);
   
   return 0;
}

示例2:验证在多个闹钟的情况下,唯有最后一个闹钟才会生效

现象:

代码:

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
	int ret;
   printf("hello\r\n");
   
   //调用alarm函数
   ret= alarm(3);//无阻塞作用
   printf("ret=%d\r\n",ret);
   
   sleep(2);
   //调用alarm函数
   ret = alarm(5);//无阻塞作用
   printf("ret=%d\r\n",ret);
   
   //调用alarm函数
   ret = alarm(7);//无阻塞作用
   printf("ret=%d\r\n",ret);
   
   printf("world\r\n");
   
   while(1);
   return 0;
}

raise

函数功能:

        给当前线程(主进程)发送任一信号

头文件:

        #include <signal.h>

函数原型:

        int raise(int sig);

函数参数:

        int sig:信号的编号—>可以使用kill -l查看

函数返回值:

        成功 0  

        失败 -1

示例:调用一下raise函数,观察一下对应的现象

现象:

代码:

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
   printf("hello\r\n");
   
   //调用raise函数
   int ret = raise(2);//终止进程
   
   printf("world\r\n");
   return 0;
}

等待接收信号
pause

函数功能:

        将当前的进程阻塞,直到接到信号

头文件:

        #include <unistd.h>

函数原型:

        int pause(void);

函数参数:

函数返回值:

        成功 -1   

        失败 错误

示例:调用一下pause函数,等待对应的信号到来。

现象:

代码:

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
	int ret;
   printf("hello\r\n");
   
   //调用alarm函数
   ret = alarm(2);//无阻塞作用
   printf("ret=%d\r\n",ret);
   
   printf("world\r\n");
   //等待对应的信号到来
   ret = pause();
   printf("ret=%d\r\n",ret);
   return 0;
}

处理信号
signal

函数功能:

        设置当前进程对信号标号的处理方式

头文件:

        #include <signal.h>

函数原型:

        sighandler_t signal(int signum, sighandler_t handler);

函数参数:

        int  signum:信号的编号kill -l

        sighandler_t handler:表示信号的处理方式,处理方式主要分为三种:

           第一个:SIG_DFL  对信号采取默认的处理方式

           第二个:SIG_IGN  忽略该信号

           第三个:信号处理函数的名称---注意函数的声明格式(自定义)

                          例子:void signal_handler(int arg);//信号处理函数

函数返回值:

        成功 返回前一次处理信号的方式  

        失败 SIG_ERR

示例1:调用一下signal函数,观察一下现象

现象:

代码:

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char *argv[])
{
	int ret;
   printf("hello\r\n");
   
   //设置一下,信号的处理方式
   signal(14, SIG_DFL);//默认
	
   //调用alarm函数
   ret = alarm(2);//无阻塞作用
   
   //等待对应的信号到来
   ret = pause();
   
   printf("world\r\n");
   
   //等待对应的信号到来
   ret = pause();
   printf("ret=%d\r\n",ret);
   return 0;
}

示例2:通过signal函数,验证arg是否是信号编号

现象:

代码:

#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>

//信号处理函数
void signal_handler(int arg)
{
	printf("arg:%d\r\n",arg);
	
	if(arg == 14)
	{
		printf("hello\r\n");
	}
}


int main(int argc,char *argv[])
{

	//设置一下,信号的处理方式
	signal(14,signal_handler);//默认
   
	//调用alarm函数
	alarm(5);//无阻塞作用
   
    pause();
	
    return 0;
}

综合实例

综合1:使用alarm定时器0~5秒段忽略 5~10秒 自定义的功能  10之后恢复默认

现象:

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
void signal_handler(int arg)//信号处理函数
{
		printf("hello_world\r\n");
}

int main(int argc,char *argv[])
{
	//忽略
	signal(14,SIG_IGN);
	//定时
	alarm(3);
	int i = 1;
	for(;i<6;i++)
	{
		printf("%dS\r\n",i);
		sleep(1);
	}
	//自定义的功能
	signal(14,signal_handler);
	//定时
	alarm(3);
	for(i=6;i<11;i++)
	{
		printf("%dS\r\n",i);
		sleep(1);
	}
	
	//默认
	signal(14,SIG_DFL);
	//定时
	alarm(2);
	for(i=11;i<15;i++)
	{
		printf("%dS\r\n",i);
		sleep(1);
	}
	

}

注意:alarm每次定时,到时间后,便会清零,不会累加;alarm无阻塞功能.

综合2:利用signal函数,观察一下信号处理函数的参数怎么变化的,利用多个信号

现象:

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
void signal_handler(int arg)//信号处理函数
{
	if(arg == 14)
	{
		printf("arg=%d\r\n",arg);
		printf("hello_world\r\n");
	}
	else if(arg == 1)
	{
		printf("arg=%d\r\n",arg);
		printf("hello_whl\r\n");
	}
	else if(arg == 3)
	{
		printf("arg=%d\r\n",arg);
		printf("hello_whl\r\n");
	}
}

int main(int argc,char *argv[])
{
	//获取当前进程的PID
   pid_t pid;
	pid=getpid();
	printf("pid:%d\r\n",pid);
	//自定义的功能-信号14
	signal(14,signal_handler);
	//定时
	alarm(10);
	
	//自定义的功能-信号1
	signal(1,signal_handler);
	
	//自定义的功能-信号3
	signal(3,signal_handler);
	
	int i = 1;
	while(1)
	{
		printf("time:%ds\r\n",i++);
		sleep(1);
	}
	
}

注意:需要触发对应的信号后,才会执行信号处理函数

综合3:利用alarm()函数—定时器的功能,循环定时的功能—例如:每隔5秒获取一下时间

现象:

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
void signal_handler(int arg)//信号处理函数
{
	if(arg == 14)
	{
		//获取系统时间
		system("date");
		//再次定时
		alarm(5);
	}
	
}

int main(int argc,char *argv[])
{
	
	//自定义的功能-信号14
	signal(14,signal_handler);
	//定时
	alarm(5);
	
	int i = 1;
	while(1)
	{
		printf("time:%ds\r\n",i++);
		sleep(1);
	}
}

综合4:改进一下上面的代码,利用2号信号,观察一下对应的现象—输出当前时间。

现象:

硬件方式触发:快捷键ctrl+c->信号2->获取时间

软件方式触发:kill -2 进程号

代码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
void signal_handler(int arg)//信号处理函数
{
	if(arg == 14)
	{
		//获取系统时间
		system("date");
		//再次定时
		alarm(5);
	}
	else if(arg == 2)
	{
		//获取系统时间
		system("date");
	}
	
}

int main(int argc,char *argv[])
{
	//获取当前进程的PID
    pid_t pid;
	pid=getpid();
	printf("pid:%d\r\n",pid);
	
	//自定义的功能-信号14
	signal(14,signal_handler);
	//定时
	alarm(5);
	
	//自定义的功能-信号2
	signal(2,signal_handler);
	int i = 1;
	while(1)
	{
		printf("time:%ds\r\n",i++);
		sleep(1);
	}

}

                                             

三、共享内存

1、定义

        共享内存(Shared Memory)允许两个或多个进程访问同一块内存区域,从而实现数据共享和交互。每个进程都有独立的虚拟地址空间和与之对应的页表,页表负责将进程的虚拟地址空间与物理地址空间进行映射,通过内存管理单元(MMU)进行管理。在共享内存通信中,不同的进程将相同的物理内存区域与各自的虚拟地址空间进行关联,从而实现通过虚拟地址直接访问共享内存。 

2、特性

        ①共享内存是进程通信最快的通信方式,并且适用多进程大数据共享。

        ②共享内存没有保护机制—通常要加上约束,互斥方法

        ③共享内存的基本单位页,大小4K,并向上取整数

        ④共享内存的生命周期随内核—如果linux系统退出了,那么共享内存就会消失。

3、通信流程

        1、创建.txt文件与一个项目id,生成唯一的标识符key--->ftok()

        2、基于这个标识符(key))创建一个新的共享内存段或访问一个已存在的共享内存段,从而获得共享内存标识符shmid--->shmget()

        3、通过获取到的id,完成共享内存在进程中的映射--->shmat()

        4、利用映射地址对共享内存进行读写操作--->scanf()、gets()/puts()

        5、取消共享内存在进程中的映射--->shmdt()

        6、删除共享内存--->shmctl()

4、相关函数

ftok

函数功能:

        获取key值

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/shm.h>

函数原型:

        key_t ftok(const char *pathname, int proj_id);

函数参数:

         const char *pathname:路径名---必须以字符串的形式存在,要求文件必须存在

         int proj_id:工程ID->0值

函数返回值:

        成功  基于pathname(文件名1.txt) + proj_id,返回值相应的key值

        失败  返回-1

示例:利用一下ftok函数,获取对应的key键值

现象:

代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc,char *argv[])
{
	key_t key;
	//获取键值
	key = ftok("1.txt",1);
	
	printf("key:%x\r\n",key);
	printf("key:%d\r\n",key);

	return 0;
}
shmget

函数功能:

        申请打开一个基于key的共享内存从而获取ID。

头文件:

        #include <sys/ipc.h>

        #include <sys/shm.h>

函数原型:

        int shmget(key_t key, size_t size, int shmflg);

函数参数:

        key_t keykey值,可以通过ftok()返回值

        size_t size:申请共享内存的大小->定义位4K大小--4096

        int shmflg:打开共享内存的方式,一般情况IPC_CREAT | 0666

函数返回值:

        成功 返回共享内存的id--shmid  

        失败 返回-1

注意: key值如果设置为0 ,也会申请一块共享内存,但是共享内存仅限本进程使用

示例:利用shmget函数,获取对应共享内存的ID号

现象:

代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);

	return 0;
}
 shmat

函数功能:

        通过shmget获取到的ID,完成共享内存在进程中的映射

头文件:

        #include <sys/types.h>

        #include <sys/shm.h>

函数原型:

        void *shmat(int shmid, const void *shmaddr, int shmflg);

函数参数:

        int shmid:共享内存的ID

       const void *shmaddr:共享内存在进程中的映射地址,一般填写为NULL 表示系统自动分配

        int shmflg:表示对该共享内存的操作方式,一般填写为0->表示可读可写

函数返回值:

        成功 返回共享内存在进程中映射地址->可以定义一个p接收

        失败 返回(void*)-1

示例1:利用一下shmat函数,可以获取对应的共享内存的首地址,往共享内容写入数据并且读取

现象:

代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	if(p != NULL)
	{
		printf("shmat success\r\n");
		printf("p=%p\r\n",p);
	}
	//往对应的共享内存里面放入数据
	fgets(p,10,stdin);
	
	//读取共享内存内的数据
	puts(p);
	
	return 0;
}

示例2:实现两个不同的进程通过共享内存实现对数据的读写

现象:

代码:

进程A

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	if(p != NULL)
	{
		printf("shmat success\r\n");
		printf("p=%p\r\n",p);
	}
	while(1)
	{
		//往对应的共享内存里面放入数据
		fgets(p,10,stdin);
	}
	return 0;
}

进程B 


#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	if(p != NULL)
	{
		printf("shmat success\r\n");
		printf("p=%p\r\n",p);
	}
	//往对应的共享内存里面放入数据
	//fgets(p,10,stdin);
	while(1)
	{
		//读取共享内存内的数据
		puts(p);
		
		sleep(5);
	}
	return 0;
}

shmdt

函数功能:

        取消共享内存在本进程中的映射

头文件:

        #include <sys/types.h>

        #include <sys/shm.h>

函数原型:

        int shmdt(const void *shmaddr);

函数参数:

        const void *shmaddr:表示共享内存在进程中的映射地址,shmat函数的返回值

函数返回值:

        成功 返回0   

        失败 返回-1

示例:调用一下shmdt函数,观察对应的现象

现象:

代码: 

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	if(p != NULL)
	{
		printf("shmat success\r\n");
		printf("p=%p\r\n",p);
	}
	//往对应的共享内存里面放入数据
	while(1)
	{
		//读取共享内存内的数据
		puts(p);
		
		sleep(5);
		
		//取消共享内存在本进程中的映射
		 int ret = shmdt(p);
		 if(ret==0)
		 printf("shmdt success\r\n");
	}
	return 0;
}

shmctl 

函数功能:

        反应共享内存状态信息的

头文件:

        #include <sys/ipc.h>

        #include <sys/shm.h>

函数原型:

         int shmctl(int shmid, int cmd, struct shmid_ds *buf);

函数参数:

        int shmid:表示的内存的ID

        int cmd:表示共享内存的操作模式

                        IPC_STAT(查看状态)  IPC_SET(设置)  IPC_RMID(删除)

        struct shmid_ds *buf:① IPC_RMID :buf里面填写为NULL  ②  IPC_STAT(查看状态)  IPC_SET(设置) 见下方

函数返回值:

        成功 返回0   

        失败 返回-1

 IPC_STAT(查看状态) 与 IPC_SET(设置)  所用到的结构体:

struct shmid_ds {

                               struct ipc_perm shm_perm; 共享内存段的权限和所有权信息

                               size_t shm_segsz;内存大小

                               time_t shm_atime;最后一次映射到共享内存段的时间

                               time_t shm_dtime;表示最后一次取消映射共享内存段的时间

                               time_t shm_ctime;最后一次改变共享内存段的状态的时间

                               pid_t shm_cpid;创建共享内存段的进程的PID(进程ID)

                               pid_t  shm_lpid;最后一次执行shmat或shmdt操作的进程的PID

                               shmatt_t  shm_nattch;当前映射到共享内存段的进程数量

                               ...

                                };

struct ipc_perm {
                               key_t  __key; 用于创建或访问共享内存段的键
                               uid_t uid;共享内存段当前所有者的有效用户ID(UID)
                               gid_t gid;共享内存段当前所有者的有效组ID(GID)
                               uid_t cuid;创建共享内存段时的有效用户ID
                               gid_t cgid; 创建共享内存段时的有效组ID
                               unsigned short mode; 共享内存段的权限标志
                               unsigned short __seq; 序列号,用于内部跟踪和同步
                               };

示例1:利用一下shmctl函数,IPC_RMID观察对应的现象

现象:

代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	if(p != NULL)
	{
		printf("shmat success\r\n");
		printf("p=%p\r\n",p);
	}
	//往共享内存写入数据
	scanf("%s",p);
	//读取共享内存内的数据
	puts(p);
		
	//删除共享内存空间
	int ret = shmctl(id, IPC_RMID, NULL);
	if(ret == 0)
	printf("remove success\r\n");

	return 0;
}

示例2:利用IPC_STAT,观察对应共享内存的状态信息

现象:

 代码:

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	if(p != NULL)
	{
		printf("shmat success\r\n");
		printf("p=%p\r\n",p);
	}
	//往共享内存写入数据
	scanf("%s",p);
	//读取共享内存内的数据
	puts(p);
	
	//定义共享内存的状态信息结构体变量
	struct shmid_ds shm_buf={0};
	
	//观察对应共享内存的状态信息
	ret=shmctl(shmid,IPC_STAT,&shm_buf);
	if(ret == 0)
	{
		printf("shm_buf.shm_segsz:%d\r\n",shm_buf.shm_segsz);//查看内存大小
		printf("shm_buf.shm_perm.mode:%d\r\n",shm_buf.shm_perm.mode);//查看修改权限
	}
	
	
	//删除共享内存空间
	int ret = shmctl(id, IPC_RMID, NULL);
	if(ret == 0)
	printf("remove success\r\n");

	return 0;
}

示例3:利用IPC_SET设置一下共享内存的权限—mode,观察

现象:

代码: 

#include<stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	int ret;
	//获取键值
	key = ftok("1.txt",1);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	if(p != NULL)
	{
		printf("shmat success\r\n");
		printf("p=%p\r\n",p);
	}
	//往共享内存写入数据
	scanf("%s",p);
	//读取共享内存内的数据
	puts(p);
	
	//定义共享内存的状态信息结构体变量
	struct shmid_ds shm_buf={0};
	
	//观察对应共享内存的状态信息
	ret=shmctl(id,IPC_STAT,&shm_buf);
	if(ret == 0)
	{
		printf("shm_buf.shm_segsz:%d\r\n",shm_buf.shm_segsz);//查看内存大小
		printf("shm_buf.shm_perm.mode:%d\r\n",shm_buf.shm_perm.mode);//查看修改权限
	}
	
	//设置权限
	shm_buf.shm_perm.mode = 0664;
	ret=shmctl(id,IPC_SET,&shm_buf);
	if(ret==0)
	{
		printf("set success\r\n");
	}
	
	
	//再次观察对应共享内存的状态信息
	ret=shmctl(id,IPC_STAT,&shm_buf);
	if(ret == 0)
	{
		printf("shm_buf.shm_segsz:%d\r\n",shm_buf.shm_segsz);//查看内存大小
		printf("shm_buf.shm_perm.mode:%d\r\n",shm_buf.shm_perm.mode);//查修改权限
	}
	
	//删除共享内存空间
	ret = shmctl(id, IPC_RMID, NULL);
	if(ret == 0)
	printf("remove success\r\n");

	return 0;
}

综合实例

示例:多进程利用共享内存进行自由通信

现象:

代码:

进程A、进程B

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc,char *argv[])
{
	key_t key;
	int id;
	char *p;
	int ret;
	/*创建共享内存*/
	//获取键值
	key = ftok("./2.txt",0);
	printf("key:%x\r\n",key);

	//获取对应共享内存的ID号
	id = shmget(key,4096, IPC_CREAT | 0666);
	printf("id:%d\r\n",id);
	
	//将共享内存映射到,当前这个进程里面
	p = (char*)shmat(id, NULL, 0);
	
	/*创建父子进程*/
	ret = fork();
	if(ret == 0)//子进程
	{
		char buff[10]={0};
		while(1)
		{
			if(strcmp(buff,p)!=0)
			{
				//读取共享内存内的数据
				puts(p);
				//将数据写入数组内
				strcpy(buff,p);
				printf("buff:%s\r\n",buff);
				
				if(strcmp(buff,"quit")==0)
				{
					break;
				}
			}
			else
			{
				sleep(1);
			}
		}
		//退出进程
		exit(0);
		
	}
	else//父进程
	{
		
		while(1)
		{
			//往共享内存写入数据
			scanf("%s",p);
			//读取数据
			puts(p);
			
			if(strcmp("quit",p)==0)
			{
				break;
			}
		}
		
		
		
	}
	
	 //删除共享内存
   ret=shmctl(id,IPC_RMID,NULL);
   printf("ret=%d\r\n",ret);
   
   //等待对应的子进程
   wait(NULL);
	return 0;
}

需要解决的问题:如何避免重复获取相同的数据?---》类似于裸机的第一次开机 

四、消息队列

1、特性

消息队列的特点:       

        ①采用链式结构

        ②消息队列上传消息之后,除非消息被接收或消息被删除,否则消息不会消失 (并且只能被正确接收一次)

       ③消息队列具阻塞特性:当缓冲区满时,写操作会阻塞,直到有空间可用;当缓冲区为空时,读操作会阻塞(消息队列读阻塞,消息队列里面没有内容, 当前的这个进程会被阻塞掉),直到有数据可读

=========================================================================

         消息队列与FIFO结构(先进先出),都有一个队列结构,并可以实现多进程往队列中写入信息,以及多进程往消息队列里面读取消息。FIFO结构正常有两个端口,事先先打开,这样才能正常传递消息。

2、通信流程

       ①创建.txt文件与一个项目id,生成唯一的标识符key--->ftok()

       ②基于这个标识符(key)创建一个新的消息队列或访问一个已存在的消息队列,从而获得消息队列标识符msgid--->msgget()

       ③利用消息队列标识符msgid以及消息编号与消息数据,往指定的消息队列上发送信息或者读取信息--->msgsnd()、msgrcv()

       ④删除消息列表--->msgctl()

3、相关函数

ftok

函数功能:

        获取key值

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/shm.h>

函数原型:

        key_t ftok(const char *pathname, int proj_id);

函数参数:

         const char *pathname:路径名---必须以字符串的形式存在,要求文件必须存在

         int proj_id:工程ID->0值

函数返回值:

        成功  基于pathname(文件名1.txt) + proj_id,返回值相应的key值

        失败  返回-1

msgget

函数功能:

        创建一个新的消息队列或者获取一个已经存在的消息队列的标识符.

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/msg.h>

函数原型:

        int msgget(key_t key, int msgflg);

函数参数:

        key_t key:key值,可以通过ftok()返回值

        int msgflg:用于控制消息队列的创建和访问权限->IPC_CREAT|0664

函数返回值:

        成功 返回消息队列的ID

        失败 返回-1

示例:利用msgget函数,获取对应的消息ID

现象:

代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
int main(int argc,char* argv[])
{
	key_t key;
	int id;
	//获取键值
	key = ftok("3.txt",4);
	printf("key:%x\r\n",key);
	
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);
	
}

msgsnd

函数功能:

        往指定的消息队列上发送信息

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/msg.h>

函数原型:

        int msgsnd(mqd_t msqid, const void *msg_ptr, size_t msg_len, int msgflg);

函数参数:

        int msqid:消息队列描述符,是通过 msgget() 函数获得的消息队列标识符。

        const void *msg_ptr:发送信息的指针。这个消息是一个结构体,包括消息编号和需要发送消息的数据

        size_t msg_len:消息数据的长度(不包括消息类型字段)

        int msgflg:控制发送操作的标志。一般设置为 0(阻塞),设置为 IPC_NOWAIT(非阻塞)

函数返回值:

        成功 返回0

        失败 返回-1

补充:

一般先确定消息的类型,定义并赋值,最后再发送消息

msgsnd函数,它的第二个参数,要指向结构体类型的:

struct msgbuf

{

long mtype;    --->消息的类型---指的就是消息编号 1

char mtext[];  ---->消息的数据

}

示例:调用msgsnd,将数据发送出去,观察现象

现象:

代码: 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
struct msgbuf
{
	long mtype;    //--->消息的类型---指的就是消息编号 1 
	char mtext[20];  //---->消息的数据
};
int main(int argc,char* argv[])
{
	key_t key;
	int msgid,ret;
	struct msgbuf buff_w={2,"hello_world"};
	//获取键值
	key = ftok("3.txt",4);
	printf("key:%x\r\n",key);
	
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);
	
	//发送数据信息
	ret = msgsnd(msgid,&buff_w,sizeof(buff_w), 0);
	if(ret == 0)
	printf("send success\r\n");
	
	return 0;
}

msgrcv

函数功能:

        接收消息队列上的消息

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/msg.h>

函数原型:

        ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

函数参数:

        int msqid:消息队列描述符,是通过 msgget() 函数获得的消息队列标识符。

        void *msgp:接收信息的指针

        size_t msgsz:指向一个用户定义的缓冲区,该缓冲区用于存储接收到的消息。这个缓冲区需要足够大,以容纳消息结构(包括消息类型和消息文本)

        long msgtyp:表示接收信息的编号

       int msgflg:控制接收操作的标志。设置为 0(阻塞等待消息),设置为 IPC_NOWAIT(非阻塞,如果没有消息立即返回-1并设置errno) 

                注意:①在阻塞模式下,msgrcv()只有在成功接收到消息或发生错误时才会返回;在阻塞等待期间, 它不会返回任何值 ②在非阻塞模式下,msgrcv()将不会阻塞调用线程以等待消息的到来;相反,它会立即返回,并根据消息队列中是否有符合条件的消息来决定返回值和后续操作。

函数返回值:

        成功 返回接收信息数据的长度(不包括信息类型)

        失败 返回-1

示例1:利用一下msgrcv()函数,接收消息队列上的消息

现象:

代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
struct msgbuf
{
	long mtype;    //--->消息的类型---指的就是消息编号 1 
	char mtext[20];  //---->消息的数据
};
int main(int argc,char* argv[])
{
	key_t key;
	int msgid,ret;
	struct msgbuf buff_r;
	//获取键值
	key = ftok("3.txt",4);
	printf("key:%x\r\n",key);
	
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);

	//接收数据信息
	ret = msgrcv(msgid, &buff_r, sizeof(buff_r), 2,0);
	printf("ret:%d\r\n",ret);
	printf("buff_r:%s\r\n",buff_r.mtext);
	printf("mtype:%ld\r\n",buff_r.mtype);
	
	return 0;
}

示例2:利用父子进程,实现父进程操作消息队列,发送信息给子进程

现象:

 代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
struct msgbuf
{
	long mtype;    //--->消息的类型---指的就是消息编号 1 
	char mtext[20];  //---->消息的数据
};
int main(int argc,char* argv[])
{
	key_t key;
	int msgid,ret;
	struct msgbuf buff_w={3,0};
	//获取键值
	key = ftok("1.txt",4);
	printf("key:%x\r\n",key);
	
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);
	
	//创建父子进程
	ret = fork();
	if(ret == 0)//子进程
	{
		struct msgbuf buff_r;
		//接收数据信息
		msgrcv(msgid, &buff_r, sizeof(buff_r), 3,0);
		printf("buff_r:%s\r\n",buff_r.mtext);
		printf("mtype:%ld\r\n",buff_r.mtype);
	}
	else//父进程
	{
		scanf("%s",buff_w.mtext);
		//发送数据信息
		ret = msgsnd(msgid,&buff_w,sizeof(buff_w), 0);
		if(ret == 0)
		printf("send success\r\n");
	}
}

 msgctl

函数功能:

        反应消息队列状态信息的

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/msg.h>

函数原型:

        int msgctl(int msqid, int cmd, struct msqid_ds *buf);

函数参数:

        int msqid消息队列描述符,是通过 msgget() 函数获得的消息队列标识符。

        int cmd:表示消息队列的操作模式

                        IPC_STAT(查看状态)  IPC_SET(设置)  IPC_RMID(删除)

        struct msqid_ds *buf: IPC_RMID buf里面填写为NULL  

        IPC_SET:buf用来设置消息队列

函数返回值:

成功 返回0   

失败 返回-1

示例:可以将对应的消息队列,删除掉。

现象:

代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
struct msgbuf
{
	long mtype;    //--->消息的类型---指的就是消息编号 1 
	char mtext[20];  //---->消息的数据
};
int main(int argc,char* argv[])
{
	key_t key;
	int msgid,ret;
	struct msgbuf buff_w={3,0};
	//获取键值
	key = ftok("1.txt",4);
	printf("key:%x\r\n",key);
	
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);
	
	//创建父子进程
	ret = fork();
	if(ret == 0)//子进程
	{
		struct msgbuf buff_r;
		//接收数据信息
		msgrcv(msgid, &buff_r, sizeof(buff_r), 3,0);
		printf("buff_r:%s\r\n",buff_r.mtext);
		printf("mtype:%ld\r\n",buff_r.mtype);
		
		//退出子进程
		exit(0);
	}
	else//父进程
	{
		scanf("%s",buff_w.mtext);
		//发送数据信息
		ret = msgsnd(msgid,&buff_w,sizeof(buff_w), 0);
		if(ret == 0)
		printf("send success\r\n");
	}
	
	//等待子进程执行完毕
	wait(NULL);
	//删除消息队列
	ret = msgctl(msgid, IPC_RMID,NULL);
	if(ret == 0)
	printf("remove success\r\n");
	
	return 0;
}
综合实例 

综合:利用进程A、进程B和进程C,操作消息队列,实现自由通信。
思路:   1: 进程A给对应的进程B发送信息的时候,信号的编号是1 
              2: 进程A给对应的进程C发送信息的时候,信号的编号是2
              3: 进程B给对应的进程A发送信息的时候,信号的编号是3
              4: 进程B给对应的进程C发送信息的时候,信号的编号是4
              5: 进程C给对应的进程A发送信息的时候,信号的编号是5
              6: 进程C给对应的进程B发送信息的时候,信号的编号是6

现象:

进程A发数据给进程B和进程C

进程B发数据给进程A和进程C

进程C发数据给进程A和进程B

代码:

进程A

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
struct msgbuf
{
	long mtype;    //--->消息的类型---指的就是消息编号 1 
	char mtext[20];  //---->消息的数据
};
int main(int argc,char* argv[])
{
	key_t key;
	int ret;
	//获取键值
	key = ftok("3.txt",4);
	printf("key:%x\r\n",key);
	
	int msgid;
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);
	
	//创建父子进程
	ret = fork();
	if(ret == 0)//子进程
	{
		struct msgbuf buff_r;
		while(1)
		{
			//接收数据信息
			ret = msgrcv(msgid, &buff_r, sizeof(buff_r),3,IPC_NOWAIT);//非阻塞,如果没有消息立即返回-1
			if(ret == -1)
			{
				;
			}
			else
			{
				printf("ret:%d\r\n",ret);
				printf("B_to_A:%s\r\n",buff_r.mtext+3);
				printf("mtype:%ld\r\n",buff_r.mtype);
			}
		
			//接收数据信息
			ret = msgrcv(msgid, &buff_r, sizeof(buff_r),5,IPC_NOWAIT);//非阻塞,如果没有消息立即返回-1
			if(ret == -1)
			{
				;
			}
			else
			{
				printf("ret:%d\r\n",ret);
				printf("C_to_A:%s\r\n",buff_r.mtext+3);
				printf("mtype:%ld\r\n",buff_r.mtype);
			}
			//清空数组
			memset(&buff_r,0,sizeof(buff_r));
		}
		
		//退出子进程
		exit(0);
	}
	else//父进程
	{
		struct msgbuf buff_1={0};
		
		while(1)
		{
			scanf("%[^\n]",buff_1.mtext);//可以忽略空格
			getchar();
			
			if(strncmp(buff_1.mtext,"toB",3)==0)
			{
				//给对应变量赋值
				buff_1.mtype = 1;
				
				//发送数据信息
				ret = msgsnd(msgid,&buff_1,sizeof(buff_1),0);
				if(ret == 0)
				printf("buff1 send success\r\n");
			}
			else if(strncmp(buff_1.mtext,"toC",3)==0)
			{
				//给对应变量赋值
				buff_1.mtype = 2;
				
				//发送数据信息
				ret = msgsnd(msgid,&buff_1,sizeof(buff_1), 0);
				if(ret == 0)
				printf("buff1 send success\r\n");
				
			}
			else if(strcmp("quit",buff_1.mtext)==0)
			{
				break;
			}
			else
			{
				printf("input toB or toC in the beginning\r\n");
			}
			//清空数组
			memset(&buff_1.mtext,0,sizeof(buff_1.mtext));
		}
	}
	//等待子进程执行完毕
	wait(NULL);
	//删除消息队列
	ret = msgctl(msgid, IPC_RMID,NULL);
	if(ret == 0)
	printf("remove success\r\n");
	
	return 0;
}

进程B 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
struct msgbuf
{
	long mtype;    //--->消息的类型---指的就是消息编号 1 
	char mtext[20];  //---->消息的数据
};
int main(int argc,char* argv[])
{
	key_t key;
	int ret;
	//获取键值
	key = ftok("3.txt",4);
	printf("key:%x\r\n",key);
	
	int msgid;
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);
	
	//创建父子进程
	ret = fork();
	if(ret == 0)//子进程
	{
		struct msgbuf buff_r;
		
		while(1)
		{
			//接收数据信息
			ret = msgrcv(msgid, &buff_r, sizeof(buff_r),1,IPC_NOWAIT);//非阻塞,如果没有消息立即返回-1
			if(ret == -1)
			{
				;
			}
			else
			{
				printf("ret:%d\r\n",ret);
				printf("A_to_B:%s\r\n",buff_r.mtext+3);
				printf("mtype:%ld\r\n",buff_r.mtype);
			}
		
			//接收数据信息
			ret = msgrcv(msgid, &buff_r, sizeof(buff_r),6,IPC_NOWAIT);//非阻塞,如果没有消息立即返回-1
			if(ret == -1)
			{
				;
			}
			else
			{
				printf("ret:%d\r\n",ret);
				printf("C_to_B:%s\r\n",buff_r.mtext+3);
				printf("mtype:%ld\r\n",buff_r.mtype);
			}
			//清空数组
			memset(&buff_r,0,sizeof(buff_r));
		}
		
		//退出子进程
		exit(0);
	}
	else//父进程
	{
		struct msgbuf buff_2={0};
		
		while(1)
		{
			scanf("%[^\n]",buff_2.mtext);//可以忽略空格
			getchar();
			
			if(strncmp(buff_2.mtext,"toA",3)==0)
			{
				//给对应变量赋值
				buff_2.mtype = 3;
				
				//发送数据信息
				ret = msgsnd(msgid,&buff_2,sizeof(buff_2),0);
				if(ret == 0)
				printf("buff2 send success\r\n");
				
				
			}
			else if(strncmp(buff_2.mtext,"toC",3)==0)
			{
				//给对应变量赋值
				buff_2.mtype = 4;
				
				//发送数据信息
				ret = msgsnd(msgid,&buff_2,sizeof(buff_2), 0);
				if(ret == 0)
				printf("buff2 send success\r\n");
				
			}
			else if(strcmp("quit",buff_2.mtext)==0)
			{
				break;
			}
			else
			{
				printf("input toA or toC in the beginning\r\n");
			}
			//清空数组
			memset(&buff_2.mtext,0,sizeof(buff_2.mtext));
		}
	}
	//等待子进程执行完毕
	wait(NULL);
	//删除消息队列
	ret = msgctl(msgid, IPC_RMID,NULL);
	if(ret == 0)
	printf("remove success\r\n");
	
	return 0;
}

进程C

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
struct msgbuf
{
	long mtype;    //--->消息的类型---指的就是消息编号 1 
	char mtext[20];  //---->消息的数据
};
int main(int argc,char* argv[])
{
	key_t key;
	int ret;
	//获取键值
	key = ftok("3.txt",4);
	printf("key:%x\r\n",key);
	
	int msgid;
	//获取对应消息队列的ID号
	msgid = msgget(key,IPC_CREAT|0664);
	printf("msgid:%d\r\n",msgid);
	
	//创建父子进程
	ret = fork();
	if(ret == 0)//子进程
	{
		struct msgbuf buff_r;
		
		while(1)
		{
			//接收数据信息
			ret = msgrcv(msgid, &buff_r, sizeof(buff_r),2,IPC_NOWAIT);//非阻塞,如果没有消息立即返回-1
			if(ret == -1)
			{
				;
			}
			else
			{
				printf("ret:%d\r\n",ret);
				printf("A_to_C:%s\r\n",buff_r.mtext+3);
				printf("mtype:%ld\r\n",buff_r.mtype);
			}
		
			//接收数据信息
			ret = msgrcv(msgid, &buff_r, sizeof(buff_r),4,IPC_NOWAIT);//非阻塞,如果没有消息立即返回-1
			if(ret == -1)
			{
				;
			}
			else
			{
				printf("ret:%d\r\n",ret);
				printf("B_to_C:%s\r\n",buff_r.mtext+3);
				printf("mtype:%ld\r\n",buff_r.mtype);
			}
			//清空数组
			memset(&buff_r,0,sizeof(buff_r));
		}
		
		//退出子进程
		exit(0);
	}
	else//父进程
	{
		struct msgbuf buff_3={0};
		
		while(1)
		{
			scanf("%[^\n]",buff_3.mtext);//可以忽略空格
			getchar();
			
			if(strncmp(buff_3.mtext,"toA",3)==0)
			{
				//给对应变量赋值
				buff_3.mtype = 5;
				
				//发送数据信息
				ret = msgsnd(msgid,&buff_3,sizeof(buff_3),0);
				if(ret == 0)
				printf("buff3 send success\r\n");
				
				
			}
			else if(strncmp(buff_3.mtext,"toB",3)==0)
			{
				//给对应变量赋值
				buff_3.mtype = 6;
				
				//发送数据信息
				ret = msgsnd(msgid,&buff_3,sizeof(buff_3), 0);
				if(ret == 0)
				printf("buff3 send success\r\n");
				
			}
			else if(strcmp("quit",buff_3.mtext)==0)
			{
				break;
			}
			else
			{
				printf("input toA or toB in the beginning\r\n");
			}
			//清空数组
			memset(&buff_3.mtext,0,sizeof(buff_3.mtext));
			
		}
	}
	//等待子进程执行完毕
	wait(NULL);
	//删除消息队列
	ret = msgctl(msgid, IPC_RMID,NULL);
	if(ret == 0)
	printf("remove success\r\n");
	
	return 0;
}

 

五、信号量

1、定义

        信号量(semaphore)是一种实现任务间通信的机制,可以实现任务之间同步(约束)或临界资源的互斥访问,常用于协助一组相互竞争的任务来访问临界资源。

        本质:信号量值的改变是通过函数来实现的加减,阻塞原因是函数。

2、PV操作

操作信号量的方式,只有两种:申请信号量 释放信号量

P(等待/减)操作:当一个进程或线程需要访问共享资源时,它会尝试执行 P 操作。如果信号量的值大于 0,则进程可以继续访问资源,并将信号量的值减 1;如果信号量的值等于 0,则进程会被阻塞,直到信号量的值变为正数。

V(释放/加)操作:当一个进程或线程完成对共享资源的访问时,它会执行 V 操作,将信号量的值加 1。如果有其他等待进程被阻塞,它们中的一个将被唤醒并获得对资源的访问权限。

3、通信流程

①创建.txt文件与一个项目id,生成唯一的标识符key--->ftok()

②基于这个标识符(key)创建一个新的信号量集或访问一个已存在的信号量集,从而获得信号量集标识符semid--->semget()

③利用信号量集标识符semid与命令来控制对应的信号量--->semctl()

④利用信号量集标识符semid对信号量的值进行P(等待)和V(信号)操作--->semop()

4、相关函数

semget

函数功能:

        用于创建并且打开信号量集,信号量的值默认值为0

头文件:

        #include <sys/ipc.h>

        #include <sys/sem.h>

函数原型:

        int semget(key_t key, int nsems, int semflg);

函数参数:

        key_t key:这是一个用于标识信号量集的键值。多个进程可以通过相同的键值来访问同一个信号量集。通常,这个键是通过 ftok() 函数生成的。

        int nsems:这个参数指定了信号量集中信号量的数量。信号量集中的每个信号量都可以独立地被操作和等待。

         int semflg:这个参数是一个标志位,用于控制信号量集的创建和访问权限。它可以是 0(表示访问已存在的信号量集)、IPC_CREAT(表示如果信号量集不存在则创建它)、IPC_EXCL(与 IPC_CREAT 一起使用,表示如果信号量集已存在则调用失败)的组合,以及权限标志(如 0666,表示赋予所有用户读写权限)。

函数返回值:

        成功 返回信号量集id

        失败 返回-1

使用:

 semget(key,1,IPC_CREAT | 0666);

 semget(key,1,IPC_EXCL | 0666); 

示例:利用semget函数,观察一下对应的现象

现象:

代码:

#include <sys/ipc.h>
#include <stdio.h>
#include <sys/sem.h>

int main(int argc,char* argv[])
{
	key_t key,semid;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,1,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	return 0;
}

semctl

函数功能:

        用来操作控制对应的信号量

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/sem.h>

函数原型:

        int semctl(int semid, int semnum, int cmd, union semun arg);

函数参数:

        int semid:信号量集的标识符,由semget()函数返回

        int semnum:信号量集中的信号量编号,从0开始

        int cmd:指定要执行的命令,它决定了后面的参数和semctl()的行为.

                cmd命令的一些常见值包括:

                IPC_STAT:获得信号量的semid_ds数据结构,并存放在由第4个参数arg中buf成员中,semid_ds是系统中描述信号量的数据结构

                IPC_RMID:从系统中删除信号量。

                IPC_GETVAL或GETVAL: 获取对应编号的信号量的值

                IPC_SETVAL或SETVAL:设置对应编号的信号量的值

                GETALL:获取所有信号量的值 (获取的时候第二个参数为0)   

                SETALL:设置所有信号量的值(获取的时候第二个参数为0)   

        union semun arg:根据cmd的不同,这个参数可以是不同的类型。

函数返回值:

        GETVAL:成功返回对应信号量的值

        其他的cmd: 成功 返回0

        错误 返回-1 

注意:信号量集里面信号量的值默认都是0;IPC_RMID、GETVAL不需要用到第四个参数

 union semun {

               int val;    /* Value for SETVAL */

               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */

               unsigned short *array;  /* Array for GETALL, SETALL */

               struct seminfo *__buf;  /* Buffer for IPC_INFO

                                           (Linux-specific) */

           };

  struct semid_ds {

               struct ipc_perm sem_perm;   包含了信号量集的权限和所有权信息

               time_t  sem_otime; 最后一次对信号量集进行semop()操作的时间戳

               time_t  sem_ctime; 最后一次改变信号量集(通过semctl()的IPC_SET命令等)的时间戳

               unsigned long  sem_nsems;信号量集中信号量的数量

           };

  struct ipc_perm {

               key_t key; 创建IPC对象时使用的键值

               uid_t uid; IPC对象当前所有者的有效用户ID

               gid_t gid; IPC对象当前所有者的组ID

               uid_t cuid;创建IPC对象时的有效用户ID

               gid_t cgid;创建IPC对象时的组ID

               unsigned short mode;IPC对象的权限模式,类似于文件系统的权限模式

               unsigned short __seq;序列号

           };

 说明:

如果我们要用到第四个参数,第四个参数传参的的时候,给共用体变量名即可,然后你要设置信号量集中哪个信号量的值的话,在共用体变量中赋值好,然后再调用semctl函数进行设置即可。

如我要设置信号量集中的第1个元素(编号为0)为4的话:

union semun sembuf;

memset(&sembuf, 0, sizeof(sembuf));

sembuf.val = 4;

int ret = semctl(semid, 0, SETVAL, sembuf);

示例1:利用semctl函数删除对应的信号量集

现象:

 代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/sem.h>
int main(int argc,char* argv[])
{
	key_t key,semid;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,1,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	
	//利用semctl函数删除信号量集
	int ret = semctl(semid,0,IPC_RMID);
	if(ret == 0)
	printf("RMID success\r\n");
	
	return 0;
}

示例2:利用semctl函数,先设置信号量的值,再获取信号量的当前值

现象:

 代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/sem.h>
//声明共用体
union semun{
               int val;    /*Value for SETVAL*/
               unsigned short *array;  /*Array for GETALL, SETALL*/
           };
int main(int argc,char* argv[])
{
	key_t key,semid;
	int ret,num;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,5,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	//定义共用体
	union semun semval;
	semval.val = 66;
	
	//利用semctl函数,先设置信号量的值
	ret = semctl(semid,2,SETVAL,semval);
	if(ret==0)
	printf("SET success\r\n");
	
	//再利用semctl函数获取信号量的当前值
	num = semctl(semid,2,GETVAL);
	printf("num:%d\r\n",num);
	
    //利用semctl函数删除信号量集
	int ret = semctl(semid,0,IPC_RMID);
	if(ret == 0)
	printf("RMID success\r\n");

	
	return 0;
}

示例3:设置所有信号量的值,再获取所有信号量的值,观察所有信号量的值

现象:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/sem.h>
//声明共用体
union semun{
               int val;    /*Value for SETVAL*/
               unsigned short *array;  /*Array for GETALL, SETALL*/
           };
int main(int argc,char* argv[])
{
	key_t key,semid;
	int ret,num;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,5,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	//定义共用体
	union semun semall;
	
	//定义设置数组
	unsigned short buff_w[]={1,2,3,4,5};
	//将需要设置的数值传入到共用体
	semall.array = buff_w;
	//利用semctl函数,设置所有信号量的值
	ret = semctl(semid,0,SETALL,semall);
	if(ret==0)
	printf("SETALL success\r\n");

	//定义获取数组
	unsigned short buff_r[10]={0};
	//将需要获取到的数值传入到数组
	semall.array = buff_r;
	//利用semctl函数,再获取所有信号量的值
	ret = semctl(semid,0,GETALL,semall);
	if(ret==0)
	{
		printf("GETALL success\r\n");
		for(int i =0;i<10;i++)
		{
			printf("buff_r[%d]=%d\r\n",i,buff_r[i]);
		}
	}
	
	//利用semctl函数删除对应的信号量集
	ret = semctl(semid,0,IPC_RMID);
	if(ret == 0)
	printf("RMID success\r\n");
	return 0;
}

示例4:利用semctl函数,调用IPC_STAT命令,观察信号量的状态信息。

现象:

代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/sem.h>
//声明共用体
union semun{
               int val;    /*Value for SETVAL*/
			   	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short *array;  /*Array for GETALL, SETALL*/
           };
int main(int argc,char* argv[])
{
	key_t key,semid;
	int ret,num;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,5,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	//定义共用体
	union semun semall;
	
	//定义设置数组
	unsigned short buff_w[]={1,2,3,4,5};
	//将需要设置的数值传入到共用体
	semall.array = buff_w;
	//利用semctl函数,设置所有信号量的值
	ret = semctl(semid,0,SETALL,semall);
	if(ret==0)
	printf("SETALL success\r\n");

	//定义获取数组
	unsigned short buff_r[10]={0};
	//将需要获取到的数值传入到数组
	semall.array = buff_r;
	//利用semctl函数,再获取所有信号量的值
	ret = semctl(semid,0,GETALL,semall);
	if(ret==0)
	{
		printf("GETALL success\r\n");
		for(int i =0;i<10;i++)
		{
			printf("buff_r[%d]=%d\r\n",i,buff_r[i]);
		}
	}
	
	//定义结构体变量
	struct semid_ds semsta = {0};
	//将需要获取到的数值传入到结构体内
	semall.buf = &semsta;
	//调用IPC_STAT命令,观察信号量的状态信息
	ret = semctl(semid,0,IPC_STAT,semall);
	if(ret == 0)
	{
		printf("STAT success\r\n");
		printf("semsta.sem_perm.key:%x\r\n",semsta.sem_perm. __key);//创建IPC对象时使用的键值
		printf("semsta.sem_perm.mode:%d\r\n",semsta.sem_perm.mode);//IPC对象的权限模式
		printf("semsta.sem_nsems:%ld\r\n",semsta.sem_nsems);//信号量集中信号量的数量
	}
	
	
	//利用semctl函数删除对应的信号量集
	ret = semctl(semid,0,IPC_RMID);
	if(ret == 0)
	printf("RMID success\r\n");
	return 0;
}

示例5:利用semctl函数,调用IPC_SET命令修改权限观察信号量的状态信息。

现象:

代码: 

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/sem.h>
//声明共用体
union semun{
               int val;    /*Value for SETVAL*/
			   	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short *array;  /*Array for GETALL, SETALL*/
           };
int main(int argc,char* argv[])
{
	key_t key,semid;
	int ret,num;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,5,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	//定义共用体
	union semun semall;
	
	//定义设置数组
	unsigned short buff_w[]={1,2,3,4,5};
	//将需要设置的数值传入到共用体
	semall.array = buff_w;
	//利用semctl函数,设置所有信号量的值
	ret = semctl(semid,0,SETALL,semall);
	if(ret==0)
	printf("SETALL success\r\n");

	//定义获取数组
	unsigned short buff_r[10]={0};
	//将需要获取到的数值传入到数组
	semall.array = buff_r;
	//利用semctl函数,再获取所有信号量的值
	ret = semctl(semid,0,GETALL,semall);
	if(ret==0)
	{
		printf("GETALL success\r\n");
		for(int i =0;i<10;i++)
		{
			printf("buff_r[%d]=%d\r\n",i,buff_r[i]);
		}
	}
	

	/*查看状态*/
	//定义结构体变量
	struct semid_ds semsta = {0};
	//将需要获取到的数值传入到结构体内
	semall.buf = &semsta;
	//调用IPC_STAT命令,观察信号量的状态信息
	ret = semctl(semid,0,IPC_STAT,semall);
	if(ret == 0)
	{
		printf("STAREAD success\r\n");
		printf("semsta.sem_perm.key:%x\r\n",semsta.sem_perm. __key);//创建IPC对象时使用的键值
		printf("semsta.sem_perm.mode:%d\r\n",semsta.sem_perm.mode);//IPC对象的权限模式
		printf("semsta.sem_nsems:%ld\r\n",semsta.sem_nsems);//信号量集中信号量的数量
	}
	
	/*设置状态*/
	//定义结构体变量
	struct semid_ds setsta = {0};
	setsta.sem_perm.mode = 0664;//修改权限
	//将需要设置的数值传入到共用体
	semall.buf = &setsta;
	//调用IPC_SET命令,观察信号量的状态信息
	ret = semctl(semid,0,IPC_SET,semall);
	if(ret == 0)
	{
		printf("STASET  success\r\n");
	}
	
	/*查看状态*/
	//将需要获取到的数值传入到结构体内
	semall.buf = &semsta;
	//调用IPC_STAT命令,观察信号量的状态信息
	ret = semctl(semid,0,IPC_STAT,semall);
	if(ret == 0)
	{
		printf("STAREAD success\r\n");
		printf("semsta.sem_perm.key:%x\r\n",semsta.sem_perm. __key);//创建IPC对象时使用的键值
		printf("semsta.sem_perm.mode:%d\r\n",semsta.sem_perm.mode);//IPC对象的权限模式
		printf("semsta.sem_nsems:%ld\r\n",semsta.sem_nsems);//信号量集中信号量的数量
	}
	
	
	//利用semctl函数删除对应的信号量集
	ret = semctl(semid,0,IPC_RMID);
	if(ret == 0)
	printf("RMID success\r\n");
	return 0;
}

semop

函数功能:

        对信号量的值进行加减操作

头文件:

        #include <sys/types.h>

        #include <sys/ipc.h>

        #include <sys/sem.h>

函数原型:

        int semop(int semid, struct sembuf *sops, unsigned nsops);

函数参数:

        int semid:信号量集的标识符,由semget()函数返回

        struct sembuf *sops :指向 sembuf 结构数组的指针,每个 sembuf 结构定义了对信 号量集中的一个信号量的操作。

struct sembuf

{

unsigned short sem_num;   /* 信号量在信号量集中的编号*/

short sem_op;   /* 信号量变化值 PV操作正数代表+,负数代表-*/

short sem_flg;  /* 一组标志 直接给0 */

}

说明:

sem_flg:信号量操作的属性标志

(1)如果为0,表示正常操作,如果信号量值为0,减操作就会阻塞

(2)如果为IPC_NOWAIT,表示对信号量的操作时非阻塞的。在信号量的值不满足条件的   情况下不会被阻塞,而是直接返回-1

(3)如果为SEM_UNDO,那么将维护进程对信号量的调整值,以便进程结束时恢复信号量   的状态。--- 暂时先不用

semop() 函数的工作方式如下:

根据 sops 数组中每个 sembuf 结构指定的信号量索引 (sem_num)、操作值 (sem_op) 和操作标志 (sem_flg),对信号量集中的信号量进行相应的操作。

如果 sem_op 为正数,表示释放相应数量的资源,信号量值增加。

如果 sem_op 为负数,表示请求相应数量的资源,如果信号量值足够,则减少信号量值并继续执行;如果信号量值不足,则根据 sem_flg 的设置,可能会阻塞等待资源释放。

如果 sem_op 为 0,通常用于测试信号量值,不会改变信号量值。

        unsigned nsops :sops 数组中 sembuf 结构的数量,即要进行的操作数量

说明:

        sizeof(结构体数组名) / sizeof(结构体数据类型名)即结构体数组中的元素数量

        struct sembuff a[10];   10 = sizeof(a)/sizeof(a[0])

        struct sembuff a[10]表示定义了一个名为 a 的数组,该数组包含10个元素,每个元素都是 struct sembuff 类型

函数返回值:

        成功 返回0

        失败 返回-1

示例1:调用一下semop,操作一个信号量,第一个信号量进行V操作,观察对应的现象。

现象:

代码:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/sem.h>
//声明共用体
union semun{
               int val;    /*Value for SETVAL*/
			   	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short *array;  /*Array for GETALL, SETALL*/
           };
int main(int argc,char* argv[])
{
	key_t key,semid;
	int ret,num;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,5,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	//定义共用体
	union semun semall;
	
	//定义设置数组
	unsigned short buff_w[]={1,2,3,4,5};
	//将需要设置的数值传入到共用体
	semall.array = buff_w;
	//利用semctl函数,设置所有信号量的值
	ret = semctl(semid,0,SETALL,semall);
	if(ret==0)
	printf("SETALL success\r\n");

	//定义获取数组
	unsigned short buff_r[5]={0};
	//将需要获取到的数值传入到数组
	semall.array = buff_r;
	//利用semctl函数,再获取所有信号量的值
	ret = semctl(semid,0,GETALL,semall);
	if(ret==0)
	{
		printf("GETALL success\r\n");
		for(int i =0;i<5;i++)
		{
			printf("buff_r[%d]=%d\r\n",i,buff_r[i]);
		}
	}
	
	//定义结构体变量-第一个信号量进行V操作
	struct sembuf op[1] = {0,+10,0};
	
	//调用semop,对信号量的值进行加减操作
	ret = semop(semid, op, sizeof(op)/sizeof(op[0]));
	if(ret == 0)
	{
	   printf("OP success\r\n");
	}
	
	//定义获取数组
	unsigned short buff[5]={0};
	//将需要获取到的数值传入到数组
	semall.array = buff;
	//利用semctl函数,再获取所有信号量的值
	ret = semctl(semid,0,GETALL,semall);
	if(ret == 0)
	{
		 //查看信号量的值是否进行加减操作
	    for(int i =0;i<5;i++)
		{
			printf("buff[%d]=%d\r\n",i,buff[i]);
		}
	}
	
	//利用semctl函数删除对应的信号量集
	ret = semctl(semid,0,IPC_RMID);
	if(ret == 0)
	printf("RMID success\r\n");
	return 0;
}

示例2:利用semop函数,操作所有的信号量的值,获取所有信号量的值,观察一下

现象:

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <pthread.h>
#include <sys/sem.h>
//声明共用体
union semun{
               int val;    /*Value for SETVAL*/
			   	struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short *array;  /*Array for GETALL, SETALL*/
           };
int main(int argc,char* argv[])
{
	key_t key,semid;
	int ret,num;
	//获取键值
	key = ftok("./5.txt",1);
	printf("key=%d\r\n",key);
	
	//创建并且打开信号量集
	semid = semget(key,5,IPC_CREAT | 0666);
	printf("semid=%d\r\n",semid);
	
	//定义共用体
	union semun semall;
     
	/*设置所有信号量的值--命令:SETALL*/
	//定义设置数组
	unsigned short buff_w[]={1,2,3,4,5};
	//将需要设置的数值传入到共用体
	semall.array = buff_w;
	//利用semctl函数,设置所有信号量的值
	ret = semctl(semid,0,SETALL,semall);
	if(ret==0)
	printf("SETALL success\r\n");

	/*获取所有信号量的值--命令:GETALL*/
	//定义获取数组
	unsigned short buff_r[5]={0};
	//将需要获取到的数值传入到数组
	semall.array = buff_r;
	//利用semctl函数,再获取所有信号量的值
	ret = semctl(semid,0,GETALL,semall);
	if(ret==0)
	{
		printf("GETALL success\r\n");
		for(int i =0;i<5;i++)
		{
			printf("buff_r[%d]=%d\r\n",i,buff_r[i]);
		}
	}
	
	/*对信号量进行PV操作*/
	//定义结构体变量-第一个信号量进行PV操作
	struct sembuf op[5] = {{0,+10,0},{1,+5,0},{2,-2,0},{3,-5,0},{4,+1,0}};
	//调用semop,对信号量的值进行加减操作
	ret = semop(semid, op, sizeof(op)/sizeof(op[0]));
	if(ret == 0)
	{
	   printf("OP success\r\n");
	}
	
	/*获取所有信号量的值--命令:GETALL*/
	//定义获取数组
	unsigned short buff[5]={0};
	//将需要获取到的数值传入到数组
	semall.array = buff;
	//利用semctl函数,再获取所有信号量的值
	ret = semctl(semid,0,GETALL,semall);
	if(ret == 0)
	{
		 //查看信号量的值是否进行加减操作
	    for(int i =0;i<5;i++)
		{
			printf("buff[%d]=%d\r\n",i,buff[i]);
		}
	}
	
	//利用semctl函数删除对应的信号量集
	ret = semctl(semid,0,IPC_RMID);
	if(ret == 0)
	printf("RMID success\r\n");
	return 0;
}

 


网站公告

今日签到

点亮在社区的每一天
去签到