Linux的一些基础知识

1、创建了 nc-tor 这个别名

alias nc-tor='nc -X 5 -x 127.0.0.1:9050'

2、在 POSIX 系统(Linux & UNIX),所有的进程构成一个【单根树】的层次关系。进程之间的“父子关系”,体现在“进程树”就是树上的【父子节点】。

  你可以使用如下命令,查看当前系统的“进程树”。

pstree

3、初始进程

  一般情况下,POSIX 系统的“进程树”的【根节点】就是系统开机之后【第一个】创建的进程,并且其进程编号(PID)通常是 1。这个进程称之为“初始进程”。
  (注:上述这句话并【不够】严密——因为某些 UNIX 衍生系统的“进程树”,位于根节点的进程【不是】“初始化进程”。这种情况与本文的主题没太大关系,不展开讨论)
  对于“大部分 UNIX 衍生系统”以及“2010年之前的 Linux 发行版”,系统中的“初始进程”名叫 init;
  如今越来越多的 Linux 发行版采用 systemd 来完成系统引导之后的初始化工作。在这些发行版中,“初始进程”名叫 systemd。
  你可以用如下命令显示“进程树”中每个节点的“进程编号”(PID),然后就能看到编号为 1 的“初始进程”。

pstree -p

4、进程的三种死法

1.自然死亡

  如果某个进程把它该干的事情都干完了,自然就会退出。
  这种是最常见的情况,也是最优雅的死法。可以称作【自然死亡】。

2.自杀

  如果某个进程的工作干到半当中,突然收到某个通知,让它立即退出。
  这时候,进程会赶紧处理一些善后工作,然后自行了断——这就是【自杀】。

3.它杀

  比“自杀”更粗暴的方式称之为【它杀】。也就是让“操作系统内核”直接把进程干掉。
  在这种情况下,进程【不会】收到任何通知,因此也【不】可能进行任何善后事宜。

“孤儿进程”及其“领养”

  如果某个进程死了(退出了),而它的子进程还【没】死,那么这些子进程就被形象地称之为“孤儿”,然后会被上述提到的【初始进程】“领养”——“初始进程”作为“孤儿进程”的父进程。
  对应到“进程树”——“孤儿进程”会被重新调整到“进程树根节点”的【直接下级】。

“Ctrl + C”背后的原理——【信号】(signal)

  当你按下了 Ctrl + C 这个组合键,当前正在执行的进程会收到一个叫做【SIGINT】的信号。
  如果进程内部定义了针对该信号的处理函数,那么就会去执行这个函数,完成该函数定义的一些动作。一般而言,该函数会进行一些善后工作,然后进程退出。
  如果进程【没有】定义相应的处理函数,则会执行一个【默认动作】。对于 SIGINT 这个信号而言,默认动作就是“进程退出”。
  上述这2种情况,都属于前面所说的自杀。这2种属于【常规情况】。
  下面再来说【特殊情况】——有时候 Ctrl + C【无法】让进程退出。为啥会这样捏?
  假如说,编写某个进程的程序员,定义了该信号的处理函数,但在这个函数内部,并【没有】执行“进程退出”这个动作。那么当该进程收到 SIGINT 信号之后,自然就【不会】退出。这种情况称之为——信号被该进程【屏蔽】了。

【谁】发出“Ctrl + C”对应的信号?

  很多人(包括很多玩命令行的老手)都有一个【误解】——他们误以为是 shell 发送了 SIGINT 信号给当前进程。其实不然!
  在上述 ping 的例子中,当 ping 进程在持续运行之时,你的键盘输入是关联到 ping 进程的“标准输入”(stdin)。在这种情况下,shell 根本【无法】获取你的按键信息。
  实际上,是【终端】获取了你的 Ctrl + C 组合键信息,并发送了 SIGINT 信号。因为【终端】处于更底层,它负责承载你所有的输入输出。因此,它当然可以截获用户的某个特殊的组合键(比如:Ctrl + C),并执行某些特定的动作。
  聊到这里,大伙儿会发现,如果没有正确理解“终端”与“shell”这两者的关系,就会犯很多错误(造成很多误解)。
  “终端”如何知道【当前进程】是哪一个?
  当 shell 启动了某个进程,它当然可以拿到这个进程的编号(pid),于是 shell 会调用某个系统 API(比如 tcsetpgrp)把“进程编号”与 shell 所属的“终端”关联起来。
  当“终端”需要发送 SIGINT 信号时,再调用另一个系统 API(比如 tcgetpgrp),就可以知道当前进程的编号。

