Linux系统中的可执行文件有多少种类?bash环境下是如何执行程序的?下面逐一分析。
1 Linux系统中可执行文件种类
1.1 二进制可执行文件
这种文件是最常见的,如/bin/ls,/sbin/ifconfig, /bin/cat等等。
[root@notebook135 ~]# file /bin/ls /bin/cat /sbin/ifconfig
/bin/ls: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
/bin/cat: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
/sbin/ifconfig: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.18, stripped
1.2 可执行脚本文件
这种文件是系统管理者常用的,通常用来粘合各种其他的程序,系统中也必不可少,如 命令service,yum等。
[root@notebook135 ~]# file $(which yum) $(which service)
/usr/bin/yum: a /usr/bin/python script text executable
/sbin/service: POSIX shell script text executable
当然,还可以有其他类型的脚本,如ruby,php,awk等等。
1.3 系统加载器对可执行文件的识别验证与加载
不论何种类型的可执行程序,都是通过统一的execve()系统调用进行加载执行的。具体的加载过程却会因为可执行程序种类的不同而不同,对于二进制文件,直接加载执行;对于脚本文件,则会加载文件第一行指定的解释器,并把脚本文件路径名作为解释器的参数。execve()是如何区分各种程序类型的呢?其实很简单,就是位于文件最开始处的“魔数”。不同类型的文件的“魔数”是不同的,对于脚本文件就是开头的#!,对于二进制文件,根据32位和64位的不同,也有所不同,感兴趣的同学可以自行研究,这里不再累述。
2 bash执行命令的过程
2.1 bash执行内建命令的过程
bash环境下可以执行的命令有两类:一类就是前面说过的可执行文件(可执行文件又分成二进制和脚本两类);另一类就是bash内建命令。
常见的内建命令有echo,cd,trap等等。内建命令也分成两类:一类是某些外部可执行文件的同功能替代,目的是提高效率,如echo;第二类是其功能无法通过执行外部文件完成的必备命令,如cd。
对于内部命令,bash直接执行其自身内部代码即可,快速无负担。对于执行外部可执行文件,则相对就麻烦了。
2.2 bash执行外部二进制可执行文件五种方式
(1)直接执行
可以有两种手段让bash执行命令,一是在交互模式下,输入命令名然后按下回车键;二是把命令路径作为参数来执行/bin/bash。两种手段的作用是相同的。
bash首先会fork出一个子进程,然后:(1)bash自身进程执行wait()等待子进程结束;(2)子进程中执行execve("命令路径“),剩下的工作就由加载器来完成了。
[root@notebook135 ~]# strace -e trace=process /bin/ls
execve("/bin/ls", ["/bin/ls"], [/* 21 vars */]) = 0
(2)使用exec命令执行(不建立子进程)
smstongtekiMac-mini:Test smstong$ exec /bin/ls
2.3 运行脚本文件的三种方式
(1)直接执行
前面我们说过,execve()可以自动识别脚本文件类型,并自动加载相应的解释器。如下:
[root@notebook135 ~]# strace -e trace=process ./test1.sh
execve("./test1.sh", ["./test1.sh"], [/* 21 vars */]) = 0
(2)人工指定解释器执行
当然,这个识别脚本的过程也可以交给用户自己来完成,比如对于上面的 test1.sh,我们可以指定ksh93为其解释器,者通过显式的执行其解释器程序来完成。
[root@notebook135 ~]# strace -e trace=process ksh93 ./test1.sh
execve("/bin/ksh93", ["ksh93", "./test1.sh"], [/* 21 vars */]) = 0
此时,脚本文件第一行指定的解释器不起作用了。
这两种执行脚本文件的方式效果是一样的,只是通过ps查看时进程的名字会有所不同,直接执行时子进程名字为脚本文件名,通过ksh93 test1.sh执行时,子进程名为ksh93。
其实第二种方式有时候是必要的,例如某些系统的加载器本身不能直接执行脚本文件,又如脚本文件本身不具有可执行权限。
前面例子test1.sh是ksh脚本,如果是awk,php,python...,过程也是完全一样的。有一中脚本也许看起来有点特殊,那就是bash,此时会导致bash启动一个子bash进程,其实也就是看起来特殊,仅仅是因为子进程的名字和父进程一样而已,本质上与其他类型的脚本解释器没有任何区别。
(3)使用 . 命令执行脚本(不生成子进程)
1 #!/bin/ksh93
2 echo $$
3 exit