深圳盐田住房和建设局网站,邯郸市教育考试院官网,wordpress开店铺,最新网页游戏开服时间表文章目录1、进程间通信1.1 进程的通信1.2 如何让进程间通信#xff1f;1.3 进程间通信的本质2、管道通信2.1 匿名管道2.2 匿名管道通信2.3 命名管道2.4 命名管道的通信3、SystemV中的共享内存通信3.1 共享内存3.2 共享内存的通信3.3 共享内存的缺点以及数据保护3.4 共享内存的…
文章目录1、进程间通信1.1 进程的通信1.2 如何让进程间通信1.3 进程间通信的本质2、管道通信2.1 匿名管道2.2 匿名管道通信2.3 命名管道2.4 命名管道的通信3、SystemV中的共享内存通信3.1 共享内存3.2 共享内存的通信3.3 共享内存的缺点以及数据保护3.4 共享内存的优点3.5 信号量以及与共享内存有关的概念1、进程间通信
1.1 进程的通信
在一些应用场景下一定要求着进程之间进行通信而通信有以下这些 数据传输进程间将数据传递给彼此。 资源共享多个进程共享同一份资源。 通知事件一个进程向另一个进程发送信息通知它发生了某种事件。比如父进程向子进程传递信号让子进程退出。 进程控制一个进程控制另一个进程。比如debug 比如在linux中通过命令ps ajx | grep hello两个命令分别对应着两个进程通过管道进行进程通信。也就是说为了完成某些业务我们是需要多进程进行协同的 1.2 如何让进程间通信
Linux的主流通信标准有以下
POSIX 让通信过程可以跨主机System V 聚焦在本地通信这种方式这种标准有着三种通信方式共享内存、消息队列、信号量这里只谈共享内存因为SystemV标准不常用。
第二套通信方案 管道是最基本的通信方案管道基于文件系统。 管道有着匿名管道和命名管道。 1.3 进程间通信的本质
首先进程是具有独立性的所以如果要让进程进行通信肯定是不能进程与进程直接联系的。 那么就需要以下 1、OS需要直接或间接的给通信双方的进程提供共享空间。 2、要让通信的进程都看到同一份共享空间。 而不同的通信种类本质就是这边共享空间的不同区别在OS哪个模块提供的。比如通过文件系统提供的资源就是管道。
综上我们需要做的就是 1、需要让进程看到同一份资源。这个是主要的 2、通信
2、管道通信
2.1 匿名管道
理解管道通信 在讲文件的时候说到过一个进程的PCB中有一个指针指向文件描述符表被打开的文件被其中文件描述符下标指向。 如果有一个父进程打开了一个文件那么文件描述符表中对应就会指向这个文件对应的结构体对象通过这个文件结构体中有着文件的操作方法以及一个内核缓冲区。 父进程创建子进程后子进程拷贝父进程对应文件描述符表中对应也会指向这个文件对应结构体对象。 综上两个进程指向同一文件资源这个文件就是一个共享空间也是一个管道。 另一个问题普通文件传输数据是需要经过磁盘的如果进程间通过文件传输数据经过磁盘那么就是内存与外设的交互效率很低。那么OS能不能只创建一个文件结构体对象只在内存中交互呢 答案是肯定的这个文件不会真的存在OS会申请一个文件结构体对象。 同时这个文件是一个内存级文件没有名字也没有对应inode所以称之为匿名管道。 管道只能单向通信 首先创建管道将读写端返回给进程。 为了让子进程也看到读写端父进程fork创建子进程。 因为管道需要单向通信所以对应读端要关闭写端对应写端要关闭读端。 综上匿名管道就是一个父进程通过读和写的方式打开一个内存级文件后通过创建子进程关闭各自读写端所构成的通信信道这个信道基于文件是一个内存级文件所以称为匿名管道。 2.2 匿名管道通信
pipe接口 pipe系统接口可以创建一个单向管道用于进程间通信pipefd[2]参数是一个返回型参数返回两个文件描述符一个读端fd[0]和一个写端fd[1]指向管道文件的两边。 知道了如何创建管道后下面需要让两个进程都看到这个共享空间 因为管道返回的是文件描述符所以通过文件描述符就可以访问管道。 综上思路就很简单 1. 创建管道接收管道文件描述符。 2. 父进程创建子进程进程会获取和父进程一样的文件描述符。 3. 可以让父进程读子进程写然后父进程需要关闭写端子进程关闭读端。 4. 通过文件操作进行通信。 测试代码:
#include iostream
#include cstdio
#include cstring
#include string
#include unistd.h
#include cassert
#include sys/types.h
#include sys/wait.husing namespace std;
int main()
{//第一步创建管道文件打开读写端int fds[2];int n pipe(fds);assert(n 0);//第二步forkpid_t id fork();assert(id 0);if(id 0){//子进程写入close(fds[0]);//子进程的通信代码int cnt 0;while(true){char buffer[1024];snprintf(buffer, sizeof buffer, child-parent say: %s[%d][%d],我是子进程,我正在发消息, cnt, getpid());//一直往管道输入端写写满也会阻塞write(fds[1], buffer, strlen(buffer));cout count: cnt endl;sleep(2); //每隔2s 写一次 }close(fds[1]); //写完 子进程关闭写端cout 子进程关闭自己写端 endl;sleep(5);exit(0);}//父进程读取close(fds[1]);//父进程的通信代码while(true){char buffer[1024];//如果管道中没有了数据读端在读默认会直接阻塞当前正在读取的进程ssize_t s read(fds[0], buffer, sizeof(buffer) - 1);if(s 0){buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;//break;}else if(s 0){//读到文件末尾cout read: s endl;break;}//父进程没有sleep}close(fds[0]);int status 0;n waitpid(id, status, 0);cout waitpid: n sig: (status 0x7f) endl;assert(n id);return 0;
} 匿名管道读写特性 之前主要做的是让进程间看到同一份资源而通信的过程是可以任意想象的但是大概就以下几个情况。 1、写的快读的慢。那么每次读取的时候就会从缓冲区读一大堆 2、写的慢读的快。 3、写后关闭写端读端继续。当读完后子进程退出父进程需要回收子进程 4、一直写读端关闭。这种情况的出现让数据没有意义 前三个很好理解如果第四种情况出现写端一直写读端却不读那么写的数据就没有任何意义造成了空间浪费。 当写端还在读端关闭这个条件发生时操作系统为了避免空间浪费就会给父进程发送SIGPIPE13信号关闭父进程。 管道特点
管道的生命周期随进程。管道可以用来进行具有血缘关系的进程间进行通信常用于父子间通信。管道是面向字节流的。管道只允许单向通信 半双工的。任何时候都是一方向另一方发送数据互斥和同步机制。共享资源保护机制这个可以和后面共享内存进行比较 2.3 命名管道
前面的匿名管道是有血缘关系的进程间进行通信的。 如果想让毫不相关的两个进程通过管道通信呢命名管道就能解决这个问题。 命名管道相较于匿名管道就是在磁盘有一个特殊的管道文件这个文件在打开后也拥有着自己的struct file。 两个不相关的进程是如何看到同一份资源的呢 可以让不同进程通过文件名路径文件名打开。匿名管道的结构体对象中的地址没有名称命名管道的结构体对象中的地址拥有名称 值得注意的是进程传递到内核缓冲区的数据不会刷新到磁盘上。 2.4 命名管道的通信
函数调用mkfifo 函数调用mkfifo用于创建命名管道也是一个特殊的文件。 pathname: 表示创建文件的路径如果只是一个文件名就和进程在同一路径。 mode: 表示创建的文件名拥有的初始权限。前提设置umask(0)不然结果就是mode ~umask 创建成功返回0失败返回-1。 函数调用unlink unlink在此用于删除管道文件只需传管道路径成功返回0错误返回-1。 对应通信也很简单。 1、创建管道文件进程1打开文件向文件里写入。 2、进程2打开文件向文件里读取读完后删除管道文件。 //comm.hpp
#pragma once
#include iostream
#include string
#include sys/types.h
#include sys/stat.h
#include unistd.h
#include cerrno
#include cstring
#include cassert
#include fcntl.h#define NAMED_PIPE mypipebool createFifo(const std::string path)
{umask(0);int n mkfifo(path.c_str(), 0666);if(n 0){return true;}else{std::cout errno: errno err stirng: strerror(errno) std::endl;return false;}
}void removeFifo(const std::string path)
{int n unlink(path.c_str());assert(n 0);(void)n;
}#include comm.hppint main()
{createFifo(NAMED_PIPE);int wfd open(NAMED_PIPE, O_WRONLY, 0666);int cnt 10;while(cnt--){char buffer[1024];std::cout client begin: std::endl;fgets(buffer, sizeof buffer, stdin);std::cout client end: std::endl;ssize_t w write(wfd, buffer, strlen(buffer) - 1);std::cout std::endl;}close(wfd);return 0;
}#include comm.hppint main()
{//createFifo(NAMED_PIPE);int rfd open(NAMED_PIPE, O_RDONLY, 0666);while(true){std::cout server begin: std::endl;char buffer[1024];ssize_t r read(rfd, buffer, sizeof(buffer));if(r 0){buffer[r] 0;std::cout buffer std::endl;std::cout server end: std::endl;std::cout std::endl;}else{std::cout read: r std::endl;break;}}close(rfd);removeFifo(NAMED_PIPE);return 0;
}3、SystemV中的共享内存通信
3.1 共享内存 共享内存的理解很简单。 用户通过接口让OS在物理空间申请一块内存。 不同的进程将进程地址空间通过页表和这块内存进行映射。 这就做到了共享同一份空间。 下面通过接口看进程是怎么和共享内存联系起来的。 认识接口 shmget: shmget让OS在物理空间上申请一个共享内存 key是一个系统层面的标识符用于标识共享内存唯一性。(这key是由一个函数生成) size 表示要创建多大的共享内存。 shmflg 是一个二进制标识符一般为IPC_CREAT 表示共享内存没有时创建有时不创建为IPC_CREAT | IPC_EXCL 表示没有时创建有时会报错。 成功返回一个用户层面的标识符用于标识共享内存唯一性错误返回-1。 也正是因为这个返回值和文件描述符没有关联使得SystemV标准作为自立的一套标准 ftok: 前面shmget所说的参数key就是ftok的返回值用于标识共享内存唯一性。 pathname和proj_id可以是符合类型的任意值ftok会根据自己算法将这两个参数转换成返回值key。 这也说明如果多个进程的pathname和proj_id都用一样的话就能通过返回值key访问同一个共享内存。 shmctl 有了创建共享内存的函数就有可以释放共享内存的函数。 值得注意的是shmclt不仅可以释放共享内存也有着访问共享内存的属性作用这里只说怎么释放内存。 shmid 这个值是shmget的返回值指的是用户层面上标识共享内存唯一性的。 cmd 是个二进制标识符IPC_RMID 代表释放共享内存。 buf 是用于访问共享内存属性的参数这里可以置空。 成功返回0失败返回-1. 有了shmget和ftok就可以创建共享内存了接下来就需要考虑如何将进程和共享内存关联起来。
不过在此之前先再认识下共享内存 首先前面说过内存中一定会在一定时刻存在多个共享内存多个共享内存就一定需要被操作系统管理管理就一定需要结构化。 共享内存 物理空间块 自身属性。 而管理共享内存的结构体struct shm其中就保存了作为系统层面的key用于标识唯一性。 3.2 共享内存的通信
共享内存的系统命令 通过ipcs -m 可以查看当前开辟的共享内存 如果要通过命令释放共享内存可以通过ipcrm -m 对应shmid 通过while :; do ipcs -m; sleep 2; done 也可以一直看到共享内存信息。 共享内存的通信
首先进程和共享内存的联系也是通过函数进行联系的。
shmat(attach 附加) 和 shmdt(detach 分离) shmat 将内存段连接到进程地址空间 shmid 是shmget返回的用户层面上的唯一标识符 一般都是shmat(id, nullptr, 0)让它自动连接将当前进程联系到共享内存段。 返回值返回一个指针指向共享内存的第一个内存段。如果失败返回void*-1 shmdt 取消进程与共享内存的关联。 只需要传一个shmat返回的地址就行就能取消联系。 成功返回0失败返回-1。 共享内存进行通信
//comm.hpp
#include iostream
#include sys/shm.h
#include sys/ipc.h
#include sys/types.h
#include sys/stat.h
#include cstring
#include unistd.h
#include cassert#define SHMSIZE 4026
#define PATHNAME .
#define PROJ_ID 0x66key_t getKey(const char* path, int proj_id)
{key_t k ftok(path, proj_id);if(k 0){std::cout ftok: errno : strerror(errno) std::endl;exit(-1);}return k;
}int getShmHelp(key_t k, int shmflg)
{int id shmget(k, SHMSIZE, shmflg);if(id 0){std::cout shmget: errno : strerror(errno) std::endl;exit(-1);}
}int createShm(key_t k)
{umask(0);return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}int getShm(key_t k)
{return getShmHelp(k, IPC_CREAT);
}void* attachShm(int shmId)
{void* mem shmat(shmId, nullptr, 0);if((long long)mem -1L) //linux 64位{std::cout shmat: errno : strerror(errno) std::endl;exit(-1);}
}void detachShm(void* start)
{if(shmdt(start) -1){std::cout shmdt: errno : strerror(errno) std::endl;exit(-1);}
}void delShm(int shmId)
{if(shmctl(shmId, IPC_RMID, nullptr) -1){std::cout shmctl: errno : strerror(errno) std::endl;exit(-1);}
}///
//client
#include comm.hppint main()
{//先获取key,用于唯一标识共享内存key_t key getKey(PATHNAME, PROJ_ID);printf(key:0x%x\n, key);//用户创建共享内存int shmId createShm(key);printf(shmId:%d\n, shmId);//进程与内存关联char* start (char*)attachShm(shmId);printf(attach success, address start: %p\n, start);//进程通信const char* message hello server, 我是另一个进程正在和你通信;pid_t id getpid();int cnt 1;int circnt 5;while(circnt--){sleep(5);snprintf(start, SHMSIZE, %s[pid:%d][消息编号:%d], message, id, cnt);}//sleep(10);//删除前最好取消关联detachShm(start);//删除共享内存//delShm(shmId);return 0;
}///
//server
#include comm.hppint main()
{//先获取key,用于唯一标识共享内存key_t key getKey(PATHNAME, PROJ_ID);printf(key:0x%x\n, key);//用户创建共享内存int shmId getShm(key);printf(shmId:%d\n, shmId);//进程与内存关联char* start (char*)attachShm(shmId);printf(attach success, address start: %p\n, start);//进程通信while(true){struct shmid_ds sd;shmctl(shmId, IPC_STAT, sd);printf(client say : %s, cpid[%d], key[0x%x]\n, start, sd.shm_cpid, sd.shm_perm.__key); sleep(1);}//sleep(9);//删除前最好取消关联detachShm(start);//删除共享内存delShm(shmId);return 0;
}输出结果:
3.3 共享内存的缺点以及数据保护
根据上一节的输出结果进行分析 首先client进程每五秒写到共享内存一次而server每隔一秒向共享内存中读取。 这也说明共享内存方式通信是有缺点的不给我进行同步和互斥操作的对数据没有进行保护。 也就是不像管道那样一份数据写完读端再读。 对共享内存进行保护需要写完通知读端再读。 没通知server的时候让server进行阻塞状态。 通过在外层再设计一层命名管道写端写入一个字符如果读端收到那么就从共享内存中读取没有收到就进入阻塞状态read读不到数据 #include iostream
#include sys/shm.h
#include sys/ipc.h
#include sys/types.h
#include sys/stat.h
#include cstring
#include unistd.h
#include fcntl.h
#include cassert
#include string#define SHMSIZE 4026
#define PATHNAME .
#define PROJ_ID 0x66
#define FIFOPATH mypipevoid createFifo(const std::string path)
{umask(0);int n mkfifo(path.c_str(), 0666);if(n 0){std::cout mkfifo: errno : strerror(errno) std::endl;exit(-1);}
}void removeFifo(const char* path)
{if(unlink(path) -1){std::cout unlink: errno : strerror(errno) std::endl;exit(-1);}
}key_t getKey(const char* path, int proj_id)
{key_t k ftok(path, proj_id);if(k 0){std::cout ftok: errno : strerror(errno) std::endl;exit(-1);}return k;
}int getShmHelp(key_t k, int shmflg)
{int id shmget(k, SHMSIZE, shmflg);if(id 0){std::cout shmget: errno : strerror(errno) std::endl;exit(-1);}
}int createShm(key_t k)
{umask(0);return getShmHelp(k, IPC_CREAT | IPC_EXCL | 0600);
}int getShm(key_t k)
{return getShmHelp(k, IPC_CREAT);
}void* attachShm(int shmId)
{void* mem shmat(shmId, nullptr, 0);if((long long)mem -1L) //linux 64位{std::cout shmat: errno : strerror(errno) std::endl;exit(-1);}
}void detachShm(void* start)
{if(shmdt(start) -1){std::cout shmdt: errno : strerror(errno) std::endl;exit(-1);}
}void delShm(int shmId)
{if(shmctl(shmId, IPC_RMID, nullptr) -1){std::cout shmctl: errno : strerror(errno) std::endl;exit(-1);}
}///
//client
#include comm.hppint main()
{//建立命名管道createFifo(FIFOPATH);//先获取key,用于唯一标识共享内存key_t key getKey(PATHNAME, PROJ_ID);printf(key:0x%x\n, key);//用户创建共享内存int shmId createShm(key);printf(shmId:%d\n, shmId);//进程与内存关联char* start (char*)attachShm(shmId);printf(attach success, address start: %p\n, start);//进程通信const char* message hello server, 我是另一个进程正在和你通信;pid_t id getpid();//向命名管道中写入数据rint wfd open(FIFOPATH, O_WRONLY, 0666);int cnt 1;int circnt 5;while(circnt--){sleep(5);char sig r;ssize_t s write(wfd, sig, sizeof(sig));assert(s sizeof(char));(void)s;snprintf(start, SHMSIZE, %s[pid:%d][消息编号:%d], message, id, cnt);}//sleep(10);//删除前最好取消关联detachShm(start);close(wfd);//删除共享内存//delShm(shmId);return 0;
}//
//server
#include comm.hppint main()
{//createFifo(FIFOPATH);//先获取key,用于唯一标识共享内存key_t key getKey(PATHNAME, PROJ_ID);printf(key:0x%x\n, key);//用户创建共享内存int shmId getShm(key);printf(shmId:%d\n, shmId);//进程与内存关联char* start (char*)attachShm(shmId);printf(attach success, address start: %p\n, start);//进程通信//如果从命名管道中读到了r,就从共享内存中读取数据int rfd open(FIFOPATH, O_RDONLY, 0666);while(true){char sig;ssize_t s read(rfd, sig, sizeof(sig));if(s 0){printf(read:%d\n, s);break;}if(sig r s 0){struct shmid_ds sd;shmctl(shmId, IPC_STAT, sd);printf(client say : %s, cpid[%d], key[0x%x]\n, start, sd.shm_cpid, sd.shm_perm.__key); }sleep(1);}//sleep(9);//删除前最好取消关联detachShm(start);//删除共享内存delShm(shmId);close(rfd);//删除命名管道removeFifo(FIFOPATH);return 0;
}3.4 共享内存的优点
共享内存是所有通信方式中速度最快的。 因为其能大大降低数据的拷贝次数。
同样的数据通信管道实现和共享内存实现。考虑键盘、显示器共享内存会有22次拷贝。 只需要将数据从输入拷贝到共享内存再从内存直接放到标准缓冲区再到屏幕。 而管道通信在进入管道多了要经过用户层面上的缓冲区FILE多了两次拷贝。
所以如果只考虑管道和共享内存通信传输大量的数据的话共享内存能快不少。 3.5 信号量以及与共享内存有关的概念 什么是信号量 信号量本身是一个计数器通常用来表示公共资源中资源数量多少的问题的。 公共资源是可以被多个进程访问的资源。 1、值得注意的是公共资源是需要保护的不然会出现数据不一致的问题。而对于数据保护提出了一些方法但这些方法也会造成问题所以问题没有解决最后只是到了被接受的程度 2、被保护起来的公共资源称为临界资源。 3、进程访问临界资源的代码称为临界区而其它的称为非临界区。 4、共享资源要么是一个整体要么划分一个一个资源部分。 如何保护共享资源呢 互斥与同步。 互斥也就是当一方访问时另一方阻塞。 同步的情况是一种原子性比如银行的两个账户各有1000元一个账号向另一个账号转账200时一方要扣200另一方要加200而如果转账失败要保证各个账户金额不变。 信号量的作用 打个比方比如电影院买票一部电影每场次座位的数量就是一种信号量在人们买票的时候都要先访问这个信号量。 也就是进程在访问公共资源前都必须申请sem信号量。