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

来源:互联网 发布:九章算法系统设计视频 编辑:程序博客网 时间:2024/06/03 17:22

       其实,写这篇文章是带着多么激动的心情,终于没有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号进程收养,也就是会出现下面的样子



 

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


  





0 0
原创粉丝点击