Linux进程之间的通信

本文介绍了Linux进程间的通信方式,包括无名管道、有名管道、信号机制(如alarm)、消息队列和内存共享。详细讲解了各种通信方式的创建、使用和管理,如如何创建无名和有名管道,如何设置和处理信号,如何使用消息队列进行数据交换,以及如何实现内存共享并解决并发访问问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、管道(无名,有名)

1.管道是单向的、先进先出的,它把一个进程的输出和另一个进程的输入连接在一起。

2.一个进程(写进程)在管道的尾部写入数据,另一个进程(读进程)从管道的头部读出数据

3.数据被一个进程读出后,将被从管道中删除, 进程试图读空管道时,进程将阻塞。

1、无名管道(用于父进程和子进程之间的通信)

    无名管道创建: int pipe(int filedis[2]);    

当一个管道建立时,它会创建两个文件描述符:         

filedis[0] 用于读管道,         filedis[1] 用于写管道

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

void ReadData(int fd)
{
	int ret;
	char buf[32] = {0};

	while (1)
	{
		ret = read(fd, buf, sizeof(buf));
		if (-1 == ret)
		{
			perror("read");
			exit(1);
		}

		if (!strcmp(buf, "bye"))
		{
			break;
		}

		printf("read from pipe : %s\n", buf);

		memset(buf, 0, sizeof(buf));
	}

	close(fd);
}

void WriteData(int fd)
{
	int ret;
	char buf[32] = {0};

	while (1)
	{
		scanf("%s", buf);

		ret = write(fd, buf, strlen(buf));
		if (-1 == ret)
		{
			perror("wirte");
			exit(1);
		}

		if (!strcmp(buf, "bye"))
		{
			break;
		}

		memset(buf, 0, sizeof(buf));
	}

	close(fd);
}

int main()
{
	int ret, fd[2] = {0};
	pid_t pid;

	ret = pipe(fd);   //创建一个无名管道
	if (-1 == ret)
	{
		perror("pipe");
		exit(1);
	}

	pid = fork();    
	if (-1 == pid)
	{
		perror("fork");
		exit(1);
	}
	else if (0 == pid)
	{
		close(fd[1]);       //关闭写端口
		ReadData(fd[0]);    //fd[0]读数据
	}
	else
	{
		close(fd[0]);       //关闭读端口
		int status;
		WriteData(fd[1]);   //fd[1]写数据

		wait(&status);
	}

	return 0;
}

 

2、有名管道(用于任意两个进程之间)

#有名管道的创建:

注意:管道名称为:“fifo.tmp”,此项操作关系到内核,所以需要在home目录或根目录下操作。

创建用于写的文件:

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

int main()
{
	int ret, fd;
	char buf[32] = {0};    //管道写数据

	fd = open("fifo.tmp", O_WRONLY);
	if (-1 == fd)
	{
		perror("open");
		exit(1);
	}

	while (1)
	{
		scanf("%s", buf);
		ret = write(fd, buf, strlen(buf));
		if (-1 == ret)
		{
			perror("read");
			exit(1);
		}

		if (!strcmp(buf, "bye"))
		{
			break;
		}

		memset(buf, 0, sizeof(buf));
	}

	close(fd);

	return 0;
}

创建有名管道并且用于读取:(应先写);

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

int main()
{
	int ret, fd;
	char buf[32] = {0};    //从管道读取数据

	ret = mkfifo("fifo.tmp", 666);  //创建有名管道
	if (ret == -1)
	{
		perror("mkfifo");
		exit(1);
	}

	fd = open("fifo.tmp", O_RDONLY);
	if (-1 == fd)
	{
		perror("open");
		exit(1);
	}

	while (1)
	{
		ret = read(fd, buf, sizeof(buf));
		if (-1 == ret)
		{
			perror("read");
			exit(1);
		}

		if (!strcmp(buf, "bye"))
		{
			break;
		}

		printf("%s\n", buf);

		memset(buf, 0, sizeof(buf));
	}

	close(fd);

	unlink("fifo.tmp");

	return 0;
}

 

3.alarm(闹钟函数)


信号机制是Unix系统中最为古老的进程间通信机制,很多条件可以产生一个信号:
1.当用户按某些按键时,产生信号
2.硬件异常产生信号

#alarm(闹钟):
    int alarm( int seconds)

alarm(1);

1秒之后发一个SIGALRM信号

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

void print(int num)
{
	alarm(1);
	printf("helloworld\n");
}

int main()
{
	alarm(1);
	signal(SIGALRM, print);

	while (1);

	return 0;
}

 

