进程间通信:采用IPC机制(进程间的用户空间相互独立,内核空间共享),有管道,信号量,共享内存,消息队列,套接字。
一.管道
管道可以用来在两个进程之间传递数据,如: ps -ef | grep “bash”, 其中‘|’就是管
道,其作用就是将 ps 命令的结果写入管道文件,然后 grep 再从管道文件中读出该数据进行
过滤。
1.1有名管道
有名管道可以在任意两个进程之间通信
有名管道的创建:
◼ 命令创建: mkfifo FIFO
◼ 系统调用创建
#include <sys/types.h>
#include <sys/stat.h>
//filename 是管道名 mode 是创建的文件访问权限
int mkfifo(const char *filename, mode_t mode);
a.c代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("./fifo",O_WRONLY);
if(fd ==-1)
{
exit(1);
}
printf("fd=%d\n",fd);
while(1)
{
printf("intput:\n");
char buff[128] ={0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3) ==0)
{
break;
}
write(fd,buff,strlen(buff));
}
close(fd);
}
b.c代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main()
{
int fd = open("./fifo",O_RDONLY);
printf("fd=%d\n",fd);
if(fd ==-1)
{
exit(1);
}
while(1)
{
char buff[128];
int n=read(fd,buff,127);
if(n==0)
{
break;
}
printf("buff=%s\n",buff);
}
close(0);
exit(0);
}
运行:
1.2无名管道
无名管道主要应用于父子进程间的通信。
无名管道的创建:
1. #include <unistd.h>
2. /*
3. pipe()成功返回 0,失败返回-1
4. fds[0]是管道读端的描述符
5. fds[1]是管道写端的描述符
6. */
7. int pipe(int fds[2]);
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
//无名管到
int main()
{
int fd[2];//fd[0] r fd[1] w
if(pipe(fd) == -1)
{
exit(1);
}
write(fd[1],"hello",5);
char buff[128] ={0};
read(fd[0],buff,127);
printf("%s\n",buff);
close(fd[0]);
close(fd[1]);
exit(0);
}
无名管道父子进程间的通信:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
int main()
{
int fd[2];//fd[0] r fd[1] w
if(pipe(fd) == -1)
{
exit(1);
}
pid_t pid =fork();
if(pid ==-1)
{
exit(1);
}
if(pid==0)
{
close(fd[1]);
while(1)
{
char buff[128]={0};
//read(fd[0],buff,127);
if(read(fd[0],buff,127) == 0)
{
//printf("child read:%s\n",buff);
break;
}
printf("chlid read:%s\n",buff);
}
close(fd[0]);
}
else
{
close(fd[0]);
while(1)
{
printf("input: ");
char buff[128] ={0};
fgets(buff,128,stdin);
if(strncmp(buff,"end",3)==0)
{
break;
}
write(fd[1],buff,strlen(buff));
}
close(fd[1]);
}
exit(0);
}
1.3管道的特点
◼
无论有名还是无名,写入管道的数据都在内存中
◼
管道是一种半双工通信方式(通信方式有单工、半双工、全双工)
◼
有名和无名管道的区别:有名可以在任意进程间使用,而无名主要在父子进程间
1.4管道的实现
二.信号量
由于父子进程间无法确定谁先谁后,所以运行结果会有顺序错乱的情况
提出使用信号量管理,使得父子进程间的通信变得有序
1.信号量
信号量是一个特殊的变量,一般取正数值。它的值代表允许访问的资源数目,获取资源
时,需要对信号量的值进行原子减一,该操作被称为 P 操作。当信号量值为 0 时,代表没有
资源可用,P 操作会阻塞。释放资源时,需要对信号量的值进行原子加一,该操作被称为 V
操作。信号量主要用来同步进程。信号量的值如果只取 0,1,将其称为二值信号量。如果信
号量的值大于 1,则称之为计数信号量。
临界资源:同一时刻,只允许被一个进程或线程访问的资源
临界区:访问临界资源的代码段
2.信号量的使用
操作信号量的接口介绍:
#include <sys/sem.h>
#include <sys/types.h>
#include <sys/ipc.h>
/*
semget()创建或者获取已存在的信号量
semget()成功返回信号量的 ID, 失败返回-1
key:两个进程使用相同的 key 值,就可以使用同一个信号量
nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号
量的个数
semflg 可选: IPC_CREAT IPC_EXCL
*/
int semget(key_t key, int nsems, int semflg);
/*
semop()对信号量进行改变,做 P 操作或者 V 操作
semop()成功返回 0,失败返回-1
struct sembuf
{
unsigned short sem_num; //指定信号量集中的信号量下标
short sem_op; //其值为-1,代表 P 操作,其值为 1,代表 V 操作
short sem_flg; //SEM_UNDO
};
*/
int semop(int semid, struct sembuf *sops, unsigned nsops);
/*
semctl()控制信号量
semctl()成功返回 0,失败返回-1
cmd 选项: SETVAL IPC_RMID
union semun
{
int val;
struct semid_ds *buf;
unsigned short *array;
struct seminfo *_buf;
};
*/
int semctl(int semid, int semnum, int cmd, ...);
例题:进程 a 和进程 b 模拟访问打印机,进程 a 输出第一个字符‘a’表示开始使用打印
机,输出第二个字符‘a’表示结束使用,b 进程操作与 a 进程相同。(由于打印机同一时刻
只能被一个进程使用,所以输出结果不应该出现 abab),如图所示:
封装信号量的接口:
sem.h 的代码如下:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/sem.h>
union semun
{
int val;
};
void sem_init();
void sem_p();
void sem_v();
void sem_destroy();
sem.c 的代码如下:
#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,IPC_CREAT|0600);//创建失败,说明已有,获取
if(semid == -1)
{
printf("semget err\n");
return;
}
}
else//创建成功,初始化
{
union semun a;
a.val =1;
if(semctl(semid,0,SETVAL,a) == -1)
{
printf("semcrl setval err\n");
}
}
}
void sem_p()
{
struct sembuf buf;
buf.sem_num =0; //信号两的下标,目前只有一个,下标为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; //信号两的下标,目前只有一个,下标为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");
}
}
a.c 的代码如下:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/sem.h>
#include"sem.h"
int main()
{
sem_init();
for(int i =0;i<5;i++)
{
sem_p();
printf("A");
fflush(stdout);
int n = rand() %3;
sleep(n);
printf("A");
fflush(stdout);;
sem_v();
n=rand() %3;
sleep(n);
}
}
//gcc -o a a.c sem.c
//gcc -o b b.c sem.c
b.c代码:
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/sem.h>
#include"sem.h"
int main()
{
sem_init();
for(int i =0;i<5;i++)
{
sem_p();
printf("B");
fflush(stdout);
int n = rand() %3;
sleep(n);
printf("B");
fflush(stdout);
sem_v();
n=rand() %3;
sleep(n);
}
sleep(10);
sem_destroy();
}
运行结果如下图所示,输出结果只截了部分:
练习题: 三个进程 a、b、c 分别输入“A”、“B”、“C”,要求输出结果必须是“ABCABCABC…”
3.ipcs/ipcrm 介绍
ipcs 可以查看消息队列、共享内存、信号量的使用情况,使用 ipcrm 可以进行删除操作。