进程间通信(IPC) 之 共享内存 和 闲扯其他一些东西

news/2024/11/30 12:27:02/

       其实,写这篇文章是带着多么激动的心情,终于没有bug,这一篇有好几处我还要在加强练习。在进程间通信(IPC),函数好多,好多都没有记住,查man的时候就特别烦,一大段一大段的英语,看的一个头有8个大,先来谝谝共享内存。

       先说说我理解,对于共享内存,我感觉这效率还是很高的,因为内核基本上说不干预,说的最好(IPC之中)也是可以的。像管道,消息队列这些都很依赖于内核,消息队列很多都是系统函数,对于调用系统函数,需要站在不同的角度说,调用系统函数好呀,快么,内核就像皇帝,系统调用就是宠妃,你要干个事,内核马上可以帮你处理好,但是另一角度,后宫佳丽3000人,每个佳丽都向皇上提出自己的要求,皇上估计要驾崩了吧,也就是说系统调用多的话,内核根本忙不过来,那么系统的效率也就低了,本来内核事情就多,能减少工作量就将少吧,内核不做,我们可以找库函数做阿,我们调用库函数,很多事情都让他做,只有少部分去库函数找找内核帮忙。库函数就相当于大臣。感觉这样说应该差不多吧。

       共享内存从字面意思都可以理解出来,大家都可以用吗?我只要知道确定ID,标识符,我就可以用,在这里还是扯一扯操作系统的一些知识,对于CPU而言,她永远看到都是都是虚拟地址,真正的物理内存地址她是看不到的,从虚地址到实际地址的转化是通过MMU,一个专门的转化地址的,这个就需要硬件支持了(至于为什么要用虚拟内存,而不用实际物理内存还是专门写一片博客吧,这里就不详细说了),那么对于每个进程而言,各自的虚拟地址空间(虚拟内存)相互之间都是不可见的,就算你知道别的进程假如某个变量的虚拟地址,拿过来没有一点用,还是看不到(现在都基本采用分页式),因为物理内存完全不知道在哪里。

        因此呢,我们搞一个共享内存出来,大家对于所有的进程都可以看见这片内存,那么大家都可以用,也就实现了通信。我们都应该知道进程的虚拟地址空间分布图吧,从高地址开始,比如有4G内存,其中1G是划分给内核的,其他3G给用户,内核的1G就是直接映射到所有的进程虚拟地址空间。而共享内存也是如此,你要用到这片内存的时候,调用shmat,直接映射到进程虚拟地址空间。原理就是这个样子吧!,这图太丑了,关键我的linux上没有画图工具,这么丑,情有可原吧

    

       我们现在用的是System V标准,不要去看posix标准了,了解一个就好。知道怎么回事。我简单和所下函数,因为这些函数网上博客中说的太多了,没必要在细说了.

     shmget    获取以俄国共享存储标识符

     shmctl     对共享存储段执行多种操作(查看状态,删除,复制)

     shmat     将共享内存映射到自己的进程空间

     shmdt      将共享内存分离出去


     我们可以利用   ipcs  -m  查看共享内存,其中那个0xabcd0001我自己创建的,对于其他几个键,其实人间是私有的,还有重要的一点忘记说了,一点私有的放在共享内存中其实挺好的,可以保存一些数据,一些配置文件比如nginx中的热部署,我直接写在共享内存中,修改配置文件我仅仅需要reload,而不需要把nginx重新启动一遍(对于服务器而言,重新启动一次压力是非常巨大的,对于一些公司,好几年应该才重新启动一次),进程死了的话,那么她的内存空间也就被释放了,内存的数据一点不剩,如果把一些重要的数据放在共享内存中,就算进程死了,重新启动进程我们还是可以从之前的进程读取出数据的,但是也要吐槽一下私有,说好的共享呢,你却私有了。




      前面的废话真是扯了好多了,也是够了吧。开始今天的代码之旅。对于代码而言,现在也是比较规范的,总是自我感觉良好。这个程序用练习了下共享内存,几个点也是特别的重要。such as:

 myinfo = (_procinfo *)((char *)pctl + sizeof(_pool_ctl)) + loop

这个就是把进程池结构体强制转化为char *加上进程池结构的大小,loop是按照(_procinfo *)大小移动的,这就计算出子进程在共享的位置,  其实我们在C语言中用到强制类型转化真是太多了,对于好的C程序猿来说,对于内存应该斤斤计较。举个简单的例子,读出一个int的地址, char *p = (char *)&x;然后p++循环4次就出来了,