对比杀进程的几个信号:SIGINT、SIGTERM、SIGQUIT、SIGKILL

  1.SIGINT

  在大部分 POSIX 系统的各种终端上,Ctrl + C 组合键触发的就是这个信号。
  通常情况下,进程收到这个信号后,做完相关的善后工作,就自行了断(自杀)。

  2.SIGTERM

  这个信号基本类似于 SIGINT。
  它是 kill & killall 这两个命令【默认】使用的信号。
  也就是说,当你用这俩命令杀进程,并且【没有】指定信号类型,那么 kill 或 killall 用的就是这个 SIGTERM 信号。

  3.SIGQUIT

  这个信号类似于前两个(SIGINT & SIGINT),差别在于——进程在退出前会执行“core dump”操作。
  一般而言,只有程序员才会去关心“core dump”这个玩意儿,所以这里就不细聊了。

  4.SIGKILL

  在杀进程的几个信号中,这个信号是是最强大的(也是最粗暴的)。
  前面三个信号都是【可屏蔽】的,而这个信号是【不可屏蔽】的。
  当某个进程收到了【SIGKILL】信号,该进程自己【完全没有】处理信号的机会,而是由操作系统内核直接把这个进程干掉。
  此种行为可以形象地称之为“它杀”。
  当你用下列这些命令杀进程,本质上就是在发送这个信号进行【它杀】。【SIGKILL】这个信号的编号是 9,下列这些命令中的 -9 参数就是这么来的。

kill -9 进程号
kill -KILL 进程号

killall -9 进程名称
killall -KILL 进程名称
killall -SIGKILL 进程名称
信号名称编号能否屏蔽默认动作俗称
SIGINT2YES进程自己退出自杀
SIGTERM15YES进程自己退出自杀
SIGQUIT3YES执行 core dump 进程自己退出自杀
SIGKILL9NO进程被内核干掉它杀

【它杀】的危险性与副作用

  请注意:【它杀】是一种比较危险的做法,可能导致一些【副作用】。只有当你用其它各种方式都无法干掉某个进程,才考虑用这个方式。
  一方面,当操作系统用这种方式杀掉某个进程,虽然可以把很多内存相关的资源释放掉,但【内存之外】的资源,内核就管不了;另一方面,由于进程遭遇“它杀”,无法完成某些善后工作。
  基于上述两点,就【有可能】会产生副作用。另外,“副作用的严重程度”取决于不同类型的软件。无法一概而论。
  举例1:某个进程正在保存文件。这时候遭遇“它杀”可能会导致文件损坏。
  (注:虽然某些操作系统能做到“写操作的原子性”,但数据存储可能会涉及多个写操作。当进程在作【多个】关键性写操作时,遭遇它杀。可能导致数据文件【逻辑上】的损坏)
  举例2:如涉及跨主机的网络通讯。某个进程可能向【远程】的某个网络服务分配了某个远程的资源,当进程“自然死亡 or 自杀”,它会在“善后工作”释放这个资源;而如果死于内核的“它杀”,这个远程的资源就【没】释放。

kill VS killall

  这两个的差别在于——前者用“进程号”,后者用”进程名“(也就是可执行文件名)。
如果用 kill 命令,需要先用 ps 命令打印出当前进程清单,然后找到你要杀的进程的编号;而如果要用 killall 命令,就比较省事(比较傻瓜化)。但万一碰到有多个【同名】进程在运行,而只想干掉其中一个,那么就得老老实实用 kill 了。

进程退出码

  任何一个进程退出的时候,都对应某个【整数类型】的“退出码”。
  按照 POSIX 系统(UNIX & Linux)的传统惯例——

当“退出码”为【零】,表示“成功 or 正常状态”;
当“退出码”【非零】,表示“失败 or 异常状态”。

暂停进程

  刚才提到“杀进程”的时候提到了“自杀 VS 它杀”。前者比较“温柔”;而后者比较“粗暴”。
  对于暂停进程,也有“温柔 & 野蛮”两种玩法。而且也是用 kill 命令发信号。
  【温柔】式暂停(SIGTSTP)

