当前位置: 首页 > wzjs >正文

程序员培训班哪家好内部搜索引擎优化

程序员培训班哪家好,内部搜索引擎优化,公司的网站推广怎么做,做电影网站用什么格式好目录 进程间通信介绍 为什么要进程间通信 进程间通信的本质 进程间通信的分类 匿名管道 匿名管道 pipe函数 管道读写规则 管道的特点 管道的四种情况 ​编辑 管道的大小 命名管道 命令行创建命名管道 在代码中创建命名管道 实现server 和 client 通信 基于server实现…

目录

进程间通信介绍

为什么要进程间通信

进程间通信的本质

进程间通信的分类

匿名管道

匿名管道

pipe函数

 管道读写规则

管道的特点

 管道的四种情况

​编辑 管道的大小

 命名管道

命令行创建命名管道

 在代码中创建命名管道

实现server 和 client 通信

基于server实现一个计算器功能

 服务器执行命令行指令

 用命名管道实现文件拷贝

system V进程间通信

system V共享内存

 共享内存的数据结构

共享内存的建立和释放

共享内存的创建

共享内存的释放

 共享内存的关联

共享内存去关联

用共享内存实现serve & client 通信

 共享内存和管道对比

system V消息队列

信息队列的基本原理

消息队列的数据结构

消息队列的创建:

消息队列的释放:

发送数据:

获取数据:

system V信号量:

概念:

信号量的数据机构

信号量相关函数

信号量集的创建

信号量集的删除

信号量集的操作

进程互斥

system V IPC


进程间通信介绍

进程间通信简称IPC(interprocess communication),进程间通信就是在不同进程之间传播或交换信息。

为什么要进程间通信

数据传输,资源共享,通知事件,进程控制。

通知事件:子进程向父进程发送中止事件,父进程可以回收子进程了。

进程间通信的本质

让不同的进程看到一份资源。

我们知道进程具有独立性,每个进程都认为自己享有整个内存空间,但实际是用多少拿多少,只是虚拟地址给了进程自信,而在物理地址中则是用多少给多少。

所以进程的数据应该是独一份的,除了父子进程没有进行写时拷贝时共用一份数据。

而我们想要做的就是让多个进程看到同一份资源。那就需要规定一块内存第三方资源来进行摆放信息,这里的信息就是多个进程都可以看见的。

进程间通信的分类

管道:匿名管道,命名管道。

System V IPC:System V 消息队列 System 共享队列 System V 信号量

POSIX IPC:消息队列 共享内存 信号量 互斥量 条件变量 读写锁

匿名管道

平常我们使用指令连接时比如,统计当前云服务器上的登录用户个数

 命令都是程序,所以上面有两个程序who和wc,who本来输出到屏幕的数据到了管道里,wc从管道里拿到数据。此时who的数据就被wc拿到了

who是以行表示当前登录用户个数 wc -l是统计当前的行数。

匿名管道

匿名管道的原理:用于父子进程之间的通信。

让不同的进程看到同一个资源,父子进程看到同一份被打开的文件,然后决定父子进程谁写入和读取。所以实现父子进程的通信

这个文件不会收到写时拷贝的影响,因为这个文件是共享的。

这里的数据不会刷新到磁盘里,因为增加了IO降低效率,所以共享资源当二者关闭后自然消失,而没有磁盘保存。 

pipe函数
int pipe(int pipefd[2]);

因为传入的是一个数组,所以是一个输入输出型参数。数组名就是一个指针。

成功返回0,失败返回-1.

 匿名管道流程

1.父进程调用pipe函数创建管道

2.父进程创建子进程,此时子进程和父进程对于管道是都连接的状态

3. 父进程关闭写端,子进程关闭读端,即父读子写

 注意点:

管道是半双工的,即只可以一端写,一端读,所以需要确认谁写谁读。

写端写入的数据会被内核缓冲,直到管道的读端读取。

看看文件描述符的视角

 1.父进程创建管道

2.父进程创建子进程

 

3.父进程关闭写端,子进程关闭读端

 代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{//fd[0]是读端fd[1]是写端int fd[2] = {0};if(pipe(fd) < 0){perror("pipe");return 1;}pid_t id = fork();if(id == 0){close(fd[0]);const char* msg = "hello father,i am child.......";int count = 10;while(count--){write(fd[1],msg,strlen(msg));sleep(1);}close(fd[1]);exit(0);}//父进程关闭写端,然后读取管道数据并输出close(fd[1]);char buff[64];while(1){ssize_t s = read(fd[0],buff,sizeof(buff));if(s > 0){//说明有数据buff[s] = '\0';printf("child send to father:%s\n",buff);}else if(s == 0){//说明文件读取完毕,后续没有printf("read file end\n");break;}else {printf("read error\n");break;}}close(fd[0]);waitpid(id,NULL,0);return 0;
}

 管道读写规则

