Linux看看这个叫啥应该怎么解决

    (这一小节应该是作为《shell编程范例の进程操作》的一些补充性质的内容)

    当我们在Linux下的命令行输入一个命令之后,这背后发生了什么

    用户使用计算机有两种常见的方式,┅种是图形化的接口(GUI)另外一种则是命令行接口(CLI)。对于图形化的接口用户点击某个图标就可启动后 台的某个程序;对于命令行嘚接口,用户键入某个程序的名字就可启动某个程序这两者的基本过程是类似的,都需要查找程序文件在磁盘上的位置加载到内存并 通过不同的解释器进行解析和运行。下面以命令行为例来介绍程序执行那一刹那发生的一些事情


    首先来介绍什么是命令行?命令行就是command line很直观的概念就是系统启动后的那个黑屏幕:有一个提示符,并有光标在闪烁的那样一个终端一般情况下可以用CTRL+ALT+F1-6切换到不同的终端;茬GUI界 面下也会有一些伪终端,看上去和系统启动时的那个终端没有什么区别也会有一个提示符,并有一个光标在闪烁就提示符和响应鼡户的键盘输入而言,它们两者 在功能上是一样的实际上它们就是同一个东西,你用下面的命令就可以把它们打印出来
    从上面的操作結果可以看出,当前命令行接口实际上是一个程序那就是/bin/bash,它是一个实实在在的程序它打印提示符,接受用户输入的命令分 析命令序列并执行然后返回结果。不过/bin/bash仅仅是当前使用的命令行程序之一还有很多具有类似功能的程序,比如/bin/tcsh, bin/ash等不过这里主要来讨论bash了,讨論它自己是怎么启动的它怎么样处理用户的输入命令等后台细节?

   先通过CTRL+ALT+F1切换到一个普通终端下面一般情况下看到的是XXX login: 提示输入用户洺,接着是提示输入密码然后呢?就直接登录到了我们的命令 行接口实际上正是你输入正确的密码后,那个程序把/bin/bash给启动了那是什麼东西提示"XXX


    /bin/login程序实际上会检查我们的/etc/passwd文件,在这个文件里头包含了用户名、密码和该用户的登录shell密码和用户名匹配用户的登录,而登录shell則作为用户登录后的命令行程序看看/etc/passwd中典型的这么一行:
    这个是我用的帐号的相关信息哦,看到最后一行没/bin/bash,这正是我登录用的命令荇解释程序至于密码呢,看到那个x没这个x说明我的密码被 保存在另外一个文件里头/etc/shadow,而且密码是经过加密的至于这两个文件的更多細节,看manual吧
    跟上面一样,切换到一个普通终端并切换到root用户,用下面的命令:
    下面再来看一个演示先在一个可以登陆的终端下执行丅面的命令。
    getty命令停留在那里貌似等待用户的什么操作,现在切回到第8个终端是不是看到有"XXX login:"的提示了。输入用户名并登录之后退出,回到第一个终端发现getty命令已经退出。
    类似地我们也可以用strace命令来跟踪getty的执行过程。在第一个终端下切换到root用户执行如下命令,

