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

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



 

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


  





评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值