shell是什么?
Shell的英语翻译是“壳”,其作用也跟名字差不多,为操作系统套个壳,人与操作系统的壳交互。与壳相对应的则是操作系统内核,一个“壳”一个“核”。核从1970年代开始就基本定型了,没什么大的改变,而壳则不断演变迭代,已经跟当初的壳大不相同。核在70年代以后就没什么大发展的根本原因在于,核是负责操控计算机硬件的,从冯诺依曼结构以后,计算机结构就没什么大的改变,操作系统核心自然也没什么大改变。
在远古40年代真空管时期,人与计算机的交互是通过传入程序打孔纸带,也没有shell的概念,如果硬要说shell的话,可能负责装填打孔纸带的计算机管理员就是shell哈哈哈哈。
再后来50年代有了晶体管,CPU速度高于IO,出现了中断机制和调度程序并在60年代演变成操作系统,人们需要与操作系统进行交互,人们提出了shell的概念,并在Multics操作系统(Unix的前身)中实现了出来,称为Multics shell。
这个“人们”指的是路易斯·普赞
注意,shell是操作系统中的一层,是一个概念,并不是指专门的一个工具,实现了该功能的工具都可以称为shell。
后面Unix之父Thompson参考Multics shell实现了第一个Unix shell:Thompson shell
总而言之,我们给shell下个定义就是:shell是一种计算机程序,它接受命令,解释命令,并将命令传递给操作系统进行处理。
注意,终端terminal和shell不是一个概念,二者也不是一个东西,terminal是终端,用于接受用户输入转变为指令序列,并展示shell输出。他们之间的关系是:terminal连接shell,shell调用操作系统接口,shell只负责执行指令,输出结果,并不保存过往结果。保存过往运行结果,复制粘贴,将组合快捷键转变成控制指令,将箭头等输入转变成控制指令等功能都是由terminal负责的。为什么会有terminal,因为在上古时期,是真的有物理终端的,物理意义上的terminal,例如电传打字机(Teletype),shell只负责接受输入并处理。另外还有Console控制台的概念,也是从上古时期物理控制台继承过来的,在上古时期,真的是有物理意义上的控制台的,是比终端权限更高一级的与计算机交互的设备,一般都在机房计算机本体上,由计算机管理员负责。
随着计算机技术的进步,PC的体积变得很小,终端,控制台合二为一都被融合到了PC里面,现在 Console 与 Terminal 基本被看作是同义词。人们与电脑交互依旧是通过终端来进行,早期终端称为文本终端,只能显示文字,后期的终端称为图形终端,可以接受图像输入并输出图像,这也是显示器和GUI的前身。后来随着科技发展,终端被键盘和显示器取代。
再随着GUI的到来,人们开始更多使用GUI交互【GUI也可以看作是Shell的一种实现】,但是还是有一部分人有CLI命令行交互的需求。所以终端模拟器 terminal emulator出现了,缩写tty【继承于打字机teletype】,没错,就是Linux中的tty,Linux默认有7个tty,1~6是全屏终端,7是图形化终端。注意,现代的显示器是为GUI设计的,即使是没有图形界面的终端,也不过是终端模拟器模拟出来的罢了,不过完全没有图形界面的全屏终端是由操作系统提供的,而桌面系统中的terminal window是由专门的终端模拟器提供的,例如gnome-terminal。不过目前大家已经不称terminal emulator而直接称terminal了。
当代Linux下的大致架构,关于TTY的讲解可以看这里
再往后随着Linux和GNU的发展,shell的发展就五花八门开枝散叶了。
开枝散叶遇见的一个问题就是如何统一?如果不同shell实现的功能不同,操作不同,语法不同,那还怎么玩?这不光是Shell的问题,操作系统也有这个问题,Unix下的各种分支,如果各自的操作系统调用各不相同,还怎么玩?这就引出了下面的POSIX。
POSIX是什么
可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为ISO/IEC 9945。此标准源于一个大约开始于1985年的项目。POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称,由POSI和X组成,X则表明其对Unix API的传承。
当前的POSIX主要分为以下部分:
- 核心服务
- 兼容测试
- 系统调用
- 命令和工具:其中就包括了对于shell的要求
- 实时扩展:包括优先级调度、实时信号、时钟和定时器、信号量、消息传递、共享内存、异步和同步 I/O、内存锁。
- 线程扩展:包括线程创建和控制、线程调度、线程同步、信号处理。
我们常见的IEEE标准是指由IEEE标准协会制定的标准,IEEE标准协会(IEEE-SA)是电气和电子工程师协会(IEEE)下辖的标准制定机构,其标准制定内容涵盖信息技术、通信、电力和能源等多个领域,已制定了900多个现行工业标准,另有400多项标准正在制定过程中。我见过的的IEEE标准有:IEEE754浮点运算标准,IEEE1003操作系统接口标准。
标准化是一个行业发展到一定程度的必然现象,我们常见的ISO,GB,IEEE等标准的关系可以看这个回答
Linux大致是实现了POSIX的要求,但是没有参加POSIX认证,Linux自己的标准叫做LSB。
Bash vs Zsh vs fish?
Bash,Zsh,fish这几种,都是shell,Bash是目前系统中常见的内置默认shell,Zsh相较于bash有一些改进,可玩性更高,搭配oh my zsh,可以构造出更加适合自己的shell,fish是05年才发布的shell,主打一个开箱即用。bash主打简单通用,zsh主打想用什么自己配,fish主打默认就够好用,弱化个人配置起到的影响。
需要注意的一点是,不同的shell他们各自的配置文件是不同的,所以在配置文件中配置的环境变量是不通用的,有时候编译一些库或者工具的时候,这些库会自动配置环境变量到当下的shell中。如果想要在zsh中使用bash的环境变量,可以在.zshrc文件最后加上一行代码:
source ~/.bashrc
同理,在bash下用zsh的环境变量就是
source ~/.zshrc
Linux vs Unix?
Unix是贝尔实验室开发的,目前由Open Group持有商标,所有通过Open Group认证的系统都可以称为Unix操作系统。
Linux最初是由Linus开发的,后续转由社区进行开发,Linus是主要的代码审核者,Linux只是一个操作系统内核,Linux中使用的配套软件有很大一部分是由GNU项目开发的。
Linux发行版本的发展分支
Unix发展分支
Linux的标准 vs Unix标准
Linux的标准是LSB,以 POSIX 和 SUS 标准为基础,并对其他领域(例如图形)中源代码的一些标准进行了扩充,还增加了对二进制可执行文件格式规范的定义,从而试图确保 Linux 上应用程序源码和二进制文件的兼容性,目前最新的LSB标准是15年3月发布的5.0版本,而15年Debin和Ubuntu已经宣布不支持LSB标准。
这都过去9年了,也没见新的LSB版本,是发生了什么吗?
Unix的标准是POSIX和SUS,SUS是POSIX的扩展,在2001年后二者趋于一致,只有经过SUS标准认证的系统才称为Unix。
相较于POSIX,LSB还制定了制定了应用程序与运行环境之间的二进制接口,基于以下的标准:
- Single UNIX Specification(SUS)
- System V Interface Definition(SVID)
- compilers for the Intel Itanium processor
- C++ ABI
- System V Application Binary Interface(ABI)
ABI应用程序二进制接口,描述了应用程序和操作系统之间,一个应用和它的库之间,或者应用的组成部分之间的低接口。要理解ABI统一的重要性,我们要从软件是如何跨平台说起。
一个软件在操作系统上运行,经常会进行两种调用:
- 编程语言库调用:即调用标准库或第三方库,一般库函数都是以lib文件+头文件存储,lib文件中包含了库函数实现,头文件中包含了相应的接口。glibc 是 Linux 下使用的开源的标准 C 库,它是 GNU 发布的 libc 库,即运行时库。这些基本函数都是被标准化了的,而且这些函数通常都是用汇编直接实现的。
- 操作系统调用:系统调用是通向操作系统本身的接口,是面向底层硬件的。通过系统调用,可以使得用户态运行的进程与硬件设备(如CPU、磁盘、打印机等)进行交互,是操作系统留给应用程序的一个接口。
跨平台两种程度:
- 源码级跨平台,即同一套源码,可以在不同的平台上编译运行。
- 二进制级跨平台,即编译出来一套二进制代码,可以在不同平台上运行。
二进制跨平台第一点需要的是可执行程序格式相同【虽然都是二进制文件,但是其中一些附加信息还是不一样的】:
- Window:PE-COFF
- Unix/Linux: Executable and Linkable Format (ELF)
- MacOS:MACH-O
所以一般情况下对于MacOS和Window这俩兄弟和其他的基本不用考虑二进制跨平台,但是对于Linux和Unix下可以尝试二进制跨平台。
第二点是需要ABI相同:严格来说,ABI并不是完全操作系统需要考虑的事情,更多是编程语言需要考虑的事情,只不过LSB把这件事情也写进了标准,尽量做到在Linux的世界一次编译到处通用。
ABI的统一需要考虑非常多的内容,对于C语言来说,需要考虑:
- 内置类型的存储方式:字节大小,字节序,对齐方式等
- 组合类型的存储方式:内存分布等
- 外部符号(external-linkage)与用户定义的符号之间的命名方式和解析方式,如函数名func在C语言的目标文件中是否被解析成外部符号_func。
- 函数调用方式,比如参数入栈顺序、返回值如何保持等。
- 堆栈的分布方式,比如参数和局部变量在堆栈里的位置,参数传递方法等。寄存器使用约定,函数调用时哪些寄存器可以修改,哪些须要保存,等等。
对于C++,要求则更多,除上面的以外,还需要考虑:
- 继承类体系的内存分布,如基类,虚基类在继承类中的位置等。
- 指向成员函数的指针( pointer-to-member)的内存分布,如何通过指向成员函数的- 指针来调用成员函数,如何传递this 指针。
- 如何调用虚函数,vtable的内容和分布形式,vtable指针在 object 中的位置等。template 如何实例化。
- 外部符号的修饰。
- 全局对象的构造和析构。
- 异常的产生和捕获机制。
- 标准库的细节问题,RTTI 如何实现等。
- 内嵌函数访问细节。
这些问题都是需要编译器考虑的,而不是操作系统需要考虑的。ABI不统一的问题也不光体现在不能跨平台,因为不同的编译器间的ABI都不统一,甚至相同的编译器不同的版本ABI都不统一,这会导致编译出来的库文件只能在对应的编译器版本下使用,对于C++,主要的ABI有两种,一种是微软自家的ABI,另一种是Intel的Itanium C++ ABI,二者不互通,但是在LSB和System V ABI的限制下,在Linux和Unix下基本已经统一了C++的ABI,但也只是基本统一。而且ABI的统一也并不代表可以完全进行二进制跨平台。
终于理清这几个概念之间的区别和联系了!如果觉得有帮助,欢迎点赞收藏+关注,thanks!