linux系统编程——进程管理——高级

1. 进程调度

进程调度是内核子系统,用于将有限的处理器使用时间资源分配给各个进程,决定哪些进程可以运行及运行多久。
调度的目标:
*最大化cpu利用率

  • 尽可能提高系统交互响应速度
  • 让每个进程都能被运行

2. 调度分类

  • 协同式:进程自己主动放弃运行,让其他进程运行
  • 抢占式:调度程序决定何时停止一个进程运行,让不同进程继续运行

linux使用抢占式。

3. 时间片

时间片是cpu分配的基本单位。
时间片的长短设置值得考虑。
通常,若希望最大化系统吞吐量及整体性能,可以使用较大时间片(减少进程切换,享受时间局部性)。
若希望最佳交互性能,则使用非常小时间片。

注意,进程可能不会用完它的所有时间片,
如,进程被分配了100ms时间,可能运行20ms,然后阻塞等待资源。
调度程序会将其从可运行进程列表中移除,当资源可用,调度程序会唤醒进程。
然后进程继续运行,直到用完时间片剩余80ms,或再次阻塞等待资源。

4. 从进程角度看调度

进程分为:

  • IO密集型:希望分配较长时间片,但不太在乎调度优先级
  • cpu密集型:很在乎调度优先级,不太在乎时间片长度
    linux调度程序会试图找到IO密集型程序,并提高优先级,并降低cpu密集型程序的优先级。

实际上,多数程序是混合型。

5. 抢占式调度

当一个进程的时间片用完,内核会暂停其运行,并允许新的进程。
若系统没有可运行的进程,则内核会给 一组已经用完时间片的进程,重新补足时间片,并再次让他们运行。
如此,所有进程都能得以运行。

若系统没有可运行的进程,内核会运行空间进程,空闲进程实际上不是进程,也不会允许(以便节省电池电力)。
空闲进程是内核的特殊例程,用于简化调度程序算法。

若有一个进程正在运行,突然一个高优先级进程变成可运行的(可能因为键盘输入),于是进程会立即暂停当前正在
运行的进程,并运行优先级高的进程。
因此,当前有时间片的优先级最高的进程绝不可能 进程可运行态,但是未运行状态。
正在运行的进程往往是系统中优先级最高的可运行进程。

5.1 完全公平调度

上面的调度方法的过时的,
完全公平调度 更优调度算法,特点是没有时间片,而用时间比例。
比如,CFS给N个进程分别分配1/N的处理器时间,然后CFS通过优先级计算每个进程的比列以调整分配,
默认优先级为0,权值为1,则比列不变,优先级的值越小,权值越高,增加分配比列。

为了避免比例太小导致只够进程切换消耗,所以 引入 最小粒度,最小粒度为 时间长度的基本单位。

6. 线程

每个线程有自己的虚拟处理器:一套寄存器,指令指针,处理状态。
对linux内核而言,线程是独特的进程,内核将两个线程所组成的进程,看作 两个共享内核资源(地址空间,所打开的文件等)的进程。

7. 让出处理器

int sched_yield(void);

linux虽然是抢占式,但也提供了一个系统调用,让进程可以主动放出执行权。

若存在其他可运行进程,sched_yield 的调用进程会暂停,内核会将其放到可运行进程列表尾部,并选出一个新的进程运行。
若不存在任何其他进程,sched_yield 的调用进程不会暂停,而继续执行。

7.1 sched_yield的用途

首先进程调度应该交给内核,因为内核能看到所有的进程,以安排最优调度策略。

sched_yield的可能用途

  • 等待外部事件。
    以生产消费者为例
do {
  while (producer_not_ready())
      sched_yield();
  process_data();
} while (!time_to_quit());

但是,unix上不会这样写,因为又更好的方法:事件驱动。
如上面用一个管道代替 sched_yield。
总之,unix程序应该把目标放在依赖可阻塞的fd的事件驱动解决方案上。

  • 线程锁定
    一个线程需要获得另一个线程持有的锁,则应该让出cpu,让另一个线程释放锁。这个方法很简单,有效。
    可替代方法: 现代linux线程实现(new posix threading library NPTL)有一个使用
    futexes(为用户空间锁提供内核的支持)的优化解决方案。

8. 进程优先级

linux根据进程优先级对进程进行调度,
优先级会影响进程何时运行,和运行多久。
nice值为 [-20, 19] 默认为0。

8.1 设置优先级

int getpriority(int which, int who);
int setpriority(int which, int who, int prio);

返回当前进程优先级

ret = getpriority(PRIO_PROCESS, 0);

设置当前进程组所有进程优先级为10

ret = setpriority(PRIO_PGRP, 10);

8.2 IO优先级

IO优先级 影响 进程IO请求,IO调度程序会先服务IO优先级高的进程的请求。

默认情况下,IO调度程序使用进程nice值确定IO优先级。因此设置nice值自动变更IO优先级。

9. 处理器亲和性

由于多核环境下,若进程移动cpu,有如下弊端:

  • 原cache数据作废
  • 不能访问原cache中的数据
    所以进程调度程序会尽可能让一个进程安排特定cpu来运行。
    但又由于cpu负载可能不均衡,所以当负载不均衡时,调度程序会让进程移动到较空闲的cpu。

有时进程需要一定一直绑定在特定处理器,linux提供了相关系统调用。
放心不会导致cpu负载不均衡,因为调度程序会移动其他进程。

获得进程pid的cpu亲和性,当Pid为0,表示获得当前进程。

cpu_set_t set;
int  i;

CPU_ZERO(&set);
sched_getaffinity(0, sizeof(cpu_set_t), &set);

for (i = 0; i < CPU_SETSIZE; i++) {
  int cpu;
  cpu = CPU_ISSET (i, &set);
  printf("cpu = %i is %s\n", i, cpu ? "set" : "unset");
}

若打印

cpu=0 is set
cpu=1 is set
cpu=2 is unset

若希望只运行在cpu0上,可以这么做

cpu_set_t set;
int i;

CPU_ZERO(&set);
CPU_SET(0, &set);
CPU_CLR(1, &set); // 这是多余的,因为上面已经清零,但为了完成性
sched_setaffinity(0, sizeof(cpu_set_t), &set);

for (i = 0; i < CPU_SETSIZE; i++) {
  int cpu;

  cpu = CPU_ISSET (i, &set);
  printf("cpu = %i is %s\n", i, cpu ? "set" : "unset");
}

成功后,打印

cpu=0 is set
cpu=1 is unset
cpu=2 is unset

posted on 2021-08-25 00:42  开心种树  阅读(75)  评论(0编辑  收藏  举报