一、进程与线程

1、进程

进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。-----------百度百科

在Linux中进程是运行中的程序的一个副本,是被载入内存的一个指令集合,使用进程ID(PID)来标记各个进程。我们可使用echo $$来查看当前程序的进程号。

[ root@vinsent ~ ]#echo $$         # 获取当前bash的进程号
3267
[ root@vinsent ~ ]#
[ root@vinsent ~ ]#pstree -p
systemd(1)─┬─NetworkManager(520)─┬─dhclient(623)
           │                     ├─{NetworkManager}(531)
           │                     └─{NetworkManager}(533)
       ...中间省略...
           ├─sshd(935)───sshd(3263)───bash(3267)───pstree(7269)
           ├─systemd-journal(361)           ---|---
           ├─systemd-logind(496)            # 通过pstree验证,和echo $$的结果一样  
           ├─systemd-udevd(380)
           ├─tuned(920)─┬─{tuned}(1105)
           │            ├─{tuned}(1109)
           │            ├─{tuned}(1111)
           │            └─{tuned}(1116)
           └─vmtoolsd(495)
[ root@vinsent ~ ]#
  • 进程得到的权限,是对应之执行者的权限,除了SUID、SGID等权限。

  • 进程是动态概念,有生命周期;可附加SUI、SGID等权限。

  • 进程号随机分配( 进程号唯一标识一个进程;一个inode唯一标识一个磁盘文件 )。

  • 在 CentOS 6 及之前的系统中第一个进程为:init;CentOS 7 中第一个进程为:systemd。

2、守护进程

守护进程(daemon)是一类在后台运行的特殊进程,用于执行特定的系统任务。很多守护进程在系统引导的时候启动,并且一直运行直到系统关闭。另一些只在需要的时候才启动,完成任务后就自动结束。用户使守护进程独立于所有终端是因为,在守护进程从一个终端启动的情况下,这同一个终端可能被其他的用户使用。守护进程没有控制终端,因此当某些情况发生时,不管是一般的报告性信息,还是需由管理员处理的紧急信息,都需要以某种方式输出。Syslog 函数就是输出这些信息的标准方法,它把信息发送给 syslogd 守护进程。例如我们用来监控某些非独立服务的程序xinetd,他就是一个超级守护进程,用来管理系统的的非独立守护进程(瞬时守护进程),这些非独立进程通常情况是处于关闭状态,由xinetd进程来监控,如果有人使用相关的服务,则唤醒这些非独立守护进程,进行工作。我们可使用chkconfig来查看。

[root@localhost ~]# yum install xinetd        # 我用的是最小化安装,自动不安装xinetd服务
[root@localhost ~]# chkconfig 
auditd             0:off    1:off    2:on    3:on    4:on    5:on    6:off    # 独立服务
autofs             0:off    1:off    2:on    3:on    4:on    5:on    6:off
blk-availability    0:off    1:on    2:on    3:on    4:on    5:on    6:off
...中间省略
xinetd based services:           # 下面的服务全是非独立服务,由xinetd来代为管理
    chargen-dgram:     off
    chargen-stream:    off
    daytime-dgram:     off
    daytime-stream:    off
    discard-dgram:     off
    discard-stream:    off
    echo-dgram:        off
    echo-stream:       off
    tcpmux-server:     off
    time-dgram:        off
    time-stream:       off
[root@localhost ~]# chkconfig --add crond     # 添独立服务是添加不到xinetd中的

NOTE:我们每次新添加(chkconfig --add server_name)一个服务或者删除(chkconfig --del server_name)一个服务后,我们必须重启xinetd服务(service restart xinetd)。

3、线程

有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。

一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。就绪状态是指线程具备运行的所有条件,逻辑上可以运行,在等待处理机;运行状态是指线程占有处理机正在运行;阻塞状态是指线程在等待一个事件(如某个信号量),逻辑上不可执行。

