(一) pipe匿名管道
管道是Linux中很重要的一种通信方式,是把一个程序的输出直接连接到另一个程序的输入,常说的管道多是指无名管道,无名管道只能用于具有亲缘关系的进程之间,这是它与有名管道的最大区别。管道是Linux支持的最初Unix IPC形式之一,具有以下特点
- 管道是半双工的,数据只能向一个方向流动; 需要双方通信时,需要建立起两个管道
- 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)
- 单独构成一种独立的文件系统: 管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,而是自立门户,单独构成一种文件系统,并且只存在与内存中
- 数据的读出和写入: 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据
无名管道由pipe()函数创建:
#include <unistd.h>
int pipe(int filedis[2]);
参数filedis返回两个文件描述符:filedes[0]为读而打开(此端只能读数据),filedes[1]为写而打开(此端只能写数据)。filedes[1]的输出是filedes[0]的输入。下面的例子示范了如何在父进程和子进程间实现通信。
#define INPUT 0
#define OUTPUT 1
void main()
{
int file_descriptors[2];
/*定义子进程号 */
pid_t pid;
char buf[256];
int returned_count;
/*创建无名管道*/
pipe(file_descriptors);
/*创建子进程*/
if((pid = fork()) == -1) {
printf("Error in fork/n");
exit(1);
}
/*执行子进程*/
if(pid == 0) {
printf("in the spawned (child) process.../n");
/*子进程向父进程写数据,关闭管道的读端*/
close(file_descriptors[INPUT]);
write(file_descriptors[OUTPUT], "test data", strlen("test data"));
exit(0);
} else {
/*执行父进程*/
printf("in the spawning (parent) process.../n");
/*父进程从管道读取子进程写的数据,关闭管道的写端*/
close(file_descriptors[OUTPUT]);
returned_count = read(file_descriptors[INPUT], buf, sizeof(buf));
printf("%d bytes of data received from spawned process: %s/n",
returned_count, buf);
}
}
(二)有名管道FIFO
无名管道应用的一个重大限制是它没有名字,因此,只能用于具有亲缘关系的进程间通信,因此就引入了有名管道FIFO,FIFO提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信.
(1)FIFO的打开规则:
如果当前打开操作是为读而打开FIFO时,若已经有相应进程为写而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为写而打开该FIFO(当前打开操作设置了阻塞标志);或者,成功返回(当前打开操作没有设置阻塞标志)。
如果当前打开操作是为写而打开FIFO时,如果已经有相应进程为读而打开该FIFO,则当前打开操作将成功返回;否则,可能阻塞直到有相应进程为读而打开该FIFO(当前打开操作设置了阻塞标志);或者,返回ENXIO错误(当前打开操作没有设置阻塞标志)。
总之就是一句话,一旦设置了阻塞标志,调用mkfifo建立好之后,那么管道的两端读写必须分别打开,有任何一方未打开,则在调用open的时候就阻塞.
总之就是一句话,一旦设置了阻塞标志,调用mkfifo建立好之后,那么管道的两端读写必须分别打开,有任何一方未打开,则在调用open的时候就阻塞。
从FIFO中读取数据:
约定:如果一个进程为了从FIFO中读取数据而阻塞打开FIFO,那么称该进程内的读操作为设置了阻塞标志的读操作。(意思就是我现在要打开一个有名管道来读数据!)
如果有进程写打开FIFO,且当前FIFO内没有数据(可以理解为管道的两端都建立好了,但是写端还没开始写数据!)
则对于设置了阻塞标志的读操作来说,将一直阻塞(就是block住了,等待数据。它并不消耗CPU资源,这种进程的同步方式对CPU而言是非常有效率的。)
对于没有设置阻塞标志读操作来说则返回-1,当前errno值为EAGAIN,提醒以后再试。
对于设置了阻塞标志的读操作说(见上面的约定)
造成阻塞的原因有两种:
FIFO内有数据,但有其它进程在读这些数据(对于各个读进程而言,这根有名管道是临界资源,大家得互相谦让,不能一起用。)
FIFO内没有数据。解阻塞的原因则是FIFO中有新的数据写入,不论信写入数据量的大小,也不论读操作请求多少数据量。
(2)FIFO读操作
读打开的阻塞标志只对本进程第一个读操作施加作用,如果本进程内有多个读操作序列,则在第一个读操作被唤醒并完成读操作后,其它将要执行的读操作将不再阻塞,即使在执行读操作时,FIFO中没有数据也一样,此时,读操作返回0。
注:如果FIFO中有数据,则设置了阻塞标志的读操作不会因为FIFO中的字节数小于请求读的字节数而阻塞,此时,读操作会返回FIFO中现有的数据量。
(3向FIFO中写入数据:
约定:如果一个进程为了向FIFO中写入数据而阻塞打开FIFO,那么称该进程内的写操作为设置了阻塞标志的写操作。(意思就是我现在要打开一个有名管道来写数据!)
注意:
{1} 对于设置了阻塞标志的写操作:
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果此时管道空闲缓冲区不足以容纳要写入的字节数,则进入睡眠,直到当缓冲区中能够容纳要写入的字节数时,才开始进行一次性写操作。
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。FIFO缓冲区一有空闲区域,写进程就会试图向管道写入数据,写操作在写完所有请求写的数据后返回。
{2} 对于没有设置阻塞标志的写操作:
当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。在写满所有FIFO空闲缓冲区后,写操作返回。
当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。如果当前FIFO空闲缓冲区能够容纳请求写入的字节数,写完后成功返回;如果当前FIFO空闲缓冲区不能够容纳请求写入的字节数,则返回EAGAIN错误,提醒以后再写;
//客户端
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#define MAXLINE 1024
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
void Perror(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
void client(int readfd, int writefd)
{
/* read msg */
int i = 0;
for (i; i<3; i++) {
char buff[MAXLINE] = {0};
int n = read(readfd, buff, MAXLINE);
if (n > 0) {
printf("read from server:%s\n", buff);
}
}
char *buff = "goodby server";
write(writefd, buff, strlen(buff));
}
int main()
{
int readfd, writefd;
/* create two FIFO; OK if they already exist */
if ((mkfifo(FIFO1, 0777) < 0) && (errno != EEXIST))
Perror("can't create FIFO1");
if ((mkfifo(FIFO2, 0777) < 0) && (errno != EEXIST)) {
unlink(FIFO1); /* rm FIFO1 */
Perror("can't create FIFO2");
}
/* 要注意open的顺序 */
writefd = open(FIFO2, O_WRONLY);
readfd = open(FIFO1, O_RDONLY);
client(readfd, writefd);
return 0;
}
//服务器端
#include <stdio.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdlib.h>
#define MAXLINE 1024
#define FIFO1 "/tmp/fifo.1"
#define FIFO2 "/tmp/fifo.2"
void Perror(const char *s)
{
perror(s);
exit(EXIT_FAILURE);
}
void server(int readfd, int writefd)
{
/* send msg */
int i = 0;
for (i; i<3; i++) {
char buff[MAXLINE] = {0};
sprintf(buff, "hello world %d", i);
int n = write(writefd, buff, strlen(buff));
sleep(1);
}
char buff[MAXLINE] = {0};
int n = read(readfd, buff, MAXLINE);
if (n > 0) {
printf("read from client:%s\n", buff);
}
}
int main()
{
int readfd, writefd;
/* create two FIFO; OK if they already exist */
if ((mkfifo(FIFO1, 0777) < 0) && (errno != EEXIST))
Perror("can't create FIFO1");
if ((mkfifo(FIFO2, 0777) < 0) && (errno != EEXIST)) {
unlink(FIFO1); /* rm FIFO1 */
Perror("can't create FIFO2");
}
printf("create fifo success\n");
/* 要注意open的顺序 */
readfd = open(FIFO2, O_RDONLY, 0);
writefd = open(FIFO1, O_WRONLY, 0);
printf("open fifo success\n");
/* 让FIFO在进程结束后自动删除 */
unlink(FIFO1);
unlink(FIFO2);
server(readfd, writefd);
return 0;
}
(三)消息队列
(1)消息队列跟命名管道有不少的相同之处,通过与命名管道一样,消息队列进行通信的进程可以是不相关的进程,同时它们都是通过发送和接收的方式来传递数据的。在命名管道中,发送数据用write,接收数据用read,则在消息队列中,发送数据用msgsnd,接收数据用msgrcv。而且它们对每个数据都有一个最大长度的限制
(2)与命名管道相比,消息队列的优势在于,1、消息队列也可以独立于发送和接收进程而存在,从而消除了在同步命名管道的打开和关闭时可能产生的困难。2、同时通过发送消息还可以避免命名管道的同步和阻塞问题,不需要由进程自己来提供同步方法。3、接收程序可以通过消息类型有选择地接收数据,而不是像命名管道中那样,只能默认地接收。
****************************************发送端**************************************
函数说明:
(1) int msgget(key_t key,int msgflg);
key:需要调用ftok函数来获取.
msgflg:IPC_CREAT,不存在则创建,存在则返回已有的qid.
IPC_CREAT|IPC_EXCL,不存在则创建,存在则返回出错.
key_t ftok(const char*pathname,int proj_id);
ftok函数通过给定的路径名称取得其stat结构中的st_dev字段和st_info字段,然后将它们和项目id结合起来,然后产生一个键返回
(2) int msgsnd(int msgid,const void* msgp,size_t magsz,int msgflg);
msgid:消息队列标识符 msgp:发送的消息结构体指针
msgsz:结构体中消息的大小(不是整个结构体的大小)
msgflg:IPC_NOWAIT,消息队列满时返回-1 0,消息队列满时阻塞.
/*send.c*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#define MSGKEY 1024
struct msgstru
{
long msgtype;
char msgtext[2048];
};
void main()
{
struct msgstru msgs;
int msg_type;
char str[256];
int ret_value;
int msqid;
//此处的MSGKEY可用ftok创建key值
msqid=msgget(MSGKEY,IPC_EXCL); /*检查消息队列是否存在*/
if(msqid < 0){
msqid = msgget(MSGKEY,IPC_CREAT|0666);/*创建消息队列*/
if(msqid <0){
printf("failed to create msq | errno=%d [%s]\n",errno,strerror(errno));
exit(-1);
}
}
while (1){
printf("input message type(end:0):");
scanf("%d",&msg_type);
if (msg_type == 0)
break;
printf("input message to be sent:");
scanf ("%s",str);
msgs.msgtype = msg_type;
strcpy(msgs.msgtext, str);
ret_value = msgsnd(msqid,&msgs,sizeof(struct msgstru),IPC_NOWAIT);
if ( ret_value < 0 ) {
printf("msgsnd() write msg failed,errno=%d[%s]\n",errno,strerror(errno));
exit(-1);
}
}
msgctl(msqid,IPC_RMID,0); //删除消息队列
}
***************************************接受端************************************************
函数说明:
ssize_t msgrcv(int qid,void *msgp,size_t msgsz,long msgtyp,int msgflg);
qid:消息队列的标识符 msgp:消息结构体指针
msgsz:消息内容大小 msgtyp:消息类型 msgflg:同上
int msgctl(int msgid,int cmd,struct msgid_ds *buf);
msgid:消息队列标识符
cmd:所要采取的命令.IPC_RMID删除消息队列
buf:权限信息
/*receive.c */
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <errno.h>
#define MSGKEY 1024
struct msgstru
{
long msgtype;
char msgtext[2048];
};
/*子进程,监听消息队列*/
void childproc(){
struct msgstru msgs;
int msgid,ret_value;
char str[512];
while(1){
msgid = msgget(MSGKEY,IPC_EXCL );/*检查消息队列是否存在 */
if(msgid < 0){
printf("msq not existed! errno=%d [%s]\n",errno,strerror(errno));
sleep(2);
continue;
}
/*接收消息队列*/
ret_value = msgrcv(msgid,&msgs,sizeof(struct msgstru),0,0);
printf("text=[%s] pid=[%d]\n",msgs.msgtext,getpid());
}
return;
}
void main()
{
int i,cpid;
/* create 5 child process */
for (i=0;i<5;i++){
cpid = fork();
if (cpid < 0)
printf("fork failed\n");
else if (cpid ==0) /*child process*/
childproc();
}
}
(四)共享内存
共享内存是System V版本的最后一个进程间通信方式。共享内存,顾名思义就是允许两个不相关的进程访问同一个逻辑内存,共享内存是两个正在运行的进程之间共享和传递数据的一种非常有效的方式。不同进程之间共享的内存通常为同一段物理内存。进程可以将同一段物理内存连接到他们自己的地址空间中,所有的进程都可以访问共享内存中的地址。如果某个进程向共享内存写入数据,所做的改动将立即影响到可以访问同一段共享内存的任何其他进程。
特别提醒:共享内存并未提供同步机制,也就是说,在第一个进程结束对共享内存的写操作之前,并无自动机制可以阻止第二个进程开始对它进行读取,所以我们通常需要用其他的机制来同步对共享内存的访问,例如信号量。
(1) int shmget(key_t key, size_t size, int shmflg)
参数:key: ftok()的返回值
size: 想要创建的共享内存的大小 (字节) shmflg: 共享内存段的创建标识
(2) int shmctl(int shmid, int cmd, struct shmid_ds *buf)
参数:shmid : shmget()返回值
buf:临时共享内存变量信息
cmd :
IPC_STAT:把shmid_ds结构中的数据设置为共享内存的当前关联值,即用共享内存的当前关联值覆盖shmid_ds的值。
IPC_SET:如果进程有足够的权限,就把共享内存的当前关联值设置为shmid_ds结构中给出的值
IPC_RMID:删除共享内存段
(3) void *shmat(int shmid, const void *shmaddr, int shmflg)
参数:shmid : shmget()返回值
shmaddr: 共享内存的映射地址,一般为0(由系统自动分配地址)
shmflg : 访问权限和映射条件,shm_flg是一组标志位,通常为0
(4) int shmdt(const void *shmaddr)
参数: shmaddr:共享内存的首地址
struct shmid_ds
{
uid_t shm_perm.uid;
uid_t shm_perm.gid;
mode_t shm_perm.mode;
}
shmdata.h的源码:
#ifndef _SHMDATA_H_HEADER
#define _SHMDATA_H_HEADER
#define TEXT_SZ 2048
struct shared_use_st
{
int written;/* 作为一个标志,非0:表示可读,0表示可写 */
char text[TEXT_SZ];/* 记录写入和读取的文本 */
};
#endif
shmread.c的源代码
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<sys/shm.h>
#include"shmdata.h"
#define MEM_KEY (1234)
int main()
{
int running =1; //程序是否继续运行的标志
void*shm = NULL; //分配的共享内存的原始首地址
struct shared_use_st *shared;//指向shm
int shmid; //共享内存标识符
//创建共享内存
shmid = shmget((key_t)MEM_KEY,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid ==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
//将共享内存连接到当前进程的地址空间
shm = shmat(shmid,0,0);
if(shm ==(void*)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("\nMemory attached at %X\n",(int)shm);
//设置共享内存
shared =(struct shared_use_st*)shm;
shared->written =0;
while(running)//读取共享内存中的数据
{
//没有进程向共享内存定数据有数据可读取
if(shared->written !=0)
{
printf("You wrote: %s", shared->text);
sleep(rand()%3);
//读取完数据,设置written使共享内存段可写
shared->written =0;
//输入了end,退出循环(程序)
if(strncmp(shared->text,"end",3)==0)
running =0;
}
else//有其他进程在写数据,不能读取数据
sleep(1);
}
//把共享内存从当前进程中分离
if(shmdt(shm)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
//删除共享内存
if(shmctl(shmid, IPC_RMID,0)==-1)
{
fprintf(stderr,"shmctl(IPC_RMID) failed\n");
exit(EXIT_FAILURE);
}
exit(EXIT_SUCCESS);
}
shmwrite.c的源代码
#include<unistd.h>
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/shm.h>
#include"shmdata.h"
#define MEM_KEY (1234)
int main()
{
int running =1;
void*shm = NULL;
struct shared_use_st *shared = NULL;
char buffer[BUFSIZ +1];//用于保存输入的文本
int shmid;
//创建共享内存
shmid = shmget((key_t)MEM_KEY,sizeof(struct shared_use_st),0666|IPC_CREAT);
if(shmid ==-1)
{
fprintf(stderr,"shmget failed\n");
exit(EXIT_FAILURE);
}
//将共享内存连接到当前进程的地址空间
shm = shmat(shmid,(void*)0,0);
if(shm ==(void*)-1)
{
fprintf(stderr,"shmat failed\n");
exit(EXIT_FAILURE);
}
printf("Memory attached at %X\n",(int)shm);
//设置共享内存
shared =(struct shared_use_st*)shm;
while(running)//向共享内存中写数据
{
//数据还没有被读取,则等待数据被读取,不能向共享内存中写入文本
while(shared->written ==1)
{
sleep(1);
printf("Waiting...\n");
}
//向共享内存中写入数据
printf("Enter some text: ");
fgets(buffer, BUFSIZ, stdin);
strncpy(shared->text, buffer, TEXT_SZ);
//写完数据,设置written使共享内存段可读
shared->written =1;
//输入了end,退出循环(程序)
if(strncmp(buffer,"end",3)==0)
running =0;
}
//把共享内存从当前进程中分离
if(shmdt(shm)==-1)
{
fprintf(stderr,"shmdt failed\n");
exit(EXIT_FAILURE);
}
sleep(2);
exit(EXIT_SUCCESS);
}