Linux 进程间通信方式(管道、命名管道、消息队列、信号量、共享内存、套接字)

Linux 进程间通信方式(管道、命名管道、消息队列、信号量、共享内存、套接字),第1张

概述Linux 进程间通信方式(管道、命名管道、消息队列信号量、共享内存、套接字)

什么是进程?

在linux系统中,进程是管理事务的基本的过程。进程拥有自己独立的处理环境和系统资源。进程整个生命可以简单划分为三种状态:

就绪态:
进程已经具备执行的一切条件,正在等待分配cpu的处理时间。
执行态:

该进程正在占用cpu运行。
等待态:
进程因不具备某些执行条件而暂时无法执行的状态。

进程间通信概念

进程是一个独立的资源分配单元,不同进行之间的资源是独立的,不能在一个进程中直接访问另一个进程的资源。所以不同的进程需要进行信息的交互状态的传递等,因此需要进程间通信。liNUX常见的进程间通信如下:
1.管道
2.命名管道
3.消息队列
4.信号量
5.共享内存
6.套接字

1~5都是同一个主机进程间通信。序号6是不同主机(网络)进程间通信;

管道

管道又称无名管道。是一种古老的IPC通信形式,管道的作用正如其名,需要通信的两个进程在管道的两端;管道是一种特殊类型的文件,存在于内核的缓冲区。管道有如下的特点:
1.半双工,数据不能在两段上传数据,数据只能在一个方向流动。
2.管道不是普通的文件,不属于某个文件系统,只存在于内存中。
3.管道没有名字,只能在亲缘关系的父子进程之间通信。

pID_t fork(voID);

fork()函数得到的子进程是父进程的复制品,它从父进程处继承了整个进程的地址空间。

返回值

当成功完成时,fork()将返回0给子进程,并将子进程的进程ID返回给父进程。这两个进程都应该继续从fork()函数执行。否则,-1将返回给父进程,不创建子进程,并将errno设置为指示错误。

int pipe(int fildes[2]);

pipe()函数应该创建一个管道,并将两个文件描述符放入参数fildes[0]和fildes[1]中,每个文件描述符都引用管道读写端打开的文件描述。
返回值
成功完成后,返回0;否则,将返回-1并设置errno表示错误,不分配任何文件描述符,不修改fildes的内容。

fork函数执行结果

举个简答例子,使用了pipe()函数来创建管道,通过无名管道通信:

#include <stdio.h>#include <unistd.h>//pipe , fork#include <sys/types.h>#include <string.h>//memsetint main(int argc, char const *argv[]){		int fd_pipe[2];//描述符,0表示为读,1表示为写	pID_t pID = 0;	char buf[128] = "minger";	int n = 0;	if (pipe(fd_pipe) < 0)	{		perror("pipe");		return -1;	}	pID = fork();	if ( pID < 0)	{		perror("fork");		return -1;	}	else if (pID > 0) //父进程	{		close(fd_pipe[1]);//关闭管道的写端		memset(buf,0,sizeof(buf));		n = read(fd_pipe[0],buf,sizeof(buf)); //从管道读出数据		printf("Read %d from the pipe: %sn",n,buf);	}	else //子进程	{		close(fd_pipe[0]);//关闭管道的读端		write(fd_pipe[1], buf, strlen(buf)); //向管道写入数据	}	return 0;}

编译结果:


在程序中,利用无名管道实现进程通信。子进程向管道写入字符串,父进程从管道中读取字符串。

命名管道

命名管道与管道不同的是,命名管道允许没有亲缘关系的进程进行通信和不相关的进程也能够进行数据交换。

int mkfifo(const char *path, mode_t mode);

mkfifo实用程序应该按照指定的顺序创建由 *** 作数指定的FIFO特殊文件。

参数:

path:文件 *** 作数用作路径参数。
mode:模式参数

写一个小例子测试一下,命名管道通信,创建两个.c文件,读进程和写进程。

读进程例子:

