C程序调度其他进程——exec函数族

0

本文涉及:

  • exec函数族
  • 调度程序
  • execl()
  • execv()

exec函数族

exec函数族提供了一个在进程中启动另一个程序执行的方法。它可以根据指定的文件名或目录名找到可执行文件,并用它来取代原调用进程的数据段、代码段和堆栈段,在执行完之后,原调用进程的内容除了进程号外,其他全部被新的进程替换了。另外,这里的可执行文件既可以是二进制文件,也可以是Linux下任何可执行的脚本文件。

使用exec函数族主要有两种情况:

(1)当进程认为自己不能再为系统和用户做出任何贡献时,就可以调用exec函数族中的任意一个函数让自己重生。

(2)如果一个进程想执行另一个程序,那么它就可以调用fork函数新建一个进程,然后调用exec函数族中的任意一个函数,这样看起来就像通过执行应用程序而产生了一个新进程。

使用man 3 exec查看exec函数族帮助:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <unistd.h>

int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

/*
参数说明:
path:要执行的程序路径。可以是绝对路径或者是相对路径。在execv、execve、execl和execle这4个函数中,使用带路径名的文件名作为参数。
file:要执行的程序名称。如果该参数中包含“/”字符,则视为路径名直接执行;否则视为单独的文件名,系统将根据PATH环境变量指定的路径顺序搜索指定的文件。
argv:命令行参数的矢量数组。
envp:带有该参数的exec函数可以在调用时指定一个环境变量数组。其他不带该参数的exec函数则使用调用进程的环境变量。
arg:程序的第0个参数,即程序名自身。相当于argv[O]。
…:命令行参数列表。调用相应程序时有多少命令行参数,就需要有多少个输入参数项。注意:在使用此类函数时,在所有命令行参数的最后应该增加一个空的参数项(NULL),表明命令行参数结束。
返回值:-1表明调用exec失败,无返回表明调用成功。
*/

The exec() family of functions replaces the current process image with a new process image。意思为exec()函数族将当前进程映像替换为新的进程映像。可以理解为使用函数时,用指定程序(进程)替换当前进程的进程映像(程序正文段、数据段、堆和栈)。

使用C程序调度其他进程

程序设计中,服务程序通过exec()函数族,周期性的启动后台程序或者常驻内存的服务程序异常终止时,在短时间内重启它。

1.先执行fork函数,创建一个子进程,让子进程调用exec执行一个新的进程。

2.新进程将替换子进程,不会影响父进程

3.在父进程中,可以调用wait函数等待新进程运行结果,这样来实现调度功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>

int main()
{
while(true)
{
if(fork() == 0)
{
//子进程
execl("/usr/bin/ls","/usr/bin/ls","-lt","/",(char*)0);
exit(0);
}else
{
//父进程
int status;
wait(&status);
sleep(5);
}
}
return 0;
}

上述程序,每隔5秒调用一次ls列出根目录下的文件。子进程调用execl函数,如果成功,子进程映像被ls进程映像替换,如果失败,执行exit(0)结束执行,防止系统中存在大量不可控的子进程;父进程等待子进程退出,可以防止子进程成为僵尸进程。

下面给出完整的调度程序示例:

1
2
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

int main(int argc,char *argv[])
{
if (argc<3)
{
printf("Using:./procctl timetvl program argv ...\n");
printf("Example:/project/tools/bin/procctl 5 /usr/bin/tar zcvf /tmp/tmp.tgz /usr/include\n\n");

printf("本程序是服务程序的调度程序,周期性启动服务程序或shell脚本。\n");
printf("timetvl 运行周期,单位:秒。被调度的程序运行结束后,在timetvl秒后会被procctl重新启动。\n");
printf("program 被调度的程序名,必须使用全路径。\n");
printf("argvs 被调度的程序的参数。\n");
printf("注意,本程序不会被kill杀死,但可以用kill -9强行杀死。\n\n\n");

return -1;
}

// 关闭信号和IO,本程序不希望被打扰。
for (int ii=0;ii<64;ii++)
{
signal(ii,SIG_IGN); close(ii);
}

// 生成子进程,父进程退出,让程序运行在后台,由系统1号进程托管。
if (fork()!=0) exit(0);

// 启用SIGCHLD信号,让父进程可以wait子进程退出的状态。
signal(SIGCHLD,SIG_DFL);

char *pargv[argc];
for (int ii=2;ii<argc;ii++)
pargv[ii-2]=argv[ii];

pargv[argc-2]=NULL;

while (true)
{
if (fork()==0)
{
execv(argv[2],pargv);
exit(0);
}
else
{
int status;
wait(&status);
sleep(atoi(argv[1]));
}
}
}
-------------本文结束感谢您的阅读-------------