Linux学习(二十):进程间通信

目录

 

1 进程间通信

2、管道通信

2.1 无名管道

     2.1.1 无名管道的创建

2.2 有名管道

2.2.1 创建有名管道mkfifo

3 信号通信

3.1 信号发送函数kill

kill例程:杀死一个进程

3.2 信号设置signal()

4、IPC通信介绍

    4.1 创建键值函数ftok()

        4.2 IPC通信步骤

        4.3 IPC相关shell命令

5、消息队列

        5.1 创建或打开消息队列

        5.2 发送消息

        5.3 接收消息

       5.4 消息队列控制函数

6、共享内存

       6.1 创建共享内存

       6.2 映射共享内存

        6.3 撤销共享内存

        6.3 共享内存控制

7、信号灯

    7.1 创建信号量

     7.2 信号量控制

    7.3 操作信号量


1 进程间通信

        在Linux学习(十八):进程概念及创建中我们将fork函数中,我们说到子进程是父进程的一个复制品,它从父进程处继承了整个进程的地址空间。包括进程上下文、代码段、进程堆栈、内存信息、打开的文件描述符、信号处理函数、进程优先级、进程组号、当前工作目录、根目录、资源限制和控制终端等等,而子进程所独有的只有它的进程号、资源使用和计时器等。那是否可以直接使用全局变量来通信呢?我们来看一个例子

#include <stdio.h>
#include <unistd.h>

int x;

int main(int argc, const char *argv[])
{
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork error");
		return -1;
	}
	else if(pid == 0)
	{
		//子进程 
		x = 5;
		printf("child  &x = %p, x = %d\n",&x,x);
	}
	else
	{
		//父进程 
		sleep(2);
		printf("parent &x = %p, x = %d\n",&x,x);
	}

	while(1);
		
	return 0;
}

程序执行结果

 

        可以看到,虽然两个进程中x的地址是一样的,但是在父进程中,x的值并没有改变。这是为何?这是因为这个地址只是虚拟地址,并不是真正的物理地址,所以虽然就会出现这样的结果,也说明进程间通信并不能使用全局变量的方式。

        UNIX平台通信方式,早期进程间通信方式
        AT&T的贝尔实验室,对Unix早期的进程间通信进行了改进和扩充,形成了“system V IPC”,其通信进程主要局限在单个计算机内。
        BSD(加州大学伯克利分校的伯克利软件发布中心),跳过了该限制,形成了基于套接字(socket)的进程间通信机制。Linux继承了上述所有的通信方式。

        常用的进程间通信方式

        1、传统的进程间通信方式

        无名管道(pipe)、有名管道(fifo)和信号(signal)

        2、System V IPC对象

        共享内存(share memory)、消息队列(message queue)和信号灯(semaphore)

        3、BSD

        套接字(socket)

2、管道通信

     管道是Linux中进程通信的一种方式,它把一个程序的输出直接连接到另一个程序的输入,Linux的管道主要包括两种:无名管道和有名管道。

2.1 无名管道

      无名管道具有如下特点:       

      1、只能用于具有亲缘关系的进程之间的通信

      2、半双工的通信模式,具有固定的读端和写端。

      3、管道可以看成是一种特殊的文件,对于它的读写可以使用文件IO(不能使用标准IO)如read、write函数。无名管道并不是普通的文件,不属于任何文件系统,并且只存在于内存中。也就是在文件系统中不可见的。

     2.1.1 无名管道的创建

管道是基于文件描述符的通信方式。当一个管道建立时,它会创建两个文件描述符fd[0]和fd[1]。其中fd[0]固定

用于读管道,而fd[1]固定用于写管道。

创建函数pipe

 

        无名管道例程:从终端读取输入,写入管道中,然后再从管道中读出,并输出到终端中,写入quit时两个进程退出。