有线程,那么就有多线程的说法。线程是程序中一个单一的顺序控制流程。进程内一个相对独立的、可调度的执行单元,是系统独立调度和分派CPU的基本单位指运行中的程序的调度单位。在单个程序中同时运行多个线程完成不同的工作,称为多线程。线程共享整个程序的内存资源;但是也存在一个线程出故障,影响其他线程,争夺资源。为了避免这种争夺资源的情况,系统加入了锁 fork()机制clone()机制。通常采用写时复制的原则:

Linux进阶之进程与线程_第1张图片

在Linux中我们通过pstree能看到线程通常在进程树中表现为"{ thread_name  }"形式。

[ root@vinsent ~ ]#yum -y install httpd  &>/dev/null
[ root@vinsent ~ ]#pstree -p
systemd(1)─┬─NetworkManager(520)─┬─dhclient(623)
           │                     ├─{NetworkManager}(531)  # NetworkManager开启的多线程
           │                     └─{NetworkManager}(533)
           ├─atd(509)
           ├─auditd(471)───{auditd}(482)
           ├─automount(929)─┬─{automount}(930)
           │                ├─{automount}(931)
           │                ├─{automount}(948)
           │                └─{automount}(960)
           ├─crond(506)
           ├─dbus-daemon(492)───{dbus-daemon}(493)
           ├─firewalld(515)───{firewalld}(619)
           ├─httpd(7491)─┬─httpd(7494)          # httpd服务开启的多个进程
           │             ├─httpd(7495)
           │             ├─httpd(7496)
           │             ├─httpd(7497)
           │             └─httpd(7498)
           ...后面省略...
[ root@vinsent ~ ]#

4、线程与进程的关系

  • 一个进程可包含多个线程;

  • 每一个程序都至少有一个线程,若程序只有一个线程,那就是程序本身;

  • 地址空间和其它资源(如打开文件):进程间相互独立,同一进程的各线程间共享。某进程内的线程在其它进程不可见。

  • 通信:进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。

  • 调度和切换:线程上下文切换比进程上下文切换要快得多。

  • 在多线程OS中,进程不是一个可执行的实体。


二、进程优先级

有时我们面临着很多选择,同一时间有许多事情要办,那么我们总会有选择性的去执行,先做什么后做什么,这就是一种优先级,同样的作为一个高速运转的计算机而言,有序的优先级约定显得异常重要。

优先级(priority)是一种约定,优先级高的先做,优先级低的后做。优先级是计算机分时操作系统在处理多个作业程序时,决定各个作业程序接受系统资源的优先等级的参数。优先级是计算机操作系统给任务指定的优先等级。它决定任务在使用资源时的优先次序。给设备指定的优先等级。它决定设备在提出中断请求时,得到处理机响应的先后次序。任务调度优先级主要是指任务被调度运行时的优先级,主要与任务本身的优先级和调度算法有关。特别在实时系统中,任务调度优先级反应了一个任务重要性与紧迫性。-------百度百科

1、系统优先级

进程优先级起作用的方式从发明以来基本没有什么变化,无论是只有一个cpu的时代,还是多核cpu时代,都是通过控制进程占用cpu时间的长短来实现的。就是说在同一个调度周期中,优先级高的进程占用的时间长些,而优先级低的进程占用的短些。在Linux系统中系统优先级的范围为 0-139 共 140 及等级 ;其值越小则优先级越高 ;CentOS 6之后为 系统优先级的范围变成了0-98。

2、实时优先级与非实时优先级

实时操作系统需要保证相关的实时进程在较短的时间内响应,不会有较长的延时,并且要求最小的中断延时和进程切换延时。对于这样的需求,一般的进程调度算法,无论是O1还是CFS都是无法满足的,所以内核在设计的时候,将实时进程单独映射了100个优先级,这些优先级都要高与正常进程的优先级(nice值),而实时进程的调度算法也不同,它们采用更简单的调度算法来减少调度开销。总的来说,Linux系统中运行的进程可以分成两类实时优先级与非实时优先级。实时优先级又称动态优先级,其的范围是0-99;其值越大,优先级越高;动态优先级不能人为调整,系统自己调度。所有优先级值在0-99范围内的,都是实时进程,所以这个优先级范围也可以叫做实时进程优先级,而100-139范围内的是非实时进程。在系统中可以使用chrt命令来查看、设置一个进程的实时优先级状态。

