前言
简单聊聊shell,shell这个名称在不同的上下文环境中,表达的含义是不同的,当我们再聊计算机语言时,此时的shell名称即表示一种语言;当我们聊计算机程序时,shell又表示一个解释器程序,比如bash就属于shell中的一种;当我们再聊操作系统的环境中时,shell这个名称又表示与内核(操作系统)通信的接口。
bash是shell解释器程序中的一种,它是Linux默认使用的shell程序,多数Linux发行版使用的都是bash程序,而bash有一套属于它的标准!
shell启动程序的原理
当我们按下回车键后,bash解释器开始解释在换行符前的文本信息
$ ls -l
1、bash读取命令行后,会将自己看到的第一个单词作为命令(备注:这是规定好的标准,比如ls)
2、接着判断该命令为内部命令还是外部命令?
3、如果为外部命令,则寻找【单词】指向的可执行文件(备注:PATH环境变量指向的目录,或者绝对路径或者以当前工作目录中寻找)
4、第一个单词后面的所有单词,都作为第一个命令的命令行参数传进去(备注:采用空白字符进行单词分隔)
5、bash接着向内核发起系统调用fork( ),此时会建立一个新的子进程,新的子进程会将命令作为程序而执行
6、bash进程向内核请求系统调用wait(),所以bash程序会默认一直等待子进程中的程序执行完成,当子进程完成工作后,会向父进程(shell进程)报告,此时bash进程会醒来
7、如果执行的是后台命令,命令行中加了nohup与&,此时的bash进程不会等待子进程中的程序执行完成,它会继续向下执行程序,bash程序执行结束后会直接返回
ls -l &
8、终端窗口则继续展示提示符,用户可以继续输入下一条命令;而子进程仍在运行中,运行结束后的标准输出仍然会显示在终端窗口中
bash运行程序流程图(感谢作者,有水印)
图中的2条执行流:排在第一行的是主执行流(bash进程),第二行是子进程的执行流(子进程程序)
当我们在java或者python中调用外部程序的执行流
我在Java程序(Java进程)中、或者Python程序(Python解释器进程)调用命令行的过程是这样的吗?(备注:结论是错的,中间的shell进程,可以调用,也可以不调用!),比如我们调用的外部程序是grep
第一种
Java进程->shell进程->命令对应的进程
Python进程->shell进程->命令对应的进程
第二种
Java进程->命令对应的进程
Python进程->命令对应的进程
试验
首先启动两个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进程)
延伸:那么C语言标准库中的system()函数又做了什么?
在Windows系统上,返回值是系统shell在运行命令后返回的值,shell由Windows环境变量COMSPEC给出:它通常是cmd.exe。返回命令运行的退出状态码,对于使用非本机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;
}
主进程等待子进程执行结束后,才能继续执行的罪魁祸首找到了(这是进程间同步的知识)