仿wordpress站3000行业关键词
仿wordpress站,3000行业关键词,为什么亿唐网不做网站做品牌,湘潭网站建设 皆来磐石网络进程通信的目的 数据传输#xff1a;一个进程需要将它的数据发送给另一个进程资源共享#xff1a;多个进程之间共享同样的资源。通知事件#xff1a;一个进程需要向另一个或一组进程发送消息#xff0c;通知它#xff08;它们#xff09;发生了某种事件#xff08;如进程…
进程通信的目的 数据传输一个进程需要将它的数据发送给另一个进程资源共享多个进程之间共享同样的资源。通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程。进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变 进程间通信的认识
进程间通信的本质是让相互通信的进程能够看到共享同一份资源。
首先我们要知道我们在打开一个文件时会形成文件描述符而0 1 2默认是标准输入标准输出标准错误。所以我们打开的文件就会从最小的下标开始进行文件描述符的分配。
那么如果一个进程以不同的方式打开同一个文件多次的话那会有几个文件描述符呢
int main()
{int fd open(test.txt,O_CREAT|O_WRONLY,0666);int fdd open(test.txt,O_RDONLY|O_CREAT);int fddd open(test.txt,O_RDONLY|O_CREAT);coutfd fdd fdddendl;return 0;
} 在验证了之后不难看出就这同一个文件打开三次就形成了三个文件描述符只不过这三个文件描述符指向的是同一个文件也就是资源共享一份只不过区别是不同文件描述符指向同一个文件的读写位置不同。 进程间通信分类
匿名管道
#include unistd.h
功能:创建一无名管道
原型
int pipe(int fd[2]);
参数
fd文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0失败返回错误代码 匿名管道的原理
匿名管道其实就是父进程创建管道也就是分别通过读写方式打开同一个文件然后创建子进程创建子进程的PCB此时子进程会和父进程的资源共享会将进程地址空间、页表都拷贝一份。而对应的文件描述符表也会进行拷贝一份浅拷贝。那么此时的父子进程的文件描述符表中指向的文件都是相同的。最后分别关闭对应的文件读写端就可以构成单向通信文件描述符指向文件会采用引用计数的方式。此时也就形成了管道。所以可以得出结论匿名管道允许的是具有血缘关系的进程进行通信。 管道读写规则
我们通过代码的方式来理解管道读写的规则方式采用父进程读数据子进程写数据的方式红色字体是结论
int main()
{//建立管道int tmp[2]{0};int npipe(tmp);if(n-1){cout管道建立失败endl;exit(-1);}//创建子进程父子进程指向同一个文件int idfork();if(id0){cout进程创建失败endl;exit(-1);}//child-wif(id0){close(tmp[0]);//关闭读文件char str[1024];int cnt10;while(1){ snprintf(str,sizeof(str)-1,hello parent, I am chilld,mypid %d,cnt %-2d,getpid(),cnt--);//防止str被写穿预留\0write(tmp[1],str,strlen(str));//写入到文件中文件中是不存在\0的if(cnt0)break;}//exit(0);//结束if语句就算子进程退出}//parent-rif(id0){close(tmp[1]);//关闭写文件char buffer[1024];while(1){int sz read(tmp[0],buffer,sizeof(buffer)-1);//防止读入buffer的数据超出临时空间预留\0//正常情况sz0的时候是因为管道没数据写端口阻塞if(sz0){buffer[sz]0;//字符串末尾加0coutbuffer parent_id:getpid()endl;}}pid_t rid waitpid(id,nullptr,0);//等待子进程退出if(ridid){coutwait okendl;}}return 0;
} 分析代码内容先创建管道本质就是通过读写方式打开同一个文件两次然后创建子进程此时子进程就会拷贝父进程的文件描述符表父子进程分别执行自己对应的代码。然后分别在父子进程中关闭关闭其不需要的操作文件方式此时关闭并不会直接将文件给关闭因为我们文件的指向是采用引用计数的方式的所以关闭进程的文件只会使引用计数--只有减到0才会真正的关闭文件。
但是我们发现我们的结果是按照顺序读写的也就是子进程写完一份内容父进程才会读取一份内容。所以就可以得出第一个结论写端目前没有向管道中写入数据此时读端就会等待直到有数据。如果其次就是进程子进程cnt0的时候写完了以后会退出此时父进程还在while(1)中死循环并不会退出。 此时我们更改一下代码的内容让读端等待写端一直写
int main()
{//建立管道...//创建子进程父子进程指向同一个文件...//child-wif(id0){close(tmp[0]);//关闭读文件char str[1024];int cnt10000;while(1){ //写入到文件中文件中是不存在\0的...coutwrite------------------cntendl;}}//parent-rif(id0){close(tmp[1]);//关闭写文件char buffer[1024];while(1){sleep(10);//向管道中读取数据...}//等待子进程退出...}return 0;
} 最终写到cnt8704的时候就会停下来此时我们知道父子进程时都没有退出的。我们其实知道此时的管道应该是被写满了所以就听下来了而父进程在sleep并没有将管道的数据给读走所以我们的进程就卡住了如果改一下时间为sleep(1)
此时我们的父进程在等待的一秒时间内子进程会一瞬间将缓冲区写满而父进程 此时就会将数据读走相当于清除缓冲区的数据只不过是允许覆盖式的清除方式并没有清除缓冲区数据然后继续写继续读...
所以可以得出结论如果管道写满了写端必须等待直到管道有空间为止读端读走数据。 再更改一下代码的内容写端写一条信息就终止读端一直读
int main()
{//建立管道...//创建子进程父子进程指向同一个文件...//child-wif(id0){close(tmp[0]);//关闭读文件char str[1024];int cnt10000;while(1){ //写入到文件中文件中是不存在\0的...coutwrite------------------cntendl;close(tmp[1]);//写一次就关闭break;}}//parent-rif(id0){close(tmp[1]);//关闭写文件char buffer[1024];while(1){//向管道中读取数据...}//等待子进程退出...}return 0;
} 此时的运行结果就是子进程写了一条消息就退出进程了所以子进程的文件描述符表也就都释放了而父进程读取一条消息以后就停住了也就是一直在while死循环中。
我们此时想要的结果其实就是子进程退出父进程回收子进程的资源然后也退出。所以我们的父进程就需要知道子进程的退出标识。
其实我们可以通过read系统调用函数的返回值来判断子进程是否写完数据并退出了。父进程read读取管道中信息当管道中没有数据了以后read函数的返回值就是0所以此时就可以设置父进程的等待了。 if(id0){ while(1){int sz read(tmp[0],buffer,sizeof(buffer)-1);//防止读入buffer的数据超出临时空间预留\0//正常情况sz0的时候是因为管道没数据写端口阻塞if(sz0)...else if(sz0){cout写端关闭了文件描述符或者子进程退出endl;break;}}//等待子进程...}
得出结论写端关闭读端一读取的话此时读端会读到read的返回值为0表示读到文件结尾。 再再更改一下代码的内容读端关闭写端一直写
int main()
{//建立管道...//创建子进程父子进程指向同一个文件...//child-wif(id0){close(tmp[0]);//关闭读文件char str[1024];int cnt10000;while(1){ //写入到文件中文件中是不存在\0的...coutwrite------------------cntendl;}}//parent-rif(id0){close(tmp[1]);//关闭写文件char buffer[1024];while(1){//向管道中读取数据...close(tmp[0]);cout读端关闭endl;break;}sleep(10)//等待子进程退出...}return 0;
} 现象在父进程关闭读端sleep十秒的时间内不难发现子进程已经退出了并且是僵尸也就是说父进程还没回收子进程的资源。而在等待完十秒后发现父进程与子进程都退出也回收资源了。
所以可以得出读端关闭写端一直写入的话操作系统会直接发送信号杀掉写端进程。 模拟实现进程池
进程池进程池Process Pool是一种用于并发执行多个任务的机制。它通过提前创建一组固定数量的子进程并将任务分配给这些子进程来执行从而实现并行处理。有了进程池可以有效地利用计算机的多核资源提高程序的运行效率。 我们其实想实现的功能就是父进程创建信道与子进程并且记录好信道与父进程的写端口对应关系然后将任务通过一个个的数字码传递给子进程然后子进程通过任务码执行对应的任务。这样就可以并发的执行多个任务。
...task.hpp...
#pragma once#include iostream
#include string
#include vector
#includectime
#includecstdlib
#include functional
#include cstring
#include unistd.h
#include sys/types.h
#include sys/wait.h
#include fcntl.h
using namespace std;typedef functionvoid() task; // using task functionvoid();// 任务
void a()
{cout ---我是任务a--- endl;
}
void b()
{cout ---我是任务b--- endl;
}
void c()
{cout ---我是任务c--- endl;
}// 需要将父进程传的任务码与对应的任务对应上
vectortask v_task{a,b,c};
int a_code 0;
int b_code 1;
int c_code 2;
...test.cpp...
#include task.hpp#define N 5
int num 1;
class tunnel // 将管道与对应的子进程关联
{
public:tunnel(int x, int y): child_id(x), write_fd(y){_name tunnel to_string(num);}public:pid_t child_id;int write_fd;string _name;
};void Printf(vectortunnel tunnels)
{for (int i 0; i N; i){cout tunnels[i]._name tunnels[i].child_id tunnels[i].write_fd endl;}
}
void work(int fd)
{while (1){int code;size_t n read(fd, code, sizeof(int));if (n 0){cout写端关闭endl;break;}// 先检查code是否合法if (!(code 0 code 3)){cout 没有该任务 endl;}v_task[code](); // 执行任务}
}
int main()
{vectortunnel tunnels;// 创建管道for (int i 0; i N; i){int pipe_fd[2] {0};int n pipe(pipe_fd);if (n -1){cout 管道建立失败 endl;exit(-1);}// 创建子进程pid_t id fork();if (id 0) // child-r{close(pipe_fd[1]);// 子进程读取命令执行任务work(pipe_fd[0]); // 所有子进程都在持续等待父进程派发任务exit(0); //}else if (id 0) // parent-w{close(pipe_fd[0]);// 将子进程与父进程关联tunnels.push_back(tunnel(id, pipe_fd[1]));}else // id-1{cout 创建进程失败 endl;exit(-1);}}//父进程创建并管理好了子进程//按顺序的向每个子进程中发送任务srand((unsigned int)time(nullptr)*11);//生成随机任务码while (1){ for (int i tunnels[0].write_fd; i tunnels[N - 1].write_fd; i){int k rand() % v_task.size();write(i, k, sizeof(int));}break;}//进程等待回收资源//for(int i0;iN;i)//存在bugfor(int iN-1;i0;i--) {close(tunnels[i].write_fd);waitpid(tunnels[i].child_id,nullptr,0);cout等待成功,pid:tunnels[i].child_idendl;}//Printf(tunnels);return 0;
} 但是该代码是存在隐患的也就是在父进程回收子进程的资源的时候可能会形成等待其实就是因为我们创建管道并且fork子进程的时候。我们首先创建管道实质就是打开同一个文件两次形成两个文件描述符然后再fork创建子进程那么子进程会拷贝文件描述符表中的内容此时父子进程文件描述符表中的文件指针所指向的就是同一个文件继续fork继续拷贝... 第一次fork创建子进程时的文件描述符关系 第二次 fork创建子进程时的文件描述符关系 不知道你是否发现了问题所在其实就是除了第一次的fork创建子进程之后其他子进程的文件描述符表中的文件指针会指向上一次父进程中的写端文件五角星位置标记。所以当我们的父进程自上向下的关闭写端文件的话是并不能关闭的。因为我们第二次foek创建子进程的时候将第一次文件描述符表中的内容都拷贝过来了。并且由于写时拷贝的原因我们关闭文件之后只是会引用计数减减但是并没有减到0所以文件写端并没有关闭因此子进程的read并不会读到0所以子进程也没有退出父进程就会一直等待...
所以我们就选择自下向上的关闭父进程的写端文件描述符此时子进程的读端read函数就会返回0从而子进程退出。所以此时该子进程的文件描述符表中指向的文件都会关闭因此就会将上一次拷贝过来的文件描述符给清理从而使得上一次的写端文件是一对一的方式。 命名管道
匿名管道的通信是有要求的必须是具有亲缘关系的进程间才能进行通信实质就是创建匿名管道然后再fork子进程所以就会将管道文件拷贝到子进程中。但是我们的命名管道就可以对任意进程进行通信其本质就是创建一个有名的管道然后任意两个进程通过打开各自读端或写端口进行数据的传输。 创建方法 命名管道可以从命令行上创建命令行方法是使用下面这个命令 $ mkfifo filename 命名管道也可以从程序里创建相关函数有 int mkfifo(const char *filename,mode_t mode); 实现两个进程的通信
...one.cpp...//写数据
int main()
{//打开文件int fd open(pipe,O_WRONLY|O_TRUNC);//写信息string tmp;while(1){cout请输入你要传送的信息;getline(cin,tmp);write(fd,tmp.c_str(),tmp.size());}close(fd);return 0;
}
...two.cpp...//读取数据
int main()
{//创建命名管道int ret mkfifo(pipe,0666);if(ret0){cout错误码:errno 错误信息:strerror(errno)endl;}//读的方式打开管道int fd open(pipe,O_RDONLY);if(fd0){cout错误码:errno 错误信息:strerror(errno)endl;exit(-1);}//读取信息char buffer[1024]{0};while(1){int cnt read(fd,buffer,sizeof(buffer)-1);//防止buffer被读穿预留\0if(cnt0){buffer[cnt]0;coutread接收到: bufferendl;}else if(cnt0){cout信息传输完毕endl;break;}}close(fd);return 0;
}匿名管道与命名管道的区别 匿名管道由pipe函数创建并打开。命名管道由mkfifo函数创建打开用openFIFO命名管道与pipe匿名管道之间唯一的区别在它们创建与打开的方式不同一旦这些工作完成之后它们具有相同的语义。 system V IPC
System V IPCInter-Process Communication也是一组用于进程间通信的机制通常在UNIX和Linux系统中使用。它包括三种主要机制共享内存shared memory、消息队列message queue和信号量semaphore。
System V 共享内存
不论是管道还是共享内存想要进行通信始终少不了让不同的进程能够看到同一份资源。
而 System V 共享内存 的通信机制是先在物理空间中创建一份共享资源空间然后对于想要通信的进程而言只需要通过页表在该进程地址空间的共享区映射好这块共享内存即可。
而物理内存上可能不仅仅只创建了一个共享内存会有多个共享内存的话就需要被操作系统进行管理而管理就需要描述组织。所以我们对于每个共享内存都会有结构体struct shmid_ds来存放共享内存中的信息的。 指令认识ipcs -m 查看系统共享内存信息 ipcrm -m shmid 移除用shmid标识的共享内存段 函数认识
shmget函数//创建共享内存
功能用来创建共享内存原型
int shmget(key_t key, size_t size, int shmflg);
参数
key:这个共享内存段名字,一般通过ftok函数生成
size:共享内存大小
shmflg:由九个权限标志构成它们的用法和创建文件时使用的mode模式标志是一样的
返回值成功返回一个非负整数shmid即该共享内存段的标识码失败返回-1
shmat函数//挂接共享内存
功能将共享内存段连接到进程地址空间原型
void *shmat(int shmid, const void *shmaddr, int shmflg);
参数
shmid: 共享内存标识
shmaddr:指定连接的地址
shmflg:它的两个可能取值是SHM_RND和SHM_RDONLY
返回值成功返回一个指针指向共享内存第一个节虚拟地址失败返回-1
shmdt函数//程序结束清空页表时相当于此
功能将共享内存段与当前进程脱离原型
int shmdt(const void *shmaddr);
参数
shmaddr: 由shmat所返回的指针
返回值成功返回0失败返回-1
注意将共享内存段与当前进程脱离不等于删除共享内存段
shmctl函数
功能用于控制共享内存原型
int shmctl(int shmid, int cmd, struct shmid_ds *buf);//shmid_ds是共享内存的标识
参数
shmid:由shmget返回的共享内存标识码
cmd:将要采取的动作有三个可取值
--IPC_RMID:删除共享内存
--IPC_STAY:将shmid_ds结构体当中的数据设为共享内存中的关联值
--IPC_SET在进程权限足够的情况下将共享内存中的关联值设为shmid_ds结构体中给的值
buf:指向一个保存着共享内存的模式状态和访问权限的数据结构一般设为0
返回值成功返回0失败返回-1 通信
...add.h...
#pragma once#include sys/ipc.h
#include sys/shm.h
#include sys/types.h
#include string
#include cstring
#include cerrno
#include unistd.h
#include iostream
#include sys/types.h
#include sys/stat.h
#include fcntl.h
using namespace std;const string pathname /home/cr/git/linux/test_2_4;
const int proj_id 20040712;
int size 4096;
const string path pipe;key_t getkey()
{key_t key ftok(pathname.c_str(), proj_id);if (key -1){cout errno errno ,strerrno strerror(errno) endl;exit(1);}return key;
}int creatshm(key_t key)
{int shmid shmget(key, size, IPC_CREAT | IPC_EXCL | 0664); // 选项相当于没有就创建有就返回错误可以保证创建的是全新的共享内存必须带有权限否则可能导致挂接不上if (shmid -1) // 共享内存已经创建好了{cout errno errno ,strerrno strerror(errno) endl;exit(1);}return shmid;
}int getshm(key_t key)
{int shmid shmget(key, size, IPC_CREAT | 0664); // 选项相当于没有就创建有就返回shmidif (shmid -1) // 共享内存已经创建好了{cout errno errno ,strerrno strerror(errno) endl;exit(1);}return shmid;
}void *shmatway(int shmid)
{char *start (char *)shmat(shmid, nullptr, 0); // 第二个参数表示将共享内存挂载到地址空间的指定地址处,返回虚拟地址起始位置if (start (char *)-1){cout errno errno ,strerrno strerror(errno) endl;exit(1);}
}// 创建命名管道
void creat_pipe(const string sp)
{int ret mkfifo(sp.c_str(), 0666);if (ret 0){cout 错误码: errno 错误信息: strerror(errno) endl;}
}
...shm_creat.cpp...
#includeadd.hint main()
{//创建共享内存key_t key getkey();//生成key值int shmid creatshm(key);//挂接共享内存cout开始将共享内存映射到进程地址空间当中endl;coutshmid shmidendl;char* start (char*)shmatway(shmid);//printf(%p\n,start);sleep(3);//进程结束以后页表销毁挂接关系消失//创建管道--提供同步机制creat_pipe(path);cout创建管道endl;int fd open(path.c_str(),O_RDONLY);//同步机制会等待另一方也打开管道才会继续向下执行cout打开管道endl;//通信(创建共享内存--先运行--读数据)while(1){int tmp;ssize_t r read(fd, tmp, sizeof(tmp));if(r0){break;//写端已经关闭}cout共享内存的数据startendl;//共享内存不提供同步机制}//取消挂接和关闭管道删除管道shmdt(start);close(fd);unlink(path.c_str());//控制(清除)共享内存cout开始将共享内存从操作系统中删除endl;shmctl(shmid,IPC_RMID,nullptr);}...shm_attach.cpp...
#include add.hint main()
{// 获取共享内存key_t key getkey(); // 生成key值int shmid getshm(key);// 挂接共享内存cout 开始将共享内存映射到进程地址空间当中 endl;cout shmid shmid endl;char *start (char *)shmatway(shmid);// printf(%p\n,start);// 管道--提供同步机制int fd open(path.c_str(), O_WRONLY | O_TRUNC);// 通信(得到共享内存--后运行运行--写数据)char c C;while (c R){start[c - a] c;c;// 共享内存不提供同步机制cout 向共享内存写数据中 endl;int tmp 0;write(fd, tmp, sizeof(tmp));sleep(1);}// 取消挂接关闭文件shmdt(start);close(fd);
} 共享内存相较于管道的优点
共享内存是所有进程通信中速度最快的。因为我们创建好共享内存之后的返回值是一个指针而我们传输数据就可以直接通过指针的方式进行传递。而对于管道而言我们必须得调用read函数和write函数将管道中的数据写入与写出实质就是拷贝而且在此还会多次经过缓冲区的拷贝。 System V 消息队列
消息队列其实就是提供一个进程给另一个进程发送数据块的能力。双方通过发送数据块和读取数据块来分别进行发送信息和接收信息。
其实消息队列也是需要进行管理所以也会有描述消息队列信息快的结构体就和共享内存其实是一样的 代码使用其实与共享内存大致一样
const string pathname /home/cr/git/linux/test_2_24;
const int proj_id 20040712;
int size 4096;std::string ToHex(int id)
{char buffer[1024];snprintf(buffer, sizeof(buffer), 0x%x, id);return buffer;
}
key_t getkey()
{key_t key ftok(pathname.c_str(), proj_id);if (key -1){cout errno errno ,strerrno strerror(errno) endl;exit(1);}return key;
}
#includeadd.hint main()
{key_t keygetkey();cout生成的key keyendl;int msqid msgget(key,IPC_CREAT|IPC_EXCL);//创建消息队列(和共享内存一样)coutmsqid msqidendl;if (msqid -1) {cout errno errno ,strerrno strerror(errno) endl;exit(1);}//获取消息队列里的内容struct msqid_ds ds;msgctl(msqid,IPC_STAT,ds);cout消息队列中的key ToHex(ds.msg_perm.__key)endl;sleep(10);//删除消息队列msgctl(msqid,IPC_RMID,nullptr);cout删除消息队列...endl;} System V 信号量
信号量是一种用于进程间同步和互斥的机制。它可以用来协调多个进程对共享资源的访问确保同一时间只有一个进程访问该资源。本质其实就是一把计数器。
互斥任何时刻只允许一个执行流进程访问公共资源加锁完成如共享内存。
同步多个执行流执行的时候按照一定的顺序执行如管道。
被保护起来的公共资源叫做临界资源而访问该临街资源的代码称作临界区。
信号量通常是一个整数变量可以采用计数器的形式来表示。如果需要申请资源的话就信号量--直到信号量的值为0的时候就说明此时已经没有多余的资源可以提供给进程所以该进程就必须等待。如果释放资源的话就信号量此时就可以将释放的空闲资源提供给等待的进程使用。它有两个基本操作P (等待)和V (发出)。
通过合理地使用信号量可以避免多个进程同时访问共享资源而引发的问题实现进程间的同步和互斥。
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/88715.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!