其实,写这篇文章是带着多么激动的心情,终于没有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号进程收养,也就是会出现下面的样子
好了这就是今晚闲扯的淡,扯淡,如果有什么问题,哪里说的不对,欢迎指正,欢迎吐槽。