使用守护进程、心跳机制、调度程序实现服务程序永不死机。
调度程序:启动服务程序,服务程序死掉后调度程序休眠n秒再次调度。
进程心跳:使用共享内存维护自己的心跳信息,当前时间减去最新时间如果大于超时时间就认为故障了,守护进程就会遍历共享内存,将其杀掉,并从共享内存中删除。
如何实现程序调度?(fork+exec函数)
在实际开发中,需要在调用execl函数之后继续运行后续代码,所以将fork函数与其结合起来。fork函数,去创建一个子进程,使用子进程去调用exe函数执行新的程序,这样就不会影响父进程代码的正常执行。父进程中,亦可调用wait函数,等待exe函数调用的新程序运行的结果。 这样即可实现程序调度的功能(比如,子进程执行了某个需要长期运行的服务程序,父进程会wait子进程的结果,如果服务程序故障,守护进程会查询到心跳信息,发送kill信号给服务程序让他结束,那么父进程收到结果继续重启该服务程序,因为最外层是一个while true循环)
在 shell 命令行执行 ps 命令,实际上是 shell 进程调用 fork 复制一个新的子进程,在利用exec 系统调用将新产生的子进程完全替换成 ps 进程。
exec系列函数都是通过execve系统调用实现的,将原进程的代码,数据,堆栈都替换成新进程,只有进程号没换。execl,execv区别是,l是参数通过列表一个一个进去且最后加一个NULL空指针,V是通过指针数组,char *const ps_argv[]={‘’,‘’}
共享内存
正常程序之间的内存是不允许相互访问的,但是共享内存允许多个程序访问同一快内存空间,共享内存区是最最高效的传递信息的IPC形式。在程序中我们通过指针映射进而操控读写共享内存内的文件。
创建共享内存函数shmget,这个函数用于获取共享内存,当没有时就创建共享内存。一般有三个参数,int shmget(key_t key , size_t size , int shmflag),第一个参数key是用来标识共享内存的,可以保证共享内存本身的唯一性
最后一个参数一般是0640|CREAT
shmat函数,其主要用处就是把共享内存链接到当前进程的地址空间,该函数的返回值是共享内存的地址,我们将该地址赋值给一个指针变量,即能实现够在当前进程中对共享内存进行读写的操作。
将共享内存与当前进程地址空间进行分离 shmdt函数,其作用为将共享内存与当前进程的联系断开。
释放共享内存 shmctl函数, 其主要目的就是将共享内存删除,将其所占用的空间进行释放,但是在实际操作中基本不会进行此操作。
我们可以通过ipcs和ipcrm命令对共享内存进行查看和删除等操作。
信号量:
P操作,计数器减一,申请资源。V操作,释放资源,计数器加1(PV是原子操作)
心跳机制
服务程序将自身的心跳信息保存在共享内存中,每隔特定时间进行更新,通过守护进程进行判断,当 当前时间与最后更新时间的差值大于设置的超时时间时,守护程序就会默认该服务程序死机,会先终止它,再发 送给信号重启该服务程序。
该心跳机制在代码实现中主要由两步构成,1.服务程序需要在共享内存中维护自己的心跳信息,包含文件名,进程ID,进程超时时间,和最后一次心跳时间等,2.守护程序终止已经死机的服务程序。(exit())
将守护进程封装成类,以便在后面进行调用,其中使用类的相关操作,回顾了下构造函数和构析函数
在写共享内存时需要加锁保护。
守护程序的实现
守护程序也是由调度程序实现。运行周期10秒。(调度程序有两种,一个就是周期性运行的,比如守护程序。另一种就是永不停机的,死掉后会重启)
避免被普通用户误杀,root用户启动
在终止流程中,会使用到析构函数和exit()函数,要注意 exit函数不会调用 局部变量的析构函数的(服务程序的局部变量),所以我们需要把包含析构函数的变量设置为全局变量,因为exit函数会调用 全局变量 的析构函数。(心跳机制类的析构函数就有删除已经超时的心跳激励,把共享内存从当前内存分离。)
为什么要kill -15?再kill -9。因为要让服务程序有善后代码,强行终止不好。一般每隔一秒测试一次是否存在进程,发送kill 0可以判断进程是否还存在。