3、静态优先级

  • 静态优先级又称非实时优先级:通常用nice值表示;

  • nice值所在范围对应系统优先级的 100-139 ;其值为:-20~19;

  • 进程默认启动时的nice值为0,优先级为120;

  • 只有根用户才能降低nice值(提高优先性);

  • nice 优先级 其值越小则优先级越高;

在几种优先级中,nice的值是我们可以手动调整的,我们可以指定以某个nice值打开某个程序;使用renice命令可以对一个正在运行的进程进行nice值的调整,我们也可以使用比如top、ps等命令查看进程的nice值,具体方法我会在下一章给大家介绍,但对于普通用户来说:renice在调整程序的优先级的时候只能将其值调低。

[ root@vinsent ~ ]#ps axo pid,cmd,pri,nice  # 只显示进程进程号,进程名,pri优先级,nice值
   PID CMD                         PRI  NI
     1 /usr/lib/systemd/systemd --  19   0

...中间省略...
  4344 -bash                        19   0
  4381 vim switch_arry.sh           19   0     # vim进程默认的NICE值为0
  4388 [kworker/0:2H]               39 -20
  4416 /sbin/dhclient -d -q -sf /u  19   0
  4422 ps axo pid,cmd,pri,nice      19   0
[ root@vinsent ~ ]#
[ root@vinsent ~ ]#renice -n -1 4381                 # 修改vim的nice值
4381 (process ID) old priority 0, new priority -1
[ root@vinsent ~ ]#ps axo pid,cmd,pri,nice
   PID CMD                         PRI  NI
     1 /usr/lib/systemd/systemd --  19   0
     ...中间省略...
  4344 -bash                        19   0
  4381 vim switch_arry.sh           20  -1         # 修改了NICE
  4388 [kworker/0:2H]               39 -20
  4428 [kworker/0:0H]               39 -20
  4429 ps axo pid,cmd,pri,nice      19   0
[ root@vinsent ~ ]#su - tom     # 切换至普通用户
[ tom@vinsent root ]$renice -n -5 4602     # 调高NICE优先级,受到限制;
renice: failed to set priority for 4602 (process ID): Permission denied
[ tom@vinsent root ]$renice -n 5 4602     #调低NICE值成功;说明普通用户只能调低NICE优先级
4602 (process ID) old priority 0, new priority 5

需要大家注意的是,我在这里都在使用nice值这一称谓,而非优先级(priority)这个说法。当然,nice和renice的man手册中,也说的是priority这个概念,但是要强调一下,请大家真的不要混淆了系统中的这两个概念,一个是nice值,一个是priority值,他们有着千丝万缕的关系,但对于当前的Linux系统来说,它们并不是同一个概念。nice值虽然不是priority,但是它确实可以影响进程的优先级。在英语中,如果我们形容一个人nice,那一般说明这个人的人缘比较好。什么样的人人缘好?往往是谦让、有礼貌的人。比如,你跟一个nice的人一起去吃午饭,点了两个一样的饭,先上了一份后,nice的那位一般都会说:"你先吃你先吃!",这就是人缘好,这人nice!但是如果另一份上的很晚,那么这位nice的人就要饿着了。这说明什么?越nice的人抢占资源的能力就越差,而越不nice的人抢占能力就越强。这就是nice值大小的含义,nice值越低,说明进程越不nice,抢占cpu的能力就越强,优先级就越高。在原来使用O1调度的Linux上,我们还会把nice值叫做静态优先级,这也基本符合nice值的特点,就是nice值设定好了之后,除非我们用renice去改它,否则它是不变的。而priority的值在之前内核的O1调度器上表现是会变化的,所以也叫做动态优先级。

4、三种优先级的关系

