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

做跨境电商有没推荐的网站微信公众平台注册不了

做跨境电商有没推荐的网站,微信公众平台注册不了,做网站拿来卖,在哪个网站做视频赚钱的目录 一、进程间通信介绍 二、管道 1.什么是管道#xff08;pipe#xff09; 2.重定向和管道 #xff08;1#xff09;为什么要有管道的存在 #xff08;2#xff09;重定向和管道的区别 3.匿名管道 #xff08;1#xff09;匿名管道原理 #xff08;2…目录 一、进程间通信介绍 二、管道 1.什么是管道pipe 2.重定向和管道 1为什么要有管道的存在 2重定向和管道的区别 3.匿名管道 1匿名管道原理  2站在文件描述符角度理解匿名管道 3创建匿名管道 4匿名管道读写规则 5匿名管道特点 6匿名管道4种特殊情况 7匿名管道大小 4.命名管道 1命名管道原理  2创建命名管道 3命名管道的数据不会刷新到磁盘 5.匿名管道和命名管道的区别 三、System V IPC 1.System V标准 2.共享内存 1原理  2步骤 3函数  shmget shmctl shmat shmdt 4使用 3.共享内存和管道区别  四、消息队列  1.原理  2.数据结构 3.步骤 4.函数  1msgget 2msgctl 3msgsnd 4msgrcv 五、信号量 1.原理  2.数据结构 3.函数 1semget 2semctl 3semop 六、System V IPC总结 一、进程间通信介绍 之前学习的进程都是各自运行互不干扰进程之间没有协同。然而有许多场景下是需要进程之间相互协同的由于进程是程序员写的因此进程之间的协同本质上就是程序员之间的协同比如一个程序员从数据库里面拿数据另一个程序员要把从数据库里面拿到的数据进行格式化写成特定格式还有一个程序员根据格式化的数据进行统计如果把这些工作量当成意见工作去处理的话如果其中这三个环节有任何一个环节出错了那么这个工作就进行不下去了需要逐一去排查到底是哪个环节出错了耗时久且效率低。 因此把这个工作可以分为3个部分分别让3个不同的进程去做1个进程从数据库拿数据1个进程做数据格式化1个进程做数据分析。这就做到了在业务层面上用进程进行解耦。一旦拿数据有问题就去找拿数据的进程一旦格式化有问题就去找格式化的进程一旦数据分析有问题就去找数据分析的进程。业务层面上的解耦能够增加代码的可维护性这就是进程之间的协同。比如过滤出文件中含字母i的行 cat fdProcess.c运行起来就是一个进程核心工作只是打印数据用grep来过滤含有字母i的行数据源是从上一个进程cat fdProcess.c通过管道来交给grep的。这就叫做协同。 就算是父子进程共享了进程的代码和数据写的时候都必须分开用写时拷贝来写。两个相互独立的进程交互数据成本很高各自连对方保存数据的地址空间都看不到因为独立的进程使用独立的进程地址空间页表映射到不同的物理内存所以看不到对方的数据因此要完成进程间通信不能只在应用层解决必须也要操作系统参与进来要让操作系统设计通信方式。 通信的本质就是传递数据这些数据需要一个进程向公共资源里面去放另一个进程从公共资源向外拿而公共资源还需要有暂存数据的能力。这个公共资源肯定不属于这两个进程因为进程具有独立性如果这个公共资源是进程A的那么进程B是看不到的 从上图可以看出进程间通信有以下3种方式目的是为了让不同的进程看到同一份资源 管道System V进程间通信POSIX进程间通信  同时需要先了解以下概念 数据传输一个进程需要将它的数据发送给另一个进程 资源共享多个进程之间共享同样的资源。 通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止 时要通知父进程。 进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另 一个进程的所有陷入和异常并能够及时知道它的状态改变 二、管道 1.什么是管道pipe 管道是Unix最古老的进程间通信的形式。把从一个进程连接到另一个进程的数据流称为管道Linux 管道使用竖线|连接多个命令这被竖线|称为管道符。 当在两个命令之间设置管道时管道符|左边命令的输出就变成了右边命令的输入。只要第一个命令向标准输出写入而第二个命令是从标准输入读取那么这两个命令就可以形成一个管道。大部分的 Linux 命令都可以用来形成管道。 如下所示对于命令cat fdProcess.c|grep -i i管道符|之前的进程cat fdProcess.c是标准输入进程管道符|之后的进程grep -i i是标准输出进程第一个命令向标准输出写入第二个命令是从标准输入读取这两个命令形成了管道管道作用于内核。 如果没有管道那么这两条命令就得分两次执行。因此用管道执行也能达到同样的效果。对于一些备份压缩复制需求的命令就可以避免创建临时文件。  管道特点 命令的语法紧凑并且使用简单。管道将多个命令串联到一起完成复杂任务。从管道输出的标准错误会混合到一起。 2.重定向和管道 1为什么要有管道的存在 既然有重定向为什么还要有管道呢比如如下命令使用重定向将可执行程序process1的输出都放入file中 process1 file 但是如果想让可执行程序process1 的输出传递到可执行程序process2呢需要 process1 temp process2 temp 这个命令做了3步 运行名为process1 将输出保存到名为temp的文件中运行名为的程序process2假装用户在键盘上输入temp的内容。 有没有发现这样做很麻烦既要创建临时文件又要用户在键盘上输入呢但是管道就很简单呀 process1 | process2 的效果和命令process1 temp process2 temp的作用是一样的。 2重定向和管道的区别 管道也有重定向的作用因为它改变了数据的输入输出方向。冲重定向使用将文件和命令连接起来用文件来接收命令的输出而管道使用I将命令和命令连接起来用第二个命令来接收第一个命令的输出。 使用重定向一定要小心一些如果连续键入如下两条命令 cd /usr/bin ls less 第一条命令将当前目录切换到了大多数程序所存放的目录第二条命令是告诉 Shell 用 ls 命令的输出重写文件 less。因为 /usr/bin 目录已经包含了名称为 less的文件第二条命令用 ls 输出的文本重写了 less 程序因此破坏了文件系统中的 less 程序这就破坏了less文件。这是使用重定向操作符错误重写文件的一个教训所以在使用重定向时要谨慎。 管道分为匿名管道和命名管道。 3.匿名管道 1匿名管道原理  匿名管道仅限于本地父子进程之间通信不支持跨网络之间的两个进程之间的通信。 进程在操作文件时通过文件描述符找到文件如果需要读文件就直接执行读方法。使用fork创建子进程之后那么子进程就拥有了自己的PCB父进程指向的struct file文件描述符表结构也需要给子进程拷贝一份。 这是因为 file_struct结构是属于进程的因为file_struct能够让进程看到已经打开了多少个文件以及文件之间的关系因此file_struct是属于进程的。file_struct属于进程那么它一定属于父进程在创建子进程的时候也必须为子进程复制这份file_struct结构。因为进程具有独立性所以内核数据结构也必须保持独立。如果让子进程也看到了父进程的文件了那么父进程的文件进行读写时缓冲区也被子进程看到了这就没有做好进程独立性。 因此操作系统会将这个结构给子进程也拷贝一份: 基于文件的通信方式就叫做管道。进程、struct_file、缓冲区、操作方法等都是操作系统提供的文件不属于进程属于操作系统。父进程先打开文件让子进程继承虽然结构上互相独立但它们指向同一个文件一个向文件写另一个从文件读两个进程看到了同一份公共资源这就满足了进程通信的前提。 2站在文件描述符角度理解匿名管道 父进程创建管道 管道可以看做文件的内核缓冲区父进程创建管道时分别以读方式和写方式打开同一文件 父进程fork出子进程 当父进程创建出子进程后父进程的所有文件描述符表信息会被子进程继承虽然父子进程各自拥有独立的文件描述符但是内容是一样的所以父子进程都可以看到曾经打开的读端和写端进行读写不过管道只能单向通信只能有一个读端一个写端。 所以父进程一开始就有两个文件描述符一个读端一个写端这样子进程继承复制了父进程的文件描述符后也有读端和写端。否则如果父进程一开始只有读端没有写端那么子进程也只有读端没有写端那么两个读端是不能进行读写的。 父进程关闭读端(写端)子进程关闭写端(读端) 至于父子进程谁关闭读端谁关闭写端取决于父进程读还是子进程读现在来看一下父进程写子进程读的情况现在关闭父进程的读端和子进程的写端 3创建匿名管道 第一步父进程使用pipe函数来创建管道 #include unistd.hint pipe(int pipefd[2]);参数pipefd文件描述符数组元素个数为2是输出型参数通过这个参数读取到打开的两个文件描述符。其中pipefd[0]为读操作pipefd[1]为写操作且顺序不能颠倒。 返回值成功返回0失败返回-1。 现在来创建一个管道 #includestdio.h #includeunistd.hint main() {int pipefd[2] {0};if(pipe(pipefd) ! 0)//匿名管道创建失败{perror(pipe error!);return 1;}printf(pipefd[0]:%d\n,pipefd[0]);printf(pipefd[1]:%d\n,pipefd[1]);return 0; }执行结果如下 可以看到文件描述符分别为3和4因为0、1、2都被标准输入、标准输出、标准错误占用了 第二步父进程fork出子进程 #includestdio.h #includeunistd.hint main() {int pipefd[2] {0};if(pipe(pipefd) ! 0)//匿名管道创建失败了{perror(pipe error!);return 1;}printf(pipefd[0]:%d\n,pipefd[0]);printf(pipefd[1]:%d\n,pipefd[1]);if(fork() 0)//子进程{}//父进程return 0; }第3步创建单向信道 现在如果想让父进程读子进程写那么就要关闭父进程的写端和子进程的读端即关闭父进程的写文件描述符和子进程的读文件描述符。为了让子进程关闭读文件描述符后不要继续向后执行使用eixt函数来终止。 #includestdio.h #includeunistd.h #includestdlib.hint main() {int pipefd[2] {0};if(pipe(pipefd) ! 0)//匿名管道创建失败了{perror(pipe error!);return 1;}printf(pipefd[0]:%d\n,pipefd[0]);printf(pipefd[1]:%d\n,pipefd[1]);if(fork() 0)//子进程{close(pipefd[0]);//子进程关闭读文件描述符exit(0);}//父进程close(pipefd[1]);//父进程关闭写文件描述符return 0; }现在已经建立了父子进程并且父子进程都看到了同一份资源。现在让子进程写入需要调用write方法让父进程读取需要调用read方法write和read方法的使用请参考文章【Linux】-- 基础IO和动静态库第一章节第1节的内容 第一章节第1节的内容。 #includestdio.h #includeunistd.h #includestdlib.h #includestring.h int main() {int pipefd[2] {0};if(pipe(pipefd) ! 0)//匿名管道创建失败了{perror(pipe error!);return 1;}printf(pipefd[0]:%d\n,pipefd[0]);printf(pipefd[1]:%d\n,pipefd[1]);if(fork() 0)//子进程{close(pipefd[0]);//子进程关闭读文件描述符const char *string_write lunch ;while(1){write(pipefd[1],string_write,strlen(string_write));//子进程向文件缓冲区写,pipe只要有缓冲区就一直写入}close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);//父进程关闭写文件描述符while(1){sleep(1);char string_read[64] {0};size_t readLength read(pipefd[0],string_read,sizeof(string_read));//父进程从文件缓冲区读pipe只要有缓冲区就一直读if(readLength 0)//读到内容为空{printf(child quit...\n);break;}else if(readLength 0)//读到了正常内容{string_read[readLength] 0;printf(child write# %s\n,string_read);}else//读出错{printf(read error...\n);break;}close(pipefd[0]);}return 0; } 可以看到执行结果如下子进程写入父进程读取 对于字节流只要缓冲区有数据就把缓冲区的所有数据全部读出来一次读取一个字节。 4匿名管道读写规则 pipe2函数与pipe函数类似也是用于创建匿名管道其函数原型如下 int pipe2(int pipefd[2], int flags); 对于flags  当没有数据可读时 O_NONBLOCK disableread调用阻塞即进程暂停执行一直等到有数据来到为止。 O_NONBLOCK enableread调用返回-1errno值为EAGAIN。 当管道满的时候 O_NONBLOCK disable write调用阻塞直到有进程读走数据 O_NONBLOCK enable调用返回-1errno值为EAGAIN 如果所有管道写端对应的文件描述符被关闭则read返回0如果所有管道读端对应的文件描述符被关闭则write操作会产生信号SIGPIPE进而可能导致write进程退出当要写入的数据量PIPE_BUF时linux将保证写入的原子性。当要写入的数据量PIPE_BUF时linux将不再保证写入的原子性。 5匿名管道特点 只能用于具有共同祖先的进程之间进行通信通常一个管道由一个进程创建然后该进程调用fork父、子进程之间就可用该管道通信具有亲缘关系的进程祖孙进程也可以管道提供流式服务原子性写入(读端读取的数据是任意的底层没有对数据做明确分割报文段不定因此是流式服务)父子进程退出管道文件释放所以管道的生命周期随进程内核会对管道操作进行同步与互斥管道是半双工的数据只能向一个方向流动需要双方通信时需要建立起两个管道 6匿名管道4种特殊情况 读端不读或者读的慢写端要等读端读端关闭写端收到SIGPIPE信号直接终止写端不写或写的慢读端要等写端写端关闭读端读完pipe内部的数据然后再读会读到0表明读到文件结尾 7匿名管道大小 如果让子进程无限循环每次往管道里写一个字符并且计数父进程从管道里面不读取数据当计数不再增长时计数值就为管道的大小 #includestdio.h #includeunistd.h #includestdlib.h #includestring.h int main() {int pipefd[2] {0};if(pipe(pipefd) ! 0)//匿名管道创建失败了{perror(pipe error!);return 1;}printf(pipefd[0]:%d\n,pipefd[0]);printf(pipefd[1]:%d\n,pipefd[1]);if(fork() 0)//子进程{close(pipefd[0]);//子进程关闭读文件描述符int count 0;while(1){write(pipefd[1],a,1);count;printf(count:%d\n,count);}close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);//父进程关闭写文件描述符while(1)//父进程不读取{sleep(1);}return 0; }运行结果如下从1打印到65536 这说明管道大小为65536B64KB。这也说明了如果写端向管道写满数据以后那么写端就不写了等待读端读同理如果读端把管道数据读完了管道没数据那么读端就不读了等待写端写。 管道在被写端写满以后读端要拿走数据如果一次拿走4KB写端才会写否则不会触发写端去写为什么是4KB呢让父进程读取的时候存放数据的数组大小从1KB开始向上递增到4KB的时候写端才写 #includestdio.h #includeunistd.h #includestdlib.h #includestring.h int main() {int pipefd[2] {0};if(pipe(pipefd) ! 0)//匿名管道创建失败了{perror(pipe error!);return 1;}printf(pipefd[0]:%d\n,pipefd[0]);printf(pipefd[1]:%d\n,pipefd[1]);if(fork() 0)//子进程{close(pipefd[0]);//子进程关闭读文件描述符const char *string_write lunch ;int count 0;while(1){//write(pipefd[1],string_write,strlen(string_write));//子进程向文件缓冲区写,pipe只要有缓冲区就一直写入write(pipefd[1],a,1);count;printf(count:%d\n,count);}close(pipefd[1]);exit(0);}//父进程close(pipefd[1]);//父进程关闭写文件描述符while(1){sleep(3);char string_read[1024*41] {0};//按照1024*1 1024*2 1024*3 1024*4向上递增size_t readLength read(pipefd[0],string_read,sizeof(string_read));//父进程从文件缓冲区读pipe只要有缓冲区就一直读printf(readLength %d\n,readLength);string_read[readLength] 0;printf(father take:%c\n,string_read[0]);}return 0; } 可以看到管道写入字符的计数一开始增加到了65536B父进程读走4KB之后子进程继续写每写一次count计数就会 为什么读走4KB的时候写端才写而读走1KB 2KB 3KB时不写呢这是因为要保证写入和读取的原子性假如还没读够4KB就把写端唤醒了那么写端就要来写了这就变成了写端在写的同时读端要来读这就违背了管道半双工通信不能同时读写的原则。同理如果写端写的特别慢读端读的特别快当缓冲区没有数据时会等待数据写入进去后读端再读 。因此要保证同步。 4.命名管道 1命名管道原理  匿名管道用于有血缘关系的进程间通信那么对于没有血缘关系的进程他们之间如何通信呢这就要用到命名管道命名管道是一种特殊的文件使用FIFO(First In First Out)来进行通信。 如何让两个没有血缘关系的不相干的进程看到操作系统提供的同一份资源对于文件系统来说当进程A把磁盘文件打开向磁盘里面写数据写完之后关闭这个磁盘文件进程B再把这个磁盘文件打开并读取数据 但是这样做有点慢因为进程A再内存中打开这个文件为这个文件建立内存相关的数据结构和缓冲区进程B也在内存中打开同一个文件这样就是一个通过读的方式打开一个通过写的方式打开进程可以向这个内存文件写进程B可以从这个内存文件读暂时先不把数据刷新到磁盘否则效率会降低这是基于内存进行数据之间的通信那么A进程和B进程就可以通过这个内存文件进行不相关的进程间的通信。 不相关的A进程和B进程是如何看到同一份资源的呢路径文件名能唯一指定一个文件这样就能让进程A和进程B打开同一份文件。现在需要1个文件同时满足 文件被打开时数据不要被刷新到磁盘上而是保存临时数据这个文件也必须在磁盘上也有对应的文件名 符合这些条件的只有命名管道。而且这个文件是有名字的通过路径文件名确定唯一性来做到的 2创建命名管道 命名管道有两种创建方式  通过mkfifo命令创建 mkfifo name 如创建一个名为testFifo的管道文件 可以看到文件类型为pp表明这是一个管道文件。创建了命名管道文件后就可以通信了 echo和cat是两个不同的指令但是运行起来是两个进程左侧的消息打印到了右侧的屏幕上一个进程把自己的内容写入到了命名管道文件中通过命名管道文件把数据传递给另一个进程。 通过mkfifo函数创建 mkfifo函数的作用是生成一个FIFO的特殊文件即命名管道  #include sys/types.h#include sys/stat.hint mkfifo(const char *pathname, mode_t mode);pathname文件名 mode管道的默认权限可用过umask来设置 返回值成功返回0失败返回-1 现在使用mkfifo函数创建命名管道server.c创建管道文件并给管道文件分配权限 #includestdio.h #includesys/stat.h #includesys/types.h#define fifo_file ./fifo_fileint main() {if(mkfifo(fifo_file,0666) 0)//创建一个命名管道{perror(mkfifo);return 1;}return 0; } client.c暂时什么都不做 #includestdio.hint main() {return 0; } Makefile一次生成两个可执行文件 .PHONY:all all:client serverclient:client.cgcc -o $ $^server:server.cgcc -o $ $^.PHONY:clean clean:rm -rf client server fifo_file 编译后生成两个可执行程序 现在通信想让client和server可执行程序互相传递详细那么 client和server可执行程序运行起来就是两个进程而且是两个毫不相干的进程没有血缘关系。 执行srver课执行程序后生成fifo_file命名管道文件类型是p但是权限是644并不是666 这是因为fifo文件的参数mode受系统umask影响可以查看到Umask的值是2 那么可以看出mode mode ~umask666~002如果修改umask的值比如创建命名管道文件时将umask清0 server.c #includestdio.h #includesys/stat.h #includesys/types.h #includefcntl.h #define fifo_file ./fifo_fileint main() {umask(0);//将umask清0if(mkfifo(fifo_file,0666) 0)//创建一个命名管道{perror(mkfifo);return 1;}int fd open(fifo_file,O_RDONLY);if(fd 0){perror(open);return 2;}while(1){char buffer[64] {0};ssize_t read_length read(fd,buffer,sizeof(buffer)-1);if(read_length 0)//读取成功{buffer[read_length-1] 0;printf(client # %s\n,buffer);}else if(read_length 0){printf(client quit\n);}else{perror(read);break;}}close(fd);return 0; } 这时可以看到命名管道文件的权限变成了666 对于client和server进程想让server读client写不推荐用c/c接口有缓冲区而系统调用没有缓冲区推荐使用系统调用接口client使用系统调用接收标准输入并写入到命名管道文件中 client.c  #includestdio.h #includeunistd.h #includesys/stat.h #includesys/types.h #includefcntl.h #includestring.h#define fifo_file ./fifo_file//client不需要创建命名管道文件只需要获取就可以了 int main() {int fd open(fifo_file,O_WRONLY);if(fd 0){perror(open);return 1;}while(1){printf(请输入# );//client的输入提示fflush(stdout);//刷新一下标准输出char buffer[64] {0};//先把数据从标准输入拿到client进程内部ssize_t read_length read(0,buffer,sizeof(buffer)-1);if(read_length 0){buffer[read_length-1] 0;//拿到了数据write(fd,buffer,strlen(buffer));}}close(fd);return 0; } 现在运行得先让server跑起来创建一个命名管道然后再运行client端就可以再client端写入数据了 从以上就可以看出对于两个不想管的进程通过命名管道一个进程把消息发给了另外一个进程。因此一旦有了命名管道只需要让通信双方进程按照文件操作即可。由于命名管道也是基于字节流的因此实际上信息传递的时候需要通信双方定制“协议”。 现在让client控制server让server去执行任务。可以让server执行程序替换比如当client接收标准输入写入到命名管道文件中的字符串为show时就会执行ls命令 server.c #includestdio.h #includesys/stat.h #includesys/types.h #includefcntl.h #includeunistd.h #includestdlib.h #includewait.h #includestring.h #includesys/wait.h#define fifo_file ./fifo_fileint main() {umask(0);if(mkfifo(fifo_file,0666) 0)//创建一个命名管道{perror(mkfifo);return 1;}int fd open(fifo_file,O_RDONLY);if(fd 0){perror(open);return 2;}//业务逻辑进行读写while(1){char buffer[64] {0};ssize_t read_length read(fd,buffer,sizeof(buffer)-1);if(read_length 0)//读取成功{buffer[read_length] 0;if(strcmp(buffer,show) 0){printf(the string is show\n);if(fork() 0){execl(/usr/bin/ls,ls,-l,NULL);//程序替换exit(1);}waitpid(-1,NULL,0);}else{printf(client # %s\n,buffer);}}else if(read_length 0){printf(client quit\n);}else{perror(read);break;}}close(fd);return 0; } client.c #includestdio.h #includeunistd.h #includesys/stat.h #includesys/types.h #includefcntl.h #includestring.h#define fifo_file ./fifo_file//client不需要创建命名管道文件只需要获取就可以了 int main() {int fd open(fifo_file,O_WRONLY);if(fd 0){perror(open);return 1;}while(1){printf(请输入# );//client的输入提示fflush(stdout);//刷新一下标准输出char buffer[64] {0};//先把数据从标准输入拿到client进程内部ssize_t read_length read(0,buffer,sizeof(buffer)-1);if(read_length 0){buffer[read_length - 1] 0;//拿到了数据write(fd,buffer,strlen(buffer));}}close(fd);return 0; } 现在运行得先让server跑起来创建一个命名管道然后再运行client端就可以再client端写入数据了在client输入show之后server就将ls的内容展示出来了 可以看到通过命名管道把数据从一个进程传递给另外一个进程并且也实现了让一个进程控制了另外一个进程去执行任务达到了进程间通信的目的。 3命名管道的数据不会刷新到磁盘 假如让server进程每隔20秒读一次而client不断往管道发消息那么数据只能在管道文件 server.c #includestdio.h #includesys/stat.h #includesys/types.h #includefcntl.h #includeunistd.h #includestdlib.h #includewait.h #includestring.h #includesys/wait.h#define fifo_file ./fifo_fileint main() {umask(0);if(mkfifo(fifo_file,0666) 0)//创建一个命名管道{perror(mkfifo);return 1;}int fd open(fifo_file,O_RDONLY);if(fd 0){perror(open);return 2;}//业务逻辑进行读写while(1){char buffer[64] {0};sleep(20);//等待20秒再读ssize_t read_length read(fd,buffer,sizeof(buffer)-1);if(read_length 0)//读取成功{buffer[read_length] 0;if(strcmp(buffer,show) 0){printf(the string is show\n);if(fork() 0){execl(/usr/bin/ls,ls,-l,NULL);//程序替换exit(1);}waitpid(-1,NULL,0);}else{printf(client # %s\n,buffer);}}else if(read_length 0){printf(client quit\n);}else{perror(read);break;}}close(fd);return 0; } client.c不用修改 #includestdio.h #includeunistd.h #includesys/stat.h #includesys/types.h #includefcntl.h #includestring.h#define fifo_file ./fifo_file//client不需要创建命名管道文件只需要获取就可以了 int main() {int fd open(fifo_file,O_WRONLY);if(fd 0){perror(open);return 1;}while(1){printf(请输入# );//client的输入提示fflush(stdout);//刷新一下标准输出char buffer[64] {0};//先把数据从标准输入拿到client进程内部ssize_t read_length read(0,buffer,sizeof(buffer)-1);if(read_length 0){buffer[read_length - 1] 0;//拿到了数据write(fd,buffer,strlen(buffer));}}close(fd);return 0; } 这20秒内client向命名管道写但是server没有从命名管道读按理来说命名管道里面有内容大小不为0但是在这20秒之内发现命名管道的fifo_file的大小为0这就说明了命名管道的数据由于效率问题不会刷新到磁盘。 5.匿名管道和命名管道的区别 创建与打开的方式不同 匿名管道由pipe函数创建并打开命名管道由mkfifo函数创建由open函数打开 后面就有相同的语义了 三、System V IPC 1.System V标准 System V是一种用于在操作系统层面上进行进程间通信的标准system V标准给用户提供了系统调用接口只要用户使用它所提供的系统调用就可以完成进程间通信。IPCInter-Process Communication是进程间通信。System V IPC不用基于文件进行通信。 如何把系统调用接口提供给用户使用呢System V是操作系统内核的一部分是为操作系统中多进程提供的一种通信方案。但是操作系统不相信任何用户采用系统调用为用户提供功能。所以System V进程间通信存在专门用来通信的接口System call系统调用 这就需要制定一套标准用来在同一主机内进行进程间通信System V。System V进程间通信分为3种 System V消息队列System V共享内存System V信号量 消息队列模型通过在协作进程间交换消息来实现通信。共享内存模型会建立起一块供协作进程共享的内存区域进程通过向此共享区域读出或写入数据来交换信息。以下是消息队列和共享内存的通信模型 消息队列的实现经常采用系统调用因此需要消耗更多时间使内核介入但是共享内存只在建立共享内存区域时需要系统调用一旦建立共享内存所有访问都是常规内存访问不需要借助内核。 由于消息队列和共享内存用来传递消息信号量用来实现进程间同步和互斥。因此主要来看看进程间通信方式中效率较高的共享内存。 2.共享内存 1原理  把申请的共享内存映射到不同进程的地址空间当中。有进程A和进程B进程A通过页表映射找到进程A的代码和数据同样进程B也通过页表映射找到进程B的代码和数据由于两个进程的数据结构相互独立且物理内存当中的代码和数据也相互独立因此两个进程不会互相干扰。 在物理内存开辟一块共享内存空间后需要通过系统调用把开辟的内存空间经过页表映射到进程地址空间那么共享内存在进程地址空间也有了虚拟地址叫做共享存储器映射区再把共享存储器映射区的虚拟地址填到页表当中这样共享内存的虚拟地址和物理地址就建立起了对应关系而且各个进程也就看到了共享内存同一份资源。 以上的过程也是让进程挂接到共享内存空间上的过程。操作系统内可能存在多个共享内存那么操作系统需要管理这些共享内存管理还是先描述再组织。 如何保证能够让多个进程看到同一个共享内存呢 共享内存一定要有唯一标识ID就能让不同进程识别到同一个共享内存资源。那么这个ID一定在描述共享内存的数据结构中。 2步骤 可以总结出使用共享内存的过程 创建共享内存关联挂接去关联去挂接释放共享内存 3函数  shmget 使用shmget函数创建共享内存来申请一块共享内存空间 #include sys/ipc.h #include sys/shm.hint shmget(key_t key, size_t size, int shmflg); key通过ftok函数生成size建议为4KB的整数倍操作系统为了提高内存和硬盘的数据交换的速度以4KB为单位shmflghmflg标志有多个先了解最常用的两个标志IPC_CREAT和IPC_EXCL就可以了返回值成功就返回共享内存地址失败就返回-1 其中shmget第一个参数key是通过ftok函数生成的 #include sys/types.h #include sys/ipc.hkey_t ftok(const char *pathname, int proj_id); pathname自定义的文件路径名proj_id序号低8位被使用非0返回值返回key会被设置进共享内存在内核的数据结构里面 shmget第三个参数shmflg标志有多个先了解最常用的两个标志IPC_CREAT和IPC_EXCL就可以了 创建共享内存后如何查看共享内存呢ipcs命令用于报告进程间通信设施状况其中 ipcs -m //查看共享内存Shared Memory Segments ipcs -q //查看消息队列Message Queue ipcs -s //查看信号量Semaphore Arrays shmctl 使用完共享内存后如果不删除的话共享内存会一直存在直到系统重启。如何删除呢有两种删除方式一种是命令删除 ipcrm -m shmid key只是用来在系统层面进行唯一标识不能用来管理共享内存。而shmid是操作系统给用户返回的id用来在用户层进行共享内存管理所以ipcrm是用户层的命令。 以上是命令删除那么如何在代码中删除共享内存呢 因此另外一种删除共享内存的方式就是使用shmctl函数控制共享内存 #include sys/ipc.h #include sys/shm.hint shmctl(int shmid, int cmd, struct shmid_ds *buf);shmid操作系统给用户返回的idcmd选项有多个bufdata structure数据结构类型指针返回值删除成功返回0失败返回-1 其中cmd选项有多个 IPC_STAT将shmid的内核数据结构拷贝到buf指向的shmid_ds结构中IPC_SET将buf指向的shmid_ds结构的一些成员的值写入与此共享内存段相关的内核数据结构同时更新其shm_ctime成员IPC_RMID删除共享内存 第三个参数​​  其中shmid_ds数据结构如下 struct shmid_ds {struct ipc_perm shm_perm; /* Ownership and permissions */size_t shm_segsz; /* Size of segment (bytes) */time_t shm_atime; /* Last attach time */time_t shm_dtime; /* Last detach time */time_t shm_ctime; /* Last change time */pid_t shm_cpid; /* PID of creator */pid_t shm_lpid; /* PID of last shmat(2)/shmdt(2) */shmatt_t shm_nattch; /* No. of current attaches */... }; shmat 使用shmat把共享内存映射到调用进程的地址空间关联增加共享内存和进程地址空间映射关系的页表项 #include sys/types.h #include sys/shm.hvoid *shmat(int shmid, const void *shmaddr, int shmflg); shmid操作系统给用户返回的idshmaddr表明把共享内存挂接到进程地址空间的哪些范围中 shmflg有多个先了解最常用的两个标志IPC_CREAT和IPC_EXCL就可以了同shmget函数的shmflg标志这里设置为0就可以了返回值返回共享内存挂接到进程地址空间的虚拟地址同申请堆空间的malloc返回值是一样的 shmdt shmdt用来断开共享内存和进程地址空间的映射去关联删除共享内存和进程地址空间映射关系的页表项而不是释放共享内存 #include sys/types.h #include sys/shm.hint shmdt(const void *shmaddr); shmaddr要断开映射的共享内存地址且必须和shmat的参数shmaddr相同返回值成功断开返回0失败返回-1 4使用 两个进程使用共享内存通信需要进行创建、关联、去关联、删除的步骤现在使用上面的函数来进行server和client两个进程间的通信。 comm.h来包含头文件 #includesys/ipc.h #includesys/shm.h #includesys/types.h #includestdio.h #includeunistd.h#define PATH_NAME /home/delia/linux/20230627-sharedMemory/shared/server.c //ftok的路径 #define PROJ_ID 0x6666 #define SIZE 4097 server端需要生成唯一ID创建共享内存关联共享内存去关联共享内存删除共享内存 server.c #include comm.hint main() {key_t key ftok(PATH_NAME,PROJ_ID);//生成唯一ID保证在统一系统当中 找到共享内存if(key 0){perror(fork);return 1;}//1.创建共享内存int shmid shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);//共享内存不存在就创建权限为666共享内存可以用文件权限来约束if(shmid 0){perror(shmget);return 2;}printf(key %u,shmid %d\n,key,shmid);sleep(1);//2.关联char *mem shmat(shmid,NULL,0);printf(attaches shm success\n);sleep(15);//通信逻辑while(1){sleep(1);//printf(%s\n,mem);}//3.去关联shmdt(mem);printf(detaches shm success\n);//4.删除共享内存shmctl(shmid,IPC_RMID,NULL);sleep(5);printf(key %u,shmid %d after shmctl\n,key,shmid);return 0; } client端需要和客户端一样生成同一个唯一ID创建使用同一个共享内存关联共享内存去关联共享内存不需要删除共享内存因为server端已经删除了 client.c #include comm.hint main() {key_t key ftok(PATH_NAME,PROJ_ID);//生成唯一ID保证在统一系统当中 找到共享内存if(key 0){perror(ftok);return 1;}printf(%u\n,key);//1.创建共享内存int shmid shmget(key,SIZE,IPC_CREAT);//共享内存已存在就返回已存在共享内存if(shmid 0){perror(shmget);return 2;}//2.关联char *mem shmat(shmid,NULL,0);sleep(5);printf(client process attaches success\n);//通信逻辑char c A;while(c G){mem[c - A] c;c;mem[c - A] 0;sleep(2);}//3.去关联shmdt(mem);printf(client process detaches success\n);return 0; } Makefile .PHONY:all all:server clientserver:server.cgcc -o $ $^ client:client.cgcc -o $ $^.PHONY:clean clean:rm -f server client make之后使用命令 while :; do ipcs -m;sleep 1;echo #################; done 来查看共享内存的挂接进程的数量变化当server端和client端进程都没有开启时看到共享内存信息的nattch的个数为0当server和client端都运行起来之后发现nattch的个数变成了2client所写的消息就会被server读取当client端去关联之后nattch变成了1最后当server端退出时共享内存被删除nattch又变成了0 key系统区别各个共享内存的唯一标识shmid共享内存的用户层id句柄owner共享内存的拥有者perms共享内存的权限bytes共享内存的大小nattch关联共享内存的进程数status共享内存的状态 从以上可以看出共享内存有以下特点 共享内存一旦建立好并映射进自己进程的地址空间该进程就可以看到该共享内存就像malloc的空间一样不需要任何系统调用接口(比如read、write会将数据从内核拷贝到用户或从用户拷贝到内核)。共享内存是所有进程间通信中速度最快的这是因为将一块共享内存映射到不同的进程地址空间共享内存地址对应在内存上的空间就拿到了所以server和Client有任何一方写了另一方马上就看到了。生命周期随内核而且不提供同步互斥机制需要程序员自行保证数据的安全。 3.共享内存和管道区别  从共享内存的特点可以看出 1创建好共享内存后就不需要再调用系统接口进行通信了 而管道创建好后还需要调用read、write等系统接口进行通信。 2 共享内存没有同步互斥机制但是管道有同步互斥机制。 3共享内存是所有进程间通信方式中速度最快的将数据从一个进程传输带另一个进程管道需要进行4次拷贝共享内存需要进行2次拷贝共享内存需要的拷贝次数少。 使用管道将文件从一个进程传到另一个进程需要4次拷贝 服务端把信息从输入文件复制到服务端的临时缓冲区把服务端的临时缓冲区信息复制到管道中客户端把信息从管道复制到客户端的缓冲区把客户端临时缓冲区的信息复制到输出文件中 使用共享内存将文件从一个进程传到另一个进程需要2次拷贝 将信息从输入文件拷贝到共享内存将信息从共享内存拷贝到输出文件 四、消息队列  1.原理  消息队列是一个消息的链表可以把消息看作一个记录具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息对消息队列有读权限的进程则可以从消息队列中读走消息。消息队列的生命周期是随内核的。  队列的每个成员都是数据块每个数据块包含类型和信息两部分。这个队列也遵循先进先出即从队头读取消息向队尾写入消息 每个数据块都有类型这就说明各个数据块的类型可以不同因此接收者进程接收的数据块可以有不同的类型值。消息队列的资源必须手动删除因为system V IPC资源的生命周期是随内核的。 2.数据结构 消息对中的数据块如何管理呢还是先描述再组织。使用命令 cat /usr/include/linux/msg.h 就能够看到消息队列的数据结构如下  struct msqid_ds { struct ipc_perm msg_perm; /* Ownership and permissions */time_t msg_stime; /* Time of last msgsnd(2) */time_t msg_rtime; /* Time of last msgrcv(2) */time_t msg_ctime; /* Time of last change */unsigned long __msg_cbytes; /* Current number of bytes inqueue (nonstandard) */msgqnum_t msg_qnum; /* Current number of messagesin queue */msglen_t msg_qbytes; /* Maximum number of bytesallowed in queue */pid_t msg_lspid; /* PID of last msgsnd(2) */pid_t msg_lrpid; /* PID of last msgrcv(2) */ }; 第一个ipc_perm 结构体是不是有点熟悉呢它和shm_perm是同类型的结构体使用命令 cat /usr/include/linux/ipc.h 就能够看到ipc_perm 的结构体定义如下 struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */ }; 3.步骤 消息队列使用过程如下  创建发送接收释放 4.函数  1msgget 使用msgget来创建消息队列 #include sys/types.h #include sys/ipc.h #include sys/msg.hint msgget(key_t key, int msgflg);key通过ftok函数生成msgflgmsgflg标志有多个先了解最常用的两个标志IPC_CREAT和IPC_EXCL就可以了返回值创建成功就返回消息队列标识符失败就返回-1 同shmget一样msgget第一个参数key是通过ftok函数生成的 #include sys/types.h #include sys/ipc.hkey_t ftok(const char *pathname, int proj_id); pathname自定义的文件路径名proj_id序号低8位被使用非0返回值返回key会被设置进共享内存在内核的数据结构里面 msgget第三个参数msgflg标志有多个先了解最常用的两个标志IPC_CREAT和IPC_EXCL就可以了 2msgctl 使用msgctl来释放消息队列 #include sys/types.h #include sys/ipc.h #include sys/msg.hint msgctl(int msqid, int cmd, struct msqid_ds *buf); 使用完消息队列后如果不删除的话消息队列会一直存在直到系统重启。如何删除呢有两种删除方式一种是命令删除 ipcrm -q msqid 那么如何在代码中删除共享内存呢因此另外一种删除消息队列的方式就是使用msgctl函数控制消息队列 #include sys/types.h #include sys/ipc.h #include sys/msg.hint msgctl(int msqid, int cmd, struct msqid_ds *buf); msqid消息队列的用户层idcmd选项有多个bufdata structure数据结构类型指针返回值删除成功返回0失败返回-1 其中cmd选项有多个  IPC_STAT将msqid的内核数据结构拷贝到buf指向的msqid_ds结构中IPC_SET将buf指向的msqid_ds结构的一些成员的值写入与此共享内存段相关的内核数据结构同时更新其msq_ctime成员IPC_RMID删除共享内存 3msgsnd 使用msgsnd向消息队列发送数据 #include sys/types.h #include sys/ipc.h #include sys/msg.hint msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); msqid操作系统给用户返回的idmsgp待发送数据块msgsz待发送数据块大小msgflg发送数据块的方式一般为0返回值0表示调用成功-1表示调用失败 其中第二个参数msgp的结构为 struct msgbuf{long mtype; /* message type, must be 0 */char mtext[1]; /* message data */ }; 其中mutex为待发送的信息mutex大小可以由我们自己指定。 4msgrcv 使用msgrcv从消息队列获取消息 #include sys/types.h #include sys/ipc.h #include sys/msg.hssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg); msqid操作系统给用户返回的idmsgp获取到的数据块msgsz获取到的数据块大小msgtyp获取到的数据块的类型msgflg获取数据块的方式一般为0返回值0表示实际获取的字节数-1表示调用失败 五、信号量 1.原理  前面的管道、共享内存、消息队列都以传输数据为目的但是信号量不以传输数据为目的通过共享资源的方式来达到多个进程同步互斥的目的。  信号量有时被称为信号灯是在多线程环境下使用的一种设施是可以用来保证两个或多个关键代码段不被并发调用。在进入一个关键代码段之前线程必须获取一个信号量一旦该关键代码段完成了那么该线程必须释放信号量。其它想进入该关键代码段的线程必须等待直到第一个线程释放信号量。本质就是一个计数器衡量临界资源中的资源数。 这就像坐火车一样并不是因为坐在座位上这个作为才属于某一个人而是买了票的时候这个作为就已经属于买票的人了因此买票的本质就是对临界资源的预订票的数量就是信号量。如以下代码 信号量相关概念 临界资源被多个执行流同时访问的资源一次只允许一个进程使用。比如管道、共享内存、消息队列、信号量。临界区进程中访问临界资源的代码和临界资源配套为了保护数据安全就要把临界区保护起来就有了信号量。原子性一件事情要么做完要么不做没有中间状态。IPC资源必须删除否则不会自动清除除非重启所以system V IPC资源的生命周期随内核。 信号量本质是对临界资源的统计更是操作系统对临界资源的预定机制信号量要诶预订所有线程要访问临界资源得先申请信号量那么所有的进程就得先看到信号量信号量就是临界资源要保护信号量这个临界资源信号量的常见操作即PV操作就必须保证原子性。 2.数据结构 使用命令 cat /usr/include/linux/sem.h 就能够看到信号量的数据结构如下  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 结构体是不是有点熟悉呢它和shm_perm、msg_perm是同类型的结构体使用命令 cat /usr/include/linux/ipc.h 就能够看到ipc_perm 的结构体定义如下 struct ipc_perm {key_t __key; /* Key supplied to msgget(2) */uid_t uid; /* Effective UID of owner */gid_t gid; /* Effective GID of owner */uid_t cuid; /* Effective UID of creator */gid_t cgid; /* Effective GID of creator */unsigned short mode; /* Permissions */unsigned short __seq; /* Sequence number */ }; 3.函数 1semget 使用semget创建信号量 #include sys/types.h #include sys/ipc.h #include sys/sem.hint semget(key_t key, int nsems, int semflg); key操作系统给用户返回的idnsems创建的信号量的个数semflgsemflg标志有多个先了解最常用的两个标志IPC_CREAT和IPC_EXCL就可以了返回值 创建成功就返回信号量标识符-1表示创建失败 2semctl 使用semctl删除信号量 #include sys/types.h #include sys/ipc.h #include sys/sem.hint semctl(int semid, int semnum, int cmd, ...); semid信号量的用户层idsemnum信号量序号cmd信号量的控制操作标识返回值 创建成功就返回信号量标识符-1表示创建失败 3semop 使用semop来进行信号量的PV操作 #include sys/types.h #include sys/ipc.h #include sys/sem.hint semop(int semid, struct sembuf *sops, unsigned nsops); semid信号量的用户层idsops是sembuf类型的操作指针nsops单个信号量的操作返回值 创建成功就返回信号量标识符-1表示创建失败 使用命令 cat /usr/include/linux/sem.h可以看到sembuf结构体 struct sembuf {unsigned short sem_num; /* semaphore index in array */short sem_op; /* semaphore operation */short sem_flg; /* operation flags */ }; sem_num指定要操作的信号量0表示第一个信号量1表示第二个信号量……sem_op信号量操作sem_flg操作标识 六、System V IPC总结 从以上内容可以看出共享内存、消息队列、信号量虽然属性和实现起来有差别但是他们维护的数据结构的成员却是一样的即ipc_perm结构体这样每次要申请System V IPC时无论是共享内存、消息队列、信号量都会在数组中开辟ipc_perm这样的结构 那么内核可以分配一个ipc_perm数组用来指向每一个IPC资源。
http://www.pierceye.com/news/750326/

