Linux之shell启动⼀个程序的原理(外部命令)
前⾔
简单聊聊shell,shell即表⽰⼀种语⾔、⼜可以表⽰⼀个解释器程序,⼜可以表⽰某种具体的程序,⽽bash是shell程序中的⼀种,它是Linux默认使⽤的shell程序(貌似限制默认使⽤dash……)
1、shell属于Linux内核吗?
shell不属于Linux内核,它只是⼀个⼯具应⽤,在⽤户空间运⾏的程序
2、shell的功能是什么呢?
解释⽤户输⼊的命令,然后执⾏命令,⽤户使⽤⽂字界⾯,则必须使⽤shell与Linux内核进⾏交互。Linux系统启动后,内核会为每个终端⽤户⾃动建⽴⼀个进程去执⾏shell解释程序(bash程序)
shell启动程序的原理
当我们按下回车换⾏键后,产⽣的换⾏字符会要求bash开始解释在命令⾏中的命令
⾸先bash进程读取到命令⾏的命令后,分析命令是内部命令还是外部命令,如果是外部命令,则以【命
令名】作为⽂件名(可执⾏⽂件),另将其它的参数改造成系统调⽤execve( )内部要求的形式(exec()函数会替换程序到⼦进程⾥执⾏)
终端进程(shell进程、bash进程)会执⾏系统调⽤fork( ),此时会建⽴⼀个新的⼦进程,此时新建⽴的⼦进程,它的⽗进程就是bash 进程本⾝(这⾥的系统调⽤,其实是C标准库的fork()函数,再由C标准库的fork()函数发起系统调⽤,这⾥为了简单起见,未过多描述)
接着bash进程调⽤系统调⽤wait(),此时的⽗进程会⼀直等待⼦进程完成程序执⾏,当⼦进程完成⼯作后,会向⽗进程(shell进程)报告,此时终端进程会醒来;如果执⾏的是后台命令(命令⾥加了nohup与&),此时的bash进程不会等待⼦进程的程序执⾏完成,它会继续向下执⾏程序,执⾏结束后直接返回,此时的控制台会继续展⽰提⽰符,⽤户则可以继续输⼊下⼀条命令;⽽⼦进程在仍在运⾏中
当⼦进程运⾏时调⽤execve()系统调⽤时,⼦进程会根据⽂件名(可执⾏⽂件),去指定的⽬录中(PATH环境变量中定义的⽬录,或者当前的⼯作⽬录)中查可执⾏⽂件(⼆进制⽂件、程序),将程序调⼊内存中,在⼦进程中执⾏程序
bash运⾏程序流程图(感谢作者,有⽔印)
图中的2条执⾏流:第⼀条是主执⾏流(bash进程),第⼆条是⼦执⾏流(⼦进程程序)
1、Linux系统的shell作为操作系统的外壳(翻译很烂),为⽤户提供使⽤操作系统【内核】功能的接⼝(CLI替代GUI),注意:shell是命令语⾔、命令解释程序及程序
2、shell是⽤户和Linux内核之间的接⼝程序(没有GUI界⾯的程序,现代计算机,⽐如Android上,图形⽤户界⾯也是⼀个进程,⽐如Launcher),当shell或其他程序
3、shell也是⼀个命令语⾔解释器程序,它拥有⾃⼰内建(内置)的命令集,shell程序也能被系统中其他应⽤程序所调⽤。(⽐如我使⽤Python,或者我使⽤Java、那
4、⼀些命令,⽐如改变⼯作⽬录的命令cd,是包含在shell内部的命令(称为内置命令集)拷贝命令cp和移动命令rm,是存在于⽂件系统中某个⽬录下的单独的程序,
5、shell⾸先检查是否为内部命令(内部命令不再起单独的⼦进程),不是内部命令会再检查是否是⼀个应⽤程序(开源+商业),接着bash在搜索路径(环境变量PA
6、输⼊的命令即不是⼀个内部命令、并且在PATH路径⾥没有到、当前⼯作路径下也没有这个可执⾏⽂件,那么将会显⽰⼀条错误的信息(bash、或者sh会在当前⼯
7、如果能够成功到命令,该内部命令或应⽤程序(外部命令)将被分解为系统调⽤并传给Linux内核
8、shell的另⼀个重要特性是它⾃⾝也是⼀个解释型的程序设计语⾔(也是⼀个语⾔的名称),shell程序设计语⾔⽀持绝⼤多数在⾼级语⾔中能见到的程序元素,如函
9、普通⽤户成功登录,系统(内核)将执⾏⼀个称为shell的程序(shell进程,也称bash进程)shell进程提供了命令⾏提⽰符作为默认值(Turbo Linux系统默认的she
10、⼀旦出现shell提⽰符,就可以输⼊命令名称,以及命令所需要的参数(可选参数、位置参数)。shell将解释并执⾏这些命令。如果⼀条命令花费很长时间运⾏(⼀
11、当⽤户准备结束登录对话进程时,可以键⼊logout命令、exit命令或⽂件结束符(EOF)(按ctrl+d实现),结束登录
初步猜测
我在Java程序(Java进程)中、或者Python程序(Python解释器进程)调⽤命令⾏的过程是这样的吗?(备注:结论是错的)shell命令属于什么语言
Java进程->shell进程->命令对应的进程
Python进程->shell进程->命令对应的进程
⽆论是Java进程、还是Python进程,都依赖于内核底层的system()系统调⽤,所以system()的原理特别重要,学习Linux是我最⼤
的收获之⼀!
实际试验
⾸先启动两个bash窗⼝A和B(Mac上,有两个bash进程,它们的pid是83129和83149),此时的两个bash进程可以在ps命令中看
接着预先准备好⼀个python脚本⽂件
import os
os.system("adb logcat")
以下的描述如果是真的,那么当启动python进程后,⾄少会存在4个进程,
第⼀个:bash进程A,是我⽤来要启动Python进程
第⼆个:python解释器进程
第三个:shell进程
第四个:外部命令对应的进程
bash进程->Python进程->shell进程->命令对应的进程
结果会是这样吗?
在命令⾏中执⾏python3 fkme.py
接着去另外⼀个bash进程B的窗⼝中输⼊ps命令
发现Python脚本⽂件中的os.system("adb logcat")的执⾏,没有去创建⼀个新的bash进程,⽽是直接启动⼀个⼦进程,我的猜测是错误的,正确的情况是⼀共只创建了3个进程
第⼀个:bashA进程是Python解释器进程的⽗进程
第⼆个:Python解释器进程是adb logcat的⽗进程
第三个:adb命令的进程
再执⾏⼀个contro+c,⼲掉正在执⾏的python解释器进程
再做⼀个试验:换⼀种⽅案
import os
os.popen("adb logcat")
使⽤了python中os模块下的popen()函数,这会在新的⼦进程执⾏,特点是python解释器进程不会等待⼦进程的执⾏
让我执⾏python3 fkme.py ,然后输⼊ps命令
此时看到Python解释器进程已经不见了,⽽adb logcat 进程还在,这就是os.popen()的特点,它不会要求⽗进程等待它执⾏完毕
新的发现
再输⼊ps - l命令,因为python解释器进程已经结束了,因为python解释器进程是它的⽗进程,这可怎
么办?内核不会让进程孤⽴⽆关的运⾏,内核会怎么做呢?
发现adb logcat的⽗进程成为1,那么pid为1的进程在mac系统下是谁呢?
原来mac系统下pid为1的进程是/sbin/launchd进程,卧槽!(Linux下是init进程)
官⽹对于os.system()的介绍
在⼦shell(⼦进程)中执⾏命令(字符串),通过调⽤标准的C函数system()实现的,⽽c语⾔的system()函数属于标准库,它会再去执⾏内核的系统调⽤。stdin等并不反映在执⾏的命令的环境中。如果命令有任何输出(标准输出与标准错误),它将直接发送到解释器进程的标准输出流中。(Python3解释器会调⽤)
在Unix上,返回值是⽤wait()指定的格式编码的进程的退出状态码。请注意,POSIX标准没有指定C语⾔的system()函数中的返回值含义,因此Python函数os.system()的返回值还依赖于操作系统的具体实现!
延伸:那么C语⾔标准库中的system()函数⼜做了什么?
在Windows系统上,返回值是系统shell在运⾏命令后返回的值,shell由Windows环境变量COMSPEC给出:它通常是。返回命令运⾏的退出状态码,对于使⽤⾮本机shell的系统,请参阅您的shell⽂档
在Linux/Unix系统中,system()函数会调⽤fork()函数产⽣⼦进程,由⼦进程执⾏command,命令执⾏后即返回原调⽤的进程,所以adb logcat命令会在Python解释器fork()出来的⼦进程中执⾏!
c标准库中的system()函数
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <unistd.h>
int system(const char * cmdstring)
{
pid_t pid;
int status;
if(cmdstring == NULL){
return (1);
}
if((pid = fork())<0){
status = -1;
}
else if(pid = 0){
execl("/bin/sh", "sh", "-c", cmdstring, (char *)0);
-
exit(127); //⼦进程正常执⾏则不会执⾏此语句
}
else{
while(waitpid(pid, &status, 0) < 0){
if(errno != EINTER){
status = -1;
break;
}
}
}
return status;
}
主进程等待⼦进程执⾏结束后,才能继续执⾏的罪魁祸⾸到了(这是进程间同步的知识)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。