/*无名管道:用于具有亲缘关系的父子进程间通信*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

#if 0
int pipe(int pipefd[2]);
功能:创建无名管道
参数:pipefd[0]作为读端 pipefd[1]作为写端
返回:成功返回0,失败-1
注意:只能使用文件IO,read/write
#endif

#define N 32

int main(int argc, const char *argv[])
{
	char buf[N];
	int fd[2];
	//创建无名管道
	if(pipe(fd) == -1)
	{
		perror("pipe error");  		//fd[0]:读端  fd[1]:写端
		exit(1);
	}

	//创建子进程
	pid_t pid = fork();
	if(pid == -1)
	{
		perror("fork error");
		exit(1);
	}
	else if(pid == 0)
	{
		//子
		while(1)
		{
			//从终端获取数据
			fgets(buf, N, stdin);
			//写到管道中
			write(fd[1], buf, N);
			//字符串比较
			if(strncmp(buf, "quit", 4) == 0)
			{
				exit(0);
			}
		}
	}
	else
	{
		//父
		while(1)
		{
			//从管道读取数据
			read(fd[0], buf, N);
			//字符串比较
			if(strncmp(buf, "quit", 4) == 0)
			{
				exit(0);
			}
			//打印到终端
			printf("---> ");
			fputs(buf, stdout);
		}
	}
	return 0;
}



 

        在创建管道要注意,要在fork()函数之前创建;若在fork()函数之后再创建,那每个进程都会创建管道,也就无法正常使用了。

        当管道中无数据时,读操作会阻塞。

        向管道中写入数据时,linux将不保证写入的原子性,管道缓冲区一有空闲区域,写进程就会试图向管道写入数据。如果读进程不读走管道缓冲区中的数据,那么写操作将会一直阻塞。

        只有在管道的读端存在时,向管道中写入数据才有意义。否则,向管道中写入数据的进程将收到内核传来的SIGPIPE信号(通常Broken pipe错误)。

2.2 有名管道

有名管道(FIFO)是对无名管道的改进,具有如下特点:

     1、它可以使互不相关的两个进程实现彼此通信

     2、该管道可以通过路径名来实现,并且在文件中可见的。在建立管道后,两个进程就可以把它当做普通文件一样进程读写操作。当然这里要明白的,它的文件属性是管道文件p,在Linux学习(四):Linux文件系统及其shell命令中讲了Linux文件的七种类型,创建的管道属于管道文件。

   3、FIFO遵循先进先出原则,不支持lseek操作。

   4、对于读进程,若当前FIFO中无数据,会一直阻塞到有数据写入或FIFO写入端关闭

   5、对于写进程,只要FIFO有空间就可写入。若空间不足,写进程就会阻塞,直到有空间为止

2.2.1 创建有名管道mkfifo

    创建FIFO的出错信息

        对于错误码errno的使用,可以参考Linux学习(十六):文件IO。对于mkfifo()函数,EEXIST是比较常见的错误信息,表明FIFO已经创建,直接使用即可(这里可以理解为一个正常信息)。

有名管道例程:一个进程负责从终端读取数据,并写入进程,另一个进程从管道读取数据并向终端发送数据,若终端数据quit,两进程退出。

写管道:

/*有名管道:可以用于没有关系的进程间通信,先进先出规则*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h> 
#include <string.h> 

#if 0
int mkfifo(const char *pathname, mode_t mode);
功能:创建一个有名管道文件
参数:pathname:指定创建文件的路径文件名
	  mode    :文件权限(mode & ~umask)
返回:成功返回0,失败返回-1
注意:管道文件不会重复创建,所以已存在是允许发生的错误
#endif

#define N 32

int main(int argc, const char *argv[])
{
	if(mkfifo("fifo", 0664) == -1)
	{
		//已存在
		if(errno == EEXIST)
		{
			puts("fifo exist");
		}
		else
		{
			perror("mkfifo error");
			exit(1);
		}
	}

	int fd_w = open("fifo", O_RDWR);
	if(fd_w == -1)
	{
		perror("open error");
		exit(1);
	}

	//从终端读取数据,写到管道内,遇到"quit"退出
	char buf[N];
	while(1)
	{
		fgets(buf, N, stdin);
		write(fd_w, buf, N);
		if(strncmp(buf, "quit", 4) == 0)
		{
			break;
		}
	}

	close(fd_w);
	return 0;
}


读管道:

/*有名管道:可以用于没有关系的进程间通信,先进先出规则*/

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <string.h> 

#if 0
int mkfifo(const char *pathname, mode_t mode);
#endif

