怎么在手机上制作网站吗,在手机上怎么制作网站吗,郑州注册公司流程及费用,北京代理网站备案电话目录
1. 介绍
1.1 进程间通信的目的
1.2 进程间通信的分类
2. 管道
2.1 什么是管道
2.2 匿名管道
2.2.1 接口
2.2.2 步骤--以父子进程通信为例
2.2.3 站在文件描述符角度-深度理解
2.2.4 管道代码
2.2.5 读写特征
2.2.6 管道特征
2.3 命名管道
2.3.1 接口
2.3.2…目录
1. 介绍
1.1 进程间通信的目的
1.2 进程间通信的分类
2. 管道
2.1 什么是管道
2.2 匿名管道
2.2.1 接口
2.2.2 步骤--以父子进程通信为例
2.2.3 站在文件描述符角度-深度理解
2.2.4 管道代码
2.2.5 读写特征
2.2.6 管道特征
2.3 命名管道
2.3.1 接口
2.3.2 代码实现 2.3.3 匿名管道和命名管道的区别
3. system V共享内存
3.1 共享内存的原理
3.2 步骤
3.3 系统接口
3.4 代码
3.5 共享内存的优缺点
4.信号量
4.1 相关概念
4.2 信号量 -- 对资源的一种预定
4.3 系统接口 1. 介绍
1.1 进程间通信的目的
数据传输一个进程需要将它的数据发送给另一个进程 资源共享多个进程之间共享同样的资源。 通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件 如进程终止 时要通知父进程。 进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望 能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变有时候也需要多进程协同进行工作
如何理解进程间通信的本质问题呢
OS需要直接/间接的给通信双方的进程提供内存空间要通信的不同进程必须看到同一份公共资源
1.2 进程间通信的分类
管道SystemV(本文只讨论共享内存) -- 让通信过程可以跨主机POSIX -- 聚焦在本地通信
2. 管道
2.1 什么是管道 管道是 Unix 中最古老的进程间通信的形式 我们把从一个进程连接到另一个进程的一个数据流称为一个 “ 管道 ” 管道又分匿名管道和命名管道两种。
2.2 匿名管道
2.2.1 接口 #include ustdio.h int pipe(int pipefd[2]); 参数piepfd[]为输出型参数pipefd[0]为读文件描述符pipefd[1]为写文件描述符若为其他的文件描述符使用一般这两个fd分别为3、4。 返回值创建成功返回0失败返回-1 2.2.2 步骤--以父子进程通信为例
父进程利用pipe接口创建管道分别以读和写打开一个文件 父进程fork出子进程父进程关闭fd[1]子进程关闭fd[0]这样父进程就可以往管道文件中写数据子进程从管道文件中读数据实现了父子进程的通信
注管道一般是单向的其实管道也是一个文件(内核级文件)--不需要进行磁盘IO(当然也可以用磁盘文件来实现这个管道操作但是要进行磁盘读取太慢了) 若是管道中没有数据了但是读端还在读OS会直接阻塞当前正在读取的进程。
2.2.3 站在文件描述符角度-深度理解 2.2.4 管道代码 在这个代码部分可以实验当读快写慢、读慢写快、只读关闭、只写关闭四种情况这里只给出了只有读关闭的情况 #include iostream #include cstdio #include unistd.h #include cassert #include sys/stat.h #include sys/wait.h #include fcntl.h #include cstring using namespace std; int main() { // 第一步创建管道文件 int fds[2]; int n pipe(fds); assert(n 0); // 0 1 2应该是被占用的 _- 3 4 cout fds[0]: fds[0] endl; cout fds[1]: fds[1] endl; // 第二步fork pid_t id fork(); assert(id 0); if(id 0) { // 子进程的通信代码 子进程写入 close(fds[0]); // 通信代码 // string msg hello, i am child!; int cnt 0; const char* s 我是子进程我正在给你发消息; while(1) { cnt; char buffer[1024]; // 只有子进程能看到 snprintf(buffer, sizeof buffer, child-parent say: %s[%d][%d], s, cnt, getpid()); // 往文件中写入数据 write(fds[1], buffer, strlen(buffer)); // sleep(50); // 细节 每隔一秒写一次 // break; }cout 子进程关闭写端 endl;close(fds[1]);exit(0);} // 父进程的通信代码 父进程读取close(fds[1]);while(1){char buffer[1024];// cout AAAAAAAAAAAAAAA endl;// 父进程在这里阻塞等待ssize_t s read(fds[0], buffer, sizeof(buffer) - 1);// cout BBBBBBBBBBBBBBB endl;if(s 0) {buffer[s] 0;cout Get Message# buffer | my pid: getpid() endl;}else if(s 0){cout read: s endl;break;}// cout Get Message# buffer | my pid: getppid() endl;// 细节父进程可没有进行sleep//sleep(5);// close(fds[0]);break;}close(fds[0]);int status 0;cout 父进程关闭读端 endl;n waitpid(id, status, 0);assert(n id);cout pid- n : (status 0x7F) endl; // 信号为13SIGPIPE 中止了写入进程return 0;}由上面代码结果可以看出当读关闭时OS会终止写端给写进程发送信号终止写端。写进程收到13号信号
2.2.5 读写特征
读快写慢 -- 读进程会阻塞等到管道中有数据时继续读取子进程没有写入的那段时间 若管道中没有数据时父进程会在read处阻塞等待读慢写快 -- 写进程正常写数据管道写满时会在write处阻塞读进程就绪时继续读取 数据写关闭 -- 管道中的数据会被读取完毕后返回EOF此时 read 函数会返回0,最后等待子进程关 闭读端读关闭 -- OS会中止写端给写端发送信号--13 SIGPIPE终止写端
2.2.6 管道特征
管道的生命周期随进程管道可以用来进行具有血缘关系的进程通信常用于父子进程管道是面向字节流的单向通信 -- 半双工通信互斥与同步机制 -- 对共享资源进行保护额方案
2.3 命名管道
管道应用的一个限制就是只能在具有共同祖先具有亲缘关系的进程间通信。 如果我们想在不相关的进程之间交换数据可以使用FIFO文件来做这项工作它经常被称为命名管道。 命名管道是一种特殊类型的文件在用命名管道实现两个进程通信时任意一个进程调用mkfifo创建命名管道即可
2.3.1 接口 #include sys/types.h #include sys/stat.h int mkfifo(const char* pathname, mode_t mode); 参数pathname命名管道的路径名 mode管道权限 返回值成功返回0失败返回-1并设置errno来指示错误原因 int unlink(const char* pathname); --在进程结束后清理和删除命名管道。 参数命名管道的路径名 返回值成功返回0失败返回-1并设置errno来指示错误原因 命名管道可以从命令行上创建命令行方法是使用下面这个命令
mkfifo filename # filename为命名管道文件名
2.3.2 代码实现 用命名管道实现 serverclient 通信 server.cc: #include iostream
#include comm.hpp
using namespace std;int main()
{bool r createFifo(NAMED_PIPE);assert(r);(void)r;cout server begin endl;int rfd open(NAMED_PIPE, O_RDONLY); // 只读方式打开cout server end endl; if(rfd 0) { cout 文件打开失败 endl; exit(1); } // read char buffer[1024]; while(true) { ssize_t s read(rfd, buffer, sizeof buffer - 1); if(s 0) { buffer[s] 0; std::cout client-server: buffer endl; } else if(s 0) { cout client quit, me too! endl; break; } else{ cout err string: strerror(errno) endl; break; } }close(rfd);// sleep(10);removeFifo(NAMED_PIPE);return 0;
}client.cc
#include iostream
#include comm.hpp
using namespace std; int main()
{ // 与server打开同一个文件 cout client begin endl; int wfd open(NAMED_PIPE, O_WRONLY); cout client end endl; if(wfd 0) { cout 文件打开失败 endl; exit(1); } // write char buffer[1024]; while(true) { cout Please Say# ; fgets(buffer, sizeof(buffer)-1, stdin); if(strlen(buffer) 0) buffer[strlen(buffer)-1] 0; ssize_t s write(wfd, buffer, strlen(buffer)); assert(s strlen(buffer)); (void)s; } close(wfd); return 0;
}
comm.hpp
#pragma once #include string
#include iostream
#include sys/types.h
#include sys/stat.h
#include cstring
#include cassert
#include cerrno
#include unistd.h
#include sys/wait.h
#include fcntl.h #define NAMED_PIPE /tmp/mypipe.106 bool createFifo(const std::string path)
{ umask(0); int n mkfifo(path.c_str(), 0600); // 只允许拥有者通信 if(n 0) return true; else{ std::cout erro errno err string: strerror(errno) std::endl; return false; }
} void removeFifo(const std::string path)
{ int n unlink(path.c_str()); assert(n 0); // debug有效release里面就无效 (void)n; // 不想有警告
} 可以看到client可以向server端发送数据server收到并打印到屏幕中实验结果如下图所示 下图为命名管道的信息 2.3.3 匿名管道和命名管道的区别 匿名管道由pipe函数创建并打开。 命名管道由mkfifo函数创建打开用open FIFO命名管道与pipe匿名管道之间唯一的区别在它们创建与打开的方式不同一但这些工作完成之后它们具有相同的语义。
3. system V共享内存 3.1 共享内存的原理 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间这些进程间数据传递不再涉及到内核换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据 原理是不同的进程通过各自的PCB和页表访问同一快共享内存
3.2 步骤
申请一块空间将创建好的内存映射(将进程和共享内存挂接)到不同的进程地址空间若未来不想通信取消进程和内存的映射关系--去关联、释放共享内存
3.3 系统接口 #include sys/ipc.h #include sys.shm.h int shmget(key_t key, size_t size, int shmflg); 参数key: 进行唯一性标识 -- 将key使用shmget设置进入共享内存属性中用来表示共享 内存在内核中的唯一性 size申请空间大小--一般为4KB的整数倍 shmflgIPC_CREAT--如果指定的共享内存不存在创建如果存在获取共享内存 IPC_EXCL--无法单独使用 使用IPC_CREAT|IPC_EXCL如果不存在 创建--创建的一定是一个新的共享内存存在则出错返回还可以通过其 设置共享内存的权限 返回值成功返回标识符shmid失败返回-1与文件不同 key_t ftok(char* pathname, char proj_id); -- 使用给定路径名命名的文件的标识(必须引用一个现有的可访问的文件)和proj_id的最低有效8位(必须是非零的)来生成key_t类型的System V IPC密钥 返回值成功返回key_t值失败返回-1 解析 创建共享内存时如何保证其在系统中是唯一的-- 通过参数key确定的只要保证另一个要通信的进程看到相同的key值通过在各个共享内存的属性中查找相同的key即可找到同一块共享内存--通过相同的pathname和proj_id在不同的进程中调用ftok获得相同的key。那么key在哪里呢 -- 在结构体struct stm中。
IPC资源的特征共享内存的生命周期是随OS的不是随进程的若没有对共享内存进行手动的删除那么该资源不会消失
查看IPC资源的命令ipcs -m(共性内存) /-q(消息队列)/-s(信号量)
删除IPC资源的执行ipcrm -m shmid 操作共享内存 int shmctl(int shmid, int cmd, struct shmid_ds* buf); 参数shmidshmget的返回值--要控制哪一个共享内存 cmdIPC_RMID -- 删除共享内存段 谁创建谁删除 IPC_STAT -- 获取共享内存属性 IPC_SET -- 设置共享内存属性 buf: 返回值失败返回-1 关联进程 void* shmat(int shmid, const void* shmaddr, int shmflg); 参数shmid shmaddr将共享内存映射到哪一个地址空间中一般设为nullptr 核心自动选择 一个地址 shmflg一般设置为0读写权限 返回值共享内存空间的起始地址 去关联并不是删除共享内存而是去除PCB和共享内存的映射关系 int shmdt(const void* shmaddr); 参数shmaddr-由shmat所返回的指针返回值失败返回-1 3.4 代码
// common.hpp
#ifndef _COMM_HPP_
#define _COMM_HPP_ #include iostream
#include sys/ipc.h
#include sys/types.h
#include sys/shm.h
#include cerrno
#include cstring
#include cstdlib
#include unistd.h #define PATHNAME .
#define PROJ_ID 0x66
#define MAX_SIZE 4096 key_t getKey()
{ key_t k ftok(PATHNAME, PROJ_ID); // 可以获取同样的key if(k 0) { // cin cout cerr - stdin stdout stderr - 0,1,2 std::cerr errno : strerror(errno) std::endl; exit(1); } return k;
} int getShmHelper(key_t k, int flags)
{ int shmId shmget(k, MAX_SIZE, flags); if(shmId 0) { std::cerr errno : strerror(errno) std::endl; exit(2); } return shmId;
}
// 给之后的进程获取共享内存
int getShm(key_t k)
{return getShmHelper(k, IPC_CREAT/*可以设定为0*/);
}// 给第一个进程使用 创建共享内存
int creatShm(key_t k)
{return getShmHelper(k, IPC_EXCL | IPC_CREAT | 0600); // 0600为权限
}void *attachShm(int shmId)
{void *mem shmat(shmId, nullptr, 0); // 64位系统 指针占8字节if((long long)mem -1L){std::cerr shmat errno : strerror(errno) std::endl;exit(3);}return mem;
}void detachShm(void *start)
{if(shmdt(start) -1){std::cerr errno : strerror(errno) std::endl;}
}void delShm(int shmId)
{if(shmctl(shmId, IPC_RMID, nullptr) -1){std::cerr errno : strerror(errno) std::endl;}
}#endif//shm_client.cc//
#include common.hpp
using namespace std; int main()
{ key_t k getKey(); printf(0x%x\n, k); int shmId getShm(k); printf(shmId:%d\n, shmId); // 关联 char *start (char*)attachShm(shmId); printf(sttach success, address start: %p\n, start); // 使用 const char* message hello server, 我是另一个进程正在和你通信; pid_t id getpid(); int cnt 1; // char buffer[1024]; while(true) { sleep(1); // 直接将需要传递的信息写在共享内存字符串中 省去了很多拷贝的过程 提高了传输信息的效率 snprintf(start, MAX_SIZE, %s[pid:%d][消息编号:%d], message, id, cnt); // snprintf(buffer, sizeof(buffer), %s[pid:%d][消息编号:%d], message, id, cnt); // memcpy(start, buffer, strlen(buffer)1); } // 去关联 detachShm(start); // done return 0;
} /shm_server.cc///
#include common.hpp
using namespace std; int main()
{ key_t k getKey(); printf(0x%x\n, k); // 申请共享内存 int shmId creatShm(k); printf(shmId:%d\n, shmId); sleep(3); // 关联 // 将共享内存看为一个字符串 char *start (char*)attachShm(shmId); printf(sttach success, address start: %p\n, start); // 使用 while(true) { printf(client say: %s\n, start); sleep(1); } // 去关联 detachShm(start); sleep(5); // 删除共享内存 delShm(shmId); return 0;
} 上面的代码我们看到的现象是 通过共享内存的方式实现了进程间通信
3.5 共享内存的优缺点
优点
共享内存是所有通信中最快的大大减少数据的拷贝次数
缺点
不会给我们进行同步和互斥没有对数据进行任何保护
问题--同样的代码管道和共享内存方式实现各需要多少次拷贝
4.信号量
4.1 相关概念
信号量 -- 本质是一个计数器通常用来表示公共资源中资源数量的多少问题
公共资源 -- 被多个进程同时访问的资源访问没有保护的公共资源时可能会导致数据不一致 问题
为什么要让不同进程看到同一份资源 -- 实现进程间的协同工作但是进程是具有独立性的 为了让进程看到同一份资源提出了进程间通信的方法但是又带来了新的问题--数据不一致问题
临界资源将被保护起来的公共资源称为临界资源但是大部分的资源是独立的只有少量的属于临 界资源资源就是内存、文件、网络等
临界区进程访问临界资源的代码被称为临界区与之对应的为非临界区
保护公共资源互斥、同步
原子性要么不做要做就做完只有两种状态
4.2 信号量 -- 对资源的一种预定
为什么要有信号量
设sem20 sem--// P操作访问公共资源sem// V操作释放公共资源 --PV操作
所有的进程在访问公共资源前都必须先申请sem信号量前提是所有进程必须先看到同一个信号量那么信号量本身就是公共资源--也要保证自己的安全--信号量、--的操作是原子操作
如果一个信号量的初始值为1二维信号量/互斥信号量
4.3 系统接口
头文件 #include sys/types.h #include sys/ipc.h #include sys/sem.h 申请信号量 int semget(key_t key, int nsems, int semflg); 参数 key对申请的信号量进行唯一性标识 nsems申请几个信号量与信号量的值无关 semflg与共享内存的flag相同含义 返回值成功返回信号量标识符semid失败返回-1 删除信号量 int semctl(int semid, int semnum, int cmd,...); 参数semid信号量idsemget返回的值 semnum信号量集的下标 cmdIPC_RMID、IPC_STAT、IPC_SET 返回值失败返回-1 操作信号量 int semop(int semid, struct sembuf* sops, unsigned nsops); 参数semid信号量id sops 信号量的详细操作会在多线程部分讲解