进程间通信(IPC)方法:管道、信号量、共享内存、消息队列、套接字
一、有名管道
管道可以用来在两个进程之间传递数据,如: ps -ef | grep bash , 其中| 就是管道,其作用就是将 ps 命令的结果写入管道文件,然后 grep 再从管道文件中读出该数据进行过滤。管道通信方式为 半双工点对点通信 创建管道文件命令:mkfifo <文件名>
- 写入数据时,写指针后移;读取数据时,读指针后移(两指针在内存中循环移动)
- 管道文件可读数据为读写指针之间的数据
- 每次读取的数据量为min(
管道可读数据量 ,期望读的数据量 ) - 当读指针追上写指针,表示管道文件读空了;当写指针追上读指针,表示管道文件写满了;
读进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
int main(){
int fd = open("fifo", O_RDONLY);
if(fd == -1){
exit(0);
}
printf("fd = %d\n", fd);
while(1){
char buff[128] = {0};
int n = read(fd, buff, 128);
printf("n = %d\n", n);
if(n == 0){
break;
}
printf("%s", buff);
}
close(fd);
exit(0);
}
写进程
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<signal.h>
void sig_fun(int sig){
printf("sig = %d\n", sig);
}
int main(){
signal(SIGPIPE, sig_fun);
int fd = open("fifo", O_WRONLY);
if(fd == -1){
exit(0);
}
printf("fd = %d\n", fd);
while(1){
char buff[128] = {0};
printf("input:");
fflush(stdout);
fgets(buff, 128, stdin);
if(strcmp(buff, "end\n") == 0){
break;
}
int n = write(fd, buff, strlen(buff));
}
close(fd);
exit(0);
}
管道文件在内存里,不在硬盘里,显示永远为0
二、无名管道
父子进程可利用无名管道进行通信,fork后管道文件描述符的引用计数分别加1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
int main(){
int fd[2];
if(pipe(fd) == -1){
printf("create pipe error\n");
exit(0);
}
pid_t pid = fork();
if(pid == -1){
close(fd[0]);
close(fd[1]);
exit(0);
}
if(pid == 0){
close(fd[1]);
char buff[128] = {0};
read(fd[0], buff, 128);
printf("child read : %s\n", buff);
close(fd[0]);
}else{
close(fd[0]);
write(fd[1], "hello", 5);
close(fd[1]);
}
exit(0);
}
管道特点:
- 无论有名还是无名,写入管道的数据都在内存中
- 管道是一种半双工通信方式(通信方式有单工、半双工、全双工)
- 有名和无名管道的区别:有名可以在任意进程间使用,而无名主要在父子进程间
无名管道创建后只有2个文件描述符,一般是通过fork给子进程进行通信;而对于有名管道,只要能open都可以进行读或写
三、信号量
临界资源: 同一时刻,只允许被一个进程或线程访问的资源 临界区: 访问临界资源的代码段
使用信号量实现3个进程打印:ABCABCABC… sem.h
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/sem.h>
#define SEM_NUM 3
#define SEM1 0
#define SEM2 1
#define SEM3 2
union semun{
int val;
};
void sem_init();
void sem_p(int index);
void sem_v(int index);
void sem_destroy();
sem.c
#include "sem.h"
static int semid = -1;
void sem_init(){
int arr[SEM_NUM] = {1,0,0};
semid = semget((key_t)2345, SEM_NUM, IPC_CREAT|IPC_EXCL|0600);
if(semid == -1){
semid = semget((key_t)2345, SEM_NUM, 0600);
if(semid == -1){
printf("semget error\n");
return;
}
}else{
union semun a;
for(int i=0; i<SEM_NUM; i++){
a.val = arr[i];
if(semctl(semid, i, SETVAL, a) == -1){
printf("semctl setval\n");
}
}
}
}
void sem_p(int index){
if(index < 0 || index >= SEM_NUM){
printf("index out of range");
return ;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = -1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1){
printf("semop error");
}
}
void sem_v(int index){
if(index < 0 || index >= SEM_NUM){
printf("index out of range");
return ;
}
struct sembuf buf;
buf.sem_num = index;
buf.sem_op = 1;
buf.sem_flg = SEM_UNDO;
if(semop(semid, &buf, 1) == -1){
printf("semop error");
}
}
void sem_destroy(){
if(semctl(semid, 0, IPC_RMID) == -1){
printf("semctl IPC_RMID error");
}
}
a.c
#include "sem.h"
int main(){
sem_init();
for(int i=0; i<5; i++){
sem_p(SEM1);
printf("A");
fflush(stdout);
sem_v(SEM2);
sleep(1);
}
}
b.c
#include "sem.h"
int main(){
sem_init();
for(int i=0; i<5; i++){
sem_p(SEM2);
printf("B");
fflush(stdout);
sem_v(SEM3);
sleep(1);
}
}
c.c
#include "sem.h"
int main(){
sem_init();
for(int i=0; i<5; i++){
sem_p(SEM3);
printf("C");
fflush(stdout);
sem_v(SEM1);
sleep(1);
}
sem_destroy();
}
ipcs -s :查看信号量 ipcrm -s <sem-id> :删除信号量
四、共享内存
共享内存为多个进程之间共享和传递数据提供了一种有效的方式。
共享内存是先在物理内存上申请一块空间,多个进程可以将其映射到自己的虚拟地址空间中。所有进程都可以访问共享内存中的地址,就好像它们是由 malloc 分配的一样,无需拷贝,就是使用的公共内存
如果某个进程向共享内存写入了数据,所做的改动将立刻被可以访问同一段共享内存的任何其他进程看到。由于它并未提供同步机制,所以我们通常需要用其他的机制来同步对共享内存的访问。
五、消息队列
FIFO
消息队列独立于进程存在,只要写入了,消息一直都在(除非读取),和程序是否运行没有关系,消除在同步命名管道的打开和关闭时可能出现的问题。
需要添加消息类型,才能写入消息队列,以消息为单位进行写入和读取
读取的时候,需要指定消息类型,同一消息类型的数据块按顺序读取,直到消息队列中此类型的消息被读空。在函数中,也可指定消息类型为0 ,表示所有类型的数据都可以读取。
|