今天对昨天的fork函数进行了补充,并且学习了exec函数
一,fork函数补充
1.open在fork之前
子进程会继承父进程已打开的相关信息,父子进程会影响同一个offset值
2.open在fork之后
父子进程各自有各自打开的文件信息,不相互影响
3.子进程做的事情和父进程差不多
4.父进程创建子进程后,子进程做的事情与父进程完全不同。
二,exec 族函数
在C语言中,exec族函数是用来执行一个指定的程序,替换当前进程的映像(image)、数据、堆和栈等,但进程ID保持不变。这意味着,当exec函数成功执行后,当前进程的代码和数据将被新程序的代码和数据替换,但进程ID等属性保持不变。exec族函数包括多个变体,这些变体在参数处理和行为上有所不同,但主要目的都是相同的:执行一个新程序。
exec族函数包括但不限于:
execl、execle、execlp、execlpe(使用列表形式传递参数)
这些函数通过参数列表的形式传递给新程序参数,而不是通过字符串数组。其中,execlp和execlpe会根据环境变量PATH来搜索可执行文件,而execl和execle则需要提供可执行文件的完整路径。execle还允许指定新程序的环境变量。
execv、execve、execvp、execvpe(使用字符串数组传递参数)
这些函数通过字符串数组的形式传递参数给新程序。与execl系列类似,execvp和execvpe会搜索PATH环境变量来找到可执行文件,而execv和execve需要可执行文件的完整路径。execve允许直接指定新程序的环境变量。
在这些函数中,execve是最基本的版本,因为它允许直接指定程序路径、参数列表和环境变量。其他exec函数都是基于execve的封装,提供了不同的参数传递方式或自动搜索可执行文件的路径。
值得注意的是,exec族函数执行成功后不会返回,因为当前进程的映像已经被新程序替换。如果exec族函数返回了,那通常意味着发生了错误(例如,指定的文件不是可执行文件,或者没有足够的权限执行该文件),此时会返回-1,并设置errno以指示错误的原因。
由于exec族函数不会返回,因此通常在调用exec族函数之前,会使用fork函数创建一个新的进程,然后在子进程中调用exec族函数来执行新程序。这样,如果exec调用失败,父进程还可以检测到这个错误,并采取相应的措施(如打印错误信息并退出)。
1.execl函数
在C语言中,execl函数是exec族函数之一,用于执行一个指定的程序,替换当前进程的映像(包括代码、数据、堆和栈等),但进程ID保持不变。然而,需要澄清的是,标准的POSIX定义中并没有直接名为execl的函数,但存在类似的execl风格的函数,如execlp和execl的变体(尽管后者在标准中通常不是直接命名的),它们通过参数列表的形式而不是字符串数组来传递参数给新程序。
int execl(const char *path, const char *arg, ..., /* (char *) NULL */);
参数:
path:要执行的可执行文件的路径。
arg:传递给新程序的参数列表,以NULL结尾。注意,这里不是使用字符串数组,而是直接在函数调用中列出所有参数,最后以NULL作为结束标志。
返回值:
如果execl函数成功,它不会返回,因为当前进程的映像已经被新程序替换。
如果execl函数失败,则返回-1,并设置全局变量errno以指示错误的原因。
2.execlp函数
execlp函数是C语言中exec函数族的一员,用于执行一个指定的程序,替换当前进程的映像(包括代码、数据、堆和栈等),但进程ID保持不变。execlp函数的特点是从PATH环境变量所指的目录中查找符合参数file的文件名,找到后便执行该文件,并将后续参数作为该程序的命令行参数。
头文件:#include <unistd.h>
函数原型:int execlp(const char *file, const char *arg, ...);
参数说明
file:要加载的程序的名字(可执行文件名),execlp会在PATH环境变量指定的目录中查找该文件。
arg:传递给新程序的参数列表,第一个参数arg通常被设置为新程序的名称(尽管这不是必须的,但有助于调试和日志记录),后续参数为实际要传递给新程序的参数。参数列表必须以NULL指针作为结束标志。
使用注意事项
参数传递:execlp函数的参数是通过可变参数列表传递的,最后一个参数必须是NULL,用于指示参数列表的结束。
PATH环境变量:execlp会搜索PATH环境变量中指定的目录来查找可执行文件。如果file参数包含斜杠(/),则execlp会将其视为路径名,并直接在该位置查找文件,而不会搜索PATH环境变量。
进程替换:当execlp函数成功执行时,当前进程的映像(包括代码、数据、堆和栈等)会被新程序的映像替换,但进程ID保持不变。这意味着新程序将在当前进程的上下文中运行,但会拥有新程序的代码和数据。
错误处理:如果execlp函数执行失败(例如,找不到指定的文件或没有执行权限),它会返回-1,并将错误原因存储在全局变量errno中。调用者可以通过检查errno的值来确定失败的具体原因。
#include <unistd.h>
#include <stdio.h>
int main()
{
// 尝试执行ls命令,列出当前目录下的文件和目录
execlp("ls", "ls", "-l", NULL);
// 如果execlp执行成功,则不会执行到这一行
// 如果执行失败(例如,因为找不到ls命令),则会执行到这里
perror("execlp failed");
return 1;
}
在这个示例中,我们尝试使用execlp函数执行ls命令,并列出当前目录下的文件和目录。如果execlp执行成功,则当前进程的映像会被ls命令的映像替换,因此后续的代码(如perror调用)将不会执行。如果execlp执行失败(例如,因为PATH环境变量中没有包含ls命令的路径),则会执行perror调用,打印错误信息。
3.execel函数
执行结果
execel函数与evn结合实现父子函数执行不同功能
执行结果
4.execv函数
execv函数是C语言中exec函数族的一员,用于在当前进程中执行一个新的程序,并替换当前进程的映像(包括代码、数据、堆和栈等),但进程ID保持不变。execv函数的主要特点是它允许用户以字符串数组的形式指定新程序的命令行参数。以下是关于execv函数的详细介绍:
1. 函数原型
execv函数的原型定义在<unistd.h>头文件中,其原型如下:
int execv(const char *pathname, char *const argv[]);
pathname:指向要执行的新程序的路径名的指针。这个路径名必须是绝对路径,或者相对于调用execv的进程的当前工作目录的相对路径。
argv:指向一个字符串数组的指针,该数组包含要传递给新程序的参数。数组的第一个元素(argv[0])通常是新程序的名称,但从argv[1]开始是传递给新程序的参数。数组的最后一个元素必须是NULL,以标记参数列表的结束。
2. 函数行为
当execv函数被调用时,它会加载并执行由pathname指定的程序。
如果成功,execv不会返回给调用者,因为当前进程的映像已经被新程序的映像替换。
如果失败,execv会返回-1,并设置全局变量errno以指示错误原因。常见的错误原因包括文件不存在、不是可执行文件等。
3. 示例代码
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
const char* path = "/bin/ls"; // 要执行的新程序的路径
char* argv[] = {"ls", "-l", NULL}; // 传递给新程序的参数数组,以NULL结尾
// 执行新程序
if (execv(path, argv) == -1)
{
// 如果execv失败,则打印错误信息
perror("execv failed");
exit(EXIT_FAILURE);
}
// 如果execv成功,则不会执行到这里
// 因为execv会替换当前进程的映像
return 0; // 这行代码实际上永远不会执行
}
4. 注意事项
execv函数不会继承调用它的进程的环境变量,除非你显式地通过某种方式(如使用execve函数)传递它们。
在使用execv函数时,当前进程的映像会被新程序的映像替换,因此execv调用之后,除了错误处理外,通常不会有额外的代码执行。
pathname参数必须是绝对路径,或者相对于调用execv的进程的当前工作目录的相对路径。
argv数组必须以NULL结尾,且数组中的每个元素都应该是有效的字符串。
4.execvp函数
execvp函数是C语言中exec函数族的一员,用于在Linux环境下执行指定的文件,并将参数传递给该文件。这个函数的特点在于它会从环境变量PATH所指定的目录中查找符合参数file的文件名,找到后执行该文件,并将第二个参数argv(一个字符串数组)作为参数传递给该文件。以下是关于execvp函数的详细解析:
1. 函数原型
execvp函数的原型定义在<unistd.h>头文件中,其原型如下:
int execvp(const char *file, char *const argv[]);
file:指向要执行的文件的名称的指针。execvp函数会在环境变量PATH所指定的目录中查找该文件。
argv:指向一个字符串数组的指针,该数组包含要传递给执行文件的参数。数组的第一个元素(argv[0])通常是被执行文件的名称(尽管execvp不直接使用它,但可以作为参数传递给执行文件),从argv[1]开始是传递给执行文件的参数。数组的最后一个元素必须是NULL,以标记参数列表的结束。
2. 函数行为
当execvp函数被调用时,它会按照环境变量PATH中指定的目录顺序搜索名为file的文件。一旦找到该文件,就会加载并执行它,替换当前进程的映像(包括代码、数据、堆和栈等)。
如果成功,execvp函数不会返回给调用者,因为当前进程的映像已经被新程序的映像替换。
如果执行失败,execvp函数会返回-1,并将全局变量errno设置为特定的错误代码,以指示失败的原因。
3. 常见错误代码
EACCES:指定的文件或目录的权限不足,无法执行或访问。
ENOENT:指定的文件不存在。
ENOEXEC:无法执行指定的文件,可能是因为文件格式错误或不是有效的可执行文件。
ENOMEM:系统内存不足,无法执行新程序。
EFAULT:argv参数指向的字符串地址超出了可访问的空间范围。
ENAMETOOLONG:指定的文件名或路径太长。
4. 注意事项
execvp函数不会继承调用它的进程的环境变量,除非通过其他方式(如使用execve函数)显式地传递它们。
在使用execvp函数时,需要确保file参数指定的文件是可执行的,并且具有适当的权限。
由于execvp函数会替换当前进程的映像,因此在其调用之后,除了错误处理代码外,通常不会有额外的代码执行。
5. 示例代码
以下是一个使用execvp函数执行新程序的示例代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
char *argv[] = {"ls", "-l", NULL}; // 要传递给ls命令的参数
// 使用execvp执行ls命令
if (execvp("ls", argv) == -1)
{
// 如果execvp失败,则打印错误信息
perror("execvp failed");
exit(EXIT_FAILURE);
}
// 如果execvp成功,则不会执行到这里
// 因为execvp会替换当前进程的映像
return 0; // 这行代码实际上永远不会执行
}
在这个示例中,execvp函数会查找并执行名为ls的程序,将-l作为参数传递给它,以列出当前目录下的文件和目录的详细信息。如果execvp调用成功,程序将不会返回,而是直接执行ls命令。如果调用失败,则会打印错误信息并退出程序
5.execvpe函数
execvpe函数是C语言中exec函数族的一个成员,它提供了在Linux环境下执行指定文件的能力,并允许调用者指定自定义的环境变量列表给新程序。这个函数结合了execvp和execve函数的功能,既可以在环境变量PATH中搜索可执行文件,又可以指定新程序的环境变量。
1. 函数原型
execvpe函数的原型定义在<unistd.h>头文件中
int execvpe(const char *file, char *const argv[], char *const envp[]);
file:指向要执行的文件的名称的指针。execvpe函数会在环境变量PATH所指定的目录中查找该文件。
argv:指向一个字符串数组的指针,该数组包含要传递给执行文件的参数。数组的第一个元素(argv[0])通常是被执行文件的名称(尽管execvpe不直接使用它,但可以作为参数传递给执行文件),从argv[1]开始是传递给执行文件的参数。数组的最后一个元素必须是NULL,以标记参数列表的结束。
envp:指向一个字符串数组的指针,该数组包含要传递给新程序的环境变量。数组的最后一个元素也必须是NULL,以标记环境变量列表的结束
2. 函数行为
当execvpe函数被调用时,它会按照环境变量PATH中指定的目录顺序搜索名为file的文件。一旦找到该文件,就会加载并执行它,替换当前进程的映像(包括代码、数据、堆和栈等),并使用envp参数指定的环境变量列表作为新程序的环境变量。
如果成功,execvpe函数不会返回给调用者,因为当前进程的映像已经被新程序的映像替换。
如果执行失败,execvpe函数会返回-1,并将全局变量errno设置为特定的错误代码,以指示失败的原因。
3. 注意事项
execvpe函数是GNU的扩展,可能不是所有系统都支持。在编写跨平台代码时,需要注意这一点。
由于execvpe函数会替换当前进程的映像,因此在其调用之后,除了错误处理代码外,通常不会有额外的代码执行。
在使用execvpe函数时,需要确保file参数指定的文件是可执行的,并且具有适当的权限。
自定义的环境变量列表envp需要由调用者提供,并且必须以NULL结尾。如果不需要传递自定义环境变量,可以使用execvp或execv等其他exec函数。
4.示例代码
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>
// 假设这个函数实现了类似于 glibc getenv_path 的功能
// 它在 PATH 环境变量中搜索可执行文件
char *find_executable(const char *file) {
// 这里需要实现实际的搜索逻辑
// 例如,解析 PATH 环境变量,遍历目录等
// 注意:这里仅作为伪代码
return strdup("/path/to/found/executable"); // 假设找到了可执行文件
}
int execvpe(const char *file, char *const argv[], char *const envp[]) {
char *executable_path = find_executable(file);
if (!executable_path) {
errno = ENOENT; // 或其他适当的错误码
return -1;
}
// 使用 execve 执行找到的可执行文件
// 注意:execve 的第一个参数是文件路径,而不是文件名
// 因此我们使用 executable_path
execve(executable_path, argv, envp);
// 如果 execve 返回,那么一定是有错误发生了
// 但实际上,execve 只在发生错误时才会返回
// 这里仅为了代码的完整性而包含
perror("execvpe failed");
free(executable_path); // 清理我们分配的内存
return -1; // 实际中不应该到达这里
}
// 注意:上面的 find_executable 函数需要实现完整的 PATH 搜索逻辑
// 并且 execvpe 函数在调用 execve 后通常不会返回
三,进程的结束
结束方式
1.正常结束
a.从main函数中返回return
b.调用exit
c.调用_exit
exit ————库函数 ———— 刷新缓冲区
_exit ————系统调用 ———— 不会刷新缓冲区
调用函数清除缓冲区