一、匿名管道
从一个进程连接到另一个进程的一个数据流称为一个管道
#include <unistd.h>
功能:创建一个无名管道
int pipe(int fd[2])
参数:
fd:文件描述符数组,其中fd[0]表示读端,fd[1]表示写端
返回值:成功返回0,失败返回-1
其内部操作如下图所示

管道读写规则:
1.若read端文件描述符被关了,则write端会产生SIGPIPE(13号)信号,进程退出
2.当写入数据大小不大于PIPE_BUF时,linux将保证写入的原子性
3.当写入数据大小大于PIPE_BUF时,linux将不保证写入的原子性
管道特点:
1.只能在有亲缘关系的进程之间通信(多用于父子进程)
2.内核会对管道操作进行互斥和同步处理
3.进程退出,管道释放(其生命周期随进程)
4.管道是单向的,数据只能朝一个方向流动
代码思想:
1.用父进程先创建一个管道(调用pipe()函数)
2.fork()一个子进程,则子进程的文件描述符继承父进程的
3.子进程关闭write端,close(fds[1])
父进程关闭read端,close(fds[0])
4.父进程写,子进程读,就可以进行通信了
代码:
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
int main()
{
int fds[2];
if(pipe(fds)<0){ //匿名管道,
perror("pipe:");
exit(1);
}
// printf("%d %d\n",fds[0],fds[1]); //3和4,相当于用两种方式打开了1个文件
pid_t id=fork(); //创建子进程
if(id == 0){
//child "w"
close(fds[0]); //关闭读的方式打开的文件,引用计数-1 ,(硬链接)
printf("Please talk: \n");
const char* msg="i have a dream!\n";
int i=5;
while(i--){
write(fds[1],msg,strlen(msg));
sleep(1);
}
close(fds[1]); //关闭pipe文件
exit(0);
}
else if(id > 0){
//parent "r"
close(fds[1]); //关闭写的方式打开的文件,引用计数-1 ,(硬链接)
char buf[1024];
while(1){
ssize_t s=read(fds[0],buf,sizeof(buf)-1); //从缓冲区读文件,留一个字节放EOF
if(s>0){
buf[s-1]=0; //把换行符一覆盖
printf("%s\n",buf);
}
else{
break;
}
}
waitpid(id,NULL,0); //等待子进程退出
}
else{
perror("fork:");
exit(1);
}
return 0;
}
二、命名管道
顾名思义,这是一个显示的特殊类型的文件,它是有名字的
创建方法:
(1)在命令行上创建 --> $ mkfifo filename
(2)调用函数 int mkfifo(const char* filename,mode_t mode)
参数:filename 文件名
mode 权限
与匿名管道之间的区别:
1.匿名管道由pipe创建并打开
2.命名管道由mkfifo函数创建,打开用open
3.匿名管道一般用于父子进程之间通信,而命名管道可用于任意两个进程之间通信
代码操作思想:
1.先创建一个管道文件
2.一个进程以读的方式打开管道文件,一个进程以写的方式打开管道文件
3.就可以通信了
三、消息队列
基本概念:
1.从一个进程向另一个发送消息块的方法
2.每个消息块都有一个类型(标签)
3.但是每个消息的最大长度有限(MSGMAX),每个消息队列的字节总数(MSGMNB)是有限的,系统上消息队列总数是有上限的(MSGMNI)