#define N 32

int main(int argc, const char *argv[])
{
	if(mkfifo("fifo", 0664) == -1)
	{
		//已存在
		if(errno == EEXIST)
		{
			puts("exist");
		}
		else
		{
			perror("mkfifo error");
			exit(1);
		}
	}

	int fd_r = open("fifo", O_RDWR);
	if(fd_r == -1)
	{
		perror("open error");
		exit(1);
	}

	//从管道读取数据,打印到终端,遇到"quit"不输出并退出
	char buf[N];
	while(1)
	{
		read(fd_r, buf, N);
		if(strncmp(buf, "quit", 4) == 0)
		{
			break;
		}
		fputs(buf, stdout);
	}

	close(fd_r);

	return 0;
}

任意执行一个进程后,会创建一个fifo文件,文件属性为p

 

两个进程的执行结果

 

3 信号通信

        信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式

        信号可以直接进行用户空间进程和内核进程之间的交互,内核进程也可以利用它来通知用户空间进程发生了哪些系统事件。

        如果该进程当前并未处于执行态,则该信号就由内核保存起来,直到该进程恢复执行再传递给它;如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程 。

        信号事件的产生有硬件来源和软件来源,常用的信号相关函数有kill()、alarm()、setitimer()、sigqueue()。

        进程可以通过3种方式来相应信号

        (1)忽略信号,对信号不做任何处理,SIGKILL和SIGSTOP两个信号不能忽略

        (2)捕捉信号,定义信号处理函数,当信号发生时,执行相应操作

        (3)执行默认操作。默认操作如下所示

 

 

3.1 信号发送函数kill

        kill函数同读者熟知的kill系统命令一样,可以发送信号给进程或进程组(实际上,kill系统命令只是kill函数的一个

用户接口)。

        kill –l 命令查看系统支持的信号列表

       raise函数允许进程向自己发送信号

 

kill函数

kill例程:杀死一个进程

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

#if 0
int kill(pid_t pid, int sig);
功能:给某一个进程或进程组发送信号sig
参数:sig:要发送的信号
	  pid:pid > 0:给指定的进程发送信号
		   pid = 0:给同组下的所有进程发送信号(进程本身)
		   pid =-1:给所有进程发送信号(除了1号init进程)
		   pid <-1:给指定组下的所有进程发送信号,PGIG=|pid|
返回:成功返回0,失败-1

atoi
功能:将数值型字符串转化成整数
#endif

int main(int argc, const char *argv[]) 	//argv[0] argv[1]
{
	if(kill(atoi(argv[1]), SIGKILL) == -1)
	{
		perror("kill error");
		exit(1);
	}

	puts("kill ok!");
	
	return 0;
}

执行结果


 

我们将进程号2778的while进程(一个空的while(1))杀死

 

进程在收到SIGKILL信号后会在终端打印Killed。

3.2 信号设置signal()

signal()函数语法要点

所需头文件:#include<signal.h>

函数原型: typedef void(* sighandler_t)(int);

                 sighandler_t signal(int signum,sighandler_t handler)

参数:       参数1:指定信号

                 参数2:SIG_IGN 忽略该信号,其中SIGKILL和SIGSTOP不能忽略

                             SIG_DEFL 采用默认方式处理指定信号

                             自定义的信号处理函数,(sighandler_t 是一个重定义的函数指针,函数要求返回值为空,参数                                为int)

返回值:   成功:信号处理函数地址

                出错:SIG_ERR

例程:

 

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>

#if 0
typedef void (*sighandler_t)(int);

sighandler_t signal(int signum, sighandler_t handler);
功能:捕捉一个指定的信号,并作相应的处理
参数:signum :要捕捉的信号
	  handler:SIG_IGN 忽略
	  		   SIG_DFL 执行默认操作
			   fun 	   执行相应的函数代码
返回:失败返回SIG_ERR
注意:SIGKILL和SIGSTOP不能被捕捉
#endif

void fun(int sig)
{
	if(sig == SIGINT)
	{
		puts("catch SIGINT");
	}

	if(sig == SIGTSTP)
	{
		puts("catch SIGTSTP");
	}
}