#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <errno.h>#include<fcntl.h>#include <string.h>int main(int argc, char const *argv[]){	int fd_read;	char buf[128] = {0};	if (mkfifo("FIFO",S_IRUSR|S_IWUSR) != 0); //0666, 0777	{		perror("mkfifo");		//return -1;	}	printf("before openn");	fd_read = open("FIFO",O_RDONLY); //只读方式打开	if (fd_read < 0)	{		perror("open");		return-1;	}	printf("after openn");	memset(buf,0,sizeof(buf));	read(fd_read,buf,sizeof(buf));	printf("read from FIFO buf: %sn",buf);	return 0;}

写进程例子:

#include <stdio.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <errno.h>#include<fcntl.h>#include <string.h>int main(int argc, char const *argv[]){	int fd_write;	char buf[128] = "minger";	if (mkfifo("FIFO",S_IRUSR|S_IWUSR) != 0); // 0666,0777	{		perror("mkfifo");		//return -1;	}	fd_write = open("FIFO",O_WRONLY); //只读方式打开	if (fd_write < 0)	{		perror("open");		return-1;	}	write(fd_write,buf,strlen(buf));	printf("Write from FIFO buf: %sn",buf);	return 0;}

打开两终端,一个终端先运行写进程,然后运行读进程,编译结果如下:


运行结果可以看到,两个没有亲缘关系的进程可以通过FIFO进行通信。在终端下,可以通过ls -alh,查看FIFO文件是否是命名管道文件;如果p开头表示是管道。

消息队列

消息队列是消息的链表,存储在内核中。一个消息队列由一个标识符(即队列ID)来标识。

消息队列的特点

1.消息队列中的消息是有类型和格式的
2.消息队列可以实现消息的随机查询。消息不一定要以先进先出次序读取。可以按消息的类型读取。
3.每个消息队列都有消息队列的标识符,消息队列的标识符在整个系统中是唯一的
4.消息队列允许一个或者多个进程向它写入或者读取消息。

函数原型

创建消息队列

int msgget(key_t key, int msgflg);

功能:

创建或打开消息队列。

参数:

key:IPC键值
msgfig:标识消息队列是否存在。并且(msgflg & IPC_CREAT)是非零的。
IPC_CREAT:创建消息队列是否存在

返回值
成功完成后,msgget()将返回一个非负整数,即消息队列标识符。
否则,它将返回-1并,表示错误。

发送消息

int msgsnd(int msqID, const voID *msgp, size_t msgsz, int msgflg);

功能:

添加消息队列

参数:

msqID:消息队列的标识符
msgp:待发送的消息队列的地址
msgsz:消息队列正文的字节数
msgflg:如果(msgflg & IPC_NowAIT)非零,则不发送消息,调用线程应立即返回。
如果(msgflg & IPC_NowAIT)为0,调用线程将暂停执行;

返回值

成功完成后,msgsnd()返回0;
否则,将不发送任何消息,msgsnd()将返回-1,并将errno设置为指示错误。

消息队列的消息格式