思想:两个进程根据 type 去接收自己想接受的消息块
操作函数:
1.msgget()函数
功能:
用来创建和访问一个消息队列
原型:
int msgget(key_t key , int msgflg);
参数:
key:某个消息队列的名字
msgflg:权限,由9个权限标志组成
返回值:
成功返回该消息队列的标识码,失败返回1;
mode权限:0644 | IPC_CREAT | IPC_EXCL 队列存在则返回错误,不存在则创建,其文件的权限为644;
IPC_CREAT 队列存在,则访问;不存在则创建
key一般是 ftok() 函数的返回值 key_t _key=ftok(PATHNAME , PROJ_ID); 参数是宏
2.msgctl()函数
功能:
用来控制消息队列
原型:
int msgctl (int msqid, int cmd, struct msqid_ds *buf);
参数:
msqid: 由msgget函数返回的消息队列标识码
cmd: 是将要采取的动作 IPC_RMID 删除消息队列
*buf: 缺省为NULL
返回值:
成功返回0,失败返回-1
3.msgsnd()函数
功能:
把一条消息添加到消息队列中
原型:
int msgsnd (int msqid, const void* msgp, size_t msgsz, int msgflg);
参数:
msgid: 消息队列标识码
msgp: 是一个指针,指针指向准备发送的消息
msgsz: 是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg: 控制着当前消息队列满或达到上限时将要发生的事情 =IPC_NOWAIT表示队列满不等待,返回EAGAIN错误
返回值:
成功返回0,失败返回-1;
消息结构:
必须小于系统规定的上限值,必须以一个long int开始,就是这个消息的标签
struct msgbuf{
long mtype; 消息类型
char mtext[1]; 消息内容,大小自己设置
}
4.msgrcv()函数
功能:
从一个消息队列接收消息
原型:
ssize_t msgrcv (int msqid, void* msgp, size_t msgz, long msgtyp, int msgflg);
参数:
msqid: 消息队列标识码
msgp: 指针,指向准备接收的消息
msgsz: 消息长度,不包含long int 长整型
msgtype: 可实现接收优先级的简单形式
msgflg: 控制着队列中没有相应类型的消息可供接收时将要发生的事
返回值:成功返回实际接受的字符个数,失败返回-1
代码思想:
1.先用一个进程创建消息队列,(server)
2.另一个进程根据消息队列名字,访问之,返回这个消息队列的标识码
3.这样两个进程就可以相互通信了
上代码!!!
有详细注释
comm.c
#include "comm.h"
int CreateMsgQueue()
{
return CommGetMsgQueue(IPC_CREAT | IPC_EXCL | 0666); //不存在,则创建,若存在,则错误
}
int OpenMsgQueue()
{
return CommGetMsgQueue(IPC_CREAT); //消息队列不存在,则创建,若存在,则打开它
}
int CommGetMsgQueue(int flags)
{
key_t _key=ftok(PATHNAME,PROJ_ID);
if(_key<0){
perror("ftok");
return -1;
}
int msgid=msgget(_key,flags); //返回消息队列的标识码
if(msgid<0){
perror("msgget");
return -1;
}
return msgid;
}
int SendMsg(int msgid,long whotype,char* msg) //发送消息,(标识码,什么类型,消息内容)
{
struct msgbuf buf; //消息结构体
buf.mtype=whotype;
strcpy(buf.mtext,msg);
if(msgsnd(msgid,(void*)&buf,sizeof(buf.mtext),0)<0){ //(标识码,消息结构体,消息大小,缺省为0)
perror("send");
return -1;
}
return 0;
}
int RecvMsg(int msgid,long recvType,char* out) //接收消息,(标识码,接收类型,存到哪)
{
struct msgbuf buf;
ssize_t s=msgrcv(msgid,(void*)&buf,sizeof(buf.mtext),recvType,0); //(标识码,消息结构体,消息大小,消息类型,缺省为0)
if(s<0){
perror("msgrcv");
return -1;
}
strcpy(out,buf.mtext);
return 0;
}
void DestoryMsgQueue(int msgid)
{
if(msgctl(msgid,IPC_RMID,NULL)<0){ //(标识码,删除指令,NULL)
perror("msgctl");
}
}
server.c
#include "comm.h" //服务器端
int main()
{
int msgid=CreateMsgQueue(); //创建消息队列
if(msgid<0){
perror("create");
return -1;
}
char buf[1024]={0}; //缓冲区
while(1){
RecvMsg(msgid,CLIENT_TYPE,buf); //接收消息(标识码,用户类型,放到缓冲区)
if(!strcmp(buf,"q")){ //收到q,则退出
printf("client# %s\n",buf);
printf("client quit! i quit too!\n");
break;
}
printf("client# %s\n",buf);
printf("Please stdin!\nserver# ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1); //读输入到缓冲区中
if(s<0){
perror("read");
return -1;
}
buf[s-1]=0;
SendMsg(msgid,SERVER_TYPE,buf); //发送消息 (标识码,服务器类型,从哪发)
printf("send done! Wait recv...\n");
}
DestoryMsgQueue(msgid); //销毁消息队列 (标识码)
return 0;
}
client.c
#include "comm.h" //用户端
int main()
{
int msgid = OpenMsgQueue(); //打开消息队列
char buf[1024]={0}; //缓冲区
while(1){
printf("Please stdin!\nclient# ");
fflush(stdout);
ssize_t s=read(0,buf,sizeof(buf)-1); //从标准输入读到缓冲区
if(s<0){
perror("read");
return -1;
}
buf[s-1]=0; //把换行符覆盖掉
SendMsg(msgid,CLIENT_TYPE,buf); //(标识码,用户类型,内容)
if(!strcmp(buf,"q")){ //如果输入q,则用户端退出
printf("i will quit!\n");
break;
}
printf("send done , wait recv...\n");
RecvMsg(msgid,SERVER_TYPE,buf); //(标识码,服务器类型,放到缓冲区)
printf("server# %s\n",buf);
}
return 0;
}
四、共享内存
思想:向系统申请一块内存空间,之后,让两个进程同时看到这块内存空间即可
操作函数:
shmget函数
功能:用来创建共享内存
原型:int shmget(key_t key, size_t size, int shmflg);
参数:key: 共享内存的名字。由ftok函数得来
size: 共享内存的大小 (按页计) 1页==4KB==4096字节
shnflg: 权限标志位
返回值:成功返回该该共享内存的标识码,失败返回-1
注意:即使用户申请了4097个字节,系统也会分配两页,但用户只能用4097个字节,其余4095个字节就浪费了
shmat函数
功能:挂接,将共享内存连接到进程地址空间
原型:void* shmat(int shmid, const void* shmaddr, int shmflg);
参数:shmid : 共享内存标识码,
shmaddr: 指定连接的地址, 一般缺省为NULL,让系统自己找,找自己进程空间的地址
shmfig: 可取两值,SHM_RND(读和写),SHM_RDONLY(只读)
返回值:成功返回指针,指向共享内存的第一个字节,失败返回-1;
shmdt函数
功能:分离,将共享内存段与当前进程脱离
原型:int shmdt(const void* shmaddr);
参数:shmaddr: 由shmat返回的指针
返回值:成功返回0,失败返回-1;
shmaddr函数
功能: int shmctl(int shmid, int cmd, struct shmid_ds, *buf);
参数:shmid: 共享内存的标识码
cmd: 命令将要采取的三个动作(IPC_RMID)删除,另外两个不重要
共享内存的特点:
1.共享内存区是最快的IPC形式,一旦这样的内存映射到共享它的进程的地址空间,这些进程间的数据传递将不再涉及内核
2.但没有同步与互斥的保护机制
3.其生命周期是随内核的
其示意图如下:

代码示例:
shm.h
#ifndef __SHM_H__
#define __SHM_H__
#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define PATHNAME "."
#define PROJ_ID 0x1111
#define MAX_SIZE 4096
int CreateShm(int size);
int DestoryShm(int shmid);
int GetShm(int size);
int CommShm(int size,int flags);
#endif //__SHM_H__
shm.c
#include "shm.h"
int CreateShm(int size)
{
return CommShm(size,IPC_CREAT | IPC_EXCL | 0666); //若共享内存已存在,则错误;若不存在,则创建
}
int DestoryShm(int shmid)
{
if(shmctl(shmid,IPC_RMID,NULL)<0){ //控制共享内存,(标识码,删除操作,缺省为空)
perror("rmid");
return -1;
}
return 0;
}
int GetShm(int size)
{
return CommShm(size,IPC_CREAT); //若共享内存不存在,则创建;若存在,则访问
}
int CommShm(int size,int flags)
{
key_t _key=ftok(PATHNAME,PROJ_ID); //生成名字_key
if(_key<0){
perror("ftok");
return -1;
}
int shmid=shmget(_key,size,flags); //打开共享内存(名字,大小,权限)
if(shmid<0){
perror("shmget");
return -1;
}
return shmid; //返回标识码
}
server.c
#include "shm.h"
int main()
{
int shmid=CreateShm(MAX_SIZE);
char* addr=(char*)shmat(shmid,NULL,0);
sleep(3);
int i=0;
while(i++<26){
printf("client# %s\n",addr);
sleep(1);
}
shmdt(addr);
sleep(2);
DestoryShm(shmid);
return 0;
}
client.c
#include "shm.h"
int main()
{
int shmid=GetShm(MAX_SIZE); //打开共享内存,4096字节,4KB
char* addr=(char*)shmat(shmid,NULL,0); //挂接到共享内存(标识码,缺省为NULL,缺省为0) 返回共享内存首地址
sleep(1);
int i=0;
while(i<26){
addr[i]='A'+i;
sleep(1);
i++;
addr[i]=0;
}
shmdt(addr); //解挂(内存地址)
sleep(2);
return 0;
}
注意:应先运行server.c,开辟一个共享内存
五、信号量
信号量本质上是一个计数器
struct semphore{
int value;
pointer_PCB queue; //指向进程队列的指针
}
其思想是这样的:多个进程想访问一份临界资源,而这份临界资源一次只能供一个进程访问(显示器),信号量可以用来描述临界资源数目。进程每申请一次,信号量值减1,用完后每释放一次,信号量值加1.如果一开始就将信号量值设置为1,就可以做到互斥利用资源(二元信号量)
P、V原语
信号量:
互斥:P、V在同一进程中
同步:P、V在不同进程中
信号量值的含义:
S > 0 : S表示可用资源数目
S = 0 : 表示无可用资源,无等待进程
S < 0 : | S |表示等待进程数目
信号量集函数
semget函数
功能:创建和访问一个信号量集
原型:int semget(key_t key, int nsems, int semflg);
参数:key: 信号量集的名字
nsems: 信号量集中信号量的个数,这些信号量分管不同的临界资源(下标)
semflg:权限
返回值:成功返回标识码,失败返回-1
semctl函数
功能:用于控制信号量集
原型:int semctl (int semid, int semnum, int cmd, ...)
参数:semid: 标识码
semnum: 要操作信号量集中的第几号信号量
cmd: 将要采取的动作,最后一个参数根据命令的不同而不同,
SETVAL 设置信号量的计数值 (初始化)在一个联合体中设置
IPC_RMID 主要用于删除
返回值:成功返回0,失败返回-1
semop函数
功能:完成对信号量的P、V操作
原型:int semop( int semid, struct sembuf* sops, unsigned nsops);
参数:semid: 标识码
sops: 指向一个结构体的指针
nsops: 信号量的个数,常设为1,指完成对一个信号量的操作
返回值:成功返回0,失败返回-1
sembuf结构体
struct sembuf{
short sem_num; 数组下标,信号量集合中的信号量编号
short sem_op; 若传入的值s > 0,进行V操作,信号量值+s,反之,信号量值 - |s|
short sem_flg; 一般缺省为0
}
sembuf结构体已经在<sys/sem.h>头文件中声明,直接拿来定义即可
代码思想:(以二元信号量为例)
1.先用一个进程创建一个信号量集,并初始化它; 创建arr[1], 初始化 arr[0]=1;
2.再创建一个进程,根据信号量集的名字_key访问它,并得到信号量集的标识码
3.设这两个进程都要在显示器上打印消息,则可对指定信号量进行P、V操作,可做到进程间互斥
代码:
comm.c
#include "comm.h"
static int CommSemSet(int nums,int flags)
{
key_t _key=ftok(PATHNAME,PROJ_ID);
if(_key < 0){
perror("ftok");
return -1;
}
int semid = semget(_key,nums,flags); //创建时nums是信号量集中信号量的个数 //访问时是信号量集中的第几号信号量
if(semid < 0){
perror("semget");
return -1;
}
return semid;
}
int CreatSemSet(int nums)
{
return CommSemSet(nums,IPC_CREAT | IPC_EXCL | 0666);
}
int GetSemSet(int nums)
{
return CommSemSet(nums,IPC_CREAT);
}
int initSem(int semid,int nums,int initVal)
{
union semun _un; //nums是第0号信号量,initVal是初始值
_un.val = initVal;
if(semctl(semid,nums,SETVAL,_un)<0){
perror("semctl");
return -1;
}
return 0;
}
static int CommPV(int semid,int who,int op)
{
struct sembuf _sf;
_sf.sem_num = who; //操作第who号信号量,
_sf.sem_op = op; // <1,P操作,申请临界资源,>1,V操作,释放临界资源
_sf.sem_flg = 0;
if(semop(semid,&_sf,1)<0){
perror("semop");
return -1;
}
return 0;
}
int P(int semid,int who)
{
return CommPV(semid,who,-1);
}
int V(int semid,int who)
{
return CommPV(semid,who,1);
}
int DestorySemSet(int semid)
{
if(semctl(semid,0,IPC_RMID)<0){
perror("destory");
return -1;
}
return 0;
}
test.c
#include "comm.h"
int main()
{
int semid = CreatSemSet(1); //创建一个信号量集,返回标识码即arr[1];
initSem(semid,0,1); //0表示信号集中信号量的下标,1表示设置该信号量的计数值
pid_t id=fork(); //创建子进程
if(id==0){ //child
int _semid = GetSemSet(0); //访问信号量集中第0号信号量
int i=2;
while(i--){
P(_semid,0); //对0号信号量进行P操作,占用临界资源
printf("A"); //此时显示器就是临界资源
fflush(stdout);
sleep(1);
printf("A");
fflush(stdout);
sleep(1);
V(_semid,0); //对0号信号量进行V操作,释放临界资源
}
exit(0);
}
else{
int i=3;
while(i--){
P(semid,0);
printf("B");
fflush(stdout);
sleep(1);
printf("B");
fflush(stdout);
sleep(1);
V(semid,0);
}
wait(NULL);
}
printf("\n");
DestorySemSet(semid);
return 0;
}
本文深入讲解了进程间通信(IPC)的各种方法,包括匿名管道、命名管道、消息队列、共享内存和信号量的原理与代码实现。通过具体示例,展示了如何在不同进程间高效传递数据和同步操作。
4万+





