目录
可以利用这种技术完成多个进程间的数据传递,消息收发。
有以下几种方式:匿名管道(pipe),有名管道(fifo),Posix消息队列,SystemV消息队列,MMAP内存共享映射,Signal信号,Socket套接字技术。
绝大多数的进程间通讯技术都是利用内核层实现的(内核层共享内存)。
pipe匿名管道
特点:1.流通性(传输介质)2.方向性3.暂存能力
创建管道,pipe(fds)穿的参数是数组的首地址,int fds[2],创建成功后管道缓冲区是4k环形队列,老版本是64k。创建成功后,系统将读写管道的两个描述符传出到fds中,让用户可以读写使用管道。
所以一般来说fds[0]=3,fds[1]=4,因为0,1,2都被占了。
匿名管道有亲缘限制,只有亲缘进程间可以利用完成进程通信。
管道使用前要确定通信方式,管道要单工使用,让进程关闭无用描述符。(单工:定义通信方向,不可改变,非读即写)。(可调节单工:在一个时刻非读即写,不同时刻可切换)(全双工:可同时读写)。
每一个指向管道描述符都是一个引用计数,当管道引用计数为0,系统会自行释放管道空间。
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/wait.h>
int main()
{
int fds[2];
pipe(fds);
pid_t pid;
pid=fork();
if(pid>0)
{
close(fds[0]);
printf("father pid %d\n",getpid());
write(fds[1],"hahahahhahhahha",strlen("hahahahhahhahha"));
wait(NULL);
close(fds[1]);
}else if(pid==0){
close(fds[1]);
char buf[1024];
bzero(buf,sizeof(buf));
read(fds[0],buf,sizeof(buf));
printf("child read %s\n",buf);
close(fds[0]);
exit(0);
}else{
printf("fail\n");
}
return 0;
}
管道为空,写端未写数据,读端读取管道,读阻塞。
管道为满,读端未读数据,写端写管道,写阻塞。
写端关闭,如果管道有数据,读端读取完管道剩余数据后再次读返回0,管道为空直接返回0.
管道读端关闭,写端尝试向管道写数据,系统会向写端进程发送SIGPIPE,杀死写端进程。
问题:
编写一个服务端客户模型,可以进行连接和基本的数据收发,客户端异常退出,服务器也异常退出,为什么?和pipe一样。
send(int sockfd,buffer,len,MSG_NOSIGNAL)//如果系统给服务端(写端)发送SIGPIPE信号,可以利用MSG_NOSIGNAL忽略信号。
缺点:亲缘限制,只能亲缘进程可以使用其完成通信。默认情况下管道使用无格式字节流传输,需要用户自行封装。
管道这种进程间通信方式效率好。在所有unix和linux系统,管道都是可以使用的,但是其它系统不一定。
有名管道
mkfifo 测试管道//创建管道文件
管道文件没有存储能力,无法编辑。创建管道文件后,系统会在内核层创建一个管道缓冲区,管道文件被删除,立即清理释放管道缓冲区。
命名管道使用时也是单工方式。匿名管道的四种情况适用于有名管道。
访问命名管道,必须满足两种权限,读写,才可以成功的打开和使用管道,如果只有一种访问权限,会阻塞等待另一种。
如下代码open阻塞了。
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
int main()
{
int rfd=open("havenamepipe",O_RDONLY);
char buf[1024];
bzero(buf,sizeof(buf));
read(rfd,buf,sizeof(buf));
close(rfd);
printf("%s\n",buf);
return 0;
}
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<sys/stat.h>
q
include<sys/types.h>
#define MEE "hello"
int main()
{
int wfd=open("havenamepipe",O_WRONLY);
write(wfd,MEE,strlen(MEE));
printf("write %d\n",getpid());
close(wfd);
return 0;
}
单进程只要满足两种访问权限(O_RDWR),可以直接打开和使用管道。
unlink函数可以删管道,文件。
有名管道使用时的特殊情况:
1.权限要求,满足两种访问权限才可以打开使用管道,否则要阻塞等待另一种。
2.如果在一个进程中有多个读序列,阻塞只会对第一个读序列生效,其他都会设置非阻塞。(多线程为进程读取数据,一个线程阻塞即可,其他线程设置非阻塞立即返回,执行其他任务,避免阻塞开销)。
3.原子使用管道(传输速度较慢,但是数据完整性比较好)和非原子使用管道(传输效率高,无法保证数据完整性)。
原子访问(一次写的大小小于等于管道大小):系统会检测管道余量,如果写端的写大小大于管道余量,系统会挂起写端进程,等管道余量满足写需求大小,再唤醒写端继续写。原子模式可以保证,读写完整,一次收发一个消息包。
非原子访问(每次写的大小大于管道大小):非原子访问情况下,系统不会控制写端,只要管道缓冲区有余量,写端可以立即写入数据,传输效率高,但是读端读取数据后要校验完整性,避免数据包的异常。
不要频繁变更传输模型。
MMAP文件共享映射
void* ptr=mmap(NULL,int Filesize,PROT_READ|PROT_WRITE|PROT_EXEC\PROT_NONE,映射方式,fd,offset);offset传页的大小或页的倍数,Filesize映射文件大小,用户返回的指针类型取决于文件的·数据内容,以及用户想以何种方式访问数据。
munmap(ptr,Filesize)//释放映射内存。
首先进程A调用mmap()创建出一个映射内存,然后加载文件中的内容到映射内存,有两种映射方式:1.私有映射(拷贝映射)2.共享映射(sync同步),然后进程B也mmap(),读取文件中内容。
mmap适合处理大数据文件,比read方式有更小的开销(拷贝开销)
通过映射方式将文件数据加载到进程内存,效率更高,开销更小。(映射文件必须有大小,如果是空文件要扩展文件大小)。
以下代码我们用16进制修改内容。
od -tcx 文件名,查看文件内容的16进制。
代码流程:首先先获得文件描述符fd,然后用lseek计算文件大小Filesize,然后mmap,关闭文件描述符,修改内容,释放映射内存。
#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/mman.h>
int main()
{
int fd=open("MMAPFile",O_RDWR);
int Filesize=lseek(fd,0,SEEK_END);
int* ptr=mmap(NULL,Filesize,PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
if(ptr==MAP_FAILED)
{
printf("on map error\n");
exit(0);
}
close(fd);
ptr[0]=0x34333231;
munmap(ptr,Filesize);
return 0;
}
映射文件是覆盖使用还是偏移缓存取决于读端如何读取数据。应将文件截断为特定大小(如结构体大小)。
#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/mman.h>
typedef struct
{
int id;
char name[1024];
}M_ess;
int main()
{
int fd=open("MMAPFile",O_RDWR);
ftruncate(fd,sizeof(M_ess));
M_ess* ptr=mmap(NULL,sizeof(M_ess),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
ptr->id=0;
bzero(ptr->name,1024);
close(fd);
while(1){
sleep(1);
sprintf(ptr->name,"zzj %d",ptr->id);
(ptr->id)++;
}
munmap(ptr,sizeof(M_ess));
return 0;
}
#include<stdio.h>
#include<sys/types.h>
#include<fcntl.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/mman.h>
typedef struct
{
int id;
char name[1024];
}M_ess;
int main()
{
int fd=open("MMAPFile",O_RDWR);
M_ess* ptr=mmap(NULL,sizeof(M_ess),PROT_READ|PROT_WRITE,MAP_SHARED,fd,0);
close(fd);
while(1){
sleep(1);
printf("%s\n",ptr->name);
}
munmap(ptr,sizeof(M_ess));
return 0;
}
进程的互斥锁
pthread_mutexattr_t arr;初始化互斥锁属性
pthread_mutexattr_setpshared设置为进程间共享
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#include<string.h>
typedef struct {
char ans[1024];
int shared_var;
pthread_mutex_t mutex;
} SharedData;
void increment(SharedData* data) {
int i;
for (i = 0; i < 1000; i++) {
pthread_mutex_lock(&data->mutex);
data->shared_var++;
pthread_mutex_unlock(&data->mutex);
}
}
int main() {
int fd = open("gongxiang", O_CREAT | O_RDWR, 0664);
if (fd < 0) exit(0);
ftruncate(fd, sizeof(SharedData));
SharedData* data = mmap(NULL, sizeof(SharedData), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
if (data == MAP_FAILED) {
perror("mmap\n");
exit(0);
}
data->shared_var = 0;
bzero(data->ans,1024);
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&data->mutex, &attr);
pthread_mutexattr_destroy(&attr);
pid_t pid = fork();
if (pid > 0) {
increment(data);
wait(NULL);
printf("最终的共享变量值是: %d\n", data->shared_var);
sprintf(data->ans,"%d\n",data->shared_var);
pthread_mutex_destroy(&data->mutex);
munmap(data, sizeof(SharedData));
}
else if (pid == 0) {
increment(data);
munmap(data,sizeof(SharedData));
exit(0);
}
else exit(0);
return 0;
}
有乱码的原因是文件里有结构体的锁不能直接显示出来不是字符串。
关于mmap权限异常
如果一个权限不足的用户,通过映射方式可以修改文件,那么权限变得不安全,映射权限取决于open打开文件的权限,要小于等于打开权限,否则映射失败,映射是否成功与用户的权限无关而是看open的权限。
映射内存的位置
映射内存在库里申请,不够向堆申请。
关于映射大小问题
当申请出映射内存后,mmap会获取文件长度,来开发内存访问权限,如果文件为0,那么没有任何访问权限,向此内存读写会出现总线错误,即访问越界问题。(段错误(非法操作内存)总线错误(内存越界引发异常)
mmap比read减少一次拷贝开销,mmap可以用于实现零拷贝计数,减少拷贝开销,sendfile更强。
mmap偏移映射(处理大文件)
int i=0;i=i*page;i++;
while(ptr=mmap(NULL,4096,PROT_READ,MAP_PRIVATE,fd,i));