pipe2与pipe函数类似,都是用来创建匿名管道的,其函数原型如下:

int pipe2(int pipefd[2], int flags);

多了一个flags选项,

选项名字为O_NONBLOCK,即不阻塞的意思。

开启时,read函数如果没有数据读本来会把程序卡主,如果加了选项,此时read调用直接返回-1,并且错误码值为EAGAIN,即下次继续,先去执行后续逻辑。

write函数本来在管道写满时,就要被卡住,此时就不会卡住,一样返回EAGAIN,先去执行后续逻辑,直到管道有空间。

如果所有写端文件描述符都被关闭,read返回0.

如果所有读端文件描述符都被关闭,说明没有接收端,此时写端没意义,则会发送SIGPIPE信号终止进程。

原子性问题:

当PIPE_BUF能一次接收完全部数据时,此时就是原子性,即数据一次性全打入缓冲区。

如果数据量很大,此时要分多次打印,又由于我们是无阻塞状态,此时的整体数据写入的原子性了。

管道的特点

1.管道自带的同步与互斥机制

一次只允许一个进程使用的资源,称为临界资源。管道在同一时刻只允许一个进程对其进行读写,因此管道也称为一种临界资源。

所以当管道被多个进程使用时,这个临界资源是需要保护的,如果同一时间多个进程都对管道进行操作,同时读写,交叉读写,就会乱。

内核中对管道操作进行同步与互斥:

  • 同步:两个或两个以上的进程按照先后次序运行,比如A任务的运行依赖于B任务产生的数据。
  • 互斥:一个公共资源同一时刻只能被一个进程使用,多个进程不能同时使用公共资源。

同步就是我资源的完成,是有特定的进程接收,等会写好后,这个进程先运行,把数据接收了以后,后面排队的进程继续写入,重复上述步骤

互斥就是说,只有一个进程能使用这个公共资源,读也只能一个人读,写也是同理,不能同时有多个进程一起使用公共资源。 

2.管道的生命周期随文件

 管道也是通过文件进行通信的,如上面的图示一样,管道依赖于文件系统,文件描述符全部关闭后,此时管道就没用了,然后就被回收,所以说当使用管道的文件都关闭后,管道关闭

3.管道提供的是流式服务

进程A写入管道的数据,进程B每次读取都是任意的,这就是流式服务。

流式服务:数据没有明确的分割。

数据报服务:数据有明确的分割,数据按报文段拿。

4.管道是半双工通信

单工通信:一端固定为发送端,一端固定为接收端

半双工通信:双方都可以当发送端或者接收端,但是同一时间只能有一边进行传输

全双工通信:双方都可以当发送端或者接收端,同一时间可以一起传输 。

管道是半双工的,但是上面我们只看出他是一端接收一端发送,我们可以用两个管道就能实现

 管道的四种情况
  • 1.写端不写,读端读完后,没数据就挂起。直到写端写数据,读端唤醒
  • 2.读端不读,写端一直写,管道写满后,写端就会挂起,直到数据被读取,写端唤醒。
  • 3.写端进程将数据写完后将写端关闭,读端会把数据读取完后继续执行逻辑,而不是被挂起。
  • 4.读端进程关闭,此时没人读,写端就没有必要写了,此时写端进程也会被关闭。

前两个情况很好说明了,管道自带的同步和互斥机制,读端和写端是有步调的,一个写一个读,没有两个一起动这个说法。这就是同步,互斥就是说二者同一时间只能一人使用管道。

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{//1.创建管道int fd[2] = {0};if(pipe(fd) < 0){perror("pipe error");return 1;}//2.创建子进程pid_t id = fork();if(id == 0){//关闭释放操作一定要清清楚楚//子进程close(fd[0]);//关闭读端int count = 10;const char* msg = "father,this is child,over....";while(count--){write(fd[1],msg,strlen(msg));sleep(1);}close(fd[1]);exit(0);}//3.父进程关闭读端,子进程被强制关闭(看状态码)close(fd[1]);//关闭写端close(fd[0]);//强制关闭读端。int status;waitpid(id,&status,0);printf("child get signal:%d\n",status);return 0;
}

我们发现子进程和父进程这次没有打印数据,因为父进程读端关闭后,此时没人读,子进程被信号13杀掉,信号13:SIGPIPE

 管道的大小

1.使用指令:

ulimit -a