kill -TSTP 进程编号

  这个【SIGTSTP】信号类似前面提及的【SIGINT】:

  1. 两者默认都绑定到组合键(【SIGINT】默认绑定到组合键【Ctrl + C】;【SIGTSTP】默认绑定到组合键【Ctrl + Z】)
  2. 这两个快捷键都是由【终端】截获,并发出相应的信号(具体原理参见本章节的某个小节)
  3. 两者都是【可】屏蔽的信号。也就是说,如果某个进程屏蔽了【SIGTSTP】信号,你就【无法】用该方式暂停它。这时候你就得改用【粗暴】的方式(如下)。

    【粗暴】式暂停(SIGSTOP)
    kill -STOP 进程编号

  这个【SIGSTOP】信号与前面提及的【SIGKILL】有某种相同之处——这两个信号都属于【不可屏蔽】的信号。也就是说,收到【SIGSTOP】信号的进程【无法】抗拒被暂停(suspend)的命运。
  与“杀进程”的风格类似——当你想要暂停某进程,应该先尝试“温柔”的方法,搞不定再用“粗暴”的方法。

恢复进程

  当你想要重新恢复(resume)被暂停的进程,就用如下命令(该命令发送信号【SIGCONT】)

kill -CONT 进程编号

4、作业控制(job)

1.什么是“作业”
  “作业”是 shell 相关的术语,用来表示【进程组】的概念(每个作业就是一组进程)。
  比如说,当用“管道符”把若干命令串起来执行,这几个命令对应的进程就被视作【一组】。
  (注:“管道符”的用法,后见面章节)
2.同步执行(前台执行) VS 异步执行(后台执行)
  大部分情况下,在 shell 中执行的命令都是“同步执行”(或者叫“前台执行”)。对于这种方式,只有当命令运行完毕,才会重新看到 shell 的“命令行提示符”。
  如果你以“异步执行”的方式启动某个外部命令,在这个命令还没有执行完的时候,你就可以重新看到“命令行提示符”。
  请注意:对于【短】寿命的外部命令(耗时很短的外部命令),“同步/异步”两种方式其实【没】啥区别。比如 ls 命令通常很快就执行完毕感觉不到上述两种方式的差异。
  只有当执行了某个【长】寿命的外部命令(其执行时间至少达到若干秒),上述这两种方式才会体现出差别。
  到目前为止,本文之前聊的命令执行方式,都属于“同步执行”;如果想用【异步】,需要在整个命令的最末尾追加一个半角的 & 符号。
  【同步】方式举例
  下列命令以【同步】的方式启动火狐浏览器,只有当你关闭了火狐,才会重新看到 shell 的命令行提示符。

firefox

  【异步】方式举例
  下列命令以【异步】的方式启动火狐浏览器。你刚敲完回车,就会重新看到 shell 的“命令行提示符”(此时火狐依然在运行)

firefox &

  以“同步”方式启动的进程,称作“【前台】进程”;反之,以“异步”方式启动的进程,称作“【后台】进程”。

“前台”切换到“后台”

  假设当前的 shell 正在执行某个长寿命的【前台】进程,你可以按【Ctrl + Z】,就可以让该进程变为【后台】进程——此时你立即可以看到“命令提示符”。
  只要你不是太健忘,应该记得前一个章节有提到过【Ctrl + Z】这个组合键——它用来实现”【温柔】式暂停“,其原理是:向目标进程发送【SIGTSTP】信号。

“后台”切换到“前台”

  假设当前 shell 正在执行某个后台进程。由于该进程在【后台】执行,此时有“命令提示符”,然后你在 shell 中执行 fg 命令,就可以把该后台进程切换到【前台】。
  如果同时启动了【多个】“后台进程”,fg 命令会切换哪一个?
  在这种情况下,fg 命令切换的是【最后启动】的那个。
  如果你有 N 个“后台进程”,你想把其中的某个切换为“前台进程”,这时候就需要用到 jobs 命令。
  举例:假设同时启动了 vim 与 emacs 作为后台进程,先用 jobs 命令列出所有的后台进程。假设该命令的输出是如下这个样子。

$ jobs
[1]  running    vim
[2]  running    emacs

  在上述的终端窗口,中括号里面的数字称作“job id”。你可以用 fg 命令搭配“job id”,把某个后台进程切换到前台。
  (在本例中)如果你想切换 emacs 到前台,就运行 fg %2,如果想切换 vim 就运行 fg %1(以此类推)

5、环境变量(environment variable)