系统优先级、实时优先级、nice值之间有其对应关系,其关系如下图:

Linux进阶之进程与线程_第2张图片

三、调度算法

1、实时优先级的调度策略

在上面第二节中我们提到了一点调度算法这个概念,这一小节重点来给大家说说一些常见的系统调度算法。调度策略主要是针对实时优先级与非实时优先级而言的。我们可以通过chrt命令来查看或设置进程实时优先级的状态。

[ root@vinsent ~ ]#chrt
Show or change the real-time scheduling attributes of a process.

Set policy:
 chrt [options]   [...]
 chrt [options] --pid  

Get policy:
 chrt [options] -p 

Policy options:
 -b, --batch          set policy to SCHED_BATCH
 -d, --deadline       set policy to SCHED_DEADLINE
 -f, --fifo           set policy to SCHED_FIFO
 -i, --idle           set policy to SCHED_IDLE
 -o, --other          set policy to SCHED_OTHER
 -r, --rr             set policy to SCHED_RR (default)

Scheduling options:
 -R, --reset-on-fork       set SCHED_RESET_ON_FORK for FIFO or RR
 -T, --sched-runtime   runtime parameter for DEADLINE
 -P, --sched-period    period parameter for DEADLINE
 -D, --sched-deadline  deadline parameter for DEADLINE
...后面省略...

从上面的Policy options选项中我们发现,系统给我们提供了6中调度策略。但是这里并没有说明的是,这五种调度策略是分别给两种进程用的,对于实时进程可以用的调度策略是:SCHED_FIFO、SCHED_RR,而对于非实时进程则是:SCHED_OTHER、SCHED_BATCH、SCHED_IDLE。系统的整体优先级策略是:如果系统中存在需要执行的实时进程,则优先执行实时进程。直到实时进程退出或者主动让出CPU时,才会调度执行非实时进程。实时进程可以指定的优先级范围为1-99,将一个要执行的程序以实时方式执行的方法为:

[ root@vinsent ~ ]#bash           # 新打开一个bash
[ root@vinsent ~ ]#chrt -p $$     # 查看该bash的调度策略
pid 5140's current scheduling policy: SCHED_OTHER
pid 5140's current scheduling priority: 0
[ root@vinsent ~ ]#

可以看到,新打开的bash已经是实时进程,默认调度策略为SCHED_OTHER,优先级为0。如果想修改调度策略,就加个参数;

[ root@vinsent ~ ]#chrt -f 10 bash         # 以优先级10 ,SCHED_FIFO策略打开一个bash
[ root@vinsent ~ ]#chrt -p $$
pid 5169's current scheduling policy: SCHED_FIFO
pid 5169's current scheduling priority: 10

SCHED_RR和SCHED_FIFO都是实时调度策略,只能给实时进程设置。对于所有实时进程来说,优先级高的(就是priority数字小的)进程一定会保证先于优先级低的进程执行。SCHED_RR和SCHED_FIFO的调度策略只有当两个实时进程的优先级一样的时候才会发生作用,其区别也是顾名思义:

SCHED_FIFO:以先进先出的队列方式进行调度,在优先级一样的情况下,谁先执行的就先调度谁,除非它退出或者主动释放CPU。

SCHED_RR:以时间片轮转的方式对相同优先级的多个进程进行处理。时间片长度为100ms。

2、非实时优先级调度策略

上面讲述了Linux对于实时进程的优先级和相关调度算法的描述。整体很简单,也很实用。而相对更麻烦的是非实时进程,它们才是Linux上进程的主要分类。对于非实时进程优先级的处理,我们首先还是要来介绍一下它们相关的调度算法:O1和CFS。