下面有一个pipe size:512 * 8 = 4096字节。

 2.自行测试

 代码如下:

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>int main()
{//1.创建管道int fd[2] = {0};if(pipe(fd) < 0){perror("pipe");return 1;}//2.创建子进程,并且子进程一直写1字节,然后每次打印countpid_t pid = fork();if(pid == 0){//子进程char a = 'a';//关闭读端close(fd[0]);int count = 0;while(1){count++;write(fd[1],&a,1);printf("%d\n",count);}//关闭写端close(fd[1]);exit(0);}//3.父进程close(fd[1]);//关闭写端waitpid(pid,NULL,0);close(fd[0]);return 0;
}

结果如下: 

我们发现管道的大小有65536字节而不是4096字节。

 命名管道

我们发现匿名管道,好像是和子进程和父进程相关,所以是有亲缘关系的进程才能使用。

但是如果我们想要两个不想干的进程使用管道通信呢?这时就要用到命名管道了。

  • 命名管道实际就是一个文件,两个进程通过这个管道文件进行数据的运输和获取。
  • 匿名管道就像C++中,匿名对象一样,用完就丢,而命名管道实际是创造了一个文件映像出来。
  • 但是命名管道虽然有文件,但是大小依然为0,和匿名管道一样二者都是不刷新到磁盘中的。内存中用完就可以丢了。 
命令行创建命名管道

mkfifo指令:

发现我们上面出现了一个fifo文件,并且文件类型为p ,pipe的意思

 并且字节数为0,说明它没有占用磁盘。

我们用一个脚本指令,持续向fifo文件输入数据,然后另一个终端读取这个fifo的数据。

两个终端的指令如下:

while :; do echo "hello fifo";sleep 1; done > fifo
cat < fifo

持续打印hello fifo进入fifo,另一个把fifo中的数据打印出来。 

 在代码中创建命名管道

函数名也是mkfifo原型如下:

int mkfifo(const char *pathname, mode_t mode);

pathname:1.以路径给出,则在指定路径下创建一个命名管道。

2.以文件名给出,则在当前路径在当前路径下。

mode:创建命名管道文件的权限。

mode 666如下:

但是由于有umask(文件默认掩码)的影响,实际创建出来的权限会变成 mode&(~umask) , 就是说掩码的值为多少,在那个比特位的值都会变为0,如果umask == 2,上面的权限就会变为

mode 664如下:

 所以可以把umask的值直接设置为0,在创建文件前使用umask(0)函数把umask的值提前关掉。

mkfifo的返回值:

创建成功,返回0,。

失败,返回-1.

实现代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
//设置宏变量文件名
#define FILE_NAME "myfifo"int main()
{//设置掩码值umask(0);//创建命名管道if(mkfifo(FILE_NAME,0666) < 0){perror("mkfifo");return 1;}//创建成功后的逻辑 ......return 0;
}

实现server 和 client 通信

服务器逻辑:

1.服务端创建一个命名管道

2.关闭写端

3.等待数据

#include "common.h"
int main()
{//创建命名管道umask(0);if(mkfifo(FILE_NAME,0666) < 0){perror("mkfifo");return 1;}//管道创建成功//读端打开管道文件int fd = open(FILE_NAME,O_RDONLY);if(fd < 0){perror("open");return 1;}char msg[128];while(1){//读端重复读取命名管道msg[0] = '\0';ssize_t size = read(fd,msg,sizeof(msg)-1);if(size > 0){//说明此时管道中存在数据msg[size] = '\0';printf("client say: %s\n",msg); }else if(size == 0){printf("client quit\n");break;}else {printf("read error");break;}}close(fd);return 0;
}

对于客户端来说,因为服务端创建了管道,此时客户端以写的方式打开管道写入即可。服务器一直在读取的。

我们启动两个终端,一个终端用作服务器,一个终端用作客户端,我们发现,启动服务器server后,客户端中myfifo文件创建,此时打开启动客户端。

我们客户端输入信息,此时我们的服务器就收到信息了。 这就是命名管道对不同进程的通信形式。

基于server实现一个计算器功能

我们的client就不用改了,因为只是发信息,而server因为要处理计算任务,所以逻辑不是显示了,而是计算后显示,所以就需要server在收到信息时做出相应的逻辑。 

