shell运行可执行文件=fork+exec

通过一个可执行文件被执行的过程理解进程的深刻性

转自:https://www.cnblogs.com/yilinglingyi/p/4098057.html

  不知大家在平时想过没有,我们放在磁盘(之前我一直认为Windows的C盘是主存,DEF盘是磁盘,哈哈,应该没有像我这样无知的人吧)上的一个可执行文件(或者应用程序)是如何得到执行的,而且为什么我们在写程序的时候怎么感觉程序中的一些变量的地址好像在各个不同的程序中都差不多,同时这个地址到底真正对应的是什么?是我们可执行文件对应所在位置的磁盘地址吗?下面我就以Linux为平台(Windows也一样,只是将命令方式变为图形方式了)为大家详细讲解一下一个可执行文件是如何得到执行的。

  在Linux中当我们打开shell时,我们相当于已经新建了一个进程,这个进程运行的是shell这个应用程序。当在shell中输入一个可执行目标文件的名字时,shell会用fork()函数创建一个新的进程,在这个新进程中调用execve()函数来加载和执行这个可执行文件。

  我准备详细来说明一下这个execve()函数是如何来工作的,比如它是如何将磁盘上的目标文件拷贝到主存中来让CPU运行的?程序中我们所看到的地址到底是什么?带着这些问题我们来一步一步分析。

  首先因为execve()函数是在shell这个进程的子进程中运行的,而子进程必定会拷贝(其实也不是拷贝,要不然这个进程设计的也太臃肿了,是一种叫写时拷贝的机制)很多父进程已存在的内容,所以必须删除掉。

然后它开始映射(看到映射有没有想到数学中叫函数映射的东西,本质上都是一样的)我们可执行文件中的内容,谈到映射那必然是X------>Y,现在Y是我们的可执行文件,那X呢?先给大家补充一点进程中的知识,等补充完了,才能说X。每个进程中都有一个叫页表的东西,页表有很多项,每一项叫页表项(为了简化问题的复杂性我们就假设Linux是一级页表吧),同时在操作系统中一般一个页或者物理块的大小为4KB(对应为12位的页内地址),所以在一个32的操作系统中只需要保存2^20个页表项就可以表示地址从0x00000000到0xffffffff的范围,其中这个地址的后12位为页内地址,而我们在程序中所见到的地址就是这个地址,根本不是什么我们程序对应的物理地址。记住,这个地址并不是真正对应的磁盘或者内存的地址,而是虚拟的,叫虚拟地址。如果现在还不太明白等我全部讲完就会懂的。

讲到这里大家先稍微理解理解,免得看的一头雾水。那我开始,刚刚我们说到进程中的页表项,每一个页表项从开始到结束对应的编号为0x00000-0xfffff(一共2^20个,大家可以画一画),这个页表项主要有两个部分,第一个部分用来指向磁盘的物理块或者内存上的块,第二个部分表明所指向的块是在磁盘上还是内存上或者这部分就根本没用。

那么我们现在可以说X是什么了,就是虚拟地址!说完了X,Y,那还有映射规则呢,对于我们程序中的文本块,数据块,栈,堆等在Linux中分别对应不同的虚拟地址,而且是固定的,对所有程序都一样。这也就可以解释为什么不同的程序不同的变量有时候地址却差不多,因为他们的虚拟地址都是从0x00000000---0xffffffff,因此当他们的变量都保存在栈中时,对应的虚拟地址也很接近。

映射完之后,execve()调用启动代码,启动代码将调用main()函数,大家一定会想现在可执行目标不是还在磁盘上吗?它是怎么拷贝到内存上,然后被CPU执行的呢?确实如此,因此当启动代码将main()函数的虚拟地址传递给CPU时,CPU通过解析虚拟地址发现内存中没有main()相对应的页或者物理块,然后CPU通过进程中的页表项找到我们可执行文件所在的磁盘位置,将磁盘上的块拷贝到内存中,这样CPU就可以顺利的执行我们的程序了。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在Node.js中,可以使用child_process模块来调用可执行文件。下面是四种调用可执行文件的方法: 1. spawn()方法:这个方法创建一个新的进程,然后将可执行文件作为参数传递给它。spawn()方法不会等待子进程完成,而是在子进程开始执行后立即返回一个ChildProcess对象。可以使用ChildProcess对象的stdout和stderr属性来获取子进程的输出和错误输出。 ```javascript const { spawn } = require('child_process'); const child = spawn('./myExecutable', ['arg1', 'arg2']); ``` 2. exec()方法:这个方法创建一个新的shell进程,并在shell进程中运行执行文件exec()方法会等待子进程完成,然后返回一个包含子进程输出和错误输出的缓冲区。可以使用回调函数来获取输出和错误输出。 ```javascript const { exec } = require('child_process'); exec('./myExecutable arg1 arg2', (error, stdout, stderr) => { if (error) { console.log(`错误: ${error.message}`); return; } if (stderr) { console.log(`错误输出: ${stderr}`); return; } console.log(`输出: ${stdout}`); }); ``` 3. execFile()方法:这个方法与exec()方法类似,但是不会在shell进程中运行执行文件。相反,它直接执行执行文件,并等待子进程完成。execFile()方法也会返回一个包含子进程输出和错误输出的缓冲区。可以使用回调函数来获取输出和错误输出。 ```javascript const { execFile } = require('child_process'); execFile('./myExecutable', ['arg1', 'arg2'], (error, stdout, stderr) => { if (error) { console.log(`错误: ${error.message}`); return; } if (stderr) { console.log(`错误输出: ${stderr}`); return; } console.log(`输出: ${stdout}`); }); ``` 4. fork()方法:这个方法创建一个新的Node.js进程,并在新进程中运行一个指定的模块。fork()方法返回一个ChildProcess对象,该对象具有与父进程相同的属性和方法,例如stdout和stderr属性。 ```javascript const { fork } = require('child_process'); const child = fork('./myScript.js', ['arg1', 'arg2']); ``` 这四种方法都可以用于调用可执行文件,但它们的用途各不相同。选择哪种方法取决于您的需求和情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值