1.添加并修改“环境变量”

  在 bash(或兼容 bash 的其它 shell),你可以用 export 设置环境变量。比如下面这个命令行设置了一个“环境变量”,其名称是 abc,其值是 xyz

export abc=xyz

  假如要设置的【值】包含空格,记得用双引号引用该值(示例如下)。

export abc="program think"

  由于“环境变量”的名称具有【唯一性】,当设置【同名】的“环境变量”就等同于对它的【修改】。

2.如何查看“环境变量”

  设置完之后,你可以用 env 命令查看。该命令会列出【当前 shell】中的【全部】“环境变量”。

3.“环境变量”的【可见性】和【可继承性】

  某个进程设置的“环境变量”,其【可见性】仅限于该进程及其子进程(也就是“进程树”中,该进程所在的那个枝节)。
  基于上述的【可见性】原则,你在某个 shell 中设置的“环境变量”,只在“该 shell 进程本身”,以及通过该 shell 进程启动的“其它子进程”,才能看到。
  另外,如果系统关机,所有进程都会退出,那么你采用上一个小节(export 方式)设置的“环境变量”也就随之消失了。
  为了让某个“环境变量”永久生效,需要把相应的 export 命令添加到该 shell 的初始化配置文件中。对于 bash 而言,也就是 ~/.bashrc 或者 ~/.profile

bashrc和profile的差异在于:
1. bashrc是在系统启动后就会自动运行。
2. profile是在用户登录后才会运行。
3. 进行设置后,可运用source bashrc命令更新bashrc,也可运用source profile命令更新profile。
PS:通常我们修改bashrc,有些linux的发行版本不一定有profile这个文件,本文用的系统是Ubuntu 15.10
4. /etc/profile中设定的变量(全局)的可以作用于任何用户,而~/.bashrc等中设定的变量(局部)只能继承/etc/profile中的变量,他们是"父子"关系。

另外,需要补充说明介绍bashrc相关的几个文件:
~/.bash_profile: 每个用户都可使用该文件输入专用于自己使用的shell信息,当用户登录时,该文件仅仅执行一次!默认情况下,他设置一些环境变量,执行用户的.bashrc文件。
~/.bash_logout: 当每次退出系统(退出bash shell)时,执行该文件。
~/.bash_profile 是交互式、login方式进入bash运行的,~/.bashrc是交互式non-login方式进入bash运行的,通常二者设置大致相同,所以通常前者会调用后者。

6、“标准流”(standard stream)与“重定向”(redirection)