4、消息队列

创建消息队列(msgget)
进程A用来发送数据(msgsnd)
进程B用来接收数据(msgrcv)

ipcs -q :查看所有进程

ipcrm -q -----------:删除进程

创建用于发送的消息队列:

注意由于share文件夹里的权限不够,此项操作关系到内核,所以只能在根目录或home目录里操作

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
 
#define MSGKEY 2048
 
struct msgbuf 
{
 
	long mtype;     /* message type, must be > 0 */
	char mtext[64];  /* message data */
};
 
int main()
{
	struct msgbuf mbuf;
	int ret;
	int msgid;
 
	msgid = msgget( MSGKEY,IPC_CREAT | IPC_EXCL );
	if( -1 == msgid )
	{
		perror("msgget");
		exit(1);
	}
 
	while(1)
	{
		memset(&mbuf, 0, sizeof(mbuf));
 
		mbuf.mtype = 1;
		scanf("%s",mbuf.mtext);
 
		ret = msgsnd( msgid, &mbuf, sizeof(mbuf.mtext), 0 );
		if( -1 == ret )
		{
			perror("msgsnd");
			exit(1);
		}
 
		if( !strcmp(mbuf.mtext,"bye") )
		{
			break;
		}
 
	}
	
	msgctl(msgid, IPC_RMID, NULL);
	return 0;
}

创建用于接收的队列:

#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/msg.h>
#include<stdlib.h>
#include<string.h>
 
#define MSGKEY 2048
 
struct msgbuf 
{
 
	long mtype;     /* message type, must be > 0 */
	char mtext[64];  /* message data */
};
 
int main()
{
	struct msgbuf mbuf;
	int ret;
	int msgid;
 
	msgid = msgget( MSGKEY, 0 );
	if( -1 == msgid )
	{
		perror("msgget");
		exit(1);
	}
 
	while(1)
	{
		memset(&mbuf, 0, sizeof(mbuf));
 
		mbuf.mtype = 1;
 
		ret = msgrcv( msgid, &mbuf, sizeof(mbuf.mtext), 1, 0 );
		if( -1 == ret )
		{
			perror("msgsnd");
			exit(1);
		}
 
		if( !strcmp(mbuf.mtext,"bye") )
		{
			break;
		}
		printf("%s\n",mbuf.mtext);
	}
	
	msgctl(msgid, IPC_RMID, NULL);
	return 0;
}

#通过四个进程实现两个程序可以互相发送信息的功能

注意,此项操作关系到内核,所以需要在home目录或者根目录先进行创建

创建程序

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define MSGKEY   1234

struct msgbuf {
	long mtype;     /* message type, must be > 0 */
	char mtext[64];  /* message data */
};

int main()
{
	struct msgbuf mbuf;
	int ret;

	int msgid = msgget(MSGKEY, IPC_CREAT | IPC_EXCL);
	if (-1 == msgid)
	{
		perror("msgget");
		exit(1);
	}

	pid_t pid = fork();
	if (-1 == pid)
	{
		perror("fork");
		exit(1);
	}
	else if (0 == pid)    //子进程发送
	{
		while (1)
		{
			memset(&mbuf, 0, sizeof(mbuf));
	
			mbuf.mtype = 1;   //消息类型
			scanf("%s", mbuf.mtext);

			ret = msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);
			if (-1 == ret)
			{
				perror("msgsnd");
				exit(1);
			}

			if (!strcmp(mbuf.mtext, "bye"))
			{
				mbuf.mtype = 2;
				msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);
				break;
			}
		}
	}
	else                 //父进程接收
	{
		while (1)
		{
			memset(&mbuf, 0, sizeof(mbuf));
		
			ret = msgrcv(msgid, &mbuf, sizeof(mbuf.mtext), 2, 0);
			if (-1 == ret)
			{
				perror("msgrcv");
				exit(1);
			}

			if (!strcmp(mbuf.mtext, "bye"))
			{	
				kill(pid, 2);
				break;
			}
			
			printf("\t\t\t%s\n", mbuf.mtext);

			memset(&mbuf, 0, sizeof(mbuf));
		}
	}

	sleep(1);
	msgctl(msgid, IPC_RMID, NULL);

	return 0;
}

接收

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#define MSGKEY   1234

struct msgbuf {
	long mtype;     /* message type, must be > 0 */
	char mtext[64];  /* message data */
};

