目录
管道
匿名管道
命名管道
systemV共享内存
认识接口
创建共享内存(shmget)
控制共享内存(shmctl)
与共享内存建立关联(shmat)
与共享内存移除关联(shmdt)
信号量
进程间通信需要交互数据,成本很高,因为进程具有独立性,一个进程看不见另一个进程的资源。因此要进行进程间通信,需要让通信的进程看见一份公共的资源(这里的资源就是一段内存),该资源属于OS。
把从一个进程连接到另一个进程的一个数据流称为一个“管道”
父子进程通过继承共享数据,其中struct_file_struct中的文件映射表中的数据内容是浅拷贝,因此能看见同一份文件资源。
父子进程进行通信的过程:
(1)父进程创建管道,得到两个⽂件描述符指向管道的两端。
(2)父进程fork出子进程,⼦进程也有两个⽂件描述符指向同⼀管道。
(3)父进程关闭fd[0],子进程关闭fd[1],即⽗进程关闭管道读端,⼦进程关闭管道写端(因为管道只支持单向通信)。⽗进程可以往管道⾥写,⼦进程可以从管道⾥读,数据从写端流⼊从读端流出,这样就实现了进程间通信。
实现代码如下
#include
#include
#include
#include
int main(){
int pipefd[2] = {0};
if(pipe(pipefd) != 0){
perror("pipe error!");
return 1;
}
//0:读取端 1:写入端
printf("pipefd[0]:%d\n",pipefd[0]);
printf("pipefd[1]:%d\n",pipefd[1]);
if(fork() == 0){//child完成写入
close(pipefd[0]);//子进程关闭读端
const char* msg = "hello Linux!";
while(1){
write(pipefd[1], msg, strlen(msg));
sleep(1);
}
exit(0);
}
//parent完成读取
close(pipefd[1]);//父进程关闭写端
while(1){
char buffer[64] = {0};
ssize_t s = read(pipefd[0], buffer, sizeof(buffer) - 1);
if(s == 0){//意味着子进程退出了
printf("child quit\n");
break;
}
else if( s > 0 ){
buffer[s] = 0;
printf("child say to father# %s\n",buffer);
}
else{
printf("read error\n");
break;
}
}
return 0;
}
特点:
1.管道只允许具有血缘关系的进程间通信,如父子进程间的通信。
2.管道只允许单向通信。
3.管道内部保证同步机制,从而保证访问数据的一致性。
4.面向字节流。
5.管道生命周期随进程。
匿名管道通信的父子进程是通过继承的方式看见了一份公共的资源才得以通信。而命名管道是不同的进程进行通信,它们是通过路径+文件名(具有唯一性)的方式看见一份公共的资源。
创建命名管道需要使用下边的接口
下边我们编写一个server.c文件,只向文件中写入,client.c文件,只读取文件中内容。
server.c文件如下
#include "comm.h"
#include
#include
#include
int main(){
umask(0);
if(mkfifo(MY_FIFO, 0666) < 0){
perror("mkfifo");
return 1;
}
//
int fd = open(MY_FIFO, O_RDONLY);
if(fd < 0){
perror("open");
return 2;
}
//业务逻辑,可以进行对应的读写了
while(1){
char buffer[64] = {0};
sleep(5);
ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
if(s > 0){
buffer[s] = 0;
if(strcmp(buffer, "show") == 0){
if(fork() == 0){
execl("/usr/bin/ls", "ls", "-l", NULL);
exit(1);
}
waitpid(-1, NULL, 0);
}
else if(strcmp(buffer, "run") == 0){
if(fork() == 0){
execl("/usr/bin/sl", "sl", NULL);
}
}
else{
printf("client say: %s\n",buffer);
}
}
else if( s == 0 ){
printf("client quit ...\n");
break;
}
else{
perror("read");
break;
}
}
close(fd);
return 0;
}
client.c文件如下
#include "comm.h"
#include
int main(){
int fd = open(MY_FIFO, O_WRONLY);
if(fd < 0){
perror("open");
return 1;
}
//业务逻辑
while(1){
printf("client输入 -->");
fflush(stdout);
//先把数据从标准输入拿到我们的client进程内部
char buffer[64] = {0};
size_t s = read(0, buffer, sizeof(buffer) - 1);//键盘输入的时候,\n也是输入字符的一部分
if(s > 0){
buffer[s - 1] = 0;//这里下标减1是将\n置为0
printf("%s\n", buffer);
//拿到了数据
write(fd, buffer, strlen(buffer));
}
}
close(fd);
return 0;
}
其中server.c文件和client.c文件中的comm.h中文件如下
#include
#include
#include
#include
#include
#define MY_FIFO "./fifo"
运行结果
进程间通信的本质:先让不同的进程看到同一份资源。
共享内存示意如下
其中shmget的第一个参数key_t类型的key值是在系统层面对共享内存进行标识的。
而shmget的返回值是OS交给用户再用户层管理共享内存的。
ps:OS可能存在多个进程同时使用不同的共享内存进行通信,因此内核需要key值对共享内存进行描述和管理。
当shmctl的第二个参数设置为IPC_RMID的时候,就标识将该内核中的shmid标识的共享内存释放掉。
ps:共享内存的生命周期是随内核的,除了使用系统接口释放或者重启OS之外还能使用命令行对其进行释放。命令行格式如下:ipcrm -m + shmid
其中shmat的第二个参数可以设为null,第三个参数一般可以设置为0
使用上述函数实现一个server.c代码和client.c代码。设计思路:在server.c中建立共享内存并建立关联,再不停读取共享内存中的数据。读完之后,server.c移除与共享内存的关联,再将共享内存释放掉。client.c通过ftok函数、相同的路径名和项目id,获得与server.c中建立共享内存的shmid,与其建立关联,再向共享内存中不断写入数据,写完之后移除与共享内存的关联。
预期运行结果应该是server.c不断从共享内存中打印出client.c向共享内存写入的数据。
server.c
#include "comm.h"
int main(){
key_t key = ftok(PATH_NAME, PROJ_ID);
if(key < 0){
perror("ftok");
return 1;
}
int shmId = shmget(key, SIZE,IPC_CREAT | IPC_EXCL | 0666);//创建全新的shm,如果和系统已经存在ID冲突,就出错返回
if(shmId < 0){
perror("shmget");
return 2;
}
printf("key->%u, shmId->%d\n", key, shmId);
//sleep(1);
char* mem = (char*)shmat(shmId, NULL, 0);//建立关联
printf("attaches shm success\n");
//sleep(15);
//开始逻辑实现的部分
while(1){
sleep(1);
printf("%s\n", mem);
}
shmdt(mem);//去关联
printf("detaches shm success\n");
shmctl(shmId, IPC_RMID, NULL);
//sleep(5);
printf("key->0x%u, shmId->%d shm delete success\n", key, shmId);
//sleep(10);
return 0;
}
client.c
#include "comm.h"
int main(){
key_t key = ftok(PATH_NAME, PROJ_ID);
if(key < 0){
perror("ftok");
return 1;
}
printf("%u\n",key);
//client这里只需要获取即可
int shmid = shmget(key, SIZE, IPC_CREAT);
if(shmid < 0){
perror("shmget");
return 1;
}
char* mem = (char*)shmat(shmid, NULL, 0);//建立关联
//sleep(5);
printf("client process attaches success!\n");
//这里是进行通信的部分
char c = 'A';
while(c <= 'Z'){
mem[c - 'A'] = c;
c++;
mem[c - 'A'] = 0;
sleep(2);
}
shmdt(mem);//移除关联
//slee(5);
printf("client process detaches success!\n");
return 0;
}
运行结果:
该结果只运行了部分
匿名管道、命名管道还是共享内存,消息队列都是以传输数据为目的,但信号量不是以传输数据为目的的。而是通过共享“资源”的方式,来达到多个进程的同步或者互斥的目的。信号量的本质是一个计数器,类似int count;用来衡量临界资源中资源数目的。
临界资源:凡是被多个执行流同时访问的资源就是临界资源,比如向显示器打印,进程通信中的匿名管道、命名管道和共享内存等。
临界区:进程的代码很多,其中用来访问临界资源的代码称为临界区
原子性:一件事要么不做,要么做完,没有中间态称为原子性
互斥:在任意一个时刻,只能允许一个执行流进入临界资源,执行它自己的临界区