struct mymsg {    long   mtype;       //消息类型.    char   mtext[1];    //消息文本。};

结构成员mtype是一种非零正长类型,可由接收流程用于消息选择。
结构成员mtext是长度为msgsz字节的任何文本。参数msgsz的范围可以从0到系统设置的最大值。

接收消息

ssize_t msgrcv(int msqID, voID *msgp, size_t msgsz, long msgtyp,int msgflg);

功能:

读取消息

参数:

msqID:消息队列的标识符。
msgp: 存放消息结构体的地址。
msgsz:消息正文的字节数。
msgtyp:消息类型,有几种类型:
如果msgtyp为0,则接收队列上的第一个消息。
如果msgtyp大于0,则接收msgtyp类型的第一条消息。
如果msgtyp小于0,则接收小于或等于msgtyp绝对值的最低类型的第一条消息。

msgflg:如果(msgflg & IPC_NowAIT)非零,调用线程应立即返回,返回值为-1;
如果(msgflg & IPC_NowAIT)为0,调用线程将暂停执行。

返回值
成功完成后,msgrcv()将返回一个与实际放入缓冲区mtext中的字节数相等的值。
否则,将不接收任何消息,msgrcv()将返回-1,并将errno设置为指示错误。

消息队列的控制

int msgctl(int msqID, int cmd, struct msqID_ds *buf);
功能:

控制消息队列,如修改消息队列的属性,或者删除消息队列等。
参数:

msqID:消息队列的标识符
cmd:函数的控制功能
buf:msqID_ds 数据类型的地址

返回值
成功完成后,msgctl()返回0;
否则,它将返回-1并,表示错误。

获得IPC键值

key_t ftok(const char *path, int ID);
功能:
获得唯一的IPC键值
参数:
path:路径名
ID:项目ID
返回值
成功完成后,ftok()将返回一个密钥。
否则,ftok()将返回(key_t)-1,表示错误。

有了这些函数原型,接下来就写消息队列简单例子:一个任务任务负责发送消息,另一个任务负责接收,通过ftok()创建子进程实现多任务。

message_read.c

#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <sys/ipc.h>#include <sys/msg.h>#include <string.h>#include <stdlib.h>typedef struct msg{	long mtype; //消息类型	char ntext[128];//消息正文}MSG;int main(int argc, char const *argv[]){	key_t key;	int msgqID;	MSG my_msg;		if ((key = ftok(".",2019)) == -1)	{		perror("ftok");		exit(-1);	}	printf("message queue key: %dn",key );	if ((msgqID = msgget(key,IPC_CREAT|0666)) == -1)//打开消息队列	{		perror("message");		exit(-1);	}	printf("msgqID: %dn",msgqID);//打印消息队列ID	msgrcv(msgqID,&my_msg,sizeof(my_msg.ntext),111,0);	printf("my_msg.ntext= %sn",my_msg.ntext);	msgctl(msgqID,IPC_RMID,NulL);//删除由msgqID指示的消息队列	return 0;}

message_write.c

#include <stdio.h>#include <sys/types.h>#include <unistd.h>#include <sys/ipc.h>#include <sys/msg.h>#include <string.h>#include <stdlib.h>typedef struct msg{	long mtype; //消息类型	char ntext[128];//消息正文}MSG;int main(int argc, char const *argv[]){	key_t key;	int msgqID;	MSG my_msg;		if ((key = ftok(".",2019)) == -1)	{		perror("ftok");		exit(-1);	}	printf("message queue key: %dn",key );	if ((msgqID = msgget(key,IPC_CREAT|0666)) == -1)//打开消息队列	{		perror("message");		exit(-1);	}	printf("msgqID: %dn",msgqID);//打印消息队列ID	my_msg.mtype = 111;	strcpy(my_msg.ntext,"hello minger");	msgsnd(msgqID,&my_msg,sizeof(my_msg.ntext),0);		return 0;}

刚开始编译的时候没有运行,查看消息队列 ipcs -q,显示已用字节数为 0,消息为0


编译结果

信号量

信号量是一个计数量,它主要用在多个进程需要对共享数据进行访问的时候。用于进程或线程间的同步和互斥。那么借助信号量就可以完成这样的事情。

流程及特点
*** 作系统的P *** 作就是上锁,V *** 作就是解锁
如果信号量值大于0,则资源可用,并且将其减1,表示当前已被使用
如果信号量值为0,则进程休眠直至信号量值大于0

函数原型

信号初始化

int sem_init(sem_t *sem, int pshared, unsigned value);

功能:
创建一个信号量并初始化她的值。

参数:

sem:信号量的地址
pshared:如果pshared=0,则信号量在线程之间共享;pshared不等于于0,信号量在进程间共享。
value:信号量的初始值;

返回值

成功完成后,sem_init()函数将在sem中初始化信号量并返回0。
否则,它将返回-1并,表示错误。

信号的p *** 作

int sem_wait(sem_t *sem);

功能:
通过对sem引用的信号量执行信号量锁 *** 作来锁定信号量。如果信号量值当前为0,那么调用线程将不会从调用返回到sem_wait(),直到锁定信号量或调用被信号中断。

参数:
sem:信号量的地址
返回值:
成功返回0,否则返回 -1;

信号量V *** 作

int sem_post(sem_t *sem);

功能:
通过对sem引用的信号量执行信号量解锁 *** 作来解锁该信号量。将信号量的值加1,并发出唤醒解锁该信号量。

参数
sem:信号量的地址

返回值:
如果成功,sem_post()函数将返回0;
否则,函数将返回-1,表示错误。

信号量实现互斥

#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h>sem_t sem;voID display(char *pstr){	sem_wait(&sem); //上锁	if (pstr == NulL)		return ;	while (*pstr != ' ')	{		putchar(*pstr); //每次只处理一个字符		fflush(stdout);//往屏幕中打印字符		pstr ++;		sleep(1);	}	sem_post(&sem);//解锁}voID *thread_task1(voID *argv){	char *pstr = "abcdef";	display(pstr);}voID *thread_task2(voID *argv){	char *pstr = "ghijkl";	display(pstr);}int main(int argc, char const *argv[]){	pthread_t tID1,tID2;	if (sem_init(&sem,0,1) != 0)  //0表示是线程,1表示初始化sem值为1	{		perror("sem_init");		return 0;	}	//线程创建	if (pthread_create(&tID1,NulL,thread_task1,NulL) != 0)	{		perror("pthread_create");		return 0;	} 	if (pthread_create(&tID2,NulL,thread_task2,NulL) != 0)	{		perror("pthread_create");		return 0;	} 	//线程等待	pthread_join(tID1,NulL);	pthread_join(tID2,NulL);	printf("nDonen");	return 0;}

运行结果:


编译时候用到了线程 所以要加上 -lpthread,结果为什么不是abcdefghijk呢,这得看cpu调度算法;

信号量实现同步

#include <stdio.h> #include <pthread.h> #include <unistd.h> #include <semaphore.h>sem_t sem1,sem2;char ch = 'a';voID *pthread_task1(voID *argv){	while (1)	{		sem_wait(&sem1);		ch ++;		sleep(1);		sem_post(&sem2);	}}voID *pthread_task2(voID *argv){	while (1)	{		sem_wait(&sem2);		putchar(ch);		fflush(stdout);		sem_post(&sem1);	}}int main(int argc, char const *argv[]){	pthread_t tID1,tID2;	if (sem_init(&sem1,0,0) != 0)	{		perror("sem_init");		return 0;	}	if (sem_init(&sem2,0,1) != 0)	{		perror("sem_init");		return 0;	}	if (pthread_create(&tID1,NulL,pthread_task1,NulL) !=0 )	{		perror("pthread_create");		return 0;	}	if (pthread_create(&tID2,NulL,pthread_task2,NulL) !=0 )	{		perror("pthread_create");		return 0;	}	pthread_join(tID1,NulL);	pthread_join(tID2,NulL);	printf("Donen");		return 0;}

输出结果:


验证了信号量实现同步的功能。

共享内存

共享内存运行多个进程共享给定的存储区域。共享内存是进程间共享数据最快的方法。使用共享内存需要注意的是多个进程之间的对一个给定的存储区访问的互斥,需要用到前面所用到的信号量来实现同步与互斥;

函数原型

共享存储的标识符

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

功能:
创建或打开一块共享内存区

参数:
key:IPC键值
size:共享内存段的长度
shmflg:标识函数的共享内存权限
IPC_CREAT:如果不存在就创建 ;
IPC_EXCL:如果已经存在则返回失败;

返回值

成功完成后,shmget()返回一个非负整数,即共享内存标识符;
否则,它将返回-1,表示错误。

共享内存映射

voID *shmat(int shmID, const voID *shmaddr, int shmflg); 

功能:
将一个共享内存段映射到调用进程的数据段中。
将与shmID指定的共享内存标识符相关联的共

总结

以上是内存溢出为你收集整理的Linux 进程间通信方式(管道、命名管道、消息队列、信号量、共享内存、套接字)全部内容,希望文章能够帮你解决Linux 进程间通信方式(管道、命名管道、消息队列、信号量、共享内存、套接字)所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/yw/1013062.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-22
下一篇 2022-05-22

发表评论

登录后才能评论

评论列表(0条)