int main()
{
	struct msgbuf mbuf;
	int ret;

	int msgid = msgget(MSGKEY, 0);
	if (-1 == msgid)
	{
		perror("msgget");
		exit(1);
	}

	pid_t pid = fork();
	if (-1 == pid)
	{
		perror("fork");
		exit(1);
	}
	else if (0 == pid)    //子进程发送
	{
		while (1)
		{
			memset(&mbuf, 0, sizeof(mbuf));
	
			mbuf.mtype = 2;   //消息类型
			scanf("%s", mbuf.mtext);

			ret = msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);
			if (-1 == ret)
			{
				perror("msgsnd");
				exit(1);
			}

			if (!strcmp(mbuf.mtext, "bye"))
			{
				mbuf.mtype = 1;
				msgsnd(msgid, &mbuf, sizeof(mbuf.mtext), 0);
				break;
			}
		}
	}
	else                 //父进程接收
	{
		while (1)
		{
			memset(&mbuf, 0, sizeof(mbuf));
		
			ret = msgrcv(msgid, &mbuf, sizeof(mbuf.mtext), 1, 0);
			if (-1 == ret)
			{
				perror("msgrcv");
				exit(1);
			}

			if (!strcmp(mbuf.mtext, "bye"))
			{
				kill(pid, 2);
				break;
			}
			
			printf("\t\t\t%s\n", mbuf.mtext);

			memset(&mbuf, 0, sizeof(mbuf));
		}
	}

	return 0;
}

 5.内存共享

    是被多个进程共享的一部分物理内存.共享内存是进程间共享数据的一种最快的方法

 

功能和效果:首先运行程序A,那么A中的整形数据count会自加,延迟usleep(1000 000) = 1s,此时在运行程序B(通过A复制而来并进行过删减修改)那么B中的count也会自加。但此时应为共享内存的原因,A和B中的count 会 “你来我往” 的个加1,从而B开始运行时,A会自增加 2个,B也会自增加 2个。并且程序B中的count 会从A 已经打印出的数字之后 开始自加 2,程序A也是如此。

1、申请共享内存 shmget

2、映射 shmat

3、使用 strcpy

4、解除映射 shmdt

5、销毁共享内存 shmctl

程序A:

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
#define SHMKEY  1024
#define SHMSIZE 4096
 
int main()
{
	int shmid;
	void *shmaddr;
	int count = 0;
 
	shmid = shmget( SHMKEY, SHMSIZE, IPC_CREAT | IPC_EXCL );
	if( -1 == shmid )
	{
		perror("shmget");
		exit(1);
	}
	shmaddr = shmat(shmid, NULL, 0);
	if( NULL == shmaddr )
	{
		perror("shmat");
		exit(1);
	}
	
	*(int *)shmaddr = count;
 
	while(1)
	{
		count = *(int *)shmaddr;
		
		if(count >= 100)
		{
			break;
		}
 
		printf("Process A : count = %d\n",count );
	
		count ++;
 
		*(int *)shmaddr = count;
 
		usleep(1000000);
 
	}
 
	shmctl(shmid, IPC_RMID ,0);
 
return 0;
}

程序B:

#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<stdlib.h>
 
#define SHMKEY 1024
#define SHMSIZE 4096
 
int main()
{
	int shmid;
	void *shmaddr;
	int count = 0;
 
	shmid = shmget( SHMKEY, SHMSIZE, 0 );
	if( -1 == shmid )
	{
		perror("shmget");
		exit(1);
	}
 
	shmaddr = shmat(shmid, NULL, 0);
	if( NULL == shmaddr )
	{
		perror("shmat");
		exit(1);
	}
 
	while(1)
	{
		count = *(int *)shmaddr;
		
		if(count >= 100)
		{
			break;
		}
 
		printf("Process B : count = %d\n",count );
	
		count ++;
 
		*(int *)shmaddr = count;
 
		usleep(1000000);
 
	}
 
return 0;
}

 

如果将UNsleep至于 打印或者循环之前,则会出现以下情况;

请在这里注意:

代码 "usleep(1000 000)"的位置。如果将 "usleep(1000 000)" 置于

count = *(int *)shmaddr;

*(int *)shmaddr = count;

之间的话,将不会出现自加 2 的效果。

 

解决方法:引入信号量

#信号量

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前,线程必须获取一个信号量;一旦该关键代码段完成了,那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。为了完成这个过程,需要创建一个信号量VI,然后将Acquire Semaphore VI以及Release Semaphore VI分别放置在每个关键代码段的首末端。确认这些信号量VI引用的是初始创建的信号量。

接下来将 信号量 添加 进置上方代码,但是运行的效果不变。

程序A:

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