相关文章:

  • 网站上传格式工信部 网站备案查询
  • 网站开发怎么让别人看到网站设计主流尺寸
  • 都匀经济开发区建设局网站华宇网站建设
  • 南谯区住房和城乡建设局网站大连百度推广优化
  • 东莞设计网站公司被邀请做刷客会不会碰到钓鱼网站
  • 阿里云服务器多个网站南充网络推广
  • 电子商务网站建设规划设计任务书wordpress 有道云笔记
  • 广州市地铁站地图潍坊网站建设方案
  • 买一个网站服务器多少钱企业 怎么建交互网站
  • 国产网站开发工具公司福建新闻最新消息
  • 云主机和云虚拟主机优化教程网站推广排名
  • 佛山网站建设开发二维码生成器永久免费版下载
  • 衡水网站建设衡水网站建设网站建设南宁
  • 汽车网站推广策划方案wordpress又拍云cdn伪静态
  • 用dw建立网站网站设计师要学什么
  • 网站模块建设中二级医院做网站
  • 求大神帮忙做网站云网站后台
  • 保护稀有动物网站建设策划书凡科建站多少钱
  • 怎么通过做网站来赚钱坪山网站建设方案
  • 网站架构设计师工作内容传媒在线网站模板
  • 重庆哪里有做网站的公司互联网公司网站建设ppt
  • 海南的网站建设公司wordpress最新版中午
  • 网站推广需要域名迁移iis7建设网站
  • 网站建设实践报告小结网页版传奇服务端
  • 安顺住房和城乡建设部网站做网站用什么开发工具
  • 网站域名后缀意义深圳买门的网站建设
  • 遵义花果园网站建设wordpress关闭rss功能
  • 建设网站需要哪些人做网站的猫腻
  • 番禺网站建设效果深圳app制作开发公司排名
  • 临沂品牌网站推广做关于时尚网站的目的