系统编程1.0-exec函数和exit()的使用
exec函数
#include <unistd.h>
#include <stdio.h>
int main()
{
pid_t x = fork();
if(x > 0)
{
sleep(1);
printf("aaa!\n");
}
if(x == 0)
{
printf("hello!\n");
//以下几个是等价
//execl("/bin/ls","ls","-l",NULL);
//execlp("ls","ls","-l",NULL);
//execle("/bin/ls", "ls","-l",NULL,NULL);
char *arg[3] = {"ls","-l",NULL};
//execv("/bin/ls",arg);
execvp("ls",arg);
// execvpe("ls",arg,NULL);
printf("world!\n");
}
return 0;
}
详细解释
char *arg[3]
定义了一个大小为 3 的字符指针数组。每个元素是一个char *
类型的指针,指向一个字符串(即一个char
数组)。{"ls", "-l", NULL}
是初始化这个数组的内容:"ls"
:这是第一个元素,表示执行的命令或程序名。在这里它表示ls
命令,用来列出当前目录的内容。"-l"
:这是第二个元素,表示传递给ls
命令的一个参数,它告诉ls
显示文件的详细信息(即长格式的列出目录内容)。NULL
:这是数组的最后一个元素,标志着参数列表的结束。exec
系列函数要求参数数组必须以NULL
结尾来表示参数的结束。
char *arg[3] = {"ls", "-l", NULL};
定义了一个包含两个命令行参数和一个结束符NULL
的数组,准备作为参数传递给execv()
或其他exec
系列函数。这个数组表示执行ls -l
命令,并列出当前目录的详细内容。
execvpe()
的作用
execvpe("ls", arg, NULL)
会执行/bin/ls
(或者在PATH
中查找ls
),并传递参数-l
。这会列出当前目录的详细文件信息。- 一旦
execvpe()
被调用,当前进程(子进程)会被/bin/ls
程序替换,printf("world!\n");
语句将不会执行,因为execvpe()
会完全替换当前进程的映像。
程序执行流程:
- 父进程:调用
fork()
,得到子进程PID,随后调用sleep(1)
暂停1秒,然后打印aaa!
。 - 子进程:调用
fork()
后,执行execvpe("ls", arg, NULL)
,替换自己成为/bin/ls
,并打印当前目录的文件列表。printf("world!\n")
永远不会执行,因为进程已经被替换。
各种 exec
系列函数:
-
execl()
:传递每个参数给新程序。示例:execl("/bin/ls", "ls", "-l", NULL);
- 第一个参数是程序路径,后面的参数是要传递给新程序的命令行参数。最后一个参数必须是
NULL
。
- 第一个参数是程序路径,后面的参数是要传递给新程序的命令行参数。最后一个参数必须是
-
execlp()
:与execl()
类似,但会在PATH
环境变量中查找程序。示例:execlp("ls", "ls", "-l", NULL);
- 不需要指定程序的完整路径,只要在
PATH
中能够找到即可。
- 不需要指定程序的完整路径,只要在
-
execle()
:与execl()
类似,但可以传递环境变量。示例:execle("/bin/ls", "ls", "-l", NULL, NULL);
- 额外的参数传递给新的程序作为环境变量。
-
execv()
:类似execl()
,但通过数组传递参数。示例:execv("/bin/ls", arg);
arg
是一个数组,包含程序的命令行参数。最后一个元素必须是NULL
。
-
execvp()
:与execv()
类似,但会在PATH
环境变量中查找程序。示例:execvp("ls", arg);
- 不需要指定程序的完整路径,直接提供程序名,系统会在
PATH
中查找。
- 不需要指定程序的完整路径,直接提供程序名,系统会在
-
execvpe()
:与execvp()
类似,但额外提供了环境变量的传递。示例:execvpe("ls", arg, NULL);
- 与
execvp()
的区别是,可以传递自定义的环境变量。
- 与
exit函数
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main()
{
printf("hello");
//exit(0);
_exit(0);
}
代码执行流程:
printf("hello");
:- 程序首先会打印
"hello"
,但是它并没有立即输出到屏幕上。printf
会将数据写入缓冲区,直到程序结束或缓冲区被刷新,才会显示出来。
- 程序首先会打印
_exit(0);
或exit(0);
:- 程序接着会调用
_exit(0)
,这是一个系统调用,通常用来退出当前进程。它的作用是终止程序并返回状态码0
(通常表示正常退出)。
- 程序接着会调用
为什么使用 _exit()
?
_exit()
更适合在子进程中使用,尤其是在调用fork()
后,父子进程的执行情况不同,可能需要在子进程中快速退出,而不需要刷新缓冲区或调用清理函数。- 例如,
fork()
创建子进程时,子进程可能调用_exit()
以避免父进程的清理函数和缓冲区刷新干扰。
在这段代码中的行为:
- 如果你使用
exit(0)
,程序会打印"hello"
,然后刷新缓冲区,确保"hello"
被显示在屏幕上,然后正常退出。 - 如果你使用
_exit(0)
,程序会打印"hello"
,但由于_exit()
不会刷新缓冲区,所以"hello"
可能不会显示出来,然后直接退出程序。
为什么使用退出函数?
程序在执行完成后会自动退出,但是我们使用
exit()
或_exit()
这样的函数来控制程序退出的方式和时机。虽然程序会在main()
函数执行完后自动退出,但使用退出函数可以让你精确地控制退出时的行为,特别是如何处理缓冲区、清理工作等。1. 程序自动退出:
- 当程序执行完
main()
函数,或者执行到return
或没有进一步的代码时,程序会自动退出。- 退出时,操作系统会回收进程占用的资源,并根据程序返回的状态码给出退出信息。
2. 使用退出函数(
exit()
或_exit()
)的区别:虽然在某些情况下,程序会在
main()
函数结束时自动退出,但通过显式调用退出函数,我们可以控制退出时的一些细节。这包括:
- 清理工作(如资源释放、内存清理)。
- 输出缓冲区的处理(如将缓冲区中的数据写入到终端或文件)。
- 退出状态码的设置(传递给操作系统,父进程或其他程序查看的退出状态)。
3.
exit()
和_exit()
的区别:
exit()
:
exit()
是一个标准库函数,通常用来在程序结束时进行清理工作。- 主要行为:
- 刷新缓冲区:它会确保所有通过
printf()
等输出缓冲区中的数据都被写入(即使你在程序中调用了多次printf()
,它们会在退出时一次性显示)。- 调用清理函数:
exit()
会执行所有通过atexit()
注册的清理函数,这对于释放资源(如关闭文件、释放内存等)很有用。- 返回退出状态码:
exit()
会返回一个状态码(通常是0
表示成功,其他数字表示出错)给操作系统,表示程序是否正常结束。
_exit()
:
_exit()
是一个系统调用,通常在程序异常或需要立即退出时使用。- 主要行为:
- 不刷新缓冲区:调用
_exit()
后,程序不会刷新缓冲区,因此如果有任何数据尚未写入屏幕或文件,它们将丢失。这是它与exit()
的主要区别。- 不调用清理函数:
_exit()
不会调用atexit()
注册的清理函数,因此程序不会执行任何资源释放操作。- 立即退出:
_exit()
会立即终止进程,不做任何额外操作,快速退出程序。4. 程序自动退出 vs 显式调用退出函数的对比:
自动退出:
- 当
main()
函数执行完后,程序会自动退出。如果程序没有使用exit()
或_exit()
,操作系统会回收进程资源,打印退出状态码(默认是0,表示程序正常结束)。- 自动退出时的默认行为:
- 程序会先执行标准的清理过程:刷新输出缓冲区,关闭文件描述符等。
- 最后,返回状态码给操作系统,表示程序的退出状态。
显式调用退出函数:
- 如果你调用了
exit()
或_exit()
,你就能控制程序退出的方式,确保在程序退出前完成特定的工作。
exit()
:刷新缓冲区、调用清理函数、并返回状态码。_exit()
:立即退出,不刷新缓冲区,也不调用清理函数。5. 何时使用
exit()
或_exit()
?
exit()
:
- 当你希望在程序退出前做一些清理工作,如释放资源、关闭文件、清空缓冲区时,使用
exit()
是合适的。- 例如,在编写多线程程序时,你可能需要确保在退出时清理资源并关闭所有打开的文件。
_exit()
:
- 当你希望在程序终止时立刻退出,且不需要做清理工作时,可以使用
_exit()
。例如,系统调用、进程失败后的处理,或者子进程通过fork()
后退出时,通常会用_exit()
来避免父进程的缓冲区和清理函数影响。总结:
- 程序自动退出:如果没有调用
exit()
或_exit()
,程序会在main()
执行结束后自动退出,操作系统会回收资源并返回退出状态。- 显式调用退出函数:通过调用
exit()
或_exit()
,你可以精确控制程序退出的时机和行为,特别是如何处理缓冲区、清理资源等。