exec(execl,execlp等)失败的原因是什么?如果您调用exec并返回,除了恐慌和调用exit之外,还有其他最佳实践吗?
答案 0 :(得分:34)
处理exec
失败的问题在于,通常在子进程中执行exec
,并且您希望在父进程中执行错误处理。但是你不能只exit(errno)
,因为(1)你不知道错误代码是否适合退出代码,(2),你无法区分失败到exec
和失败退出来自新计划的代码exec
。
我所知道的最佳解决方案是使用管道来传达exec
:
exec
,则父级读取eof(零长度读取),因为close-on-exec使成功exec
关闭管道的写入端。或者,如果exec
失败,则父级会读取错误代码并可以相应地继续。无论哪种方式,父母都会阻止,直到孩子打电话给exec
。答案 1 :(得分:19)
execl()
,execle()
,execlp()
,execvp()
和execvP()
函数可能会失败,并为为库函数指定的任何错误设置errnoexecve(2)
和malloc(3)
。
execv()
函数可能会失败,并为库函数execve(2)
指定的任何错误设置errno。
<强>错误强>
如果出现以下情况,
Execve()
将失败并返回调用进程:
[E2BIG]
- 新进程参数列表中的字节数大于系统强加的限制。此限制由sysctl(3)
MIB变量KERN_ARGMAX
指定。[EACCES]
- 对路径前缀的组件拒绝搜索权限。[EACCES]
- 新的处理文件不是普通文件。[EACCES]
- 新的进程文件模式拒绝执行权限。[EACCES]
- 新的流程文件位于已禁用执行的文件系统上(MNT_NOEXEC
中的<sys/mount.h>
)。[EFAULT]
- 新处理文件的长度不会超过其标题中的尺寸值。[EFAULT]
- Path,argv或envp指向非法地址。[EIO]
- 从文件系统读取时发生I / O错误。[ELOOP]
- 在翻译路径名时遇到了太多符号链接。这被视为表示循环符号链接。[ENAMETOOLONG]
- 路径名的组件超出{NAME_MAX}
个字符,或整个路径名超过{PATH_MAX}
个字符。[ENOENT]
- 新的流程文件不存在。[ENOEXEC]
- 新的处理文件具有相应的访问权限,但格式无法识别(例如,标题中包含无效的幻数)。[ENOMEM]
- 新进程需要的虚拟内存超过强加的最大值(getrlimit(2)
)所允许的数量。[ENOTDIR]
- 路径前缀的组件不是目录。[ETXTBSY]
- 新的流程文件是一个纯过程(共享文本)文件,目前可供某些流程写入或读取。
malloc()
不那么复杂,仅使用ENOMEM
。来自malloc(3) man page
:
如果成功,
calloc()
,malloc()
,realloc()
,reallocf()
和valloc()
函数会返回指向已分配内存的指针。如果出现错误,则会返回NULL
指针并将errno
设置为ENOMEM
。
答案 2 :(得分:8)
在exec()
调用返回后执行的操作取决于上下文 - 程序应该执行的操作,错误是什么以及您可以采取哪些措施来解决此问题。
一个麻烦来源可能是您指定了一个简单的程序名而不是路径名;也许您可以使用execvp()
重试,或将命令转换为sh -c 'what you originally specified'
的调用。这些是否合理取决于应用。如果涉及重大安全问题,您可能不会再试一次。
如果您指定了路径名并且存在问题(ENOTDIR,ENOENT,EPERM),那么您可能没有任何明智的回退,但您可以有意义地报告错误。
在过去(10多年前),有些系统不支持'#!' shebang表示法,如果您不确定是否正在执行可执行文件或shell脚本,则将其作为可执行文件进行尝试,然后将其作为shell脚本重试。如果您运行Perl脚本,那可能会或可能不会起作用,但在那些日子里,您编写了Perl脚本以检测它们是由shell运行并使用Perl重新执行。幸运的是,那些日子已经结束了。
在可能的范围内,确保进程报告问题以便跟踪问题非常重要 - 将其消息写入日志文件或仅写入stderr(甚至可能syslog()
),以便那些必须解决出错的人有更多的信息来帮助他们,除了倒霉的最终用户的报告“我试过X但它没有用”。至关重要的是,如果没有任何作用,则退出状态不为0,因为这表示成功。即使这可能会被忽略 - 但你做了你能做的事。
答案 3 :(得分:3)
不仅仅是恐慌,你可以根据errno的价值做出决定。
答案 4 :(得分:0)
Exec应该永远成功。 (shell除外,即如果用户输入了伪造的命令)
如果exec失败,则表示:
对于任何严重错误,通常的方法是在stderr上写入错误消息,然后使用失败代码退出。几乎所有的标准工具都可以做到这一点。对于exec:
execl("bork", "bork", NULL);
perror("failed: exec");
exit(127);
shell也会这样做(或多或少)。
通常,如果子进程失败,父进程也会失败并应该退出。无论孩子是在执行失败还是在运行程序时都没有关系。如果exec失败,exec失败的原因并不重要。如果子进程因任何原因失败,则调用进程出现问题并需要停止。
不要浪费大量时间来预测所有可能的错误情况。不要编写试图以最佳方式处理每个错误代码的代码。你只会膨胀代码,并引入许多新的bug。如果您的程序被破坏,或者它被滥用,它应该会失败。如果你强迫它继续下去,那就会出现更糟的麻烦。
例如,如果系统内存不足并且交换掉,我们不想一遍又一遍地试图运行一个进程;这只会使情况变得更糟。如果我们收到文件系统错误,我们不希望继续在该文件系统上运行;它可能会使腐败变得更糟。如果程序安装错误,或有错误或内存损坏,我们希望尽快停止,然后破坏的程序会造成一些真正的损害(例如向客户端发送损坏的报告,破坏数据库,。 ..)。
一种可能的替代方法:失败的进程可能会寻求帮助,暂停自己(SIGSTOP),然后在被告知继续时重试该操作。当系统内存不足或磁盘已满时,或者即使程序出现故障,这也可能有所帮助。很少有操作是如此昂贵和重要,以至于这是值得的。
如果您正在制作交互式GUI程序,请尝试将其作为可重复使用的命令行工具(如果出现问题则退出)的瘦包装器。程序中的每个函数都应该可以通过GUI,命令行和函数调用访问。写你的功能。编写一些工具来为任何函数创建commmand-line和GUI包装器。也使用子流程。
如果您正在制造一个真正关键的系统,例如核电站的控制器或预测海啸的程序,那么您在阅读我的愚蠢建议是什么?关键系统不应完全依赖于计算机或软件。需要有一个“手动覆盖”,有人来驱动它。特别是,不要试图在MS Windows上建立一个关键系统,就像在水下建造沙堡一样。