#include "common.h"
int main()
{//创建命名管道umask(0);if(mkfifo(FILE_NAME,0666) < 0){perror("mkfifo");return 1;}//管道创建成功//读端打开管道文件int fd = open(FILE_NAME,O_RDONLY);if(fd < 0){perror("open");return 1;}char msg[128];while(1){//读端重复读取命名管道msg[0] = '\0';ssize_t size = read(fd,msg,sizeof(msg)-1);if(size > 0){//说明此时管道中存在数据//对信息进行解析msg[size] = '\0';printf("client say: %s\n",msg);char* label = "+-*/%";char* p = msg;int flag = 0;while(*p){//获取是什么符号switch(*p){case '+':flag = 0;break;case '-':flag = 1;break;case '*':flag = 2;break;case '/':flag = 3;break;case '%':flag = 4;break;}p++;}//解析出两个数字char* snum1 = strtok(msg,"+-*/%");char* snum2 = strtok(NULL,"+-*/%");int num1 = atoi(snum1);int num2 = atoi(snum2);int ret = 0;switch (flag){case 0:ret = num1 + num2;break;case 1:ret = num1 - num2;break;case 2:ret = num1 * num2;break;case 3:ret = num1 / num2;break;case 4:ret = num1 % num2;break;}//打印计算结果printf("%d %c %d = %d\n",num1,label[flag],num2,ret);}else if(size == 0){printf("client quit\n");break;}else {printf("read error");break;}}close(fd);return 0;
}

由于我们只实现了计算逻辑,而对其他信息的逻辑没进行处理,所以会导致输入其他内容直接出错。

 服务器执行命令行指令

#include "common.h"
int main()
{//创建命名管道umask(0);if(mkfifo(FILE_NAME,0666) < 0){perror("mkfifo");return 1;}//管道创建成功//读端打开管道文件int fd = open(FILE_NAME,O_RDONLY);if(fd < 0){perror("open");return 1;}char msg[128];while(1){//读端重复读取命名管道msg[0] = '\0';ssize_t size = read(fd,msg,sizeof(msg)-1);if(size > 0){//说明此时管道中存在数据msg[size] = '\0';printf("client say: %s\n",msg); if(fork() == 0){//进程替换,使用对应的指令execlp(msg,msg,NULL);exit(1);}waitpid(-1,NULL,0);}else if(size == 0){printf("client quit\n");break;}else {printf("read error");break;}}close(fd);return 0;
}

 用命名管道实现文件拷贝

先来介绍一下为什么要学这个实现文件拷贝,事实就是我们文件的拷贝,其实就是网络在做的事情,网络就是从客户端或者服务器,把文件粘贴到另一端的过程。

所以这里实现文件拷贝,其实和网络下载的总体道理是差不多的。

逻辑就是我们客户端的一个文件,将数据传到管道,服务器端通过读取数据,在服务器下面创建一个副本。

服务器代码如下:

#include "common.h"
int main()
{//创建命名管道umask(0);if(mkfifo(FILE_NAME,0666) < 0){perror("mkfifo");return 1;}//管道创建成功//读端打开管道文件int fd = open(FILE_NAME,O_RDONLY);if(fd < 0){perror("open");return 1;}int fdout = open("file-copy.txt",O_CREAT | O_WRONLY,0666);if(fd < 0){perror("open");return 3;}char msg[128];while(1){//读端重复读取命名管道msg[0] = '\0';ssize_t size = read(fd,msg,sizeof(msg)-1);if(size > 0){//说明此时管道中存在数据write(fdout,msg,size);}else if(size == 0){printf("client quit\n");break;}else {printf("read error");break;}}close(fd);close(fdout);return 0;
}

客户端代码如下:

#include "common.h"int main()
{//打开管道int fd = open(FILE_NAME,O_WRONLY);if(fd < 0){perror("open");return 1;}//打开file.txt文件int fdin = open("file.txt",O_RDONLY);if(fdin < 0){perror("open");return 3;}//写入数据char msg[128];while(1){msg[0] = '\0';size_t size = read(fdin,msg,sizeof(msg));if(size > 0){msg[size - 1] = '\0';//信息写入管道write(fd,msg,size);}else if(size == 0){printf("read end of file\n");break;}else{printf("read error\n");break;}}close(fdin);close(fd);return 0;
}

 结果如下:

我们发现此时就出现了copy文件了。 

二者内容一样。 

system V进程间通信

和管道一样,都是为了让不同的进程看到同一份资源。

三种通信方式

1. system V共享内存

2. system V消息队列

3. system V信号量

共享内存和消息队列是用来进行数据传输的,而信号量则是为了进程间同步与互斥设计的。

system V共享内存

共享内存,顾名思义就是在物理内存申请一块共享内存,这块内存在每一个进程中的虚拟内存中都建立了映射,之后不管是任何进程只要使用这个共享内存,映射后都是同一个物理内存,所以实现了不同进程看到同一份信息。

 共享内存的数据结构
struct shmid_ds {struct ipc_perm     shm_perm;   /* operation perms */int         shm_segsz;  /* size of segment (bytes) */__kernel_time_t     shm_atime;  /* last attach time */__kernel_time_t     shm_dtime;  /* last detach time */__kernel_time_t     shm_ctime;  /* last change time */__kernel_ipc_pid_t  shm_cpid;   /* pid of creator */__kernel_ipc_pid_t  shm_lpid;   /* pid of last operator */unsigned short      shm_nattch; /* no. of current attaches */unsigned short      shm_unused; /* compatibility */void            *shm_unused2;   /* ditto - used by DIPC */void            *shm_unused3;   /* unused */
};

