Linux进程的原理及与信号的联系

 第1节 程序、进程、守护进程、僵尸进程的区别

程序、进程、守护进程、僵尸进程

  • 程序:c/php/java,代码文件,静态的,放在磁盘里的数据。
  • 进程:正在内存中运行的程序,进程是动态的,会申请和使用系统资源,并与操作系统内核进行交互。
  • 进程运行:系统把程序放在内存里执行。
  • 守护进程:在内存持续保持运行着的程序。
  • 僵尸进程:是因为子进程在没有执行完毕之前,自己的父进程被kill掉,父进程无法通知内核清理回收;子进程就成为了一个没娘的孩子,任由它自身自灭。

第2节 多任务与CPU

 多任务与CPU:

  1. 现在所有的操作系统(分时设计,例如CentOS)都能"同时"运行多个进程,也就是多任务或者说是并行执行。但实际上这是人类的错觉,一颗物理cpu在同一时刻只能运行一个进程,只有多颗物理cpu才能真正意义上实现多任务。
  2. cpu如何选择下一个要执行的进程,这是一件非常复杂的事情。在Linux上,决定下一个要运行的进程是通过"调度类"(调度程序)来实现的。程序何时运行,由进程的优先级决定(renice命令调整进程的优先度),但要注意,优先级值越低,优先级就越高,就越快被  调度类选中。除此之外,优先级还影响分配给进程的时间片长短。在Linux中,改变进程的nice值,可以影响某类进程的优先级值。
  3. 进程切换被CPU执行时,内核会将每个进程临时停止时的运行时环境(寄存器中的内容和页表)保存下来(保存位置为内核占用的内存),这称为保护现场,在下次进程恢复运行时,将原来的运行时环境加载到cpu上,这称为恢复现场,这样cpu可以在当初的运行时环境下继续执行。
  4. 在进程的优先级相同下;Linux的调度器不是通过cpu的时间片流逝来选择下一个要运行的进程的,而是考虑进程的等待时间,即在就绪队列等待了多久
  5. cpu的衡量单位是时间,就像内存的衡量单位是空间大小一样。进程占用的cpu时间长,说明cpu运行在它身上的时间就长。注意,cpu的百分比值不是其工作强度或频率高低,而是"进程占用cpu时间/cpu总时间",这个衡量概念一定不要搞错。

第3节 父子进程及创建方式(资源,shell实现)

图1:

[jeson@mage-jump-01 ~/]$echo "echo $a" >>text.sh
[jeson@mage-jump-01 ~/]$a=123
[jeson@mage-jump-01 ~/]$sh text.sh

[jeson@mage-jump-01 ~/]$export a=456
[jeson@mage-jump-01 ~/]$sh text.sh  
456

执行shell脚本 :               基本上与执行bash命令基本一致,只加载父进程的各命令路径。
执行shell的内置bash命令:fork出来的子进程完成继承父进程,但为保护父进程的环境,所有子进程会重新加载环境,使用新的变量。在内置命令角度,没有创建子进程,$BASH_SUBSHELL的值为0。在新的环境及父进程的角度,可以是创建子进程。
图2:

[jeson@mage-jump-01 ~/]$exec echo "123"
123
回到了登录界面

执行shell内置命令:父进程不会创建子进程运行内置命令,若内置命令在管道后面,则与管道左边的进程属于一个进程组。
shell内置命令source :将脚本的变量继承到当前进程中。一般用于加载环境配置类脚本

图4:

[jeson@mage-jump-01 ~/]$cp text.sh /tmp/
[jeson@mage-jump-01 ~/]$