#define SHMKEY   1234
#define SHMSIZE  4096       //以页为单位分配共享内存
#define SEMKEY   1234

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) */
};

void sem_p(int semid)
{
	int ret;
	struct sembuf sbuf;

	sbuf.sem_num = 0;      //第一个 从0开始
	sbuf.sem_op = -1;      //p操作
	sbuf.sem_flg = SEM_UNDO;  

	ret = semop(semid, &sbuf, 1);
	if (-1 == ret)
	{
		perror("semop");
		return;
	}
}

void sem_v(int semid)
{
	int ret;
	struct sembuf sbuf;

	sbuf.sem_num = 0;      //第一个 从0开始
	sbuf.sem_op = 1;      //v操作
	sbuf.sem_flg = SEM_UNDO;  

	ret = semop(semid, &sbuf, 1);
	if (-1 == ret)
	{
		perror("semop");
		return;
	}
}

int main()
{
	int shmid, semid, ret;
	void *shmaddr;
	int count = 0;

	shmid = shmget(SHMKEY, SHMSIZE, IPC_CREAT | IPC_EXCL);   //创建共享内存
	if (-1 == shmid)
	{
		perror("shmget");
		exit(1);
	}

	semid = semget(SEMKEY, 1, IPC_CREAT | IPC_EXCL);    //创建信号量
	if (semid == -1)
	{
		perror("semget");
		exit(1);
	}

	union semun unsem;
	unsem.val = 1;    //初始化成二值信号量
	ret = semctl(semid, 0, SETVAL, unsem);    //初始化信号量
	if (-1 == ret)
	{
		perror("semctl");
		exit(1);
	}

	shmaddr = shmat(shmid, NULL, 0);    //映射到虚拟地址空间
	if (NULL == shmaddr)
	{
		perror("shmat");
		exit(1);
	}

	*(int *)shmaddr = count;       //数据写到共享内存

	while (1)
	{
		sem_p(semid);             //p操作  拔钥匙
		count = *(int *)shmaddr;     //读取数据
		usleep(100000);
		if (count >= 100)
		{
			break;
		}

		printf("Process A : count = %d\n", count);

		count++;

		*(int *)shmaddr = count;    //写回共享内存
		sem_v(semid);           //v操作  加一操作  插钥匙
	}

	shmdt(shmaddr);               //解除映射
	shmctl(shmid, IPC_RMID, NULL);

	semctl(semid, 0, IPC_RMID);
	return 0;
}

程序B:

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

#define SHMKEY   1234
#define SHMSIZE  4096       //以页为单位分配共享内存
#define SEMKEY   1234

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) */
};

void sem_p(int semid)
{
	int ret;
	struct sembuf sbuf;

	sbuf.sem_num = 0;      //第一个 从0开始
	sbuf.sem_op = -1;      //p操作
	sbuf.sem_flg = SEM_UNDO;  

	ret = semop(semid, &sbuf, 1);
	if (-1 == ret)
	{
		perror("semop");
		return;
	}
}

void sem_v(int semid)
{
	int ret;
	struct sembuf sbuf;

	sbuf.sem_num = 0;      //第一个 从0开始
	sbuf.sem_op = 1;      //v操作
	sbuf.sem_flg = SEM_UNDO;  

	ret = semop(semid, &sbuf, 1);
	if (-1 == ret)
	{
		perror("semop");
		return;
	}
}

int main()
{
	int shmid, semid, ret;
	void *shmaddr;
	int count = 0;

	shmid = shmget(SHMKEY, SHMSIZE, 0);   //创建共享内存
	if (-1 == shmid)
	{
		perror("shmget");
		exit(1);
	}

	semid = semget(SEMKEY, 1, 0);    //创建信号量
	if (semid == -1)
	{
		perror("semget");
		exit(1);
	}

	shmaddr = shmat(shmid, NULL, 0);    //映射到虚拟地址空间
	if (NULL == shmaddr)
	{
		perror("shmat");
		exit(1);
	}

	while (1)
	{
		sem_p(semid);             //p操作  拔钥匙
		count = *(int *)shmaddr;     //读取数据
		usleep(100000);
		if (count >= 100)
		{
			break;
		}

		printf("Process B : count = %d\n", count);

		count++;

		*(int *)shmaddr = count;    //写回共享内存
		sem_v(semid);           //v操作  加一操作  插钥匙
	}

	shmdt(shmaddr);               //解除映射
	return 0;
}

解决上述问题,结果为

 

shmdt (shmaddr); 解除映射;

shmctl (shmid, IPC_RMID, NULL);  

semctl (semid, 0, IPC_RMID); 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值