因为共享内存不止一个,并且使用不同共享内存的进程也可能是不同的,所以要把这些数据管理起来,可以看到我们结构体中存在了cpid 和 lpid 这两个id 就是记录创造共享内存的id和最近使用共享内存id的进程。

其中的shm_perm中这里面就是用来识别不同的共享内存的,它的内部结构如下:

struct ipc_perm{__kernel_key_t  key;__kernel_uid_t  uid;__kernel_gid_t  gid;__kernel_uid_t  cuid;__kernel_gid_t  cgid;__kernel_mode_t mode;unsigned short  seq;
};

可以看到里面存在一个key变量,这个变量就类似于pid,pid用来识别不同的进程,key就是用来识别不同的共享内存。

共享内存的建立和释放

建立:

  1. 在物理内存中申请共享内存的空间。
  2. 申请到的共享内存的物理地址和创建的进程虚拟内存建立映射关系。

释放:

  1. 把建立好的映射关系都去掉。
  2. 释放共享内存的内存空间。
共享内存的创建

函数:

int shmget(key_t key, size_t size, int shmflg);

 参数说明:

  • key就是共享内存的id,但是是在系统层面的。
  • size表示要创建的共享内存的大小。
  • shmflg表示要创建的共享内存的方式。

返回值说明:

  • 调用成功,返回一个用户层面的共享内存id。
  • 调用失败,返回-1.

attention:

句柄的意思就是用来表达某种资源能力的东西,shmget返回的用户层面的共享内存id,就是一个句柄,后续要对共享内存进行操作,都要搭配这个句柄进行使用。

ftok函数

我们的key需要用ftok函数进行获取。 

key_t ftok(const char *pathname, int proj_id);

传入一个pathname和proj_id 此时会转换出一个key值,pathname必须要存在且可取。

所以说这个ftok函数有什么用?如果让我们自己指定key值,用数字一个个来写有时候我们是记不住的。而用pathname + id 的方法构建出一个key,此时记忆路径名会相对简单。

shmget的第三个参数

上述两种方法就是:不存在都创建,上面那个存在则返回这个共享内存,下面的则是存在出错返回。

 我们可以使用ftok和shmget函数创建共享内存观察一下:

#include <stdio.h>
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <unistd.h>#define PATHNAME "/home/ubuntu/blog-code"#define PROJ_ID 0x6666//整数标识符
#define SIZE 4096int main()
{//1.ftok获取keykey_t key = (PATHNAME,PROJ_ID);if(key < 0){perror("ftok");return 1;}//2.shmget 获取 用户级id句柄int shmid = shmget(key,SIZE,IPC_CREAT | IPC_EXCL);if(shmid < 0){perror("shmget");return 1;}//3.打印 key 和 id句柄printf("key : %x\n",key);printf("shmid : %d\n",shmid);return 0;
}

 使用ipcs,会默认弹出消息队列,共享内存以及信号量相关的信息

 

attention:key是内核层面用来保证共享内存唯一性的,shmid是用户层面保证唯一性的。

类似于fd 和 FILE*的关系,一个是给用户使用的,一个是系统使用的

共享内存的释放

和其他文件一样,共享内存也是要释放的,但是我们发现上述我们在程序结束后,共享内存依然存在,也就变相的说明共享内存不是进程管理的,虽然由进程创建但是不跟随进程的生命周期,所以共享内存的生命周期是跟随系统的。

释放共享内存的两种方法:

1.使用命令释放共享内存

 ipcrm -m + shmid 删除指定shmid 的 共享内存

ipcrm -m 1

 attention: 要用的是 shmid 因为我们是用户层。

2.使用程序释放共享内存资源

 shmctl函数:

int shmctl(int shmid, int cmd, struct shmid_ds *buf);

参数说明:

shmid :用户层使用的共享内存标识符

cmd :控制动作

buf :获取或设置控制共享内存的数据结构

返回值说明:

shmctl成功,返回0。

失败,返回-1 。

第二个参数可传入的选项

 释放共享内存代码测试:

#include <stdio.h>
#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
#include <unistd.h>#define PATHNAME "/home/ubuntu/blog-code"#define PROJ_ID 0x6666//整数标识符
#define SIZE 4096int main()
{//1.ftok获取keykey_t key = (PATHNAME,PROJ_ID);if(key < 0){perror("ftok");return 1;}//2.shmget 获取 用户级id句柄int shmid = shmget(key,SIZE,IPC_CREAT | IPC_EXCL);if(shmid < 0){perror("shmget");return 1;}//3.打印 key 和 id句柄printf("key : %x\n",key);printf("shmid : %d\n",shmid);sleep(2);//4.释放共享内存shmctl(shmid,IPC_RMID,NULL);sleep(2);return 0;
}

