Skip to content

LV020-在进程中执行程序

本文主要是进程——在进程中执行程序相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。

很多时候,我们希望父子进程可以执行不同的程序,下边就来看一看如何转而执行其他的程序吧。

一、exec函数族

1. 七个函数

exec 函数族共有 7 种不同形式的函数:

c
int execl(const char *pathname, const char *arg, ... /* (char  *) NULL */);
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);

int execv(const char *pathname, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

int execve(const char *pathname, char *const argv[], char *const envp[]);

【命名规律】 exec[l or v][p][e]

e参数必须带环境变量部分, 环境变量部分参数会成为执行exec函数期间的环境变量。
l命令参数部分必须以 " , " 相隔, 最后1个命令参数必须是NULL,表示结束。
v命令参数部分必须是1个以NULL结尾的字符串指针数组的头部指针。
p执行文件部分可以不带路径, exec 函数会在 $PATH 中找。

【参数分类】 exec 函数里的参数大概可以分成 3 个部分:执行文件部分、命令参数部分和环境变量部分。

2. execl开头

2.1 execl()

2.1.1 函数说明

在 linux 下可以使用 man 3 execl 命令查看该函数的帮助手册。

c
#include <unistd.h>
extern char **environ; /* 手册上有写,但是似乎不写也没什么 */
int execl(const char *pathname, const char *arg, ... /* (char  *) NULL */);

函数说明】该函数将新程序加载到某一进程的内存空间,将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。

函数参数

  • pathname : const char * 类型,参数 pathname 指向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是相对路径。
  • arg : const char * 类型,传递给执行的程序的参数列表,使用可变参数形式传递,本质上是多个字符串,以 NULL 结尾。

返回值】返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <unistd.h>

/* 至少应该有的语句 */
execl("./newTest", "./newTest", "Hello", "World", NULL, "!");

注意事项】 none

2.1.2 使用实例
c
#include <stdio.h>

#include <unistd.h>
int main(int argc, char *argv[])
{
	if(execl("/bin/ls", "ls", "-a", "-l", "./", NULL) < 0)
	{
		perror("execl");
	}
	return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
总用量 198
drwxrwxrwx 1 root root  4096  5月 23 11:49 .
dr-xr-xr-x 1 root root  4192  5月 23 11:49 ..
drwxrwxrwx 1 root root  4096  3月  1 20:41 1Pictures
# 剩下的省略 ...

2.2 execlp()

2.2.1 函数说明

在 linux 下可以使用 man 3 execlp 命令查看该函数的帮助手册。

c
#include <unistd.h>
extern char **environ; /* 手册上有写,但是似乎不写也没什么 */
int execlp(const char *file, const char *arg, ... /* (char  *) NULL */);

函数说明】该函数借助 PATH 环境变量将新程序加载到某一进程的内存空间,然后从新程序的 main() 函数开始执行,与 execl 函数功能一样,只是该函数不需要给出新程序的路径。

函数参数

  • file : const char * 类型,参数 file 指向需要载入当前进程空间的新程序的名称,不需要给出路径,但是要保证新程序路径存在于环境变量 PATH 中。
  • arg : const char * 类型,传递给执行的程序的参数列表,本质上是多个字符串,以 NULL 结尾。

返回值】返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <unistd.h>

/* 至少应该有的语句 */
execlp("newTest", "newTest", "Hello", "World", NULL, "!"); /* newTest 路径需要存在于 $PATH 中 */

注意事项】相对于 execl , execlp 仅要求提供新程序的名称即可,但是需要保证新程序路径存在于环境变量 $PATH 中。

2.2.2 使用实例
c
#include <stdio.h>

#include <unistd.h>
int main(int argc, char *argv[])
{
	if(execlp("ls", "ls", "-a", "-l", "./", NULL) < 0)
	{
		perror("execlp");
	}
	return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
总用量 198
drwxrwxrwx 1 root root  4096  5月 23 13:17 .
dr-xr-xr-x 1 root root  4192  5月 23  2022 ..
drwxrwxrwx 1 root root  4096  3月  1 20:41 1Pictures
# 剩下的省略 ...

2.3 execle()

2.3.1 函数说明

在 linux 下可以使用 man 3 execle 命令查看该函数的帮助手册。

c
#include <unistd.h>
int execle(const char *pathname, const char *arg, ... /*, (char *) NULL, char *const envp[] */);

函数说明】该函数将新程序加载到某一进程的内存空间,将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。

函数参数

  • pathname : const char * 类型,参数 pathname 指向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是相对路径。
  • arg : const char * 类型,传递给执行的程序的参数列表,本质上是多个字符串,以 NULL 结尾。
  • envp[] : char *const 类型数组,即是一个字符串指针数组,指定了新程序的环境变量列表,参数 envp 其实对应于新程序的 environ 数组,同样也是以 NULL 结束,所指向的字符串格式为 name=value 。

返回值】返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <unistd.h>

/* 至少应该有的语句 */
char *env_arr[5] = {"NAME=app", "AGE=25", "SEX=man", NULL};
execle("./newTest", "./newTest", "Hello", "fanhua", NULL, env_arr);

注意事项

(1)对 execle() 的成功调用将永不返回,而且也无需检查它的返回值,一旦该函数返回,就表明它发生了错误。

(2)当使用 execle() 传入命令行及环境变量参数时,都是遇到 NULL 就结束,新执行程序的命令行参数的 argv[0] 由调用该新程序的程序传入的第一个参数决定。

2.3.2 使用实例
  • test.c
c
#include <stdio.h>

#include <unistd.h>
int main(int argc, char *argv[])
{
	int ret = 0;
	char *env_arr[5] = {"NAME=app", "AGE=25", "SEX=man", NULL, "Fanhua=1"};
	
	printf("This is test.c!\n");
	ret = execle("./newTest", "./newTest", "Hello", "fanhua", NULL, env_arr);
	if(ret < 0)
	{
		perror("execle error");
	}

	return 0;
}

在终端执行以下命令生成可执行文件:

shell
gcc test.c -Wall # 生成可执行文件 a.out
  • newTest.c(被调用的程序)
c
#include <stdio.h>
#include <stdlib.h>

extern char **environ;

int main(int argc, char *argv[])
{
	char **ep = NULL;
	int i = 0;
	printf("This is new application!argc = %d\n", argc);
	for (i = 0; i < argc; i++)
	{
		printf("argv[%d]: %s\n", i, argv[i]);
	}

	puts("env:");
	for (ep = environ; *ep != NULL; ep++)
	{
		printf(" %s\n", *ep);
	}

	return 0;
}

在终端执行以下命令生成可执行文件:

shell
gcc newTest.c -o newTest -Wall # 生成可执行文件 newTest
  • 测试效果

我们在终端执行以下命令运行 a.out :

shell
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
This is test.c!
This is new application!argc = 3
argv[0]: ./newTest
argv[1]: Hello
argv[2]: fanhua
env:
 NAME=app
 AGE=25
 SEX=man

可以发现, NULL 后边的参数都没有打印出来,且新执行程序的 argv[0] 不再是程序名称,而是与传入的参数保持一致。

3. execv开头

3.1 execv()

3.1.1 函数说明

在 linux 下可以使用 man 3 execv 命令查看该函数的帮助手册。

c
#include <unistd.h>
extern char **environ; /* 手册上有写,但是似乎不写也没什么 */
int execv(const char *pathname, char *const argv[]);

函数说明】该函数将新程序加载到某一进程的内存空间,将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。

函数参数

  • pathname : const char * 类型,参数 pathname 指向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是相对路径。
  • argv[] : char *const 类型的数组,指定了传递给新程序的命令行参数。该参数是一个字符串数组,该数组对应于 main(int argc, char *argv[]) 函数的第二个参数 argv ,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。 argv[0] 对应的便是新程序自身路径名。

返回值】返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <unistd.h>

/* 至少应该有的语句 */
char  *arg[] = {"./newTest", "Hello", "World", NULL, "!"};
execv("./newTest", arg);

注意事项】 none

3.1.2 使用实例
c
#include <stdio.h>

#include <unistd.h>
int main(int argc, char *argv[])
{
	char  *arg[] = {"ls", "-a", "-l", "./", NULL};
	if(execv("/bin/ls", arg) < 0)
	{
		perror("execv");
	}
	return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
总用量 198
drwxrwxrwx 1 root root  4096  5月 23 13:47 .
dr-xr-xr-x 1 root root  4192  5月 23  2022 ..
drwxrwxrwx 1 root root  4096  3月  1 20:41 1Pictures
# 剩下的省略 ...

3.2 execvp()

3.2.1 函数说明

在 linux 下可以使用 man 3 execvp 命令查看该函数的帮助手册。

c
#include <unistd.h>
extern char **environ; /* 手册上有写,但是似乎不写也没什么 */
int execvp(const char *file, char *const argv[]);

函数说明】该函数借助 PATH 环境变量将新程序加载到某一进程的内存空间,然后从新程序的 main() 函数开始执行,与 execv 函数功能一样,只是该函数不需要给出新程序的路径。

函数参数

  • file : const char * 类型,参数 file 指向需要载入当前进程空间的新程序的名称,不需要给出路径,但是要保证新程序路径存在于环境变量 PATH 中。
  • argv[] : char *const 类型的数组,指定了传递给新程序的命令行参数。该参数是一个字符串数组,该数组对应于 main(int argc, char *argv[]) 函数的第二个参数 argv ,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。 argv[0] 对应的便是新程序自身路径名。

返回值】返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <unistd.h>

/* 至少应该有的语句 */
char  *arg[] = {"newTest", "Hello", "World", NULL, "!"};
execvp("newTest", arg);

注意事项】相对于 execv , execvp 仅要求提供新程序的名称即可。

3.2.2 使用实例
c
#include <stdio.h>

#include <unistd.h>
int main(int argc, char *argv[])
{
	if(execvp("ls", "ls", "-a", "-l", "./", NULL) < 0)
	{
		perror("execvp");
	}
	return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
总用量 198
drwxrwxrwx 1 root root  4096  5月 23 13:17 .
dr-xr-xr-x 1 root root  4192  5月 23  2022 ..
drwxrwxrwx 1 root root  4096  3月  1 20:41 1Pictures
# 剩下的省略

3.3 execvpe()

3.3.1 函数说明

在 linux 下可以使用 man 3 execvpe 命令查看该函数的帮助手册。

c
#include <unistd.h>
extern char **environ; /* 手册上有写,但是似乎不写也没什么 */
int execvpe(const char *file, char *const argv[], char *const envp[]);

函数说明】该函数将新程序加载到某一进程的内存空间,将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。

函数参数

  • file : const char * 类型,参数 file 指向需要载入当前进程空间的新程序的名称,不需要给出路径,但是要保证新程序路径存在于环境变量 PATH 中。
  • argv[] : char *const 类型的数组,指定了传递给新程序的命令行参数。该参数是一个字符串数组,该数组对应于 main(int argc, char *argv[]) 函数的第二个参数 argv ,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。 argv[0] 对应的便是新程序自身路径名。
  • envp[] : char *const 类型数组,即是一个字符串指针数组,指定了新程序的环境变量列表,参数 envp 其实对应于新程序的 environ 数组,同样也是以 NULL 结束,所指向的字符串格式为 name=value 。

返回值】返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <unistd.h>

/* 至少应该有的语句 */
char *arg_arr[5] = {"newTest", "Hello", "World", NULL, }; 
char *env_arr[5] = {"NAME=app", "AGE=25", "SEX=man", NULL};
execle("newTest", arg_arr, NULL, env_arr);

注意事项

(1)对 execvpe() 的成功调用将永不返回,而且也无需检查它的返回值,一旦该函数返回,就表明它发生了错误。

(2)当使用 execvpe() 传入命令行及环境变量参数时,都是遇到 NULL 就结束,新执行程序的命令行参数的 argv[0] 由调用该新程序的程序传入的第一个参数决定。

3.3.2 使用实例

首先添加环境变量(不添加的话,可能会报文件找不到。):

shell
# 临时改变当前终端的环境变量
export PATH=/home/hk/2Sharedfiles:$PATH
# 查看环境变量是否添加成功
echo $PATH
  • test.c
c
#include <stdio.h>

#include <unistd.h>
int main(int argc, char *argv[])
{
	int ret = 0;
    char *arg_arr[5] = {"newTest", "Hello", "qidaink", NULL, "!"};
	char *env_arr[5] = {"NAME=app", "AGE=25", "SEX=man", NULL, "Fanhua=1"};
	
	printf("This is test.c!\n");
	ret = execvpe("newTest", arg_arr, env_arr);
	if(ret < 0)
	{
		perror("execvpe error");
	}

	return 0;
}

在终端执行以下命令生成可执行文件:

shell
gcc test.c -Wall # 生成可执行文件 a.out

不过很奇怪的就是,它报了下边的警告:

shell
test.c: In function ‘main’:
test.c:11:8: warning: implicit declaration of function ‘execvpe’; did you mean ‘execvp’? [-Wimplicit-function-declaration]
   11 |  ret = execvpe("newTest", arg_arr, env_arr);
      |        ^~~~~~~
      |        execvp

我查了手册,这个函数是存在的,所以目前还未知原因。后边再补充吧。

  • newTest.c
c
#include <stdio.h>
#include <stdlib.h>

extern char **environ;

int main(int argc, char *argv[])
{
	char **ep = NULL;
	int i = 0;
	printf("This is new application!argc = %d\n", argc);
	for (i = 0; i < argc; i++)
	{
		printf("argv[%d]: %s\n", i, argv[i]);
	}

	puts("env:");
	for (ep = environ; *ep != NULL; ep++)
	{
		printf(" %s\n", *ep);
	}

	return 0;
}

在终端执行以下命令生成可执行文件:

shell
gcc newTest.c -o newTest -Wall # 生成可执行文件 newTest
  • 测试效果

在终端执行以下命令运行 a.out :

shell
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
This is test.c!
This is new application!argc = 3
argv[0]: newTest
argv[1]: Hello
argv[2]: qidaink
env:
 NAME=app
 AGE=25
 SEX=man

可以发现, NULL 后边的参数都没有打印出来,且新执行程序的 argv[0] 不再是程序名称,而是与传入的参数保持一致。

3.4 execve()

3.4.1 函数说明

在 linux 下可以使用 man 3 execve 命令查看该函数的帮助手册。

c
#include <unistd.h>
int execve(const char *pathname, char *const argv[], char *const envp[]);

函数说明】该函数将新程序加载到某一进程的内存空间,将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。

函数参数

  • pathname : const char * 类型,参数 pathname 指向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是相对路径。
  • argv[] : char const 类型的数组,指定了传递给新程序的命令行参数。该参数是一个字符串数组,该数组对应于 main(int argc, charargv[]) 函数的第二个参数 argv ,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。 argv[0] 对应的便是新程序自身路径名。
  • envp[] : char *const 类型数组,即是一个字符串指针数组,指定了新程序的环境变量列表,参数 envp 其实对应于新程序的 environ 数组,同样也是以 NULL 结束,所指向的字符串格式为 name=value 。

返回值】返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <unistd.h>

/* 至少应该有的语句 */
char *arg_arr[5] = {NULL, "Hello", "World", NULL}; 
char *env_arr[5] = {"NAME=app", "AGE=25", "SEX=man", NULL};
execve(argv[1], arg_arr, env_arr);

注意事项

(1)对 execve() 的成功调用将永不返回,而且也无需检查它的返回值,一旦该函数返回,就表明它发生了错误。

(2)当使用 execve() 传入命令行及环境变量参数时,都是遇到 NULL 就结束,新执行程序的命令行参数的 argv[0] 由调用该新程序的程序传入的第一个参数决定。

3.4.2 使用实例
  • test.c
c
#include <stdio.h>

#include <unistd.h>
int main(int argc, char *argv[])
{
	int ret = 0;
	char *arg_arr[5] = {"Hello", "world", "!", NULL, "Fanhua"};
	char *env_arr[5] = {"NAME=app", "AGE=25", "SEX=man", NULL, "Fanhua=1"};
	
	printf("This is test.c!\n");
	ret = execve("./newTest", arg_arr, env_arr);
	if(ret < 0)
	{
		perror("execve error");
	}

	return 0;
}

在终端执行以下命令生成可执行文件:

shell
gcc test.c -Wall # 生成可执行文件 a.out
  • newTest.c
c
#include <stdio.h>
#include <stdlib.h>

extern char **environ;

int main(int argc, char *argv[])
{
	char **ep = NULL;
	int i = 0;
	printf("This is new application!argc = %d\n", argc);
	for (i = 0; i < argc; i++)
	{
		printf("argv[%d]: %s\n", i, argv[i]);
	}

	puts("env:");
	for (ep = environ; *ep != NULL; ep++)
	{
		printf(" %s\n", *ep);
	}

	return 0;
}

在终端执行以下命令生成可执行文件:

shell
gcc newTest.c -o newTest -Wall # 生成可执行文件 newTest
  • 测试效果

在终端执行以下命令运行 a.out :

shell
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
This is test.c!
This is new application!argc = 3
argv[0]: Hello
argv[1]: world
argv[2]: !
env:
 NAME=app
 AGE=25
 SEX=man

可以发现, NULL 后边的参数都没有打印出来,且新执行程序的 argv[0] 不再是程序名称,而是与传入的参数保持一致。

二、 system()

1. 函数说明

在 linux 下可以使用 man 3 system 命令查看该函数的帮助手册。

c
#include <stdlib.h>
int system(const char *command);

函数说明】该函数可以在我们当前程序当中执行任意 shell 命令。

system() 函数其内部的是通过调用 fork() 、 execl() 以及 waitpid() 这三个函数来实现它的功能。 system() 会调用 fork() 创建一个子进程来运行 shell (可以把这个子进程称为 shell 进程),并通过 shell 执行参数 command 所指定的命令。

函数参数

  • command : const char * 类型,参数 command 指向需要执行的 shell 命令,以字符串的形式提供,如 "ls -al" 等。

返回值】返回值为 int 类型,当参数 command 为 NULL ,如果 shell 可用则返回一个非 0 值,若不可用则返回 0 ;针对一些非 UNIX 系统,该系统上可能是没有 shell 的,这样就会导致 shell 不可能;如果 command 参数不为 NULL ,则返回值从以下的各种情况所决定:

(1)如果无法创建子进程或无法获取子进程的终止状态,那么 system() 返回-1;

(2)如果子进程不能执行 shell ,则 system() 的返回值就好像是子进程通过调用 _exit(127) 终止了;

(3)如果所有的系统调用都成功, system() 函数会返回执行 command 的 shell 进程的终止状态。

使用格式】一般情况下基本使用格式如下:

c
/* 需要包含的头文件 */
#include <stdlib.h>

/* 至少应该有的语句 */
system("ps -l");

注意事项】system() 的主要优点在于使用上方便简单,编程时无需自己处理对 fork() 、 exec 函数、 waitpid() 以及 exit() 等调用细节, system() 内部会代为处理;当然这些优点通常是以牺牲效率为代价的,使用 system() 运行 shell 命令需要至少创建两个进程,一个进程用于运行 shell 、另外一个或多个进程则用于运行参数 command 中解析出来的命令,每一个命令都会调用一次 exec 函数来执行;所以从这里可以看出,使用 system() 函数其效率会降低,如果我们的程序对效率或速度有所要求,那么建议不要直接使用 system() 。

2. 函数实现

上边的了解仅仅够我们了解到如何使用这个函数,以及这个函数有什么功能,那他内部是怎样的,使用过程中会有什么影响?我们先来看一下这个函数的源码,很遗憾,我没找到,它在 stdlib.h 文件中声明,但是我还是没有找到源码,就网上搜了一个,应该大差不差:

c
int system(const char *cmdstring)

{
	pid_t pid;
	int status;

	if (cmdstring == NULL)
	{
		return (1);
	}

	if ((pid = fork()) < 0)
	{
		status = -1;
	}

	else if (pid == 0)
	{

		execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
		-exit(127); // 子进程正常执行则不会执行此语句
	}
	else
	{
		while (waitpid(pid, &status, 0) < 0)
		{
			if (errno != EINTER)
			{
				status = -1;
				break;
			}
		}
	}
	return status;
}

从这里可以看出其实system函数调用之后会创建一个子进程,子进程调用execl()去执行相应的shell命令,然后父进程会调用waitpid等待子进程退出,对子进城进程回收,这也就意味着,调用system的进程在这个时候会被阻塞,当执行的shell命令需要较长时间才能执行完毕的时候,调用system的进程就会等待很久的时间。

3. 使用实例

c
#include <stdio.h>

#include <stdlib.h>
int main(int argc, char *argv[])
{
	system("ps -l");
	return 0;
}

在终端执行以下命令:

shell
gcc test.c -Wall # 生成可执行文件 a.out
./a.out # 执行程序

程序执行后,终端将会显示以下信息:

shell
F S   UID     PID    PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
0 S  1000   11992    9420  0  80   0 -  5035 do_wai pts/1    00:00:00 bash
0 S  1000   17347   11992  0  80   0 -   595 do_wai pts/1    00:00:00 a.out
0 S  1000   17348   17347  0  80   0 -   658 do_wai pts/1    00:00:00 sh
0 R  1000   17350   17348  0  80   0 -  5254 -      pts/1    00:00:00 ps

4. 耗时测试实例

这部分会用到后边线程的一些知识,耗时的测试是通过线程来完成。

4.1 debug_printf.h

c
#ifndef __DEBUG_PRINTF_H__
#define __DEBUG_PRINTF_H__

#include <stdio.h>

#define DEBUG_ENABLE  1           /* 是否开启调试信息 */
#define LOG_LEVEL     DEBUG_ALL   /* 调试信息显示级别 */
 
#define ERROR   "ERROR"           /* 错误 */
#define WARN    "WARN "           /* 警告 */
#define INFO    "INFO "           /* 信息 */
/* 颜色定义——字体颜色 */
#define CLS_ALL        "\033[0m"  /* 清除所有颜色 */
#define F_BLACK        "\033[30m" /* 黑色字体 */
#define F_RED          "\033[31m" /* 红色字体 */
#define F_GREEN        "\033[32m" /* 绿色字体 */
#define F_YELLOW       "\033[33m" /* 黄色字体 */
#define F_BLUE         "\033[34m" /* 蓝色字体 */
#define F_PURPLE       "\033[35m" /* 紫色字体 */
#define F_CYAN         "\033[36m" /* 青色字体 */
#define F_WHITE        "\033[37m" /* 白色字体 */
/* 颜色定义——背景颜色 */
#define B_BLACK        "\033[40m" /* 黑色背景 */
#define B_RED          "\033[41m" /* 红色背景 */
#define B_GREEN        "\033[42m" /* 绿色背景 */
#define B_YELLOW       "\033[43m" /* 黄色背景 */
#define B_BLUE         "\033[44m" /* 蓝色背景 */
#define B_PURPLE       "\033[45m" /* 紫色背景 */
#define B_CYAN         "\033[46m" /* 青色背景 */
#define B_WHITE        "\033[47m" /* 白色背景 */
/* 颜色定义——背景的字体 */
#define BLACK_RED      "\033[30;41m" /* 红底黑字 */
#define BLACK_GREEN    "\033[30;42m" /* 绿底黑字 */
#define BLACK_YELLOW   "\033[30;43m" /* 黄底黑字 */

/* 调试等级枚举类型定义 */
enum DEBUG_LEVEL
{
    DEBUG_OFF   = 0,
    DEBUG_ERROR = 1,
    DEBUG_WARN  = 2,
    DEBUG_INFO  = 3,
    DEBUG_ALL   = 4
};
//===========================================================================
/* 自定义类型和字体颜色 */
#define MYDEBUG(TYPE, COLOR, FMT, ARGS...) \
        do \
        { \
            if(DEBUG_ENABLE && (DEBUG_##TYPE <= LOG_LEVEL)) \
            { \
                printf(COLOR); \
                printf("[%s][%s:%s:%d]"FMT, TYPE, __FILE__, __FUNCTION__, __LINE__, ##ARGS); \
                printf(CLS_ALL"\n"); \
            } \
        } while(0)
//===========================================================================
/* 平时调试使用 */
#include <string.h>
#include <time.h> // 时间函数用
#define filename(x) (strrchr(x,'/')?(strrchr(x,'/')+1):x)
#define NORMAL_DEBUG 1 // 注意使用下边两个宏需要自己添加换行,比较符合平时的习惯
// 获取时间函数
char *timeString(void)
{
  struct timespec ts;
  clock_gettime(CLOCK_REALTIME, &ts);
  struct tm *timeinfo = localtime(&ts.tv_sec);
  static char timeString[30];
  sprintf(timeString, "%.2d-%.2d %.2d:%.2d:%.2d", 
          timeinfo->tm_mon + 1, 
          timeinfo->tm_mday, 
          timeinfo->tm_hour, 
          timeinfo->tm_min, 
          timeinfo->tm_sec);
  return timeString;
}
// 获取系统时间戳 单位 ms
int sys_pts_get_ms(void)
{
    struct timespec tv;
    long long lasttime = 0;

    clock_gettime(CLOCK_MONOTONIC, &tv);
    lasttime = tv.tv_sec * 1000 + tv.tv_nsec / (1000 * 1000);
    return lasttime;

}

#define HKPRT(fmt...) \
        do \
        { \
            if(NORMAL_DEBUG) \
            { \
                printf(F_YELLOW); \
                printf("[%s][HK_LOG][%s:%d][%s]", timeString(), filename(__FILE__), __LINE__, __FUNCTION__); \
                printf(CLS_ALL); \
                printf(fmt); \
            } \
        } while(0)
#define HKPRTE(fmt...) \
        do \
        { \
            if(NORMAL_DEBUG) \
            { \
                printf(F_RED); \
                printf("[%s][HK_LOG][%s:%d][%s]", timeString(), filename(__FILE__), __LINE__, __FUNCTION__); \
                printf(CLS_ALL); \
                printf(fmt); \
            } \
        } while(0)

#endif

4.2 system_called.c

c
#include <stdio.h>
#include <unistd.h>
#include "debug_printf.h"
int main(int argc, char *argv[])
{
    /* code */ 
    int time0 = 0;
    int time1 = 0;
    time0 = sys_pts_get_ms();
    HKPRTE("This is system_called!!!\n");
    sleep(1);
    time1 = sys_pts_get_ms();
    HKPRTE("system_called use time:%d\n", time1-time0);
    return 0;
}

这个文件我们使用以下命令编译:

shell
gcc system_called.c -o system_called -Wall

然后便会生成 system_called 可执行文件,这个文件我们会在测试程序中通过system函数执行这个可执行文件。

4.3 test.c

c
/* 头文件 */
#include <stdio.h>   /* perror fgets shmat*/
#include <sys/shm.h> /* shmget shmat*/
#include <sys/ipc.h> /* shmget ftok */
#include <string.h>  /* strcpy */
#include <stdlib.h>  /* system */
#include <sys/prctl.h>
#include <unistd.h>

#include <fcntl.h>    /* sem_init For O_* constants */
#include <sys/stat.h> /* sem_init For mode constants */
#include <semaphore.h>/* sem_init sem_post */
#include <pthread.h>  /* pthread_create */
#include <signal.h> /* signal */

#include "debug_printf.h"

sem_t sem_sys; /* 定义信号量 */

void *thread(void *arg);    /* 读取数据线程   */
void deleteSemfile(int sig);/* 删除无名信号量 */
void sigintHandle(int sig); /* 捕获 Ctrl+\  */


/* 主函数 */
int main(int argc, char *argv[])
{

	pthread_t tid;

	/* 信号捕获 */
	signal(SIGINT, deleteSemfile);
	signal(SIGQUIT, sigintHandle);
	/* 信号量初始化) */
	sem_init(&sem_sys, 0, 0);
	/* 创建线程 */
	pthread_create(&tid, NULL, thread, NULL);
	/* 写入数据 */
	while(1)
	{
		sleep(1);
	}

	return 0;
}

/* 线程函数 */
void *thread(void *arg)
{
	int time0 = 0;
	int time1 = 0;
	char * main_name  = "thread";
	prctl(PR_SET_NAME, (unsigned long)main_name);
	pthread_detach(pthread_self());
	while(1)
	{
		sem_wait(&sem_sys);
		time0 = sys_pts_get_ms();
		HKPRT("system call ./system_called\n");
		system("./system_called");
		time1 = sys_pts_get_ms();
		HKPRT("system use time:%d\n", time1-time0);
	}
	pthread_exit("thread return!");
}
/* 捕获 Ctrl+\ */
void sigintHandle(int sig)
{
	static int count = 0;
	HKPRT("I catch the SIGQUIT![%d times] \n", ++count);
	sem_post(&sem_sys);
}
/* 删除信号量 */
void deleteSemfile(int sig)
{
	sem_destroy(&sem_sys);
	HKPRT("Destroy sem_sys!\n");
	exit(0);
}

这是我们真正的测试函数,我们使用以下命令编译:

shell
gcc test.c -lpthread -Wall

这样我们便会生成一个 a.out 可执行文件,a.out可执行文件运行后,我们在终端按下 ctrl + \ 就可以产生一个 SIGQUIT 信号。

4.4 测试结果

我们的上边的几个文件都放在同一目录下,然后我们执行a.out 可执行文件,再按下 ctrl+\ ,会有以下内容输出:

shell
hk@vm:~/6temp/test$ ./a.out 
^\[01-15 15:18:19][HK_LOG][test.c:70][sigintHandle]I catch the SIGQUIT![1 times] 
[01-15 15:18:19][HK_LOG][test.c:59][thread]system call ./system_called
[01-15 15:18:19][HK_LOG][system_called.c:10][main]This is system_called!!!
[01-15 15:18:20][HK_LOG][system_called.c:13][main]system_called use time:1001
[01-15 15:18:20][HK_LOG][test.c:62][thread]system use time:1004

可以看到,我们的 a.out 中的 thread 线程执行一次一共耗时1004ms,而 system_called 可执行程序就耗费了1001ms,于是我们知道,system在某个线程中被调用的时候我们是要考虑到system执行的时间的。