进程间通信:采用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] wif(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] wif(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, 失败返回-1key:两个进程使用相同的 key 值,就可以使用同一个信号量nsems:内核维护的是一个信号量集,在新建信号量时,其指定信号量集中信号
量的个数semflg 可选: IPC_CREAT IPC_EXCL*/int semget(key_t key, int nsems, int semflg);/*semop()对信号量进行改变,做 P 操作或者 V 操作semop()成功返回 0,失败返回-1struct 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,失败返回-1cmd 选项: SETVAL IPC_RMIDunion 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; //信号两的下标,目前只有一个,下标为0buf.sem_op =-1;//pbuf.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; //信号两的下标,目前只有一个,下标为0buf.sem_op =1;//vbuf.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 可以进行删除操作。