闲言碎语
现在是23:58,因为我写的服务管理程序存在种种问题,今天是第二天加班到十一点半了,今天看样子是解决了问题,原因竟然是让人啼笑皆非的低级问题,在此记录一下
程序简介
为了提升系统性能,便于管理由我们自己启动的服务而写的一个总的管理程序。逻辑为:读取配置文件,killall 配置的服务(防止管理程序重启后多开服务),启动配置中的服务,如果配置文件中的服务被杀死,接收到信号后重启,具体原理如下图:

此程序的第一版就如上图,后开考虑到管理程序自己可能出意外,被脚本重启,在读取配置文件后先killall一次配置文件中的程序,然后再去启动,killall的动作和启动调用的是同一个函数,函数如下:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 
 | int do_system(int *svc_pid, const char *exec_file, list<string> arg_list){
 pid_t pid;
 int status;
 sigset_t chldmask, savemask;
 list<string>::iterator list_iterator;
 
 if (exec_file == NULL)
 {
 return (-1);
 }
 
 
 sigemptyset(&chldmask);
 sigaddset(&chldmask, SIGCHLD);
 if (sigprocmask(SIG_BLOCK, &chldmask, &savemask))
 {
 return (-2);
 }
 
 pid = fork();
 if (pid < 0)
 {
 
 return (-3);
 }
 else if (pid == 0)
 {
 
 int n = 0;
 char **argv;
 int argc = 0;
 char **p;
 n = arg_list.size();
 p = argv = (char **)malloc((n + 2) * sizeof(char *));
 p[argc] = (char *)exec_file;
 if (n > 0)
 {
 for (list_iterator = arg_list.begin(); list_iterator != arg_list.end(); list_iterator++)
 {
 p[++argc] = (char*)(*list_iterator).c_str();
 }
 }
 
 p[++argc] = NULL;
 
 n = execvp(exec_file, argv);
 
 free(argv);
 exit(n);
 }
 else
 {
 
 if (svc_pid)
 {
 *svc_pid = pid;
 }
 
 sigprocmask(SIG_SETMASK, &savemask, NULL);
 return (0);
 }
 
 | 
执行killall时:do_system(NULL, “killall”, service_list)
启动程序时:do_system(NULL, service_name, arg_list)
第一个坑
在挂测时发现低概率会有僵尸进程产生,而且总是第一个进程变成僵尸进程,看log发现在killall执行完的信号来之前,就开始启动进程了,因为killall中服务的顺序是启动的逆序,所以很可能启动第一个进程时,正好也在kill他,由于进程在初始化,不能完全杀死,没有信号的返回,导致了僵尸进程的产生,所以修改了函数,可以选择是否等待结果返回:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 
 | int do_system(unsigned char wait, int *svc_pid, const char *exec_file, list<string> arg_list){
 pid_t pid;
 int status;
 sigset_t chldmask, savemask;
 list<string>::iterator list_iterator;
 
 if (exec_file == NULL)
 {
 return (-1);
 }
 
 
 sigemptyset(&chldmask);
 sigaddset(&chldmask, SIGCHLD);
 if (sigprocmask(SIG_BLOCK, &chldmask, &savemask))
 {
 return (-2);
 }
 
 pid = fork();
 if (pid < 0)
 {
 
 sigprocmask(SIG_SETMASK, &savemask, NULL);
 return (-3);
 }
 else if (pid == 0)
 {
 
 int n = 0;
 char **argv;
 int argc = 0;
 char **p;
 n = arg_list.size();
 p = argv = (char **)malloc((n + 1) * sizeof(char *));
 p[argc] = (char *)exec_file;
 if (n > 0)
 {
 for (list_iterator = arg_list.begin(); list_iterator != arg_list.end(); list_iterator++)
 {
 p[++argc] = (char*)(*list_iterator).c_str();
 ALOGI("param:%s",p[argc]);
 }
 }
 
 p[++argc] = NULL;
 
 n = execvp(exec_file, argv);
 
 free(argv);
 exit(n);
 }
 else
 {
 
 if (svc_pid)
 {
 *svc_pid = pid;
 }
 
 if (wait)
 {
 while (waitpid(pid, &status, 0) < 0)
 {
 if (errno != EINTR)
 {
 sigprocmask(SIG_SETMASK, &savemask, NULL);
 return (-1);
 }
 }
 }
 else
 {
 sigprocmask(SIG_SETMASK, &savemask, NULL);
 return (0);
 }
 }
 
 sigprocmask(SIG_SETMASK, &savemask, NULL);
 return WEXITSTATUS(status);
 }
 
 | 
当第一个参数传1时,就会一直wait直到执行完成,这样就可以在执行一些shell命令时传1,在启动服务时传0
第二个坑
整个程序的结构分为三层,最上面是service,负责和client通讯,保证可以通过client端打开/关闭/刷新配置等操作;最下面是进程的实例类,存放进程的信息包括name,pid,enable,status等;中间层维护一个进程信息的列表,主要逻辑在这一层完成,所以这一层使用了单例
在终于爬出第一个坑之后,发现程序还是有概率启动进程失败,这次是一个都没启动,使用strace查看后发现是死锁了,为了找到哪里死锁也是耗费了一晚上的时间,最后恍然大悟是单例造成的死锁:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 
 | 
 
 function *function:get_instance()
 {
 if(!m_function)
 {
 std::lock_guard<std::mutex> lk(m_mutex);
 if(!m_function)
 {
 m_function = std::shared_ptr<Singleton>(new m_function);
 }
 }
 
 return m_function;
 
 }
 
 | 
我本意是启动一个线程负责开机的一些操作,包括处理信号,所以有了下面的写法:
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 
 | void signal_function(int sig)
 {
 switch (sig)
 {
 case SIGCHLD:
 
 function::get_instance()->check_srv();
 break;
 
 default:
 break;
 }
 }
 
 function::function()
 {
 
 read_conf();
 
 start();
 }
 
 void function::run()
 {
 signal(SIGCHLD, signal_function);
 
 killall_srv();
 sleep(1);
 startall_srv();
 }
 
 | 
在最上层调用get_instance()时,会上锁然后去创建function类,这时注册好信号处理函数后,恰好来了信号,此时单例还没创建完成,锁也还没释放,就又去get_instance()导致又上了一次锁,此问题的解决方法共有三种:
信号处理函数不使用单例
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 
 | function *g_func = NULL;void signal_function(int sig)
 {
 switch (sig)
 {
 case SIGCHLD:
 
 g_gunc->check_srv();
 break;
 
 default:
 break;
 }
 }
 
 function::function()
 {
 g_gunc = this;
 
 read_conf();
 
 start();
 }
 
 | 
不用线程
不开线程,run()函数只是个普通函数,上层初始化完成后,主动调用run() :
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 
 | service::service()
 {
 m_function = function::get_instance();
 m_function->run();
 }
 
 function::function()
 {
 
 read_conf();
 }
 
 
 | 
在main()中注册信号处理函数
| 12
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 
 | int main(){
 signal(SIGCHLD, signal_function);
 service m_service();
 
 while(1)
 {
 sleep(1000 * 1000);
 }
 
 return 0;
 }
 
 | 
总结
- 写代码要认真,要提前规划好,不要想到哪写到哪
- 使用锁要小心,仔细分析代码走向,防止死锁,或者没有锁住
- 不能停止学习,尤其是编程语言的基础数据结构和特性
其实解决问题并不难,难的是发现问题在哪,两个晚上的时间基本都花在了找原因上了,所以要学习掌握一些基本的调试工具,如gdb, strace, valgrind等,接下来我会抽空学习并总结一下这三个工具的使用方法