#include <stdio.h>                                                                                                                                                           
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>#define SHMKEY_MAIN_NET 0xabcd0001#define POOLMAX   128      //最大子进程的数目
#define IDLE      0        // 表示子进程空闲
#define BUSY      1        // 表示子进程繁忙
#define OK        0
#define NG        -1#define   SIG_WAKEUP    SIGUSR1
#define   SIG_END       SIGUSR2// 进程的信息
typedef struct procinfo{pid_t pid;int isbusy;
}_procinfo;//进程池的控制结构体
typedef struct pool_ctl{  int        shmid;    // key值int        procnum;  // 子进程个数_procinfo  *info;    // 指向子进程的指针
}_pool_ctl;_pool_ctl *pctl;
int poolrun = 1;// 函数的接口
void *shm_init(key_t key,void **maddr,size_t size);  // 共享内存的初始化
void _sig_wake(int sig);
void _sig_end(int sig);
void _proc_start();                 // 接受信号的函数
int make_pool();      // 创建进程池
void do_serv();       // 给子进程发信号// 函数的实现
void *shm_init(key_t key,void **maddr,size_t size)
{int  shmid = -1;void *addr = NULL;if(maddr == NULL){return NULL;}   if(size > 0){shmid = shmget(key,size,IPC_CREAT|0666);}else{shmid = shmget(key,0,0);}  if(shmid < 0){return NULL;}addr = shmat(shmid,NULL,0);*maddr = addr;if(maddr){memset(maddr,0x00,size);}return addr;}
//  三个信号的处理函数,这里面可以什么东西都不写
void _sig_wake(int sig)
{   //just wake up
}    void _sig_end(int sig)
{poolrun = 0;printf("got sig %d\n",getpid());
}   //void _sig_int(int sig)
//{// ctrl + c  终止程序
//}  //  三个注册信号的函数
void _proc_start()
{    if(signal(SIG_WAKEUP,_sig_wake) == SIG_ERR){//error}if(signal(SIG_END,_sig_end) == SIG_ERR){//error}if(signal(SIGINT,_sig_end) == SIG_ERR){//error}
}   // 创建进程池
int make_pool()
{pid_t chid = -1;int loop = 0;int rst = OK;if(pctl == NULL){return NG;}for(loop = 0; loop < pctl->procnum; loop++){chid = fork();if(chid < 0){rst =NG;break;}else if(chid == 0){_procinfo *myinfo = NULL;pctl = (_pool_ctl *)shm_init(SHMKEY_MAIN_NET,(void **)&pctl,0);if(pctl == NULL){fprintf(stderr,"child pctl is NULL \n");exit(1);}//很重要,计算逻辑地址,这里需要强制类型转化myinfo = (_procinfo *)((char *)pctl + sizeof(_pool_ctl)) + loop;printf("child born [%d]\n",getpid());while(poolrun){pause();myinfo->isbusy = BUSY;printf("%d I'm working\n",myinfo->pid);myinfo->isbusy = IDLE;}exit(0);}else{  pctl->info[loop].pid = chid;}       }        return rst;  
}   //  给子进程发信号
void do_serv()
{   int index = 0;if(pctl->procnum <=  0){printf("No sons\n");return ;}while(poolrun){index = (index + 1)% pctl->procnum;if(pctl->info[index].isbusy == BUSY){continue;}kill(pctl->info[index].pid,SIG_WAKEUP);sleep(1);}for(index = 0;index < pctl->procnum; index++){kill(pctl->info[index].pid,SIG_END);}   index = 0;while(index < pctl->procnum){wait(NULL);index++;}   }   int main(int argc,char *argv[])
{   int pnum = 0;int rst  = NG;if(argc < 1){fprintf(stderr,"argc argv input error\n");exit(1);}pnum = atoi(argv[1]);if(pnum <= 0 || POOLMAX < pnum){fprintf(stderr,"pnum  error\n");exit(2);}   //很重要pctl = shm_init(SHMKEY_MAIN_NET,(void **)&pctl,sizeof(_pool_ctl) + pnum * sizeof(_procinfo));if(pctl == NULL){fprintf(stderr,"shm_init error\n");exit(3);}               //很重要pctl->info = (_procinfo *)((char *)pctl + sizeof(_pool_ctl));pctl->procnum = pnum;_proc_start();  rst = make_pool();if(rst == OK){do_serv();}return 0;
}   


  对于上述程序的结果演示演示:

[root@localhost IPC]# ./pro_pool_shm    6

6就代表创建6个子进程




 重新打开一个终端,输入

<span style="font-size:18px;">[root@localhost IPC]# pstree -ap | grep pro_pool_shm
</span>



   然后我在这个终端发送kill -12 7917, 第一个终端出现了



这个进程已经捕捉到用户自定义停止的信号,由got可以看出来,在got的下一行7917 I'm working 之后,再也不执行了,因为已经死了,可是却是僵尸进程,资源已经释放,可是PCB中的东西还在。如果我执行连续kill -10   7918那么他就一直唤醒执行。如果在主进程中,我ctrl + c 那么父进程收到终止信号,那么子进程也会收到的,这样才不会进程都会被终止,就像这个样子




  正常退出,也不会产生孤儿进程,如果我杀死父进程,那么他的儿子该进去孤儿院了,由1号进程收养,也就是会出现下面的样子



 

好了这就是今晚闲扯的淡,扯淡,如果有什么问题,哪里说的不对,欢迎指正,欢迎吐槽。


  






http://www.ppmy.cn/news/842150.html

相关文章

python从零写一个采集器:获取网页信息

博客链接 https://uublog.com/article/20170216/python-extarct-html-info/ 前言 获取内容&#xff0c;比较纠结是用BeautifulSoup还是直接用正则匹配好。BeautifulSoup简单清晰&#xff0c;但是不够灵活。 正则则相反。 正文 信息位置的分析 像网盘&#xff0c;我们要提取…

技术人生的职场众生相

前言 这篇文章是 我的软件开发生涯 (10年开发经验总结和爆栈人生) 的新篇&#xff0c;大家有兴趣的话可以先看看这篇。 另外&#xff0c;我还接受过代码时间的采访&#xff1a;爆栈之旅 - 从接触到成为经理&#xff0c;从中国到澳洲 - 我这10年来的开发历程 我是个码农&#xf…

大牛的十多年技术人生的经验与心得

我是个码农&#xff0c;在职场干了多年&#xff0c;在超过10个公司服务过&#xff0c;遇到过各种怪现状&#xff0c;拍案惊奇葩&#xff0c;不吐不快&#xff0c;太想写篇文章吐槽一下。 这篇文章汇集了我10多年来的工作中遇到的各种经历&#xff0c;总结的心得&#xff0c;分别…

老九C语言41课项目实战-皇帝的后宫

Tips&#xff1a; 1、二维数组里面高维可以不要&#xff0c;低维需要保留。 2、if(strcmp(tempName,names[i]) 0) 需要引入头文件 #include <string.h> //支持字符串操作 0表示两个字符串相等&#xff0c;1&#xff0c;前一个大于后一个&#xff0c;-1&#xff0c;前一…

灵感之源之十多年技术人生的经验与心得

我是个码农&#xff0c;在职场干了多年&#xff0c;在超过10个公司服务过&#xff0c;遇到过各种怪现状&#xff0c;拍案惊奇葩&#xff0c;不吐不快&#xff0c;太想写篇文章吐槽一下。 这篇文章汇集了我10多年来的工作中遇到的各种经历&#xff0c;总结的心得&#xff0c;分…

IOS大牛的技术人生的经验与心得

我是个码农&#xff0c;在职场干了多年&#xff0c;在超过10个公司服务过&#xff0c;遇到过各种怪现状&#xff0c;拍案惊奇葩&#xff0c;不吐不快&#xff0c;太想写篇文章吐槽一下。 这篇文章汇集了我多年来的工作中遇到的各种经历&#xff0c;总结的心得&#xff0c;分别讨…

MySQL优化二:如何创建高性能索引之高性能的索引策略

正确的创建和使用索引是实现高性能查询的基础。前面已经介绍了各种类型的索引及其对应的优缺点。现在我们一起来看看如果真正的发挥这些索引的优势。 高效的选择和使用索引有很多种方式&#xff0c;其中有些是针对特殊案例的优化方法&#xff0c;有些则是针对特定行为的优化。…

技术人生的职场众生相 - 十多年的经验与心得

这篇文章是 我的软件开发生涯 (10年开发经验总结和爆栈人生) 的新篇&#xff0c;大家有兴趣的话可以先看看这篇。 另外&#xff0c;我还接受过代码时间的采访&#xff1a;爆栈之旅 - 从接触到成为经理&#xff0c;从中国到澳洲 - 我这10年来的开发历程 我是个码农&#xff0c…