进程IPC 的 7种方式
linux下 进程通讯IPC的方式主要有以下7种:
1.文件
2.共享内存
3.信号
4.管道
5.套接字
6.消息列队
7.信号量
以下正文 中 一一 分析下:
1.文件 ,记得 加文件锁 lockf.使用少,略去
2.共享内存
由于内存的保护机制的作用,进程不会简单地将自己的内存空间暴露给其他进程,并允许这些进程 进行读写,根据linux下的内存保护机制,进程中的一个指针指向的内存地址 是 一个虚拟地址,所以不会涉及到真实的物理地址,传递这个地址 到其他进程并不能达到 我们期望的结果。总之,一个虚拟地址,仅仅在创建它的那个进程中有意义。
任何要被共享的内存都必须经过 显示的分配,而不是简单的 在堆上分配,
POSIX 共享内存的api:
函数 | 作用 |
shm_open | 创建一个新的共享区域或者附加在已有的共享区域上,区域被其名字标识,函数返回各文件的描述符,类似于open系统调用 |
shm_unlink | 对 shm_open 返回的文件描述符 共享区域进行释放,类似于使用 unlink系统调用 对文件进行操作,知道所有的进程都不再饮用该内存后才对其进行释放。 |
mmap | 用于将一个文件 映射到 某一个内存区中,其中 也使用来shm_open返回的文件描述符,函数的返回值 是指向文件映射到内存地址的指针。mmap同样可以 使用纯文本文件 和一些 驱动程序的文件描述符 |
munmap | 用于释放mmap所映射的内存区域 |
msync | 同步存取一个映射区域 并将高速缓存的数据 回写到物理内存中,以便其他进程可以舰艇这些改变 |
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<sys/file.h> #include<sys/mman.h> #include<sys/wait.h> void error_out(constchar* msg) { perror(msg); exit(EXIT_FAILURE); } int main(int argc,char*argv[]) { int r; constchar*memname ="/mymen"; constsize_t region_size = sysconf(_SC_PAGE_SIZE); //create the share memory region int fd = shm_open(memname,O_CREAT | O_TRUNC | O_RDWR,0666); if(fd ==-1) error_out("shm_open"); //allocate some memory in the region , r = ftruncate(fd,region_size); if(r !=0) error_out("ftruncate"); //map the region into the memory void*ptr = mmap(0,region_size,PROT_READ | PROT_WRITE ,MAP_SHARED,fd,0); if(ptr == MAP_FAILED) error_out("mmap"); //useness after mmap called close(fd); pid_t pid = fork(); //child if(pid ==0) { u_long *d =(u_long*)ptr; *d =0xfdefdddd; exit(0); } //parent else{ int status; waitpid(pid,&status,0); printf("child wrote %#1x ",*(u_long*)ptr); } //release memory //umap r = munmap(ptr,region_size); if(r !=0) error_out("munmap"); r = shm_unlink(memname); if(r !=0) error_out("shm_ulink"); return0; }
gcc -o posix_shm posix_shm.c -lrt
-lrt 不可缺少
System V 共享内存的api
函数 | 作用 |
shmget | 创建一个新的共享区域或者已有的共享区域上(同shm_open +一个内存申请函数) |
shmat | 用于将一个文件 映射到内存区域中(同mmap) |
shmdt | 用于释放所映射的内存区域(同munmap ) |
shmct | 对用多个用户,断开其对共享区域的连接(同shm_unlink) |
说明:shmget函数 创建并分配了共享内存 区域,所以不需要 截断ftruncate 文件描述符的 步骤
总体而言,system V的api更得我心啊!
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include <sys/ipc.h> #include<sys/shm.h> #include<sys/wait.h> void error_out(constchar* msg) { perror(msg); exit(EXIT_FAILURE); } int main(int argc,char*argv[]) { int r; key_t mykey =12345678; constsize_t region_size = sysconf(_SC_PAGE_SIZE); //create the shared memory region int shmid = shmget(mykey,region_size,IPC_CREAT |0666); if(shmid ==-1) error_out("shmget"); //map the region into memory void*ptr = shmat(shmid,NULL,0); if(ptr ==(void*)-1) error_out("shmat"); //useness after mmap called pid_t pid = fork(); //child if(pid ==0) { u_long *d =(u_long*)ptr; *d =0xfdefdddd; exit(0); } //parent else{ int status; waitpid(pid,&status,0); printf("child wrote %#1x ",*(u_long*)ptr); } //release memory //umap r = shmdt(ptr); if(r ==-1) error_out("shmdt"); //remove the shared memory region r = shmctl(shmid,IPC_RMID,NULL); if(r ==-1) error_out("shmctl"); return0; }
gcc -o sysv_shm sysv_shm.c
3.信号
可以使用,但是 很复杂,且容易出错,略去
4.管道
未命名管道(unnamed pipe)用于父子进程之间,
命名管道 (fifo),用于一个计算机上平行的进程之间
api说明:
系统调用int pipe(int filedes[2]) ;
返回一对 文件描述符
第一个文件描述符fd[0] 的作用 表示只读,第二个fd[1]表示 只写,
mkfifo(char const *pathname,mode_t mode)用于创建一个 有名管道
5.套接字
网络 编程 另讲
6.消息列队
同样有两套实现,posix和system V
都采用 固定大小、基于优先级的消息,接受者必须确定消息的大小,并一次对消息进行读取,否则读取失败,而且要确保所用的进程遵循进程相同的消息结构
system V api:
函数 | 作用 |
int msgget(key_t key,int msgflg); | 创建或者附加一个消息列队,返回一个msgID |
int msgctl(int msgid,int cmd,struct msgid_ds *buf) | 可以用于删除一个msgID,当buf参数为空的情况下,删除消息列队的cmd为ipc_rmid |
int msgsnd(int qid,void* msg,size_t msgsz,int msgflg) | 发送() |
ssize_t msgrcv(int qid,void* msg,size_t msgsz,int msgflg) | 接收() |
例子:
#include<stdio.h> #include<string.h> #include<stdlib.h> #include<unistd.h> #include<sys/wait.h> #include<sys/stat.h> #include<sys/file.h> #include<sys/msg.h> #include<sys/ipc.h> struct message { longint mtype; char mtext[128]; }; void error_out(constchar* msg) { perror(msg); exit(EXIT_FAILURE); } //send msg int send_msg(int qid,int mtype,constchar text[]) { struct message msg ={ .mtype = mtype }; strncpy(msg.mtext,text,sizeof(msg.mtext)); int r = msgsnd(qid,&msg,sizeof(msg),0); if(r ==-1) error_out("msgsnd"); return r; } //recv msg int recv_msg(int qid,int mtype,struct message* msg) { int r = msgrcv(qid,msg,sizeof(struct message),mtype,0); switch(r) { casesizeof(struct message): break; case-1: perror("msgrcv"); break; default: printf("only recv %d bytes data ",r); break; } return r; } void producer(int mqid) { send_msg(mqid,1,"type 1 - first"); send_msg(mqid,2,"type 2 - second"); send_msg(mqid,1,"type 1 - thrid"); } void consumer(int qid) { struct message msg; int r ; int i ; for(i =0;i <3;++i) { r = msgrcv(qid,&msg,sizeof(struct message),-2,0); printf("[%s] ",msg.mtext); } } int main(int argc ,char*argv[]) { //create a private (unname) message queue int mqid; mqid = msgget(IPC_PRIVATE,S_IREAD | S_IWRITE); if(mqid ==-1) error_out("msgget"); pid_t pid = fork(); if(pid ==0) { consumer(mqid); exit(0); } else { int status; producer(mqid); wait(&status); } //remove the message queue int r = msgctl(mqid,IPC_RMID,0); if(r) perror("msgctl"); return0; }
gcc -o systemV_mq systemV-mgq.c
./systemV_mq
[type 1 - first]
[type 1 - thrid]
[type 2 - second]
特别指出的是,在消费者 函数中将 msgrecv可以接受的消息类型指定为 -2 了,它表明可以接受 类型为 2 ,或者优先级更低的消息,而且该数值越低,优先级越高,则越先被接受,为了 使 接受和发送的次序保持一致,需要将该值设定为0;
总之:当msgrecv的类型为非0 时,消息是按优先级高低进行接收的,
- 正数,只接受 该类型的消息
- 负数,只接受 小于或者等于 指定类型绝对值 的消息
posix api : 略去
7.信号量 semaphore
用于 保持 并发进程的同步,信号量 类似于 并发进程的 交通信号灯。
其本质 是 一个计数器,一种常用的用法是 为每个资源都分配一个信号量,所以,信号量计数的增量从来不会 大于1 ,注意 :是增量 或者减量
posix api:
函数 | 作用 |
sem_t *sem = sem_open() | 创建信号量 并初始化为0 |
sem_post(sem ) | 信号量 +1 |
sem_wait(sem ) | 信号量 -1 |
system V API
函数 | 作用 |
semget | 生成 信号量ID, |
semop | 通过不同的op,增或者 减 信号量 |
基础知识可以参考:
本文参考:《linux开发工具箱》