命令 ipcs可以查看所有的管道,共享内存和消息队列
管道
管道中的数据存储在系统的内存中,而不是存储在磁盘上,因此我们在查看管道文件大小时,大小永远是0.
有名管道
有名管道可以在任意两个进程间通讯。
创建有名管道:mkfifo+管道名
例如a程序给有名管道fifo写入数据,再b程序从有名管道fifo中读取数据:
代码:
a.c:
b.c:
运行结果:
无名管道
无名管道只能在父子进程间通讯(先创建管道再fork,这样子进程就有和父进程一样的文件描述符)
创建无名管道:int pipe(int pipefd[2])
其中 pipefd[0]:只能读的文件描述符
pipefd[1]:只能写的文件描述符
举例:父进程给无名管道写入数据,子进程通过无名管道读数据:
结果:
信号量
就像生活中的红绿灯,当一方为绿灯时此方可以通行,而另一方需要等待,等红灯变成绿灯方可通过。信号量是一种特殊的变量,对信号量的操作都是原子操作,信号量-1意味着使用资源(p操作),信号量+1意味着释放资源(v操作)。
举例:两个进程访问同一块资源(A,B程序使用同一个打印机)
解决方法:使用信号量(当前程序访问信号量为1时,才可以进行打印;为0时需要等待)
使用信号量同步进程
所需头文件:
#include<sys/sem.h>
所需接口函数:
int semget( key_t key,int nsems,int semflg ):用于创建信号量,或者获取别人已经创建好的信号量
key:需要创建或者已有信号量的密码key值,例如(key_t)1234等
nsems:需要创建信号量的个数
semflg:标志位
IPC_CREAT | IPC_EXCL | 0600: 全新创建一个信号量(可读可写),如果此信号量已存在,就失败返回-1
返回值:信号量的id
semop( int semid,struct sembuf* sops,int nsops ):用于对信号量进行原子操作
semid:信号量id
sops:结构体指针
struct sembuf
{
unsigned short sem_num; //对哪个信号量进行操作(如果只有一个信号量就写0,也就是信号的下标)
short sem_op; //设置为-1时,进行p操作;为1时,进行v操作
short sem_flg; //标志位 SEM_UNDO
};
nsops:操作信号量的个数
semctl( int semid,int semnum,int cmd, ... ):对信号量的状态进行设置(初始化,移除信号量等操作)
semid:信号量id
semnum:对哪个信号量进行操作(如果只有一个信号量,就写0,也就是信号量的下标)
cmd:设置信号量
SETVAL:设置信号量
IPC_RMID:移除整个信号量集合
最后还需要自己定义一个联合体作为参数传入
union semun
{
int val; //信号量的初始值
};
代码实现:
sem.h:
#include<sys/sem.h>
#include<unistd.h>
union semun
{
int val; //信号量初始值
};
void sem_init(); //初始化信号量
void sem_p(); //p操作
void sem_v(); //v操作
void sem_destory(); //销毁信号量
sem.c:
#include"sem.h"
static int semid = -1; //信号量id
void sem_init()
{
semid = semget((key_t)1234,1,IPC_CREAT | IPC_EXCL | 0600);
if(semid == -1) //信号量已经存在
{
semid = semget((key_t)1234,1,0600);
if(semid == -1) //创建失败
{
perror("semget err\n");
}
}
else //全新创建的信号量
{
union semun a;
a.val = 1;
if(semctl(semid,0,SETVAL,a) == -1)
{
perror("semctl err\n");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1) == -1)
{
perror("p err\n");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid,&buf,1) == -1)
{
perror("v err\n");
}
}
void sem_destory()
{
if(semctl(semid,0,IPC_RMID))
{
perror("destory err\n");
}
}
A.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include"sem.h"
int main()
{
sem_init(); //创建信号量
int i = 0;
for(i;i<5;i++)
{
sem_p(); //p操作
printf("A");
fflush(stdout);
int n = rand()%3;
sleep(n);
printf("A");
fflush(stdout);
sem_v(); //v操作
n = rand()%3;
sleep(n);
}
sleep(10); //不知道哪个进程先结束,因此让此进程后结束再销毁信号量
sem_destory(); //销毁信号量
return 0;
}
B.c:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<assert.h>
#include"sem.h"
int main()
{
sem_init(); //创建信号量
int i = 0;
for(i;i<5;i++)
{
sem_p(); //p操作
printf("B");
fflush(stdout);
int n = rand()%3;
sleep(n);
printf("B");
fflush(stdout);
sem_v(); //v操作
n = rand()%3;
sleep(n);
}
return 0;
}
运行结果:
共享内存
概念:
将物理内存的一块空间映射到不同进程的逻辑地址空间中,此时A进程向共享内存中写入数据,进程B可以直接使用这个数据。
所需头文件:
#include<sys/shm.h>
所需接口函数:
int shmget( key_t key,size_t size,int shmflg ): 创建或获取已有的共享内存
key:不同进程可以使用相同key(例如(key_t)1234)值获取同一块共享内存
size:申请共享内存的大小
shmflg:标志位
IPC_CREAT | 0600:创建共享内存(可读可写)
返回值:共享内存id
void* shmat( int shmid,const void* shmaddr,int shmflg ):将共享内存映射到进程的逻辑地址空间
shmid:共享内存id
shmaddr:一般给NULL
shmflg:一般给0(可读可写)
返回值:共享内存的地址
int shmdt( const void* shmaddr ):断开当前进程与共享内存的映射
shmaddr:共享内存的地址
举例:a程序向共享内存中写入数据hello,b程序从共享内存中读出数据
代码实现:
a.c:
b.c:
运行结果:
消息队列
所需头文件:
#include<sys/msg.h>
所需接口函数:
int msgget( key_t key,int msgflg ):创建或者获取已有的消息队列id
key:多个进程可以通过相同的key值(例如(key_t)1234)访问同一消息队列
msgflg:标志位。
IPC_CREAT | 0600:创建消息队列(可读可写)
返回值:消息队列id
int msgsnd( int msgid,const void* msqp,size_t size,int msgflg ):向消息队列中发送数据
msgid:消息队列id
msqp:结构体指针
struct msgbuf //需要自己定义
{
long mtype; //第一个元素必须是long类型
char mtext[]; //第二个元素是存放数据的类型(char*,int*,结构体...)
}
msgflg:标志位,一般写0
int msgrcv( int msgid,void* msqp,size_t size,long msqtyp,int msgflg ):从消息队列中接收数据
msgid:消息队列id
msqp:存放信息的结构体指针
size:结构体中存放信息的大小(不是结构体的大小)
msgtyp:消息类型(写0的话就是可以接收所有类型的数据)
msgflg:标志位,一般写0
举例:进程A向消息队列中发送不同类型数据,进程B从消息队列中接收不同类型数据
代码实现:
A.c:
B.c:
运行结果: