进程间通信:简称ipc(Inter-Process Communication)。进程间通信是指两个进程的数据之间产生交互。
目录
一.无名管道(pipe)
二.命名管道(FIFO)
三.消息队列
四.信号量
五.共享内存
一.无名管道(pipe)
1.特点:
①半双工(即数据只能在一个方向上流动),具有固定的写端和读端
②只能用于父子进程或兄弟进程之间
③可以把它当做是一个文件使用read等函数操作,但它不是普通文件,并不属于其他文件系统,只存于内存中。
2.原型:
1. #include2. Int pipe(int fd[2]); //返回值,若成功返回 0,失败返回-1 当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而
3.例子:
#include#include int main() { int pid; int fd[2]; char buff[128]; if(pipe(fd)==-1){ //创建管道 printf("pipe failedn"); } if((pid=fork())==-1){ //创建子进程 printf("fork failedn"); }else if(pid==0){ //child close(fd[1]); read(fd[0],buff,128); printf("child:message is %sn",buff); }else if(pid>0){ //father close(fd[0]); write(fd[1],"hello world",128); } return 0; }
二.命名管道(FIFO)
1.特点:
① FIFO 可以在无关的进程之间交换数据,与无名管道不同
②FIFO 有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。
2.原型
#include//返回值:成功返回 0,出错返回-1 Int mkfifo (const char *pathname, mode_t mode );
3例子:
读端:
include#include #include #include #include #include #include #include int main() { int fd; char read_buff[128]; if(mkfifo("./file",0600)==-1 && errno!=EEXIST){ //当fifo创建失败,并且不是因为文件已存在 perror("mkfifo"); } fd = open("./file",O_RDONLY); //打开管道,以只读方式 read(fd,read_buff,128); //读数据,没有数据时阻塞 printf("message is: %sn",read_buff); close(fd); return 0; }
写端:
#include#include #include #include #include #include #include #include int main() { int fd; if(mkfifo("./file",0600)==-1 && errno!=EEXIST){ perror("mkfifo"); } fd = open("./file",O_WRONLY); write(fd,"hello world",128); close(fd); return 0; }
三.消息队列
1.介绍:消息队列是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列 ID)来标识。
2.特点:①消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。
②消息队列独立于发送与接收进程,进程终于时,消息队列及其内容并不会被删除。
③消息队列可以实现消息的随即查询,消息不一定要以先进先出的次序读取, 也可以按消息的类型读取。
3.常用api
ftok()函数
系统IPC键值的格式转换函数,系统建立IPC通讯 (消息队列、信号量和共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
key_t ftok( const char * fname, int id ) //系统IPC键值的格式转换函数
参数
fname:就是你指定的文件名(已经存在的文件名),一般使用当前目录
id:是子序号。虽然是int类型,但是只使用8bits(1-255)。
如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。
msgget()
int msgget(key_t key, int flag); // 创建或打开消息队列:成功返回队列ID,失败返回-1
参数:
key :消息队列的键值多个进程可以通过它访问同一个消息队列,如果key为IPC_PRIVATE 意思就是创建一个只能被创建进程读写的消息队列;如果不是IPC_PRIVATE .则我们可以指定一个值0x1234,还可以用ftok()函数来获得一个唯一的键值。
flag : 创建消息队列的创建方式或权限。IPC_CREAT :(需要指定权限:IPC_CREAT|0600)
如果消息队列对象不存在,则创建消息队列,否则则进行打开操作;
IPC_EXCL:和IPC_CREAT 一起使用(用”|”连接),如果消息对象不存在则创建,否则产生一个错误并返回。
msgsnd()
int msgsnd(int msqid, const void *ptr, size_t size, int flag); //例 msgend(msgId,&sndBuf,strlen(sndBuf.mtext),0);
参数:
msqid:消息队列的ID
ptr:发送给队列的消息。写入的数据,指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构:
struct msgbuf {
long mtype; //第一个字段必须是long类型
char mtext[1];
};
size:ptr指向的消息的长度,注意是消息的长度,而不是整个结构体的长度,也就是说size是不包括长整型消息类型成员变量的长度。
flag: 为0表示在消息队列满或者空时都等待,直到队列中出现满足要求的消息为止(阻塞方式),设置IPC_NOWAIT 表示非阻塞方式
msgrcv()
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag); //例 msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
参数
msqid:消息队列的ID
ptr:写入的数据,指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构,形态如下:
struct msgbuf {
long mtype;
char mtext[1];
};
type:消息类型
msgtyp=0 则返回队列的最早的一个消息。
msgtyp>0,则返回其类型为msgtyp的第一个消息。
msgtyp<0,则返回其类型小于或等于mtype(在msgbuf结构体中的mtype)参数的绝对值的最小的一个消息。
操作系统提供的消息队列,确保同一个类型的消息是先进先出的,整个队列的消息可能不是先进先出的。
size:数据的长度(同msgsnd)
flag:为0表示在消息队列满或者空时都等待,直到队列中出现满足要求的消息为止(阻塞方式),设置IPC_NOWAIT 表示非阻塞方式
msgctl()
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
msqid:消息队列的ID
cmd:函数要对消息队列进行的操作,IPC_RMID将队列从系统内核中删除(用来删除队列)
buf:队列内容(一般写NULL)
例子:
send.c
#include#include #include #include #include #include struct msgbuf{ long mtype; char mtext[128]; }; int main() { key_t key; int msgId; struct msgbuf sendBuf; //定义一个用于发送消息的结构体 key = ftok(".",1); //使用ftok函数获得一个key值 if((msgId = msgget(key,IPC_CREAT|0600))==-1){ //创建消息队列 perror("msgget"); } sendBuf.mtype=8888; //给发消息的结构体添加内容 strcpy(sendBuf.mtext,"hello world"); if(msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0)){ perror("magsnd"); } return 0; }
read.c
//除了一些内容其他同上个代码差不多
if(msgrcv(msgId,&readBuf,128,8888,0)==-1){ //消息的接收
perror("msgrcv");
}
printf("readbuf:%sn",readBuf.mtext); //打印出拿到的消息
if(msgctl(msgId,IPC_RMID,NULL)==-1){ //销毁消息队列
perror("msgctl");
}
四.信号量
1.介绍:信号量主要用于实现进程间的互斥与同步。
临界资源:各进程采取互斥的方式,实现共享的资源称作临界资源。
2.特点:①信号量用于进程间同步,若要在进程间传递数据需要结合共享内存
②信号量基于操作系统的 PV(p:拿钥匙,v:放钥匙) 操作,程序对信号量的操作都是原子操作。
③每次信号量的 PV 操作不仅限于对信号量值加 1 或减 1 ,而且可以加减任意正整数 ④支持信号量组 最简单常用的信号量是只能读取 0 和 1 的变量,叫二值信号量 3.常用api 创建或获取一个信号量组:int semget(key_t key, int nsems, int semflg); //若成功返回信号量集ID,失败返回-1
参数
key:所创建或打开信号量集的键值。
nsems:信号量的个数,通常为1;如果是引用一个现有的集合,则将nsems指定为 0,该参数只在创建信号量集时有效(也可以设置多个,比如设置成2,理解成一个管理a事件,另一个管理b事件)
semflg:调用函数的操作类型,也可用于设置信号量集的访问权限,两者通过 | 表示。
对信号量组进行操作,改变信号量的值:
int semop(int semid, struct sembuf *sops, unsigned nsops); //成功返回0,失败返回-1
参数
semid:semget()返回的信号量标识符。
*sops:结构的定义如下:
struct sembuf{
short sem_num; // 除非使用一组信号量,否则它为0
short sem_op; // 信号量在一次操作中需要改变的数据,
//通常是两个数,一个是-1,即P(拿钥匙)操作,一个是+1,即V(放回)操作。
short sem_flg; // 通常为SEM_UNDO,使操作系统跟踪信号,
// 并在进程没有释放该信号量而终止时,操作系统释放信号量
};
nsops:信号操作结构的数量,恒大于或等于1。
控制信号量
int semctl(int semid, int sem_num, int cmd, ...);
参数:
semid:semget()返回的信号量标识符。
semnum:是操作信号在信号集中的编号,第一个信号的编号是0。
cmd:cmd通常是下面的两个值:
SETVAL:用来把信号量初始化为一个已知的值。这个值通过union semun中的val成员设置,其作用是在信号量第一次使用前对它进行设置。IPC_RMID:
用于删除一个已经无需继续使用的信号量标识符。
如果有第四个参数(需要用户自行定义),它通常是一个union semun结构,定义如下:
union semun {
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *__buf;
};
如何对多个信号量初始化:
semid =semget(key,3,IPC_CREAT|0666)
union semun arg;
unsigned short vals[3]={0};//假设初始化信号量集的值都为0
arg.array = vlas
semctl(sem_id, 0, SETALL, arg) //使用SETALL
例子:;
include#include #include #include #include #include union semun{ int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; void p_getkey(int id) //定义一个拿锁操作 { struct sembuf sops; sops.sem_num = 0; sops.sem_op = -1; // 信号量在一次操作中需要改变的数据 sops.sem_flg = SEM_UNDO; //SEM_UNDO阻塞方式 if (semop(id, &sops, 1) == -1) { perror("semop"); } printf("get the keyn"); } void v_putkey(int id) //放锁 { struct sembuf sops; sops.sem_num = 0; sops.sem_op = 1; sops.sem_flg = SEM_UNDO; if (semop(id, &sops, 1) == -1) { perror("semop"); } printf("put the keyn"); } int main() { key_t key; int semId; union semun semun1; key = ftok(".",2); semId = semget(key,1,IPC_CREAT|0666); semun1.val=1; semctl(semId,0,SETVAL,semun1);//"0"第0个信号量 p_getkey(semId); //调用拿锁函数 //................. v_putkey(semId); //调用放锁函数 return 0; }
五.共享内存
1.特点
1、共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。
2、因为多个进程可以同时操作,所以需要进行同步。
3、信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。
2.如何查看/删除系统中的共享存储段:
查看:
ipcs -m
删除:
ipcrm -m shmid(通过ipcs -m查看得到)
3.常用api
获取创建:
int shmget(key_t key, size_t size, int flag); // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
参数
key:由ftok生成的key标识
size:size以字节为单位指定需要共享的内存容量 ,在操作系统中,申请内存的最小单位为页,一页是4k字节,为了避免内存碎片,我们一般申请的内存大小为页的整数倍。
0:只获取共享内存时指定为0
shmflg:如果要创建新的共享内存,需要使用IPC_CREAT(要附上权限类似open函数的mode参数),IPC_CREAT | IPC_EXCL,如果是已经存在的,可以使用IPC_CREAT或直接传0。
返回值
成功返回共享内存的标识符;不成功返回-1,errno储存错误原因
映射:
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1 void *shmat(int shm_id, const void *addr, int flag);
参数
shm_id:是由shmget函数返回的共享内存标识。
*shm_addr:指定共享内存连接到当前进程中的地址位置,通常为0
shm_flg:若指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段(为0)
返回值
调用成功时返回一个指向共享内存第一个字节的指针,如果调用失败返回-1.
断开:
// 断开与共享内存的连接:成功返回0,失败返回-1 int shmdt(void *addr);
断开。注意,并不是删除它,只是使该共享内存对当前进程不再可用
参数
*shmaddr:是shmat函数返回的地址指针。
返回值
调用成功时返回0,失败时返回-1.
// 控制共享内存的相关信息:成功返回0,失败返回-1 int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
参数
shm_id:shm_id是shmget函数返回的共享内存标识符。
command:command是要采取的操作,它可以取下面的三个值 :
IPC_STAT
IPC_SET
IPC_RMID 删除共享内存段
*buf:设置为0即可。
例子(与信号量配合):
write:
#include#include #include #include #include #include "config.h" int main() { key_t key; int shmId; int semId; char *sh_addr; key=ftok(".",2); if(key==-1){ perror("ftok"); } shmId=shmget(key,1024*2,IPC_CREAT|0600); if(shmId==-1){ perror("shmget"); } semId=get_semId(key); //自己写的函数用于获取信号量的id sh_addr=shmat(shmId,0,0); printf("shmat successn"); int i=0; while(1){ sleep(8); p_getkey(semId); //拿锁 strcpy(sh_addr,"liuzheyu shuain"); //用strcpy,用其他会出问题 v_putkey(semId); //放锁 } shmdt(sh_addr); //断开共享;内存的链接 return 0; }
read:
#include#include #include #include #include #include "config.h" int main() { key_t key; int shmId; int semId; char *sh_addr; key=ftok(".",2); if(key==-1){ perror("ftok"); } shmId=shmget(key,1024*2,IPC_CREAT|0600); if(shmId==-1){ perror("shmget"); } semId=get_semId(key); sh_addr=shmat(shmId,0,0); printf("shmat successn"); while(1){ sleep(5); p_getkey(semId); printf(":%sn",sh_addr); v_putkey(semId); } shmdt(sh_addr); shmctl(shmId,IPC_RMID,0); return 0; }
config.h(信号量部分):
#include#include #include #include #include #include union semun{ int val; struct semid_ds *buf; unsigned short *array; struct seminfo *__buf; }; union semun semun1; int get_semId(int key){ int semId = semget(key,1,IPC_CREAT|0666); if(semId==-1){ perror("semId"); } semun1.val=1; semctl(semId,0,SETVAL,semun1); return semId; } void p_getkey(int id) { struct sembuf sops; sops.sem_num = 0; sops.sem_op = -1; sops.sem_flg = SEM_UNDO; if (semop(id, &sops, 1) == -1) { perror("semop"); } printf("get the keyn"); } void v_putkey(int id) { struct sembuf sops; sops.sem_num = 0; sops.sem_op = 1; sops.sem_flg = SEM_UNDO; if (semop(id, &sops, 1) == -1) { perror("semop"); } printf("put the keyn"); }