1.进程的3个“标准流”

  在 POSIX 系统(Linux & UNIX)中,每个进程都内置了三个“标准流”(standard stream),分别称作:“标准输入流”(stdin),“标准输出流”(stdout),“标准错误输出流”(stderr)。
  当进程启动后,在默认情况下,stdin 对接到终端的【输入】;stdout & stderr 对接到终端的【输出】。
  当在程序中打开某个文件,会得到一个“文件描述符”(“file descriptor”,简称 fd)。fd 本身是个整数,程序员可以通过 fd 对该文件进行读写。
  而进程的三个【标准流】,就相当于是三个特殊的 fd。当进程启动时,操作系统就已经把这三个 fd 准备好了。
  由于这三个玩意儿是预先备好的,所以它们的数值分别是:0、1、2(参见上图中 # 后面的数字)。

2.“标准流”的【重定向】

  所谓的【重定向】大体上分两种:

  1. 【输入流】重定向

  把某个文件重定向为 stdin;此时进程通过 stdin 读取的是该文件的内容。
  这种玩法使用小于号(<)

  1. 【输出流】重定向

  把 stdout 重定向到某个文件;此时进程写入 stdout 的内容会【覆盖 or 追加】到这个文件。
  这种玩法使用【单个】大于号(>)或【两个】大于号(>>)。前者用于【覆盖】文件内容,后者用于【追加】文件内容。
  另外,有时候会看到 2>&1 这种写法。它表示:把 stderr 合并到 stdout。
  (注:前面提到过——stdout 是“数值为 1 的文件描述符”;stderr 是“数值为 2 的文件描述符”)
3.【重定向】举例
  cat 的例子
  下面这个命令把某个文件重定向到 cat 的 stdin。

cat < 文件名

  不要把上面的命令与下面的命令搞混淆。
  请注意:上面的命令用的是【输入重定向】,而下面的命令用的是【命令行参数】。

cat 文件名

  cat 命令还可以起到类似“文件复制”的效果。
  比如已经有个 文件1,用下面这种操作,会创建出一个内容完全相同的 文件2。

cat < 文件1 > 文件2

  既然能这么操作,为什么还需要用 cp 命令进行文件复制?
  原因在于:cat 操作,只保证内容一样,其它的不管;而 cp 除了复制文件内容,还会确保“目标文件”与“源文件”具有相同的属性(比如 mode)。

7、匿名管道(anonymous pipe)

1.“匿名管道”的【原理】

  在大部分 shell 中,使用竖线符号(|)来表示【管道符】。用它来创建一个【匿名管道】,使得前一个命令(进程)的“标准输出”关联到后一个命令(进程)的“标准输入”。
  以用 netstat 查看当前系统的监听端口为例。
  对于 Windows 系统,可以用如下命令:

netstat -an | find "LISTEN"

  对于 POSIX 系统,可以用如下命令:

netstat -an | grep "LISTEN"

  在上述两个例子中,都用到了【管道符】。因为 netstat -an 这个命令的输出可能会很多,先把它的输出通过【匿名管道】丢给某个专门负责过滤的命令(比如:POSIX 的 grep 或 Windows 的find)。当这个过滤命令拿到 netstat 的输出内容,再根据你在命令行参数中指定的【关键字】(也就是上述例子中的 LISTEN),过滤出包含【关键字】的那些【行】。
  最终,你看到的是“过滤命令”(grep 或 find)的输出。

2.【串联的】匿名管道(chained pipeline)

  前面的例子,可以用来列出当前系统中所有的监听端口。
  现在,假设你运行了 Tor Browser,然后想看看它到底有没有开启 9150 这个监听端口,那么你就可以在上述命令中进行【二次过滤】(具体命令大致如下)。这就是所谓的【串联】。
netstat -an | grep "LISTEN" | grep "9150"

3.“匿名管道”与“作业”(进程组)

  用“匿名管道”串起来的多个进程,构成一个“作业”。
  你可以尝试执行某个长寿命的,带管道符的命令行,然后用 Ctrl + Z 切到后台,再执行 jobs 看一下,就能看出——该命令行对应的【多个】进程属于同一个 job。

8、批处理(batch)

1.【无】条件的“批处理”

  如果你把多个命令写在同一行,并且命令之间用半角分号隔开,这种玩法就属于【无条件】的批处理执行。
  举例:假设当前目录下有一个 abc.txt 文件,然后要在当前目录下创建一个名为 xxx 的子目录,并把 abc.txt 移动到这个新创建的子目录中。你可以用如下方式搞定(只用【一行】命令)
mkdir ./xxx/; mv abc.txt ./xxx/
  为什么这种方式叫做“【无条件】批处理”?因为不管前一个“子命令”是否成功,都会继续执行下一个“子命令”。
  请注意:虽然上述举例只使用了两个“子命令”,但实际上这种操作可以把 N 个“子命令”串起来。

2.【有】条件的“批处理”

  与“无条件”相对应的,当然是“有条件”啦。
  这种玩法的意思是——后一个“子命令”是否执行,取决于【前一个】“子命令”的结果(成功 or 失败)。
  (注:如何界定“成功/失败”,请参见前面某个章节聊到的【进程退出码】)
  【有】条件的批处理,常见的方式有两种,分别是【逻辑与】、【逻辑或】。
  逻辑与(语法:&&)
  只要前面的某个“子命令”【失败】了,就【不再】执行后续的“子命令”。
  举例:还是拿前一个小节的例子。如下方式使用了“逻辑与”。如果创建子目录失败,就【不再】执行“移动文件”的操作
mkdir ./xxx/ && mv abc.txt ./xxx/
  逻辑或(语法:||)
  只要前面的某个“子命令”【成功】了,就【不再】执行后续的“子命令”。
  举例:把上述例子进一步扩充,变为如下:
mkdir ./xxx/ && mv abc.txt ./xxx/ || echo "FAILED!!!"
  把前面两句看作一个【整体】。其执行的逻辑参见前面所说的“逻辑与”。然后这个“整体”与后面的那句 echo 再组合成【逻辑或】的关系。
  也就是说,如果前面的“整体”成功了,那么就【不】执行 echo(【不】打印错误信息);反之,如果前面的“整体”失败了,就会打印错误信息。


扫描二维码,在手机上阅读!

发表评论

电子邮件地址不会被公开。 必填项已用*标注