exec(execl,execlp等)可能会失败的原因是什么? 如果你打电话给exec,它会返回,除了恐慌和调用退出之外,还有什么最佳实践吗?
从exec(3)
手册页 :
execl()
,execle()
,execlp()
,execvp()
和execvP()
函数可能会失败,并为库函数execve(2)
和malloc(3)
指定的任何错误设置errno。
execv()
函数可能会失败,并为库函数execve(2)
指定的任何错误设置errno。
然后从execve(2)
手册页 :
错误
如果出现以下情况,
Execve()
将失败并返回到调用进程:
[E2BIG]
– 新进程参数列表中的字节数大于系统限制。 此限制由sysctl(3)
MIB变量KERN_ARGMAX
。[EACCES]
– 对于路径前缀的组件,搜索权限被拒绝。[EACCES]
– 新的进程文件不是一个普通的文件。[EACCES]
– 新的进程文件模式拒绝执行权限。[EACCES]
– 新的进程文件位于禁用执行的文件系统上(MNT_NOEXEC
位于<sys/mount.h>
)。[EFAULT]
– 新的进程文件没有其标题中的大小值所指示的那么长。[EFAULT]
– 路径,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
。
处理exec
失败的问题是,通常exec
是在子进程中执行的,而你想在父进程中执行错误处理。 但是,你不能只是exit(errno)
因为(1)你不知道错误代码是否适合退出代码,(2),你不能区分从新程序到失败exec
和失败退出代码你exec
。
我知道的最好的解决方案是使用管道来传达exec
的成功或失败:
exec
,则父母读取eof(零长度读取),因为执行close-on-exec使exec
成功关闭了管道的写入结束。 或者,如果exec
失败,父节点读取错误代码并可以相应地继续。 无论哪种方式,父母阻止,直到孩子调用exec
。 exec()
调用返回后的操作取决于上下文 – 程序应该执行什么操作,错误是什么,以及可以怎样解决这个问题。
麻烦的一个来源可能是你指定了一个简单的程序名而不是路径名。 也许你可以用execvp()
重试,或者把命令转换成sh -c 'what you originally specified'
的调用。 这些是否合理取决于应用。 如果涉及重大的安全问题,可能你不要再试。
如果你指定了一个路径名,并且有一个问题(ENOTDIR,ENOENT,EPERM),那么你可能没有任何明智的回退,但你可以有意义地报告错误。
在过去的十多年前,有些系统不支持“#!” shebang符号,如果您不确定是否执行可执行文件或shell脚本,则将其作为可执行文件尝试,然后将其作为shell脚本重试。 如果你正在运行一个Perl脚本,这可能会也可能不会工作,但在那些日子里,你编写了你的Perl脚本来检测它们是否被shell运行,并用Perl重新执行自己。 幸运的是,那些日子已经过去了。
在可能的范围内,确保进程报告问题是很重要的,以便能够跟踪 – 将其消息写入日志文件或写入stderr(甚至可能是syslog()
),以便那些必须工作的人出了什么问题有更多的信息来帮助他们,而不是倒霉的最终用户的报告“我试过X,它不工作”。 至关重要的是,如果什么都不起作用,那么退出状态不是0就表示成功。 即使这可能被忽略 – 但你做了你能做的。
无论是恐慌,你可以根据errno的价值做出决定。
执行应该总是成功。 (除了shell,即如果用户输入了伪命令)
如果exec失败,则表示:
对于任何严重的错误,正常的做法是在stderr上写入错误消息,然后用失败代码退出。 几乎所有的标准工具都是这样做的。 对于exec:
execl("bork", "bork", NULL); perror("failed: exec"); exit(127);
shell也这样做(或多或少)。
通常如果一个子进程失败,父进程也失败了,应该退出。 孩子在执行中失败还是在运行程序时都没有关系。 如果exec失败,为什么exec失败并不重要。 如果因为任何原因儿童程序失败,呼叫过程就麻烦了,需要停止。
不要浪费大量时间来预测所有可能的错误状况。 不要编写试图以最好的方式处理每个错误代码的代码。 你只会膨胀代码,并引入许多新的错误。 如果你的程序被破坏,或者被滥用,它应该只是失败。 如果迫使它继续下去,那么更糟糕的麻烦就会出现。
例如,如果系统内存不足和交换颠倒,我们不想一遍又一遍地尝试运行一个进程; 这只会让情况变得更糟。 如果我们遇到文件系统错误,我们不想继续在该文件系统上运行; 这可能会使腐败变得更糟。 如果程序安装错误,或者有错误,或者内存损坏,我们希望尽快停止,然后破坏的程序会造成一些真正的破坏(比如向客户端发送损坏的报告,摧毁数据库等)。 ..)。
一种可能的选择:一个失败的进程可能需要帮助,暂停(SIGSTOP),然后重试该操作,如果被告知继续。 当系统内存不足,或者磁盘已满,或者即使程序出现故障时,这也可能有所帮助。 很少的操作如此昂贵和重要,这是值得的。
如果您正在制作交互式GUI程序,请尝试将其作为可重复使用的命令行工具(如果出现错误则退出)的薄包装。 程序中的每个函数都应该可以通过GUI,命令行和函数调用来访问。 写你的功能。 编写几个工具来为任何函数制作命令行和GUI包装器。 也使用子流程。
如果你正在制造一个真正的关键系统,比如一个核电站的控制器,或者预测海啸的计划,那么你在阅读我的愚蠢建议是什么? 关键系统不应完全取决于计算机或软件。 需要有一个“手动控制”,有人来驱动它。 特别是,不要试图在MS Windows上构建一个关键系统,就像在水下建造沙堡一样。