system)哦它是Linux系统默认启动的第一个程序,负责进行Linux系统的一些初始化工作而这些初始化工作的配置则是通过 /etc/inittab来做的。那么来看看/etc/inittab的一个简單的example吧可以通过man inittab查看相关帮助。


    整个配置文件的语法非常简单就是下面一行的重复,
  • id就是一个唯一的编号不用管它,一个名字而言无关紧要。
  • runlevels是运行级别整个还是比较重要的,理解运行级别的概念很有必要它可以有如下的取值,
        不过真正在配置文件里头用的昰1-5了,而0和6非常特别除了用它作为init命令的参数关机和重启外,似乎没有哪个“傻瓜”把它写在系统的配置文件 里头让系统启动以后就關机或者重启。1代表单用户而2-5则代表多用户。对于2-5可能有不同的解释比如在slackware
  •     respawn,这个指定的进程将被重启任何时候当它退出时。这意菋着你没有办法结束它除非init自己结束了。例如
        这一行的意思非常简单,就是系统运行在级别1,2,3,5时将默认执行/sbin/agetty程序(这个类似于上面提箌的getty程序),这个程序非常有意思就是无论什么时候它退出,init将再次启动它这个有几个比较有意思的问题:
        在slackware 12.0下,当你把默认运行级別修改为4的时候只有第6个终端可以用。原因是什么呢因为类似上面的配置,因为那里只有1235而没有4,这意味着当 系统运行在第4级别时其他终端下的/sbin/agetty没有启动。所以如果想让其他终端都可以用,把1235修改为12345即可
        还有一个问题:无论我们退出哪个终端,那个"XXX login:"总是会被打茚原因是respawn动作有趣的性质,因为它告诉init无论/sbin/agetty什么时候退出,重新把它启动起来 那跟"XXX     而init程序作为“万物之王”,它是所有进程的“父”(也可能是祖父……)进程那意味着其他进程最多只能是它的儿子进程。而这个子进程是怎么创建的 fork调用,而不是之前提到的execve调用前者创建一个子进程,后者则会覆盖当前进程因为我们发现/sbin/getty运行时,init并没 有退出因此可以判断是fork调用创建一个子进程后,才通过execve执荇了/sbin/getty
        通过ps和pstree命令看看实际情况是不是这样,前者打印出进程的信息后者则打印出调用关系。
    是/sbin/getty和/bin/login的父进程说明init启动或者间接启动了咜们。下面通过pstree来查看调用树更清晰的看出上述关 系。
        从上面的结果发现init作为所有进程的父进程,它的父进程ID饶有兴趣的是0它是怎麼被启动的呢?谁才是真正的“造物主”

        如果用过Lilo或者Grub这两个操作系统引导程序,你可能会用到Linux内核的一个启动参数init当你忘记密码时,可能会把这个参数设置成/bin/bash让系统直接进入命令行,而无须输入帐号和密码这样就可以方便地登录密码修改掉。 /bin/sh如果找不到这几个攵件中的任何一个,内核就要恐慌(panic)了并呆在那里一动不动了。
    启动选项标签等信息从而能够让它们顺利把内核启动起来。
        那Lilo和Grub本身又是怎么被运行起来的呢还记得以前介绍的MBR不?MBR就是主引导扇区一般情况下这里存放着Lilo和Grub的代码,而 谁知道正好是这里存放了它们呢BIOS,如果你用光盘安装过操作系统的话那么应该修改过BIOS的默认启动设置,通过设置你可以让系统从光盘、硬盘甚

        到这里/bin/bash的神秘面纱僦被揭开了,它只是系统启动后运行的一个程序而已只不过这个程序可以响应用户的请求,那它到底是如何响应用户请求的呢

        在执行磁盘上某个程序时,我们通常不会指定这个程序文件的绝对路径比如要执行echo命令时,我们一般不会输入/bin/echo而仅仅是输入 echo。那为什么这样bash吔能够找到/bin/echo呢原因是Linux操作系统支持这样一种策略:shell的一个环境变量PATH里头存放 了程序的一些路径,当shell执行程序时有可能去这些目录下查找which作为shell(这里特指bash)的一个内置命令,如果用户输入的命令是 磁盘上的某个程序它会返回这个文件的全路径。

        有三个东西和终端的关系佷大那就是标准输入、标准输出和标准错误,它们是三个文件描述符一般对应描述符0,1,2。在C语言程序里头我们可以把它们 当作文件描述符一样进行操作。在命令行下则可以使用重定向字符>,<等对它们进行操作。对于标准输出和标准错误都默认输出到终端,对于标 准输叺也同样默认从终端输入。

    1.3.1 哪种命令先被执行

        在C语言里头要写一段输入字符串的命令很简单调用scanf或者fgets就可以。这个在bash里头应该是类似嘚但是它获取用户的命令以后,如何分析命令如何响应不同的命令呢?

        从上面的演示中发现一个问题如果输入一个命令,这个命令偠么就不存在要么可能同时是shell的内置命令、也有可能是磁盘上环境变量PATH所指定的目录下的某个程序文件。
        考虑到test内置命令和/usr/bin/test命令的响应結果一样我们无法知道哪一个先被执行了,怎么办呢把/usr/bin/test替换成 一个我们自己的命令,并让它打印一些信息(比如hello,world!)这样我们就知道到底誰被执行了。写完程序编译好,命名为test放到 /usr/bin下(记得备份原来那个)开始测试:

        下面看看更多有趣的东西,键盘键入的命令还有可能昰什么呢因为bash支持别名和函数,所以还有可能是别名和函数另外,如果PATH环境变量指定的不同目录下有相同名字的程序文件那到底哪個被优先找到呢?

        实际上type命令会告诉我们这些细节,type -a会按照bash解析的顺序依次打印该命令的类型而type -t则会给出第一个将被解析的命令的类型,之所以要做上面的实验是为了让大家加印象。
        可以找出这么一个规律:shell从PATH列出的路径中依次查找用户输入的命令考虑到程序的优先级最低,如果想优先执行磁盘上的程序文件test呢那么就可以用test -P找出这个文件并执行就可以了。

    补充:对于shell的内置命令可以通过help command的方式獲得帮助,对于程序文件可以查看用户手册(当然,这个需要安装一般叫做xxx-doc),man command关于用户手册安装办法见

        在命令行上,除了输入各種命令以及一些参数外比如上面type命令的各种参数-a, -P等,对于这些参数是传递给程序本身的,非常好处理比如if,else条件分之或者switch, case都可以处理。当然在bash里头可能使用专门的参数处理函数getopt和getopt_long来处理它们。


        而|,>,<,&等字符则比较特别,shell是怎么处理它们的呢它们也被传递给程序本身吗?可我们的程序内部一般都不处理这些字符的所以应该是shell程序自己解析了它们。
        这主要归功于dup/fcntl等函数它们可以实现:复制文件描述符,让多个文件描述符共享同一个文件表项比如,当把文件test.c重定向为标准输 入时假设之前用以打开test.c的文件描述符是5,现在就把5复制为了0这样当cat试图从标准输入读出内容时,也就访问了文件描述符5指向的文件 表项接着读出了文件内容。输出重定向与此类似其他的重定姠,诸如>>, <<, <>等虽然和>,<的具体实现功能不太一样但本质是一样的,都是文件描述符的复制只不过可能对文件操作有一些附加的限制,比 如>>茬输出时追加到文件末尾而>则会从头开始写入文件,前者意味着文件的大小会增长而后者则意味文件被重写。

        那么|呢"|"被形象地称为“管道”,实际上它就是通过C语言里头的无名管道来实现的先看一个例子,

        在这个例子中cat读出了test.c文件中的内容,并输出到标准输出上但是实际上输出的内容却只有一行,原因是这个标准输出被“接到”了grep命令的标准输入上而grep命令只打印了包含“hi”字符串的一行。
        这昰怎么被“接”上的cat和grep作为两个单独的命令,它们本身没有办法把两者的输入和输出“接”起来这正是shell自己的“杰作”,它通过C 语言裏头的pipe函数创建了一个管道(一个包含两个文件描述符的整形数组一个描述符用于写入数据,一个描述符用于读入数据)并且通过 dup/fcntl把cat的输絀复制到了管道的输入,而把管道的输出则复制到了grep的输入这真是一个奇妙的想法。
        那&呢当你在程序的最后跟上这个奇妙的字符以后僦可以接着做其他事情了,看看效果
        实际上&正是shell支持作业控制的表征,通过作业控制用户在命令行上可以同时作几个事情(把当前不莋的放到后台,用&或者CTRL +Z或者bg)并且可以自由的选择当前需要执行哪一个(用fg调到前台)这在实现时应该涉及到很多东西,包括终端会话(session)、终端信号、前 台进程、后台进程等而在命令的后面加上&后,该命令将被作为后台进程执行后台进程是什么呢?这类进程无法接收用户发送给终端的信号(如 SIGHUP,SIGQUIT,SIGINT)无法响应键盘输入(被前台进程占用着),不过可以通过fg切换到前台而享受作为前台进程具有的特权
        洇此,当一个命令被加上&执行后shell必须让它具有后台进程的特征,让它无法响应键盘的输入无法响应终端的信号(意味忽略这些信号),并 且比较重要的是新的命令提示符得打印出来并且让命令行接口可以继续执行其他命令,这些就是shell对&的执行动作

    文件中相应用户名所在行的最后就可以。不过貌似到现在还没介绍shell是怎么执行程序是怎样让程序变成进程的,所以继续

    1.3.3 /bin/bash用什么魔法让一个普通程序变成叻进程

        当我们从键盘键入一串命令,shell奇妙的响应了对于内置命令和函数,shell自身就可以解析了(通过switch case之类的C语言语句)但是,如果这个命令是磁盘上的一个文件呢它找到该文件以后,怎么执行它的呢

        通过这个演示发现,当前shell的环境变量中被设置为export的变量被复制到了新嘚程序里头不过虽然我们认为shell执行新程序时是在一个新的 进程里头执行的,但是strace并没有跟踪到诸如fork的系统调用(可能是strace自己设计的时候並没有跟踪fork或者是在fork之后才跟 踪)。但是有一个事实我们不得不承认:当前shell并没有被新程序的进程替换所以说shell肯定是先调用fork(也有可能昰vfork)创建了一 个子进程,然后再调用execve执行新程序的如果你还不相信,那么直接通过exec执行新程序看看这个可是直接把当前shell的进程替换掉的。
    execve可以看到该系统调用实际上exec的那一组调用都只是libc库提供的,而execve才是真正的系统调用也就是说无论使用exec调用 中的哪一个,最终调用的嘟是execve如果使用execlp,那么execlp将通过一定的处理把参数转换为execve的参数因此,虽然我们没有 传递任何环境变量给execlp但是默认情况下,execlp把父进程的環境变量复制给了子进程而这个动作是在execlp函数内部完成的。
        第一个是程序本身的绝对路径对于刚才使用的execlp,我们没有指定路径这意菋着它会设法到PATH环境变量指定的路径下去寻找程序的全路径。
        第二个参数是一个将传递给被它执行的程序的参数数组指针正是这个参数紦我们从命令行上输入的那些参数,诸如grep命令的-v等传递给了新程序可以通过main函数的第二个参数char *argv[]获得这些内容。
        第三个参数是一个将传递給被它执行的程序的环境变量这些环境变量也可以通过main函数的第三个变量获取,只要定义一个char *env[]就可以了只是通常不直接用它罢了,而昰通过另外的方式通过extern char **environ全局变量(环境变量表的指针)或者getenv函数来获取某个环境变量的值。
        当然实际上,当程序被execve执行后它被加载箌了内存里,包括程序的各种指令、数据以及传递给它的各种参数、环境变量等都被存放在系统分配给该程序的内存空间中
        关于程序加載和进程内存映像的更多细节请参考《》。

        到这里关于命令行的秘密都被“曝光”了,可以开始写自己的命令行解释程序了

    补充:上媔没有讨论到一个比较重要的内容,那就是即使execve找到了某个可执行文件如果该文件属主没有运行该程序的权限,那么也没有办法运行程序可通过ls -l查看程序的权限,通过chmod添加或者去掉可执行权限

  • 文件属主具有可执行权限时才可以执行某个程序
}

VIP专享文档是百度文库认证用户/机構上传的专业性文档文库VIP用户或购买VIP专享文档下载特权礼包的其他会员用户可用VIP专享文档下载特权免费下载VIP专享文档。只要带有以下“VIP專享文档”标识的文档便是该类文档

VIP免费文档是特定的一类共享文档,会员用户可以免费随意获取非会员用户需要消耗下载券/积分获取。只要带有以下“VIP免费文档”标识的文档便是该类文档

VIP专享8折文档是特定的一类付费文档,会员用户可以通过设定价的8折获取非会員用户需要原价获取。只要带有以下“VIP专享8折优惠”标识的文档便是该类文档

付费文档是百度文库认证用户/机构上传的专业性文档,需偠文库用户支付人民币获取具体价格由上传人自由设定。只要带有以下“付费文档”标识的文档便是该类文档

共享文档是百度文库用戶免费上传的可与其他用户免费共享的文档,具体共享方式由上传人自由设定只要带有以下“共享文档”标识的文档便是该类文档。

}

我要回帖

更多关于 看看这个叫啥 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信