(1)O1调度器的调度过程

  • 首先,进程产生(fork)的时候会给一个进程分配一个时间片长度。这个新进程的时间片一般是父进程的一半,而父进程也会因此减少它的时间片长度为原来的一半。就是说,如果一个进程产生了子进程,那么它们将会平分当前时间片长度。比如,如果父进程时间片还剩100ms,那么一个fork产生一个子进程之后,子进程的时间片是50ms,父进程剩余的时间片是也是50ms。这样设计的目的是,为了防止进程通过fork的方式让自己所处理的任务一直有时间片。不过这样做也会带来少许的不公平,因为先产生的子进程获得的时间片将会比后产生的长,第一个子进程分到父进程的一半,那么第二个子进程就只能分到1/4。对于一个长期工作的进程组来说,这种影响可以忽略,因为第一轮时间片在耗尽后,系统会在给它们分配长度相当的时间片。

  • 针对所有R状态进程,O1算法使用两个队列组织进程,其中一个叫做活动队列,另一个叫做过期队列。活动队列中放的都是时间片未被耗尽的进程,而过期队列中放时间片被耗尽的进程。

  • 新产生的进程都会先获得一个时间片,进入活动队列等待调度到CPU执行。而内核会在每个tick间隔期间对正在CPU上执行的进程进行检查。一般的tick间隔时间就是cpu时钟中断间隔,每秒钟会有1000个,即频率为1000HZ。每个tick间隔周期主要检查两个内容:

    (1) 当前正在占用CPU的进程是不是时间片已经耗尽了?

    (2) 是不是有更高优先级的进程在活动队列中等待调度?如果任何一种情况成立,就把则当前进程的执行状态终止,放到等待队列中,换当前在等待队列中优先级最高的那个进程执行。

(2)CFS完全公平调度

O1已经是上一代调度器了,由于其对多核、多CPU系统的支持性能并不好,并且内核功能上要加入cgroup等因素,Linux在2.6.23之后开始启用CFS作为对一般优先级(SCHED_OTHER)进程调度方法。在这个重新设计的调度器中,时间片,动态、静态优先级以及IO消耗,CPU消耗的概念都不再重要。

CFS采用了一种全新的方式,对上述功能进行了比较完善的支持。其设计的基本思路是,我们想要实现一个对所有进程完全公平的调度器。又是那个老问题:如何做到完全公平?答案跟上一篇IO调度中CFQ的思路类似:如果当前有n个进程需要调度执行,那么调度器应该再一个比较小的时间范围内,把这n个进程全都调度执行一遍,并且它们平分cpu时间,这样就可以做到所有进程的公平调度。那么这个比较小的时间就是任意一个R状态进程被调度的最大延时时间,即:任意一个R状态进程,都一定会在这个时间范围内被调度相应。这个时间也可以叫做调度周期,其英文名字叫做:sched_latency_ns。进程越多,每个进程在周期内被执行的时间就会被平分的越小。调度器只需要对所有进程维护一个累积占用CPU时间数,就可以衡量出每个进程目前占用的CPU时间总量是不是过大或者过小,这个数字记录在每个进程的vruntime中。所有待执行进程都以vruntime为key放到一个由红黑树组成的队列中,每次被调度执行的进程,都是这个红黑树的最左子树上的那个进程,即vruntime时间最少的进程,这样就保证了所有进程的相对公平。在基本驱动机制上CFS跟O1一样,每次时钟中断来临的时候,都会进行队列调度检查,判断是否要进程调度。当然还有别的时机需要调度检查,发生调度的时机可以总结为这样几个:

  • 当前进程的状态转换时。主要是指当前进程终止退出或者进程休眠的时候。

  • 当前进程主动放弃CPU时。状态变为sleep也可以理解为主动放弃CPU,但是当前内核给了一个方法,可以使用sched_yield()在不发生状态切换的情况下主动让出CPU。

  • 当前进程的vruntime时间大于每个进程的理想占用时间时(delta_exec > ideal_runtime)。这里的ideal_runtime实际上就是上文说的sched_latency_ns/进程数n。当然这个值并不是一定这样得出,下文会有更详细解释。

  • 当进程从中断、异常或系统调用返回时,会发生调度检查。比如时钟中断。


参阅:http://www.linuxidc.com/Linux/2016-05/131244.htm