我们发现这一次的共享内存直接被释放了。 

使用脚本指令:持续的观察共享内存的状况

while :; do ipcs -m;echo "###################################";sleep 1;done

 共享内存的关联

shmat函数

shared memory attach, 贴上共享内存,就可以当做,共享内存的关联了。

void *shmat(int shmid, const void *shmaddr, int shmflg);

参数说明:

shmid:用户级共享内存标识符

shmaddr:指定共享内存映射到进程地址空间的某一位置,通常设置为NULL,让内核决定

shmflg:关联共享内存时设置的某些属性。

返回值说明:

调用成功,返回一个共享内存映射到进程地址空间中的起始地址

调用失败,返回(void*)-1.

第三个参数的选项:

 进行共享内存关联的代码测试:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define PATHNAME "/home/ubuntu/blog-code" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小int main()
{//1.ftok函数做keykey_t key = ftok(PATHNAME,PROJ_ID);if(key < 0) {perror("key");return 1;}//2.shmget制造共享内存int shmid = shmget(key,SIZE,IPC_CREAT | IPC_EXCL | 0666);if(shmid < 0){perror("shmid");return 1;}printf("key:%x\n",key);printf("shmid:%d\n",shmid);//3.shmat关联共享内存printf("attach begin\n");sleep(2);//以读写打开共享内存char* mem = shmat(shmid,NULL,SHM_RDONLY);if(mem == (void*)-1){perror("shmat");return 1;}printf("attach end\n");sleep(2);//4.释放共享内存shmctl(shmid,IPC_RMID,NULL);return 0;
}

上面可以看到我们d 

attention:这里的权限要注意,普通用户不能打开共享内存,所以切换为root用户

 当然也可以在创建共享内存时,就给它设置好权限,就是更改shmget函数传参如下:

    int shmid = shmget(key,SIZE,IPC_CREAT | IPC_EXCL | 0666);

发现此时我们除了root用户也可以执行shmat命令了。 

共享内存去关联
int shmdt(const void *shmaddr);

shared memory detach 取消关联。

参数说明:

shmaddr:共享内存在进程空间的起始地址,在上面shmat中的返回值

返回值说明:

调用成功返回0,失败返回-1

解除关联的代码测试:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define PATHNAME "/home/ubuntu/blog-code" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小int main()
{//1.ftok函数做keykey_t key = ftok(PATHNAME,PROJ_ID);if(key < 0) {perror("key");return 1;}//2.shmget制造共享内存int shmid = shmget(key,SIZE,IPC_CREAT | IPC_EXCL | 0666);if(shmid < 0){perror("shmid");return 1;}printf("key:%x\n",key);printf("shmid:%d\n",shmid);//3.shmat关联共享内存printf("attach begin\n");sleep(2);//以读写打开共享内存char* mem = shmat(shmid,NULL,SHM_RDONLY);if(mem == (void*)-1){perror("shmat");return 1;}printf("attach end\n");sleep(2);//解除关联printf("detach begin\n");sleep(2);shmdt(mem);printf("detach end\n");sleep(2);//4.释放共享内存shmctl(shmid,IPC_RMID,NULL);return 0;
}

上图我们发现我attach后 nattch数量为1,detach后 nattch 数量为0,最后共享内存被释放。

 attention:

共享内存的解关联不是释放共享内存,只是当前进程和共享内存之间的关系解除,最后还是需要释放共享内存

用共享内存实现serve & client 通信

我们要实现的就是让客户端和服务器都连接到同一个共享内存,服务器创建并连接,客户端负责连接。

服务器代码:

#include "common.h"int main()
{//1.创造key值key_t key = ftok(PATHNAME,PROJ_ID);if(key < 0){perror("ftok");return 1;}//2.创建共享内存int shmid = shmget(key,SIZE,IPC_CREAT | IPC_EXCL | 0666);if(shmid < 0){perror("shmget");return 1;}//3.连接并死循环等待client连接char* mem = shmat(shmid,NULL,0);if(mem == (void*)-1){perror("shmat");return 1;}while(1){//......}//4.解除连接并释放共享内存int ret = shmdt(mem);if(ret < 0){perror("shmdt");return 1;}shmctl(shmid,IPC_RMID,NULL);return 0;
}

客户端代码:

