![为什么不能在单独的进程中更改目录?](https://linux22.com/image/181891/%E4%B8%BA%E4%BB%80%E4%B9%88%E4%B8%8D%E8%83%BD%E5%9C%A8%E5%8D%95%E7%8B%AC%E7%9A%84%E8%BF%9B%E7%A8%8B%E4%B8%AD%E6%9B%B4%E6%94%B9%E7%9B%AE%E5%BD%95%EF%BC%9F.png)
为什么我们不能创建一个更改当前工作目录的进程。就像光盘命令呢?
答案1
想想你在问什么。您有一个 shell,并且想要编写一个外部程序来更改 shell 的 cwd。这还有道理吗?对我来说,听起来您不太了解 shell 只是一个接受字符串并生成一些子进程的程序(这些子进程自然继承了其父进程 shell 的 cwd)。
我怀疑您可以通过使用 ptrace 将命令注入到 shell 进程中来实现这一点。您可以编写自己的 shell,为“child wd”保留不同的设置(主要变化是在分叉子项之后,它需要记住 chdir())。您可以编写一个内核模块,让您强制更改任意进程的 cwd,尽管我预计这会破坏更多的东西而不是有用的。
如果这种事情有意义,您可以(具有适当的权限)执行“ln -fs /somewhere /proc/$pid/cwd”。当我以 root 身份尝试时,我从 strace 得到“ln: cwd/: 无法覆盖目录”,看起来它正在检查并注意到目标已经存在。在这种情况下 symlink() 似乎会失败,因此 symlink 命令可能必须首先删除现有链接,并且我认为内核不会让这种情况发生。
答案2
进程是在计算机上执行任何操作的主要单元。它们封装了虚拟内存及其内容、CPU 寄存器的状态和指令指针(指向代码运行位置的指针)等内容,以及操作系统级别的打开文件和工作目录等内容。这些是正在运行的进程需要被外部事件改变才能使程序逻辑正常工作。
如果程序运行时CPU寄存器或指令指针发生变化,就会发生非常意想不到的事情。类似地,从程序下更改打开的文件可能会导致 HTTP 服务器突然切换到提供错误的文件,或者在不知情的情况下读取错误的配置文件。如果使用相对路径,则更改进程的工作目录将导致它找到错误的文件。或者删除它们;想象一下如果运行rm -rf somedir
突然移动到另一个目录会发生什么:它会开始从错误的位置删除文件。
因此,在没有流程本身合作的情况下允许这样做并不是一个好主意。您当然可以创建一个程序来更改其自己的工作目录,但让另一个程序为 shell 执行此操作并不是一个好主意。这也不是必需的,因为 shell 可以使用内置命令自行完成,如果需要自定义它,可以使用 shell 函数来完成。可能使用外部程序来选择要更改的目录,同时仍然使用 shell 自己的程序cd
来执行实际更改。例如,想想类似的事情cdfoo() { cd -- "$(get_foo_dir "$1")"; }
。
答案3
您无法更改子进程中的当前工作目录,因为...你实际上可以做得很好。
这clone(2)
用于创建进程(并实现诸如 之类的遗留接口fork(2)
)的 Linux 系统调用有一个CLONE_FS
标志,可用于让子进程共享根目录,当前工作目录和 umask 及其父级:
#define _GNU_SOURCE
#include <sched.h>
#include <unistd.h>
#include <syscall.h>
#include <sys/wait.h>
#include <err.h>
int main(void){
pid_t cpid = syscall(SYS_clone, CLONE_FS, (void*)0);
if(cpid == -1) err(1, "SYS_clone");
if(cpid == 0){
/* the child, change the cwd and exit */
execl("/bin/sh", "sh", "-c", "cd /etc", (void*)0);
err(1, "execl /bin/sh");
}else{
/* the parent, wait for the child, then print the cwd */
waitpid(-1, 0, __WALL);
execl("/bin/sh", "sh", "-c", "pwd", (void*)0);
err(1, "execl /bin/sh");
}
}
这在 shell 中不起作用,因为 bourne shell(bash 是其实现)在分叉单独的进程和运行外部命令时不使用该标志;但是 shell 是一个过时的作品,有很多荒谬的限制(它也没有词法范围、直接访问系统接口、健全和常规的语法,以及许多其他东西)。
你不应该将 45 年前的玩具语言的无能推断到整个系统,因为整个系统已经经历了很大的演变。
cd
“不可能”是一个外部命令(以及当前目录的必要性!)这一事实远非自然而明显的事情,更多地与 Unix 的逐步演变和 PDP-7 系统的限制有关它首先运行在其上。
尽管与当前的文件系统非常相似,但 PDP-7 文件系统在某一方面有显着的不同:没有路径名,系统的每个文件名参数都是一个简单的名称(没有“/”)相对于当前目录。
...
尽管多进程的想法确实很容易被接受,但还是产生了一些意想不到的后果。在新系统出现并明显发挥作用后不久,其中最令人难忘的事情就变得显而易见了。正当我们欢呼雀跃时,我们发现 chdir(更改当前目录)命令已停止工作。我们阅读了大量代码,并焦虑地反思添加 fork 是如何破坏 chdir 调用的。最终真相大白:在旧系统中 chdir 是一个普通命令;而在旧系统中,chdir 是一个普通命令。它调整了附加到终端的(唯一)进程的当前目录。在新系统下,chdir 命令正确地更改了为执行它而创建的进程的当前目录,但该进程立即终止,并且对其父 shell 没有任何影响!
来自评论:
任何理智的 shell(执行任意外部提供的命令的程序)都无法使用
CLONE_FS
,因为这样它的所有子进程都会争夺当前目录的控制权
这是一个谬论。这就像“我们不能允许水管工,因为不会再有公交车司机”。 shell 显然只能在以下情况下做到这一点:一些案例。子级已经在标准文件描述符(使子级中的 stdin 非阻塞也使其在父级中非阻塞)、当前名称空间(在子级中挂载目录也会将其挂载在父级中)上进行“斗争” ,以及可以通过CLONE_*
其他系统接口共享或分割的所有各个部分。 “工作目录”只是执行环境的一部分,而是一种兼容性,而不是必需的(在每个现代系统上,都可以打开另一个目录的相对路径 - 查看所有openat(2)
、linkat(2)
等功能)。
这不是子进程不应该能够执行的某些自然法则改变其父级的工作目录,但它应该完全能够移动或者消除它(因为它已经是)。 50 年前的默认设置并不是什么黄金标准——例如,我宁愿有一个 shell,其中默认设置是以可以更改 shell 工作目录的方式运行awk
or过滤命令,而不是以某种方式运行jq
它可以擦除我的文件(现在就是这种情况)。