服务器开发之调度程序实现


一、调度程序的基本概念

后台服务程序往往需要长期运行,运行中的进程可能出现意外终止等情况,调度程序的功能就是要检测后台服务程序是否意外终止,若服务程序终止,需要负责再次启动服务程序。还有一类程序可能需要周期性启动,比如每个60秒运行一次数据生成程序来生成测试数据,此类需要周期性启动的程序也需要服务程序定期将其启动。

二、调度模块的实现

分别实现用于长期运行程序的调度程序,和用于周期性启动程序的调度程序代码。

1. 用于保持服务程序长期运行的调度程序

对于需要长期运行的程序,调度程序可以通过fork()函数创建一个子进程,子进程中运行实际的服务程序,父进程作为监控进程通过wait(pid)判断子进程是否退出,若退出再重新启动子进程继续运行服务程序。
在上述调度程序中,子进程需要调用其他的代码文件(即服务程序代码),如何实现在当前代码文件中调用另一份代码文件是关键。这里采用execl函数实现该功能,部分函数的详细说明放在服务程序整体代码实现之后。

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>

int main(int argc, char* argv[]){
	if(argc < 2){
		//程序输入参数有误,提示正确输入:被调度程序名 被调度程序的参数
		printf("Using:./program argvs\n"); return -1;
	}
	//关闭所有信号
	for(int i = 0; i < 64; ++i) signal(i, SIG_IGN);
	
	//生成被调度程序的参数数组,用于传递给execv函数
	char * arg[argc];
	for(int i = 1; i < argc; ++i){
		arg[i - 1] = argv[i];
	}
	arg[argc - 1] = NULL;
	
	while(true){
		pid_t pid = fork();
		if(pid == 0){  //子进程
			execv(argv[1], arg);
			exit(0);
		}else if(pid < 0){
			//fork函数创建失败,将错误信息写入日志或在控制台输出错误信息,该程序是简单案例采用在控制台输出错误的方式
			printf("fork failed!\n"); return -1;
		}else{         //父进程
			int status = 0;
			wait(&status);
			printf("program crash!\n");
			sleep(2);
		}
	}
}

相关函数

  • wait函数:用来判断当前进程的子进程是否已经退出。调用该函数后父进程阻塞并等待子进程结束。
pit_t wait(int* status);

status为传出参数,用于记录子进程退出状态,一般不关心。函数执行成功,返回退出的子进程的PID,函数执行失败返回-1。

  • execl函数:该函数的作用是加载一个新进程以替换当前进程的内容,函数执行成功后,该进程原本的后续代码均不会被执行。
int execv(const char* path, const char* arg[]);

path为要启动程序的路径(可以是绝对路径也可以是相对路径),arg表示启动程序的参数数组(第一个参数为程序名./program),数组中最后一个位置必须为NULL。上述程序中需要将传入调度程序中的参数进行提取,提取出替换程序的参数形成数组,并加入NULL传入execv函数中。该函数执行成功则不会执行原本的后续代码,如果执行失败则会执行原本的后续代码。
其他注意事项:

  • 在子进程部分,execv函数后添加exit(0)语句的原因:如果不加exit(0),execv函数执行失败会立刻进入到下一次循环,继续执行失败,进入死循环。正确的方法是添加eixt(0),若execv函数执行失败则会转而执行原本的后续代码即exit(0),程序退出。
  • 父进程中在子进程终止后执行sleep(2)的原因:为了防止马上fork下一个进程,而部分资源还在上一个进程中没来得及释放。
  • 调度程序通常需要关闭所有信号

2.用于周期性启动服务程序的调度程序

周期性启动的程序的被调度程序执行时间较短,调度程序需要做的是周期性启动该程序执行。具体上述思路类似,需要修改函数传入参数,增加输入一个程序执行周期参数即可,并将父进程中sleep函数的参数用给定周期参数替换即可。代码如下:

#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char* argv[]){
	if(argc < 3){
		//程序输入参数有误,提示正确输入:被调度程序名 执行周期(s) 被调度程序的参数
		printf("Using:./program timetvl argvs\n"); return -1}
	//生成被调度程序的参数数组,用于传递给execv函数
	char * arg[argc];
	for(int i = 1; i < argc; ++i){
		arg[i - 1] = argv[i];
	}
	arg[argc - 1] = NULL;
	
	while(true){
		pid_t pid = fork();
		if(pid == 0){  //子进程
			execv(arg[0], arg);
			exit(0);
		}else if(pid < 0){
			//fork函数创建失败,将错误信息写入日志或在控制台输出错误信息,该程序是简单案例采用在控制台输出错误的方式
			printf("fork failed!\n"); return -1;
		}else{         //父进程
			int status = 0;
			wait(&status);
			printf("program crash!\n");
			sleep(stoi(argv[2]));   //周期参数作为sleep函数的参数传入 
		}
	}
}

三、测试程序

1.长期运行服务程序的调度程序测试

被调度程序是一个需要长期运行的程序,当它意外退出的时候调度程序应该发现并再次启动该程序。本例中被调度程序是一个连续计数的函数。

/*程序名为:continous_test.cpp
  编译指令:g++ -g -o continous_test continous_test.cpp
  编译后执行该程序的命令为:./continous_test "Current number is"*/

#include <stdio.h>
#include <unistd.h>

int main(int argc, char* argv[]){
	int i = 0;
	while(true){
		printf("%s, %d\n", argv[1], i); ++i;
		sleep(1); 
	}
	return 0;
}

单独运行后该程序的预期结果如下:
在这里插入图片描述
若不向进程发送终止信号或意外终止,程序会一直计数并打印结果。接下来用调度程序运行该计数程序,输入ps -ef命令查看所有进程,找到该调度程序和被调度程序进程:

在这里插入图片描述
进程963672为被调度进程,其父进程963671为调度程序进程。接下来执行kill -9 963672强行杀死被调度进程,再查看系统中所有进程:
在这里插入图片描述
在这里插入图片描述
被调度程序被再次启动,验证成功!

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值