#include "common.h"int main()
{//1.创造key值key_t key = ftok(PATHNAME,PROJ_ID);if(key < 0){perror("ftok");return 1;}//2.创建共享内存int shmid = shmget(key,SIZE,IPC_CREAT);if(shmid < 0){perror("shmget");return 1;}//3.连接并死循环等待client连接char* mem = shmat(shmid,NULL,0);if(mem == (void*)-1){perror("shmat");return 1;}while(1){//......}//4.解除连接int ret = shmdt(mem);if(ret < 0){perror("shmdt");return 1;}return 0;
}

common.h:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <unistd.h>#define PATHNAME "/home/ubuntu/blog-code" //路径名#define PROJ_ID 0x6666 //整数标识符
#define SIZE 4096 //共享内存的大小

二者代码的区别就在于:服务器是创建并且打开,所以要释放 ,客户端只用打开就可以了。

但是注意我们的共享内存可能释放不了,但是可以用命令行关闭,因为我们代码死循环卡在一半了

执行情况:

可以发现我们此时客户端和服务器都连接上了共享内存 (nattch为2)。

客户端向共享内存不断的写入数据:

int i = 0;while(1){//向共享内存写入数据mem[i] = 'A' + i;i++;mem[i] = '\0';sleep(1);}

服务器不断读取客户端数据: 

while(1){//不断从共享内存读取数据printf("client say: %s\n",mem);sleep(1);}

 结果:

 共享内存和管道对比

共享内存创建后,只用向共享内存直接添加数据就好,而管道创建好后,是有文件创立的,我们要对文件写入和读取还是需要用系统调用的例如:open write read......

管道通信步骤:

由上图看出我们管道在一次通信中,有4次拷贝

第一次拷贝:从输入设备或者文件复制数据到服务器的缓冲区。

第二次拷贝:从服务器的缓冲区的数据复制到管道中。

第三次拷贝:从管道中将数据拷贝到客户端的缓冲区中。

第四次拷贝:从客户端的缓冲区中拷贝到要输出的设备或文件中。

共享内存通信步骤:

 由上图我们看出我们的文件传输只用了两步:

首先我们创建好共享内存,之后的读取和输入都是直接到位的。

所以共享内存是所有进程间通信方式中最快的一种,因为该通信方式只需要两次拷贝。

缺点:我们知道管道自带同步与互斥机制,同步:输入给谁谁从管道拿。互斥:同一时间只有一个进程使用管道。共享内存没有任何保护机制。

system V消息队列

信息队列的基本原理

顾名思义:队列就是在系统中创建了一个队列,多个进程可以向这个队列发送数据,获取数据时都从队头获取数据,发送数据都是发送到队尾和队列一样。

怎么判别数据是给谁的,在数据块类型。

消息队列的数据结构
struct msqid_ds {struct ipc_perm msg_perm;struct msg *msg_first;      /* first message on queue,unused  */struct msg *msg_last;       /* last message in queue,unused */__kernel_time_t msg_stime;  /* last msgsnd time */__kernel_time_t msg_rtime;  /* last msgrcv time */__kernel_time_t msg_ctime;  /* last change time */unsigned long  msg_lcbytes; /* Reuse junk fields for 32 bit */unsigned long  msg_lqbytes; /* ditto */unsigned short msg_cbytes;  /* current number of bytes on queue */unsigned short msg_qnum;    /* number of messages in queue */unsigned short msg_qbytes;  /* max number of bytes on queue */__kernel_ipc_pid_t msg_lspid;   /* pid of last msgsnd */__kernel_ipc_pid_t msg_lrpid;   /* last receive pid */
};

我们发现第一个变量依然是结构体 ipc_perm 和共享内存一样的。

内容如下:

struct ipc_perm{__kernel_key_t  key;__kernel_uid_t  uid;__kernel_gid_t  gid;__kernel_uid_t  cuid;__kernel_gid_t  cgid;__kernel_mode_t mode;unsigned short  seq;
};
消息队列的创建:
int msgget(key_t key, int msgflg);

我们发现和共享内存一样也需要key,所以也要用到ftok函数制作key。

第二个参数和共享内存的第三个参数一样。设定消息队列的信息。

创建成功返回一个用户级标识符,失败返回-1.

消息队列的释放:
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

第一个参数:msqid 是 msgget的返回值。

第二个参数:cmd 是 要怎么操控信息队列

第三个参数:buf是消息队列结构体的相关数据结构

发送数据:
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);

第一个参数:消息队列用户级标识符

第二个参数:msgp 待发送的数据块

第三个参数:msgsz 待发送的数据块的大小

第四个参数:发送数据块的模式,一般填0就好

返回值:成功返回0,失败返回-1.

attention:第二个参数必须为以下结构

struct msgbuf{long mtype;       /* message type, must be > 0 */char mtext[1];    /* message data */
};

第二个成员就是我们要发送的数据,当使用时可以自己改变大小。

获取数据:
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);

第一个参数:消息队列用户级标识符

