一、管道
1.1 有名管道
有名管道可以在任意两个进程之间通信
1.1.1 有名管道的创建:
命令创建: mkfifo +管道名
系统调用创建
1.1.2 与普通文件区别
打开管道文件,在内存分配一块空间,往管道文件里面写数据,实际是写入内存,读取也是,这样比较高效,所以管道文件的大小永远为0.
1.1.3 使用有名管道
管道文件只有两种打开方式:只读 O_RDONLY 只写 O_WRONLY
一个进程往管道里面写入数据
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<assert.h>
int main()
{
int fdw=open("./FIFO",O_WRONLY);
assert(fdw!=-1);
printf("fdw=%d\n",fdw);
while(1)
{
printf("input:\n");
char buff[128]={0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fdw,buff,strlen(buff));
}
close(fdw);
}
另外一个进程从管道里面读出数据
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<string.h>
#include<unistd.h>
#include<fcntl.h>
int main()
{
int fdw=open("./FIFO",O_RDONLY);
assert(fdw!=-1);
printf("fdw=%d\n",fdw);
while(1)
{
char buf[128]={0};
if(read(fdw,buf,127)==0)
{
break;
}
printf("read=%s\n",buf);
}
close(fdw);
}
结果
注意
需要两个进程同时进行,至少一个读一个写,保证读和写都得有。
当写端关闭,读端自动返回0。
如果读端关闭,系统会给写端发送信号--13,引起异常。
加上这段代码,当读端结束后,写端会打印系统返回的信号。
1.2 无名管道
无名管道主要应用于父子进程间的通信
1.2.1 创建无名管道
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<assert.h>
int main()
{
int fd[2];
assert(pipe(fd)!=-1);
pid_t pid =fork();
assert(pid!=-1);
if(pid ==0)
{
close(fd[1]);
char buff[128]={0};
read(fd[0],buff,127);
printf("child read:%s\n",buff);
close(fd[0]);
}
else
{
close(fd[0]);
write(fd[1],"hello",5);
close(fd[1]);
}
}
结果
半双工,单工,全双工
单工:方向固定
半双工:一次只允许一方到另一方传递
全双工:允许同时进行双方传递
1.3 管道实现
二、信号量
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有 资源可用,P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V 操作。信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信 号量的值大于 1,则称之为计数信号量。
当信号量取0和1:二值信号量
当信号量取3和5:计数信号量
临界资源:同一时刻,只允许被一个进程或线程访问的资源
临界区:访问临界资源的代码段
2.1 没有信号量控制
A和B去打印,需求是A,B同时打印次数为偶数
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
int main()
{
for(int i=0;i<5;i++)
{
printf("A");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("A");
fflush(stdout);
n=rand()%3;
sleep(n);
}
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
int main()
{
for(int i=0;i<5;i++)
{
printf("B");
fflush(stdout);
int n=rand()%3;
sleep(n);
printf("B");
fflush(stdout);
n=rand()%3;
sleep(n);
}
}
结果
在B未结束时A已经出现了,所以表示一个进程未结束另一个进程已经开始了。
2.2 信号量的封装
为解决2.1的问题,我们增加一个信号量,值为1,当执行A时p操作-1,值为0,这时表明没有资源可以使用,进程B无法运行,当A结束后v操作+1,这时进程B就可以有资源去使用,执行B资源然后同上,这样就解决了两个进程依次执行的
2.2.1 介绍一些函数
2.2.1.1 semget()
2.2.1.2 semop()
2.2.1.3 semget()
2.2 实现创建一个信号量
#include "sem.h"
static int semid = -1;
void sem_init()
{
semid = semget((key_t)1234,1,IPC_CREAT|IPC_EXCL|0600);//全新创建信号量,如果存在就失败
if ( semid == -1 )//失败,表示已存在
{
semid = semget((key_t)1234,1,0600);//获取已存在的信号量id
if ( semid == -1)
{
printf("semget err\n");
}
}
else//全新创建成功,那么要进行初始化
{
union semun a;
a.val = 1;//信号量的初始值
if ( semctl(semid,0,SETVAL,a) == -1)//设置初始值
{
printf("semctl err\n");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = -1;//p
buf.sem_flg = SEM_UNDO;
if ( semop(semid,&buf,1) == -1)
{
printf("semop p err\n");
}
}
void sem_v()
{
struct sembuf buf;
buf.sem_num = 0;
buf.sem_op = 1;//v
buf.sem_flg = SEM_UNDO;
if ( semop(semid,&buf,1) == -1)
{
printf("semop v err\n");
}
}
void sem_destroy()
{
if ( semctl(semid,0,IPC_RMID) == -1)
{
printf("semctl destroy err\n");
}
}
给2.1中的a.c b.c 添加信号量就会输出下图
三、共享内存
两个进程在进行通讯时,共同使用同一块内存。
3.1 介绍一些函数
3.1.1 shemget()创建共享内存
3.1.2 shmat() 用来创建映射
3.1.3 shmdt()用来断开映射
3.1.4 shmctl()用来控制共享内存
3.2 实现共享内存
这里的共享内存的实现方式个人感觉和无名管道很像,都是使用两个信号量来控制输入输出进程的执行,只不过无名管道的信号量是来源于fork()复制来的。
这个是进程1的实现,主要就是往共享内存里面传输数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if ( shmid == -1 )
{
printf("shmget err\n");
exit(1);
}
char* s = (char*)shmat(shmid,NULL,0);
if ( s == (char*)-1)
{
printf("shmat err\n");
exit(1);
}
sem_init();
while( 1 )
{
printf("input\n");
char buff[128] = {0};
fgets(buff,128,stdin);
sem_p(SEM1);
strcpy(s,buff);
sem_v(SEM2);
if ( strncmp(buff,"end",3) == 0)
{
break;
}
}
shmdt(s);
}
在进程1结束后我们只用断开进程1和共享内存之间的映射就行,不需要结束共享内存,因为后续还会有其他进程使用这个共享内存。
这个是进程2的实现方式,主要就是从共享内存中读出数据
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/shm.h>
#include "sem.h"
int main()
{
int shmid = shmget((key_t)1234,128,IPC_CREAT|0600);
if ( shmid == -1 )
{
printf("shmget err\n");
exit(1);
}
char * s = (char*)shmat(shmid,NULL,0);
if ( s == (char*)-1)
{
printf("shmat err\n");
exit(1);
}
sem_init();
while( 1 )
{
sem_p(SEM2);
if ( strncmp(s,"end",3) == 0 )
{
break;
}
printf("read:%s\n",s);
sem_v(SEM1);
}
shmdt(s);
shmctl(shmid,IPC_RMID,NULL);
sem_destroy();
}
这里如果直接去实现就会无限迅速读入读出,所以需要我们引入信号量加以管制,和2的代码很像,只需要大家看得懂我前面画的图,实现这个功能并不复杂。
四、信息队列
根据前面的数字类型进行分类。
4.1 认识一些函数接口
4.1.1 msgget() 获取消息队列
4.1.2 msgsnd()发送信息
4.1.3 msgrcv()接收消息
4.1.4 msgctl()控制消息队列
4.2 示例
进程 a 发送一条消息,进程 b 读取消息。
进程a
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
struct message
{
long int type;//固定
char buff[32];
};
int main()
{
int msgid=msgget((key_t)1234,IPC_CREAT|0600);
if(msgid==-1)
{
printf("msgget err\n");
exit(1);
}
struct message dt;
dt.type=1;
strcpy(dt.buff,"hello1");
msgsnd(msgid,&dt,32,0);
}
进程b
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<assert.h>
#include<sys/msg.h>
struct message
{
long int type;//固定
char buff[32];
};
int main()
{
int msgid=msgget((key_t)1234,IPC_CREAT|0600);
if(msgid==-1)
{
printf("msgget err\n");
exit(1);
}
struct message dt;
msgrcv(msgid,&dt,32,1,0);//0代表不区分类型
printf("read message:%s\n",dt.buff);
}