int main(int argc, const char *argv[])
{
	if(signal(SIGINT, fun) == SIG_ERR) 				//ctrl+C     SIGINT 
	{
		perror("signal error");
		exit(1);
	}

	if(signal(SIGTSTP, fun) == SIG_ERR) 			//ctrl+\     SIGQUIT
	{
		perror("signal error");
		exit(1);
	}

	if(signal(SIGQUIT, SIG_DFL) == SIG_ERR) 			//ctrl+Z     SIGTSTP
	{
		perror("signal error");
		exit(1);
	}

	puts("signal");
	while(1);
	
	return 0;
}

执行结果


 

我们用的ctrl+c和ctrl+z都不能正常结束程序了。而是执行相应的函数

4、IPC通信介绍

        以上三种属于传统进程间通信方式,下面介绍消息队列、共享内存和信号灯,这属于System V IPC方式。我们也称这三种通信方式为IPC对象通信。

       三种通信方式都需要一个标识符,共享内存标识符、消息队列标识符、信号灯标识符。 标识符只是IPC对象的内部名,如果多个进程需要在同一个IPC对象上进行回合通信,需要一个外部名。为此,使用了键值 (key值)。

    4.1 创建键值函数ftok()

头文件:       #include <sys/types.h>
               #include <sys/ipc.h>
函数原型:     key_t ftok(const char *pathname, int proj_id);

参数:          参数1:路径名 参数2 任意一个非0整形数据

返回值:      成功:返回键值,失败:-1

        4.2 IPC通信步骤

        对于消息队列、共享内存和信号灯通信来讲,第一步是打开或创建IPC通道。第二步就是去操作相应的IPC对象。部分函数是相近的(如打开或创建IPC通道函数,控制函数),方便记忆和使用。

        4.3 IPC相关shell命令

        1.ipcs命令用于查看系统中的IPC对象 ipcs -q,ipcs -m,ipcs -s 查看消息队列/共享内存/信号灯

        2.ipcrm命令用于删除系统中的IPC对象

       ipcrm -M shmkey  移除用shmkey创建的共享内存段
ipcrm -m shmid    移除用shmid标识的共享内存段
ipcrm -Q msgkey  移除用msqkey创建的消息队列
ipcrm -q msqid  移除用msqid标识的消息队列
ipcrm -S semkey  移除用semkey创建的信号
ipcrm -s semid  移除用semid标识的信号

 

 

5、消息队列

        消息队列是IPC对象的一种。消息队列由消息队列ID来唯一标识。消息队列就是一个消息的列表。用户可以在消息队列中添加消息、读取消息等。消息队列可以按照类型来发送/接收消息。我们之前学的管道通信和消息队列比较类似,但消息队列可以区别不同的消息(如消息类型1,消息类型2),管道中所有数据没有类别区分。

        5.1 创建或打开消息队列

        参数flag和open()函数中的flag类似。IPC_CREAT标志为创建一个先的IPC对象(若存在则不创建新的),IPC_EXCL也是创建一个新的IPC对象,若存在返回出错。后面再或上创建的IPC对象的权限,同样可用八进制表示。

        5.2 发送消息

        5.3 接收消息

       5.4 消息队列控制函数

例程:

发送消息文件,发送三个消息

/*消息队列:*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#if 0
int msgget(key_t key, int msgflg);
功能:创建并打开一个消息队列
返回:成功返回消息队列的ID(非负数),失败-1

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送一条消息
参数:msqid :消息队列的ID
	  msgp  :发送消息的地址
	  msgsz :消息中消息信息的大小
	  msgflg:0阻塞,IPC_NOWAIT非阻塞模式

查看:ipcs -q
删除:ipcrm -q ID号
#endif

struct msgbuf{
	long mtype; 	//消息类型(>0)
	int a;
	float b;
	char c;
};

#define N sizeof(struct msgbuf) - sizeof(long)

int main(int argc, const char *argv[])
{
	//产生一个key值
	key_t key = ftok(".", 1);

	//创建并打开消息队列
	int msgid = msgget(key, IPC_CREAT|0664);
	if(msgid == -1)
	{
		perror("msgget error");
		exit(1);
	}

	//发送消息 
	struct msgbuf msgbuf;
	msgbuf.mtype = 1;
	msgbuf.a = 10;
	msgbuf.b = 12.34;
	msgbuf.c = 'A';
	msgsnd(msgid, &msgbuf, N, 0);

	msgbuf.mtype = 2;
	msgbuf.a = 20;
	msgbuf.b = 22.34;
	msgbuf.c = 'B';
	msgsnd(msgid, &msgbuf, N, 0);

	msgbuf.mtype = 3;
	msgbuf.a = 30;
	msgbuf.b = 32.34;
	msgbuf.c = 'C';
	msgsnd(msgid, &msgbuf, N, 0);

	return 0;
}

 

接收消息:接收消息类型为3的消息

/*消息队列:*/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>

#if 0
int msgget(key_t key, int msgflg);
功能:创建并打开一个消息队列
返回:成功返回消息队列的ID(非负数),失败-1

int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
功能:向消息队列发送一条消息
参数:msqid :消息队列的ID
	  msgp  :发送消息的地址
	  msgsz :消息中消息信息的大小
	  msgflg:0阻塞,IPC_NOWAIT非阻塞模式

ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
功能:接收消息
参数:msgtyp:=0 接收第一条消息
			  >0 接收类型为msgtyp的第一条消息
			  <0 接收小于等于|msgtyp|中最小的第一条数据
	  msgflg:0阻塞,IPC_NOWAIT非阻塞模式

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
功能:删除消息队列、获取属性信息、设置属性
参数:cmd:IPC_RMID  IPC_STAT   IPC_SET
返回:成功返回0  失败返回-1

查看:ipcs -q
删除:ipcrm -q ID号
#endif

struct msgbuf{
	long mtype; 	//消息类型(>0)
	int a;
	float b;
	char c;
};

#define N sizeof(struct msgbuf) - sizeof(long)

int main(int argc, const char *argv[])
{
	//产生一个key值
	key_t key = ftok(".", 1);

	//创建并打开消息队列
	int msgid = msgget(key, IPC_CREAT|0664);
	if(msgid == -1)
	{
		perror("msgget error");
		exit(1);
	}

	//接收消息 
	struct msgbuf msgbuf;
	msgrcv(msgid, &msgbuf, N, 3, 0);
	printf("a = %d, b = %.2f, c = %c\n",msgbuf.a, msgbuf.b, msgbuf.c);

	//删除消息队列
	system("ipcs -q");
	msgctl(msgid, IPC_RMID, NULL);
	system("ipcs -q");
	
	return 0;
}


执行结果:

 

6、共享内存

        共享内存是一种最为高效的进程间通信方式,进程可以直接读写内存,而不需要任何数据的拷贝。为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一内存区而不需要进行数据的拷贝,从而大大提高的效率。由于多个进程共享一段内存,因此也需要依靠某种同步机制,如互斥锁和信号量等 。

共享内存我们可以类比成普通程序中的全局变量,全局变量对于所有函数都是可见的,而共享内存对于多个进程都是可操作的,当然需要有一定的步骤,如下:

1、创建/打开共享内存

 

2、映射共享内存,即把指定的共享内存映射到进程的地址空间用于访问

 

3、撤销共享内存映射

 

4、删除共享内存对象

       6.1 创建共享内存

 

       6.2 映射共享内存

 

        6.3 撤销共享内存

        6.3 共享内存控制