第二个参数:msgp 表示获取的数据块,是输出型参数

第三个参数:msgsz表示获取的数据块的大小

第四个参数:msgtyp表示要接收数据块的类型

第五个参数:获取数据块的方式

返回值:调用成功返回实际获取到mtext数组中的字节数,失败返回-1.

system V信号量:

概念:

我们知道上述两种共享方式,消息队列和共享内存,如果多进程竞争使用,是不保证安全的。

互斥资源 临界资源:多进程竞争使用一个资源。这个资源一次只能一个进程正在使用。

在进程中涉及到临界资源的程序段叫临界区。

IPC资源必须删除,不会自动删除,因为IPC资源的生命周期跟随内核

信号量的数据机构
struct semid_ds {struct ipc_perm sem_perm;       /* permissions .. see ipc.h */__kernel_time_t sem_otime;      /* last semop time */__kernel_time_t sem_ctime;      /* last change time */struct sem  *sem_base;      /* ptr to first semaphore in array */struct sem_queue *sem_pending;      /* pending operations to be processed */struct sem_queue **sem_pending_last;    /* last pending operation */struct sem_undo *undo;          /* undo requests on this array */unsigned short  sem_nsems;      /* no. of semaphores in array */
};

我们可以看到信号量的第一个成员变量也是ipc_perm

信号量相关函数
信号量集的创建
int semget(key_t key, int nsems, int semflg);

我们可以看到有key 所以也要用 ftok生成一个key值。

第二个参数:nsems 代表要创建信号量的个数

第三个参数:semflg 表示怎么创建信号量

返回值:创建成功返回用户级标识符,失败返回-1.

信号量集的删除
int semctl(int semid, int semnum, int cmd, ...);
信号量集的操作
int semop(int semid, struct sembuf *sops, unsigned nsops);
进程互斥

我们进程共享资源是爽了,但是也有新的问题,就是我们进程共用的临界资源,若是不保护,就会出现数据不一致的情况。

信号量就是用来保护临界区的,信号量分为二元信号量和多元信号量

假设我们有100字节的数据,如果算他25字节一份,此时就有4份,那就可以用4个信号量进行标识。

 信号量本质就是一个计数器,在二元信号量中,信号量的个数为1(相当于将临界资源看成一整块)。

 由上面的伪代码中我们可以看到,我们有一个sem,在sem==1时,我们进入的是 sem--这个逻辑,代表这个信号量我拿走了,其他进程此时进来就发现sem == 0走到else中挂起。而使用信号量的这个进程完成操作后对sem++就相当于将信号量归还了。

这样就保证了同一时间内只有一个进程能访问临界区。

PV操作

 

我们把计数器sem--的操作叫做P操作,sem++叫做V操作,P就是申请信号量,V就是释放信号量。 

system V IPC

我们学习了上述三个共享内存,消息队列,信号量后我们发现他们的函数中都有一个ipc perm这个结构体大家都是一样的,所以操作系统维护system V 的东西的时候,就进行了统一管理。

http://www.dtcms.com/wzjs/129738.html

相关文章:

  • 17网站一起做网店白沟百度浏览官网
  • 苏州市最新疫情免费刷seo
  • 触屏手机网站拼多多seo 优化软件
  • 盐城做网站的哪家公司好怎么创建网站平台
  • 无广告自助建站百度邮箱登录入口
  • 温州专业网站建设seo推广方法集合
  • 深圳市建设网站营销培训方案
  • 国外商品网站百度关键词排名价格
  • 让做网站策划没经验怎么办网店如何引流与推广
  • 网站建设合同标准版搜索引擎营销方案例子
  • 萧山网站优化seo模板建站
  • wordpress法律主题关键词优化教程
  • 朝阳区北京网站建设快速建站教程
  • 国外优秀论文网站百度开户需要什么条件
  • 上海建设网站制作国产长尾关键词拘挖掘
  • 滨湖区知名做网站价格清远今日头条新闻
  • 做网站最大的公司友情链接获取的途径有哪些
  • seo推广岗位职责四川seo优化
  • 福州网站建设招商网络软营销
  • 替代 wordpress基本seo技术在线咨询
  • 怎样做网站收录seo工程师是什么职业
  • 株洲市哪里有做公司官方网站沧州网站seo公司
  • 平台建网站恢复正常百度
  • 网站做研究生毕业论文代写文章质量高的平台
  • 体育网站建设的必要性市场seo是什么意思
  • 网站账户上的余额分录怎么做做seo需要哪些知识
  • 网站开发能从事那些职业网站seo视频狼雨seo教程
  • 南昌网站开发公司电话seo网站建设公司
  • 昆明网络推广昆明网站建设昆明昆明什么是sem和seo
  • 织梦网站广告公众号代运营