执行非bash内置命令:    fork一个进程出来,然后利用exec替代子进程。
非内置命令的命令替换: 遇到替换命令 `command` 或 $(command),将开启一个fork子进程处理。

 小结:

  在bash环境中,执行bash命令所产生的子进程,是不会全部继承父进程的全部环境变量,一般只继承少量的环境变量(Path路径等) ,因此在脚本设计中或有计划的创建子进程实现某些功能时,考虑父进程的环境变量是否能被子进程继承是重点之一(比如Java变量,java在父进程可以运行,在脚本中却报错了,就是因为脚本(这个儿子没有继承父亲的某些彪悍的优点)没有继承父进程的java变量,所以在脚本中需要另外添加Java的变量即可。

第4节 各进程与CPU的状态

以 cp /etc /tmp/ 为例进行说明:

 进程与CPU状态

  在当前bash环境下,当执行cp命令时,首先fork出一个bash子进程(即前台),然后在子bash执行exec加载cp程序,cp子进程进入等待队列,由于在命令行下敲的命令,所以优先级较高,调度类很快选中它。在cp这个子进程执行过程中,父进程bash会进入睡眠状态(即后台),并等待被唤醒,此刻bash无法和人类交互。当cp命令执行完毕,它将自己的退出状态码告知父进程,此次复制是成功还是失败,然后cp进程自己消逝掉,父进程bash被唤醒再次进入等待队列,并且此时bash已经获得了cp退出状态码,根据状态码这个"信号",父进程bash知道了子进程已经终止,所以通告给内核,内核收到通知后将进程列表中的cp进程项删除。至此,整个cp进程正常完成。

  有时也会发生意外,导致僵尸进程的产生;是因为子进程在没有执行完毕之前,自己的父进程被kill掉,父进程无法通知内核清理回收;子进程就成为了一个没娘的孩子,任由它自身自灭。

第5节 jobs与父子进程的前后台,fuser,lsof

 用以下的例子进行说明:

 [jeson@mage-jump-01 ~/]$sleep 100& sleep 150& sleep 200&           

 [1] 5027

 [2] 5028

 [3] 5029

 [jeson@mage-jump-01 ~/]$jobs -l    #表示 list jobs 里的tables

 [1]    5027  Running   sleep 100 &   #([1]: 后台进程的编号     空: CPU正在执行    5027: PID号   Done:     运行完成)

 [2] 5028  Running   sleep 150 &   #([2]: --------------      -:  CPU下个执行    5028: PID号   Stopped:运行暂停)

 [3] +  5029  Running   sleep 200 &   #([3]: --------------    +:  在队列中等待    5029: PID号   Running:正在运行)  

  进程前、后台的切换以及转移到服务端init进程下:

 小结:

  bash 环境下,将子进程放入后台运行,是实现无限接近并发的效果。

第5节 信号与进程与kill,pkill,killall 

信号是软件层次上对(硬)中断的一种模拟,通知进程发送了什么事件(而不是对进程传递数据),令进程改变某些行为。信号是操作系统规范进程改变自身的某种行为。

一般情况下完善的程序设计,在子进程终止、退出的时候,会发送SIGCHLD信号给父进程,父进程收到信号就会通知内核清理该子进程相关信息。

同时,信号也提供了人工干预进程的方式(比如手机上要有关机键,操作系统中有关机的选项,电脑主机上要有重启键),以下命令实现给进程发送信号

kill -s signal pid # kill -s HUP pid1 ... 或 kill -s HUP pid

kill -signal pid  # kill -9 pid1 pid2 ... 或 kill -HUP pid

kill pid #默认 -15

pkill

killall

键盘上的组合键

如:ctrl+c , ctrl+z ,ctrl+d

常用信号(kill -l #列出所有信号):

Signal   Value Comment
SIGHUP    1 终端退出时,此终端内的进程都将被终止
SIGINT 2 中断进程,可被捕捉和忽略,几乎等同于sigterm,所以也会尽可能的释放执行clean-up,释放资源,保存状态等(CTRL+C)
SIGQUIT  3 从键盘发出杀死(终止)进程的信号
SIGKILL 9 强制杀死进程,该信号不可被捕捉和忽略,进程收到该信号后不会执行任何clean-up行为,所以资源不会释放,状态不会保存
SIGTERM 15 杀死(终止)进程,可被捕捉和忽略,几乎等同于sigint信号,会尽可能的释放执行clean-up,释放资源,保存状态等
SIGCHLD 17 当子进程中断或退出时,发送该信号告知父进程自己已完成,父进程收到信号将告知内核清理进程列表。所以该信号可以解除僵尸进程,也可以让非正常退出的进程工作得以正常的clean-up,释放资源,保存状态等。
SIGSTOP 19 该信号是不可被捕捉和忽略的进程停止信息,收到信号后会进入stopped状态
SIGCONT 18 发送此信号使得stopped进程进入running,该信号主要用于jobs,例如bg & fg 都会发送该信号。可以直接发送此信号给stopped进程使其运行起来 
SIGTSTP 20 该信号是可被忽略的进程停止信号(CTRL+Z)
SIGUSR1 10 用户自定义信号1
SIGUSR2 12 用户自定义信号2

第5节 利用信号实现跳板机功能

       链接为: trap实现跳板机

第6节 进程与管道

无名管道(pipe):

  在Linux下,你可以通过"|"连接两个程序,这样就可以用它来连接后一个程序的输入和前一个程序的输出,因此被形象地叫做个管道。父进程往第一个文件描述符里头写入东西后,子进程可以从第一个文件描述符中读出来。

  实际上当我们输入这样一组命令的时候(echo "hi fd" | cat ),前面一个进程的输出关联(重定向)到管道的输出文件描述符,把后面一个进程的输入关联到管道的输入文件描述符。注意:两个命令都要同时存在。

管道原理图

   进程echo将内容到标准输出(文件描述符为fd 1)连接到了管道的写入端,读取进程cat就将其标准输入(文件描述符为fd 0)连接到了管道的输出端。实际上,这两个进程并不知道管道的存在,它们只是从标准文件描述符中读取数据和写入数据。(文件描述符可以参考此处

有名管道(named pipe)

  有名管道实际上是一个文件(无名管道也像一个文件,虽然关系到两个文件描述符,不过只能一边读另外一边写),不过这个文件比较特别,操作时要满足先进先出,而且如果试图读一个没有内容的有名管道,那么就会被阻塞(等待数据过来),同样地,如果试图往一个有名管道里头写东西,而当前没有程序试图读它,也会被阻塞(等待数据被读取)。

  有名管道与无名管道的区别:有名管道将无名管道的两边进程(同时存在的写与读)分别独立出来,通过一个中间的文件进行数据传递,因此有名管道特别适合有两个独立程序构成某些任务的模式(可以理解A程序实现菜单及对应的功能,B程序实现read读取用户选择,而有名管道实现B程序传参给A程序),如A程序(类似一个脚本无限的循环菜单,及菜单对应的功能)无限循环的读取有名管道文件,通过获取数据,对应执行那些事件(若没有数据,则阻塞从而避免无限循环导致资源浪费),B程序(类似read命令,读取用户的输入)则作为控制程序往有名管道文件输入指令。

窗口1 窗口2

[root@server01 tmp]# mkfifo fifo_a

[root@server01 tmp]# echo "hello" > fifo_a

[root@server01 tmp]# echo "hello again" > fifo_a

[root@server01 tmp]# cat fifo_a hello

[root@server01 tmp]# cat fifo_a hello again

[root@server01 tmp]#

第7节 进程的并发与文件锁(flock)~劝告锁(advisory lock)

  Linux中软件、硬件资源都是文件(一切皆文件),文件在多用户环境中是可共享的。当多个进程共享一个资源进行操作时,会出现一些不可预见的情况。

 代码(2000个1累加)   效果(原来并发也可以做随机数哦,只是我用的不是神威,奢侈不起。)

cat a.sh

#!/bin/bash
countfile=/tmp/count
echo 0 > $countfile
do_count(){
echo $((`cat $countfile`+1)) > $countfile
}
for i in `seq 1 2000`
do
  do_count &
done
wait
cat $countfile
rm $countfile

[root@server01 ~]# bash a.sh

1999

[root@server01 ~]# bash a.sh

1366

[root@server01 ~]# bash a.sh

580

[root@server01 ~]# bash a.sh

78

 

[root@server01 ~]# bash a.sh

 

789

 

原文地址:https://www.cnblogs.com/jeson-lbb/p/9733222.html