7、信号灯

        信号灯主要用于进程间的同步,对于信号量,我们可以用停车场外面指示牌简单类比一下,停车场是一个公用的资源,但能停的车是有限的,比如一个停车场最多可以停10辆车;停车场的指示牌最初显示是10,这是车辆可以进入停车场,每次进一辆指示牌就减一,当指示牌显示为0的时候,外面的车就不能再进入停车场,也就是不能占用这项资源。但此时如果有停车场内的车出来了,指示牌就可以加1,停车场这个资源又可以使用了。

     信号灯使用步骤

     1、创建信号量

     2、初始化信号量

     3、进行信号量PV操作,P操作也就是占用一个资源,信号量减1。V操作,释放一个资源,信号量加1。

     4、删除信号量

    7.1 创建信号量

     所需头文件:#include <sys/types.h>
                #include <sys/ipc.h>
                #include <sys/sem.h>

     函数原型:int semget(key_t key, int nsems, int semflg);
     功能:创建并打开信号灯
     参数:key:键值;nsems:指定信号灯中信号量的个数。semflg:权限
     返回:成功返回ID,失败-1

     7.2 信号量控制

     int semctl(int semid, int semnum, int cmd, union semun);
     参数:semid 信号量ID

           semnum:信号灯中信号量的编码(从0开始)

           cmd   :IPC_RMID(删除)  IPC_STAT(获取信号量状态)  IPC_SET
             SETVAL(设置信号量值)    GETVAL(获取信号量值)
           semun :使用SETVAL使用第四个参数
  

    7.3 操作信号量

     int semop(int semid, struct sembuf *sops, unsigned nsops);
     功能:执行PV操作
     参数:nsops:指定同时操作信号量的个数

           sops :

                 unsigned short sem_num;  //信号量的编码
                 short          sem_op;   //正数执行V操作,负数执行P操作
                 short          sem_flg;  //0阻塞,IPC_NOWAIT非阻塞
     返回:成功返回0,失败-1

例程:获取终端输入的数据,放入共享内存中,再打印出来。通过信号灯进行同步。

读取终端数据程序

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h> 

#define N 32

union semun{
	int val;
};

int main(int argc, const char *argv[])
{
	//key值
	key_t key = ftok(".", 1);

	/***************** 共享内存 ********************/
	//创建共享内存
	int shmid = shmget(key, N, IPC_CREAT|0664);
	if(shmid == -1)
	{
		perror("shmget error");
		exit(1);
	}
	//映射
	char *p = NULL;
	p = (char *)shmat(shmid, NULL, 0);


	/****************** 信号灯 *********************/
	//创建信号灯
	int semid = semget(key, 1, IPC_CREAT|0664);
	if(semid == -1)
	{
		perror("semget error");
		exit(1);
	}
	//设置信号量值
	union semun semun;
	semun.val = 0;
	semctl(semid, 0, SETVAL, semun);

	//V操作(增加)
	struct sembuf sembuf;
	sembuf.sem_num = 0; 			//信号量编码
	sembuf.sem_op  = 1; 			//增加信号量值
	sembuf.sem_flg = 0; 			//阻塞模式


	/*******************  通信 **********************/
	while(1)
	{
		//从终端获取数据写到共享内存
		fgets(p, N, stdin);
		//执行V操作(增加)
		semop(semid, &sembuf, 1);
		if(strncmp(p ,"quit",4) == 0)
		{
			break;
		}
	}

	//解除映射
	shmdt(p);

	return 0;
}

从共享内存获取数据,并打印到终端中去

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/sem.h>
#include <string.h> 

#define N 32

int main(int argc, const char *argv[])
{
	//key值
	key_t key = ftok(".", 1);

	/***************** 共享内存 ********************/
	//创建共享内存
	int shmid = shmget(key, N, IPC_CREAT|0664);
	if(shmid == -1)
	{
		perror("shmget error");
		exit(1);
	}
	//映射
	char *p = NULL;
	p = (char *)shmat(shmid, NULL, 0);

	/****************** 信号灯 *********************/
	//创建信号灯
	int semid = semget(key, 1, IPC_CREAT|0664);
	if(semid == -1)
	{
		perror("semget error");
		exit(1);
	}
	//P操作(减少)
	struct sembuf sembuf;
	sembuf.sem_num = 0; 			//信号量编码
	sembuf.sem_op  = -1; 			//增加信号量值
	sembuf.sem_flg = 0; 			//阻塞模式


	/*******************  通信 **********************/
	while(1)
	{
		//执行P操作(减少)
		semop(semid, &sembuf, 1);
		if(strncmp(p, "quit", 4) == 0)
		{
			break;
		}
		//从共享内存读取数据打印到终端
		fputs(p, stdout);
	}


	/*******************  删除 **********************/
	//解除映射
	shmdt(p);

	//删除共享内存
	shmctl(shmid, IPC_RMID, NULL);
	system("ipcs -m");

	//删除信号灯
	semctl(semid, 0, IPC_RMID);
	system("ipcs -s");

	return 0;
}


 

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值