本文主要是进程——在进程中执行程序相关笔记,若笔记中有错误或者不合适的地方,欢迎批评指正😃。
点击查看使用工具及版本
Windows windows11 Ubuntu Ubuntu16.04的64位版本 VMware® Workstation 16 Pro 16.2.3 build-19376536 SecureCRT Version 8.7.2 (x64 build 2214) - 正式版-2020年5月14日 开发板 正点原子 i.MX6ULL Linux阿尔法开发板 uboot NXP官方提供的uboot,NXP提供的版本为uboot-imx-rel_imx_4.1.15_2.1.0_ga(使用的uboot版本为U-Boot 2016.03) linux内核 linux-4.15(NXP官方提供) STM32开发板 正点原子战舰V3(STM32F103ZET6)
点击查看本文参考资料
点击查看相关文件下载
很多时候,我们希望父子进程可以执行不同的程序,下边就来看一看如何转而执行其他的程序吧。
一、exec函数族 1. 七个函数 exec 函数族共有 7 种不同形式的函数:
1 2 3 4 5 6 7 8 9 int execl (const char *pathname, const char *arg, ... ) ;int execlp (const char *file, const char *arg, ... ) ;int execle (const char *pathname, const char *arg, ... ) ;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 命令查看该函数的帮助手册。
1 2 3 #include <unistd.h> extern char **environ; int execl (const char *pathname, const char *arg, ... ) ;
【函数说明】 该函数将新程序加载到某一进程的内存空间,将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。
【函数参数】
pathname : const char * 类型,参数 pathname 指向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是相对路径。
arg : const char * 类型,传递给执行的程序的参数列表,使用可变参数形式传递,本质上是多个字符串,以 NULL 结尾。
【返回值】 返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 #include <unistd.h> execl("./newTest" , "./newTest" , "Hello" , "World" , NULL , "!" );
【注意事项】 none
2.1.2 使用实例 点击查看实例
test.c 1 2 3 4 5 6 7 8 9 10 11 #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 ; }
在终端执行以下命令:
1 2 gcc test.c -Wall # 生成可执行文件 a.out ./a.out # 执行程序
程序执行后,终端将会显示以下信息:
1 2 3 4 5 总用量 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 命令查看该函数的帮助手册。
1 2 3 #include <unistd.h> extern char **environ; int execlp (const char *file, const char *arg, ... ) ;
【函数说明】 该函数借助 PATH 环境变量将新程序加载到某一进程的内存空间,然后从新程序的 main() 函数开始执行,与 execl 函数功能一样,只是该函数不需要给出新程序的路径。
【函数参数】
file : const char * 类型,参数 file 指向需要载入当前进程空间的新程序的名称,不需要给出路径,但是要保证新程序路径存在于环境变量 PATH 中。
arg : const char * 类型,传递给执行的程序的参数列表,本质上是多个字符串,以 NULL 结尾。
【返回值】 返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 #include <unistd.h> execlp("newTest" , "newTest" , "Hello" , "World" , NULL , "!" );
【注意事项】 相对于 execl , execlp 仅要求提供新程序的名称即可,但是需要保证新程序路径存在于环境变量 $PATH 中。
2.2.2 使用实例 点击查看实例
test.c 1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <unistd.h> int main (int argc, char *argv[]) { if (execlp("ls" , "ls" , "-a" , "-l" , "./" , NULL ) < 0 ) { perror("execlp" ); } return 0 ; }
在终端执行以下命令:
1 2 gcc test.c -Wall # 生成可执行文件 a.out ./a.out # 执行程序
程序执行后,终端将会显示以下信息:
1 2 3 4 5 总用量 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 命令查看该函数的帮助手册。
1 2 #include <unistd.h> int execle (const char *pathname, const char *arg, ... ) ;
【函数说明】 该函数将新程序加载到某一进程的内存空间,将一个外部的可执行文件加载到进程的内存空间运行,使用新的程序替换旧的程序,而进程的栈、数据、以及堆数据会被新程序的相应部件所替换,然后从新程序的 main() 函数开始执行。
【函数参数】
pathname : const char * 类型,参数 pathname 指向需要载入当前进程空间的新程序的路径名,既可以是绝对路径、也可以是相对路径。
arg : const char * 类型,传递给执行的程序的参数列表,本质上是多个字符串,以 NULL 结尾。
envp[] : char *const 类型数组,即是一个字符串指针数组,指定了新程序的环境变量列表,参数 envp 其实对应于新程序的 environ 数组,同样也是以 NULL 结束,所指向的字符串格式为 name=value 。
【返回值】 返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 6 #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 使用实例 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #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 ; }
在终端执行以下命令生成可执行文件:
1 gcc test.c -Wall # 生成可执行文件 a.out
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #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 ; }
在终端执行以下命令生成可执行文件:
1 gcc newTest.c -o newTest -Wall # 生成可执行文件 newTest
在终端执行以下命令运行 a.out :
程序执行后,终端将会显示以下信息:
1 2 3 4 5 6 7 8 9 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 命令查看该函数的帮助手册。
1 2 3 #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 。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 6 #include <unistd.h> char *arg[] = {"./newTest" , "Hello" , "World" , NULL , "!" };execv("./newTest" , arg);
【注意事项】 none
3.1.2 使用实例 点击查看实例
test.c 1 2 3 4 5 6 7 8 9 10 11 12 #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 ; }
在终端执行以下命令:
1 2 gcc test.c -Wall # 生成可执行文件 a.out ./a.out # 执行程序
程序执行后,终端将会显示以下信息:
1 2 3 4 5 总用量 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 命令查看该函数的帮助手册。
1 2 3 #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 。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 6 #include <unistd.h> char *arg[] = {"newTest" , "Hello" , "World" , NULL , "!" };execvp("newTest" , arg);
【注意事项】 相对于 execv , execvp 仅要求提供新程序的名称即可。
3.2.2 使用实例 点击查看实例
test.c 1 2 3 4 5 6 7 8 9 10 11 #include <stdio.h> #include <unistd.h> int main (int argc, char *argv[]) { if (execvp("ls" , "ls" , "-a" , "-l" , "./" , NULL ) < 0 ) { perror("execvp" ); } return 0 ; }
在终端执行以下命令:
1 2 gcc test.c -Wall # 生成可执行文件 a.out ./a.out # 执行程序
程序执行后,终端将会显示以下信息:
1 2 3 4 5 总用量 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 命令查看该函数的帮助手册。
1 2 3 #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 。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 6 7 #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 使用实例 点击查看实例
首先添加环境变量(不添加的话,可能会报文件找不到。):
1 2 3 4 # 临时改变当前终端的环境变量 export PATH=/home/hk/2Sharedfiles:$PATH # 查看环境变量是否添加成功 echo $PATH
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #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 ; }
在终端执行以下命令生成可执行文件:
1 gcc test.c -Wall # 生成可执行文件 a.out
不过很奇怪的就是,它报了下边的警告:
1 2 3 4 5 6 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
我查了手册,这个函数是存在的,所以目前还未知原因。后边再补充吧。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #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 ; }
在终端执行以下命令生成可执行文件:
1 gcc newTest.c -o newTest -Wall # 生成可执行文件 newTest
在终端执行以下命令运行 a.out :
程序执行后,终端将会显示以下信息:
1 2 3 4 5 6 7 8 9 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 命令查看该函数的帮助手册。
1 2 #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, char *argv[]) 函数的第二个参数 argv ,且格式也与之相同,是由字符串指针所组成的数组,以 NULL 结束。 argv[0] 对应的便是新程序自身路径名。
envp[] : char *const 类型数组,即是一个字符串指针数组,指定了新程序的环境变量列表,参数 envp 其实对应于新程序的 environ 数组,同样也是以 NULL 结束,所指向的字符串格式为 name=value 。
【返回值】 返回值为 int 类型,调用成功将不会返回;失败将返回 -1 ,并设置 errno 。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 6 7 #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 使用实例 点击查看实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 #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 ; }
在终端执行以下命令生成可执行文件:
1 gcc test.c -Wall # 生成可执行文件 a.out
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 #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 ; }
在终端执行以下命令生成可执行文件:
1 gcc newTest.c -o newTest -Wall # 生成可执行文件 newTest
在终端执行以下命令运行 a.out :
程序执行后,终端将会显示以下信息:
1 2 3 4 5 6 7 8 9 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 命令查看该函数的帮助手册。
1 2 #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 进程的终止状态。
【使用格式】 一般情况下基本使用格式如下:
1 2 3 4 5 #include <stdlib.h> system("ps -l" );
【注意事项】 system() 的主要优点在于使用上方便简单,编程时无需自己处理对 fork() 、 exec 函数、 waitpid() 以及 exit() 等调用细节, system() 内部会代为处理;当然这些优点通常是以牺牲效率为代价的,使用 system() 运行 shell 命令需要至少创建两个进程,一个进程用于运行 shell 、另外一个或多个进程则用于运行参数 command 中解析出来的命令,每一个命令都会调用一次 exec 函数来执行;所以从这里可以看出,使用 system() 函数其效率会降低,如果我们的程序对效率或速度有所要求,那么建议不要直接使用 system() 。
2. 函数实现 上边的了解仅仅够我们了解到如何使用这个函数,以及这个函数有什么功能,那他内部是怎样的,使用过程中会有什么影响?我们先来看一下这个函数的源码,很遗憾,我没找到,它在 stdlib.h 文件中声明,但是我还是没有找到源码,就网上搜了一个,应该大差不差:
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 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. 使用实例 点击查看实例
test.c 1 2 3 4 5 6 7 8 #include <stdio.h> #include <stdlib.h> int main (int argc, char *argv[]) { system("ps -l" ); return 0 ; }
在终端执行以下命令:
1 2 gcc test.c -Wall # 生成可执行文件 a.out ./a.out # 执行程序
程序执行后,终端将会显示以下信息:
1 2 3 4 5 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 点击查看详情
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 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 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 #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; } 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 点击查看详情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include <stdio.h> #include <unistd.h> #include "debug_printf.h" int main (int argc, char *argv[]) { 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 ; }
这个文件我们使用以下命令编译:
1 gcc system_called.c -o system_called -Wall
然后便会生成 system_called 可执行文件,这个文件我们会在测试程序中通过system函数执行这个可执行文件。
4.3 test.c 点击查看详情
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 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 #include <stdio.h> #include <sys/shm.h> #include <sys/ipc.h> #include <string.h> #include <stdlib.h> #include <sys/prctl.h> #include <unistd.h> #include <fcntl.h> #include <sys/stat.h> #include <semaphore.h> #include <pthread.h> #include <signal.h> #include "debug_printf.h" sem_t sem_sys; void *thread (void *arg) ; void deleteSemfile (int sig) ;void sigintHandle (int sig) ; 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!" ); } 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 ); }
这是我们真正的测试函数,我们使用以下命令编译:
1 gcc test.c -lpthread -Wall
这样我们便会生成一个 a.out 可执行文件,a.out可执行文件运行后,我们在终端按下 ctrl + \ 就可以产生一个 SIGQUIT 信号。
4.4 测试结果 我们的上边的几个文件都放在同一目录下,然后我们执行a.out 可执行文件,再按下 ctrl+\ ,会有以下内容输出:
1 2 3 4 5 6 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执行的时间的。